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.可读性更强的接口请求数据日志打印
+ ![接口请求数据打印](https://img-blog.csdnimg.cn/20210101160327602.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1J1bnQwMg==,size_16,color_FFFFFF,t_70)
+# 结语
+    虽然标题写的是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