From 024fabb9b4098b3842019d496b0fb15fec0191a0 Mon Sep 17 00:00:00 2001 From: Runt <qingingrunt2010@qq.com> Date: Sun, 14 Aug 2022 07:11:23 +0000 Subject: [PATCH] README.md 完善 --- app/src/main/java/com/runt/open/mvvm/base/activities/BaseFragmentActivity.java | 7 /dev/null | 178 ----------------------------- app/src/main/java/com/runt/open/mvvm/base/activities/LoadPageActivity.java | 6 app/src/main/java/com/runt/open/mvvm/retrofit/Interceptor/AddHeadersInterceptor.java | 3 README.md | 151 ++++++++++++++++++++++++ 5 files changed, 156 insertions(+), 189 deletions(-) diff --git a/README.md b/README.md index 5e33ec2..d66bdfa 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,151 @@ -## OpenMvvm +## OpenMvvm 开源的mvvm项目 -开源的mvvm项目 +# 简介 + 项目是基于mvvm模式开发,引用了Google新出的ViewBinding框架。为了提高开发效率,开发了这套开源项目。此项目是基于多年的工作经验总结出来的。代码高效、简洁,可大大提高Android开发人员的工作效率。 +# 为什么用ViewBinding? + 为什么用ViewBinding而不是现在大部分人用的DataBinding? + 1.首先,我在用Android studio创建demo的时候,Google官方给的demo用的就是ViewBinding,基于此才使用的ViewBinding。既然Google推荐,我也就继续使用。 + 2.其次,网上搜了相关帖子,我也简单总结了两者的优劣,参考文章(https://blog.csdn.net/Runt02/article/details/126330609)。 + +### 主要目录结构 + +``` +com.runt.open.mvvm +│ +├─base 封装类 +│ ├─activities activity封装类 +│ │ ├─ BaseActivity activity封装父类 +│ │ ├─ BaseFragmentActivity 带有fragment切换的activity +│ │ ├─ BaseTabActivity 带有tablayout activity封装(带有viewpager的视图父类) +│ │ └─ LoadPageActivity 含有上拉刷新的分页Activity封装 +│ ├─adapter 适配器封装类 +│ │ ├─ BaseAdapter adapter封装父类 +│ │ └─ FragmentAdapter 加载fragment适配器 +│ ├─fragments fragment封装类 +│ │ ├─ BaseFragment fragment封装父类 +│ │ ├─ BaseTabFragment 带有tablayout fragment封装(带有viewpager的视图父类) +│ │ └─ LoadPageFragment 含有上拉刷新的分页fragment封装 +│ └─model viewmodel封装类 +│ ├─ BaseViewModel ViewModel封装父类 +│ ├─ ImpViewModel ViewModel空实现 +│ ├─ LoadPageViewModel 含有分页请求的ViewModel封装 +│ └─ ViewModelFactory ViewModel工厂 +│ +├─conf 所有配置 +│ +├─data 数据bean +│ ├─ HttpApiResult 接口回到数据类 +│ ├─ PageResult 分页数据类 +│ ├─ PhoneDevice 设备信息数据类 +│ └─ Results 所有数据类集合 +│ +├─retrofit 网络请求模块 +│ ├─api 接口请求地址 +│ ├─Interceptor 接口请求监听器(包含log打印) +│ └─observable.java 接口请求观察 +│ +├─ui 项目UI布局 +│ ├─adapter 适配器 +│ └─loadpage 含有分页的UI布局 +│ +├─util 工具类 +│ +└─widgets 自定义view + + +``` +### 引入的第三方框架 + 1、permissionx权限请求 + 2、smartrefresh 下拉刷新 + 3、okhttp+retrofit网络请求 + 4、BottomMenu 底部菜单 + 5、glide 图片加载 + 6、PictureSelector 图片选择框架 + +## 项目特色 +### 1.项目特别色 +### 2.优化封装类中的泛型对象的实例化过程 + 众多分装类都使用了泛型<T>,但是在实现的过程中都需要单独一个方法来创建对象。在我的项目里对此进行了优化。 + ---也就是说,继承带有泛型的封装类,不需要专门实现某个方法来创建对象。 +##### 例如:MsgDetailActivity + //继承了BaseActivity,声明了binding和viewmodel类,BaseActivity已经完成了对俩个对象的创建,代码中可以直接使用。 + class MsgDetailActivity extends BaseActivity<ActivityMsgDetailBinding,MsgDetailViewModel> { + @Override + public void initViews() { + mViewModel.detailLive.observe(this, message -> { + mBinding.txtMsgTitle.setText(message.title); + mBinding.txtAuthor.setText(message.cUName); + mBinding.txtTime.setText(HandleDate.getTimeStateNew(HandleDate.getDateTimeToLong(message.cTime))+" · "+getString(R.string.created_at)); + WebSettings settings = mBinding.txtContent.getSettings(); + settings.setTextZoom(80); // 通过百分比来设置文字的大小,默认值是100。 + settings.setDefaultTextEncodingName("UTF-8"); + mBinding.txtContent.loadData(message.content,"text/html","UTF-8"); + }); + } + + @Override + public void loadData() { + mViewModel.getMsgDetail(getIntent().getStringExtra("id")); + } + } + +### 3.对下拉刷新、上拉加载分页数据进行封装 + 很多开源项目并没有对带有分页数据请求和展示进行封装的,而此处恰恰有很多的封装优化控件。 + 在本项目中对此进行了优化,大大减少了代码量,提高开发效率 +##### 例 1: + 页面仅仅只是展示分页数据,没有太多复杂交互的情况下,HomeFragment继承LoadPageFragment,仅仅声明ViewBinding,ViewModel,Adapter和数据类就可以了,完全没有多余的代码实现 + class HomeFragment extends LoadPageFragment<RefreshRecyclerBinding, HomeViewModel, MsgAdapter, Message> { } + +##### 例 2: + 页面除了展示分页数据,还有其他数据展示和交互的情况下,CoinRecordActivity继承LoadPageActivity,声明ViewBinding,ViewModel,Adapter和数据类 + 还需要实现标题和标题栏右侧图片,也可以在xml中实现,仅需对应的ViewBinding即可。 + requestParams是接口请求传递的参数(默认page和size已经在ViewModel中) + 若有其他操作可在initViews和loadData中实现 + class CoinRecordActivity extends LoadPageActivity<ActivityRecyclerBinding, CoinRecordViewModel, CoinTransAdapter, CustomCoin>{ + @Override + protected String initTitle() { + return "金币记录"; + } + + @Override + public void initViews() { + super.initViews(); + Drawable drawable = getResources().getDrawable(R.mipmap.icon_white_setting); + drawable.setTint(getResources().getColor(R.color.txt_color)); + setTitleRight(drawable); + titleBarView.setRightClick(new CustomClickListener() { + @Override + protected void onSingleClick(View view) { + startActivity(new Intent(mContext, CoinSettingActivity.class));//打开设置 + } + }); + } + + @Override + protected Map requestParams() { + Map map = super.requestParams(); + map.put("inOrOut",0); + return map; + } + } + +##### loadPageViewModel实现更简洁 例如: + 仅需实现接口地址和数据类即可 + CoinRecordViewModel extends LoadPageViewModel<CustomCoin>{ + @Override + protected String requestUrl() { + return "coinRecord"; + } + } +### 4.可读性更强的接口请求数据日志打印 +  +# 结语 + 虽然标题写的是mvvm模式,但不论从结构还是代码封装来看,跟mvp并没有核心的区别。我也看过很多mvvm的开源项目,结构都跟mvp没有本质的区别。 + mvvm最标准的结构还是Google官方login demo的 + 其结构构成非常复杂,且流程化、标准化。东西太多,介绍不完。有兴趣的可以自己研究下 + +### 开发者 + +- QQ群:721765299 + <a target="_blank" href="https://qm.qq.com/cgi-bin/qm/qr?k=5XjXWFh7YsRaofRoqoO6YRPzVE9ED0fA&jump_from=webapi"><img border="0" src="https://images.gitee.com/uploads/images/2019/0530/203513_ac6773bf_123301.png" alt="Android OpenMVVM" title="Android OpenMVVM"></a> \ No newline at end of file diff --git a/app/src/main/java/com/runt/open/mvvm/base/activities/BaseFragmentActivity.java b/app/src/main/java/com/runt/open/mvvm/base/activities/BaseFragmentActivity.java index 298bfa5..e9abcc8 100644 --- a/app/src/main/java/com/runt/open/mvvm/base/activities/BaseFragmentActivity.java +++ b/app/src/main/java/com/runt/open/mvvm/base/activities/BaseFragmentActivity.java @@ -4,7 +4,6 @@ import androidx.fragment.app.FragmentTransaction; import androidx.viewbinding.ViewBinding; -import com.runt.open.mvvm.R; import com.runt.open.mvvm.base.fragments.BaseFragment; import com.runt.open.mvvm.base.model.BaseViewModel; @@ -17,7 +16,7 @@ * 试用于加载各种fragment需求的activity * Created by Runt (qingingrunt2010@qq.com) on 2022/8/13. */ -public class BaseFragmentActivity <VB extends ViewBinding,VM extends BaseViewModel> +public abstract class BaseFragmentActivity <VB extends ViewBinding,VM extends BaseViewModel> extends BaseActivity<VB,VM>{ @@ -55,7 +54,7 @@ * 添加fragment * @param fragment */ - protected void addAndShowFragment(BaseFragment fragment){ + protected void addAndShowFragment(int viewId,BaseFragment fragment){ if(fragments.contains(fragment)){ showFragment(fragment); return; @@ -65,7 +64,7 @@ for(int i = 0 ; i < fragments.size() ; i ++){ transaction.hide(fragments.get(i)); } - transaction.add(R.id.framelayout,fragment).commit(); + transaction.add(viewId,fragment).commit(); fragments.add(fragment); } diff --git a/app/src/main/java/com/runt/open/mvvm/base/activities/LoadPageActivity.java b/app/src/main/java/com/runt/open/mvvm/base/activities/LoadPageActivity.java index 44a86d1..4e72287 100644 --- a/app/src/main/java/com/runt/open/mvvm/base/activities/LoadPageActivity.java +++ b/app/src/main/java/com/runt/open/mvvm/base/activities/LoadPageActivity.java @@ -1,14 +1,10 @@ package com.runt.open.mvvm.base.activities; -import android.view.MotionEvent; -import android.view.View; - import androidx.annotation.NonNull; import androidx.lifecycle.Observer; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import androidx.viewbinding.ViewBinding; - import com.runt.open.mvvm.R; import com.runt.open.mvvm.base.adapter.BaseAdapter; import com.runt.open.mvvm.base.model.LoadPageViewModel; @@ -24,7 +20,7 @@ import java.util.Map; /** - * 含有上拉刷新的Activity + * 含有上拉刷新的分页Activity * 继承此类,有效优化代码60行 * 试用于 有下拉刷新,上拉加载等分页需求的界面 * Created by Administrator on 2021/11/4 0004. diff --git a/app/src/main/java/com/runt/open/mvvm/retrofit/Interceptor/AddHeadersInterceptor.java b/app/src/main/java/com/runt/open/mvvm/retrofit/Interceptor/AddHeadersInterceptor.java index 66cd343..fa38538 100644 --- a/app/src/main/java/com/runt/open/mvvm/retrofit/Interceptor/AddHeadersInterceptor.java +++ b/app/src/main/java/com/runt/open/mvvm/retrofit/Interceptor/AddHeadersInterceptor.java @@ -8,6 +8,7 @@ import com.runt.open.mvvm.util.DeviceUtil; import java.io.IOException; +import java.nio.charset.Charset; import okhttp3.Interceptor; import okhttp3.Request; @@ -20,6 +21,8 @@ */ public class AddHeadersInterceptor implements Interceptor { + protected final Charset UTF8 = Charset.forName("UTF-8"); + @Override public Response intercept(Chain chain) throws IOException { return chain.proceed(addHeaders(chain.request())); diff --git a/app/src/main/java/com/runt/open/mvvm/retrofit/utils/RSAUtils.java b/app/src/main/java/com/runt/open/mvvm/retrofit/utils/RSAUtils.java deleted file mode 100644 index 229561b..0000000 --- a/app/src/main/java/com/runt/open/mvvm/retrofit/utils/RSAUtils.java +++ /dev/null @@ -1,178 +0,0 @@ -package com.runt.open.mvvm.retrofit.utils; - -import android.util.Base64; - -import java.io.ByteArrayOutputStream; -import java.security.Key; -import java.security.KeyFactory; -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.PrivateKey; -import java.security.PublicKey; -import java.security.Signature; -import java.security.spec.PKCS8EncodedKeySpec; -import java.security.spec.X509EncodedKeySpec; - -import javax.crypto.Cipher; - - -public class RSAUtils { - - public static final String PRIVATE_KEY = "MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBALmG22Y82CPx/n7yE6Y/lh33ZoQxWdp9LeWH7DN07dp/5Od9PY6Eix2XBjI4mRWoRv71gtbA3+nq9aaztibWGvjD/akMGTTgWD2BXQI3kw4CrGI0P1YzoFFM0oGUqd9duUzJpl8Lpq/3JpB1vo+c6EsVqggWJoPpIJNb2pcEBC3fAgMBAAECgYEAmmd5C3KR9AB/7Qjtf4wmFJSFNnmYLXXHD+N6miyzlAii6mHaLFV1LlA1sPBXv6WISMLtfGuTLywD/BRPAa1pTbrQTz3BDvtHHrCaL124ZdIr1j49TGyyYRCplwDoP9QWKTtEcZ6mP0P0rzh2Zfe2nBaJDn2qFPmYd9P4WBGMW1kCQQDop0oMK/oDc7gFQqXf0CnEETsOjJrvVzOuRue54WLl1H62PXhsGFzNvlm4J4OqMlLpnX6et7dzgX2MAVlJKP3jAkEAzCTqOo1l/E5bN00HFcQ3odCr5Pi985OK17R1ueAiLoY6g3I6O88zDWCiB5XmQPMSJ6QcNPIxAsJI6JGVqMZQ1QJACyhStci6IcY/8gijOdua1StaYLU/jPDqqpX98P0tKAaL2SOTjeORN9DELr++YcAuF8QU2XnIE4MHSVqbNJYBrwJBAKxkwCiSH/3hbcZVlhYbjZ9oyMCkDkUT47wk+QXu8O65C9DVNbgsUcCKSkp9m+RdYId5XxiXLixWRZug1fGhB8ECQQDTPTxN9RhunrUaT0brQsd+0btrYMrm/suaBzJyBpspFzjxRcM+AhlAHa1szkMqE6r01ZHCFQopaZEWijeh4Kgf"; - - public static final String PUBLIC_KEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCVj4BkEYtlv8z4quUUrkRvW4xuWQvXegMuLKPQZky8LObbxfFZniSvQ4gllFlyFuCjeeInjyQFPC3ARdbihV3P88drBsB2gCG9lwlCkgMjZfSc/hxC4VirsHbGGSIN5oPyCZMQNAUnIojpKBRlE0TJmHvP+FpAe46Yb+oPs8R5DQIDAQAB"; - - - /** - * RSA最大加密明文大小 - */ - private static final int MAX_ENCRYPT_BLOCK = 117; - - /** - * RSA最大解密密文大小 - */ - private static final int MAX_DECRYPT_BLOCK = 128; - - static final String KEY_RSA = "RSA"; //android标准 “RSA/ECB/PKCS1Padding” 服务端标准 “RSA” - - /** - * 获取密钥对 - * - * @return 密钥对 - */ - public static KeyPair getKeyPair() throws Exception { - KeyPairGenerator generator = KeyPairGenerator.getInstance(KEY_RSA); - generator.initialize(1024); - return generator.generateKeyPair(); - } - - /** - * 获取私钥 - * - * @param privateKey 私钥字符串 - * @return - */ - public static PrivateKey getPrivateKey(String privateKey) throws Exception { - KeyFactory keyFactory = KeyFactory.getInstance(KEY_RSA); - byte[] decodedKey = Base64.decode(privateKey.getBytes(), Base64.URL_SAFE); - PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decodedKey); - return keyFactory.generatePrivate(keySpec); - } - - /** - * 获取公钥 - * - * @param publicKey 公钥字符串 - * @return - */ - public static PublicKey getPublicKey(String publicKey) throws Exception { - KeyFactory keyFactory = KeyFactory.getInstance(KEY_RSA); - byte[] decodedKey = Base64.decode(publicKey.getBytes(), Base64.URL_SAFE); - X509EncodedKeySpec keySpec = new X509EncodedKeySpec(decodedKey); - return keyFactory.generatePublic(keySpec); - } - - /** - * RSA加密 - * - * @param data 待加密数据 - * @param key 密钥 - * @return - */ - public static String encrypt(String data, Key key) throws Exception { - Cipher cipher = Cipher.getInstance("RSA/None/PKCS1Padding"); - cipher.init(Cipher.ENCRYPT_MODE, key); - int inputLen = data.getBytes().length; - ByteArrayOutputStream out = new ByteArrayOutputStream(); - int offset = 0; - byte[] cache; - int i = 0; - // 对数据分段加密 - while (inputLen - offset > 0) { - if (inputLen - offset > MAX_ENCRYPT_BLOCK) { - cache = cipher.doFinal(data.getBytes(), offset, MAX_ENCRYPT_BLOCK); - } else { - cache = cipher.doFinal(data.getBytes(), offset, inputLen - offset); - } - out.write(cache, 0, cache.length); - i++; - offset = i * MAX_ENCRYPT_BLOCK; - } - byte[] encryptedData = out.toByteArray(); - out.close(); - // 获取加密内容使用base64进行编码,并以UTF-8为标准转化成字符串 - // 加密后的字符串 - return Base64.encodeToString(encryptedData,Base64.URL_SAFE | Base64.NO_WRAP); - } - - /** - * RSA解密 - * - * @param data 待解密数据 - * @param key 密钥 - * @return - */ - public static String decrypt(String data, Key key) throws Exception { - Cipher cipher = Cipher.getInstance("RSA/None/PKCS1Padding"); - cipher.init(Cipher.DECRYPT_MODE, key); - byte[] dataBytes = Base64.decode(data.getBytes(), Base64.URL_SAFE); - int inputLen = dataBytes.length; - ByteArrayOutputStream out = new ByteArrayOutputStream(); - int offset = 0; - byte[] cache; - int i = 0; - // 对数据分段解密 - while (inputLen - offset > 0) { - if (inputLen - offset > MAX_DECRYPT_BLOCK) { - cache = cipher.doFinal(dataBytes, offset, MAX_DECRYPT_BLOCK); - } else { - cache = cipher.doFinal(dataBytes, offset, inputLen - offset); - } - out.write(cache, 0, cache.length); - i++; - offset = i * MAX_DECRYPT_BLOCK; - } - byte[] decryptedData = out.toByteArray(); - out.close(); - // 解密后的内容 - return new String(decryptedData, "UTF-8"); - } - - /** - * 签名 - * - * @param data 待签名数据 - * @param privateKey 私钥 - * @return 签名 - */ - public static String sign(String data, PrivateKey privateKey) throws Exception { - byte[] keyBytes = privateKey.getEncoded(); - PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes); - KeyFactory keyFactory = KeyFactory.getInstance(KEY_RSA); - PrivateKey key = keyFactory.generatePrivate(keySpec); - Signature signature = Signature.getInstance("MD5withRSA"); - signature.initSign(key); - signature.update(data.getBytes()); - return Base64.encodeToString(signature.sign(),Base64.DEFAULT); - } - - /** - * 验签 - * - * @param srcData 原始字符串 - * @param publicKey 公钥 - * @param sign 签名 - * @return 是否验签通过 - */ - public static boolean verify(String srcData, PublicKey publicKey, String sign) throws Exception { - byte[] keyBytes = publicKey.getEncoded(); - X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes); - KeyFactory keyFactory = KeyFactory.getInstance(KEY_RSA); - PublicKey key = keyFactory.generatePublic(keySpec); - Signature signature = Signature.getInstance("MD5withRSA"); - signature.initVerify(key); - signature.update(srcData.getBytes()); - return signature.verify(Base64.decode(sign.getBytes(),Base64.DEFAULT)); - } - -} -- Gitblit v1.9.1