深入探索Android稳定性优化


 https://juejin.cn/post/6844903972587716621--深入探索Android稳定性优化
 
 
 https://mp.weixin.qq.com/s/g-WzYF3wWAljok1XjPoo7w?
 -//-Android 平台 Native 代码的崩溃捕获机制及实现
 
 CountDownTimer.onFinish on a null()
 
 四、捕捉native crash-native crash-native
 native crash-native()-》
 3) /proc/self/maps:检查各个模块加载在内存的地址范围
 
 
 /proc/self/maps:检查各个模块加载在内存的地址范围()
 
 
 5.0以上:安卓系统中没有了libcorkscrew.so,使用自己编译的libunwind
 
 美团外卖Android Crash治理之路----
 https://mp.weixin.qq.com/s?__biz=MjM5NjQ5MTI5OA==&mid=2651748107&idx=1&sn=55dff1b286e92cfb6aaee776df8ec89e&chksm=bd12ae468a652750a7624c30eca56f6f83347b16cdfb9153b647c6e5229a822b16724a1bbd9d&scene=38#wechat_redirect
 
 
 
 notifyDataSetChanged() notifyDataSetChanged()
 
 
 常用的如JDK里的ArrayList和Android里的SparseArray、ArrayMap
 
 ArrayList  SparseArray
 自己维护通知, 同时也极大的避免了
 
 The content of the adapter has changed but ListView did not receive a notification
 
 ArrayList            通过Hook来解决,Hook分为Java Hook和Native Hook
 
 通过Hook来解决,Hook分为Java Hook和Native Hook。        Native Hook
 
 
 。我们在定位这个Crash的可疑点无果后决定通过Hook的方式解决
 
 AsyncTask$SerialExecutor()
 
 
 public static void setFinalStatic(Field field, Object newValue) throws Exception {
        field.setAccessible(true);
        Field artField = Field.class.getDeclaredField("artField");
        artField.setAccessible(true);
        Object artFieldValue = artField.get(field);
        Field accessFlagsFiled = artFieldValue.getClass().getDeclaredField("accessFlags");
        accessFlagsFiled.setAccessible(true);
        accessFlagsFiled.setInt(artFieldValue, field.getModifiers() & ~Modifier.FINAL);
        field.set(null, newValue);
    }
    
    Java反射之如何判断类或变量、方法的修饰符(Modifier解析)--https://blog.csdn.net/xiao__gui/article/details/8141216
    
 
 int   getModifiers()

          返回此类或接口以整数编码的 Java语言修饰符。


 
 getModifiers 
 
 需要用到java.lang.reflect.Modifier这个类
 
 
        System.out.println(Modifier.toString(field.getModifiers()));

 Java基础篇:反射机制详解---https://blog.csdn.net/a745233700/article/details/82893076
 
 
 Class.forName("com.mysql.jdbc.Driver"); // 动态加载mysql驱动
 
 
 
 Stude
 
 mysql
 
 通过class类的静态方法:forName(String className)(最常用)            
 
 forName
 
 public Constructor getConstructor(Class... parameterTypes)
 
     Constructor con = clazz.getConstructor(null);
 
 
 
        Class stuClass = Class.forName("fanshe.method.Student");

 、反射main方法:
 
 数组在Java里是比较特殊的一种类型,它可以赋值给一个Object Reference。
 
 
 
import java.lang.reflect.Method;
 
 
 10、反射方法的其他使用--通过反射越过泛型检查:
        Field field = Integer.class.getDeclaredField("value");
        
        
        
 
 
 55
 
 java.lang.reflect.Filed.class中setInt与set的区别
 
 set    
 设置字段的值:
 *         Field --> public void set(Object obj,Object value):
 *                     参数说明:
 *                     1.obj:要设置的字段所在的对象;
 *                     2.value:要为字段设置的值;

 要求用包装类型(wrapped type),不能传基本类型参数(primitive type)。
 
 Java Field.get()取得对象的Field属性值--http://www.51gjie.com/java/795.html
 
 java.lang.reflect.Filed.class中setInt与set的区别---https://www.cnblogs.com/buguge/p/13914947.html
 
 
     //拿到了Field类的实例后就可以调用其中的方法了
    //方法:get(Object obj) 返回指定对象obj上此 Field 表示的字段的值
    System.out.println("属性值:  " + field.get(obj));

    //方法: set(Object obj, Object value)  将指定对象变量上此 Field 对象表示的字段设置为指定的新值
 
 
 set     get Object obj
 
  
        67 field=A.class.getDeclaredField("fild");   
        Field field=A.class.getDeclaredField("fild");  
 
 artFieldValue.getClass().getDeclaredField("accessFlags");
 
 accessFlags = true;
 
 
     Object artFieldValue = artField.get(field);
        Field accessFlagsFiled = artFieldValue.getClass().getDeclaredField("accessFlags");
        accessFlagsFiled.setAccessible(true);
        
        accessFlagsFiled.setInt(artFieldValue,getModifiers() &)
        
 
         field.set(null, newValue);
         field.set(null,newValue);78
 
 我们发现原生系统上对应系统版本的AbsListView里并没有UpdateBottomFlagTask
 
 setFinalStatic
 
 s.AsyncTask$3.done
 
 
 .AbsListView$UpdateBottomFlagTask.doInBackground
 .AbsListView$UpdateBottomFlagTask(doInBackground)
 
 
  at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:231)
  
  Android ROM编译时会将framework、app、bin等目录打入system.img中
 
 ,文件格式一般为yaffs2或ext
 yaffs2 ext
 
 yaffs2 ext
 
 LeakCanary
 
 另外我们可以在debug下使用StrictMode来检查Activity的泄露、
 
 Closeable对象没有被关闭等问题。
 
 另外我们可以在debug下使用StrictMode来检查Activity的泄露、Closeable对象没有被关闭等问题。
 debug StrictMode(
 )
 
 closeable
 
 另外我们可以在debug下使用StrictMode来检查Activity的泄露、Closeable对象没有被关闭等问题。
 
 
 StrictMode Activity Closeable对象没有被关闭等问题。
 
 
 矢。Android Studio也提供了非常好用的Memory Profiler,堆转储和分配跟踪器功能可以帮我们迅速定位问题。
 


 https://juejin.cn/post/6844903972587716621--深入探索Android稳定性优化
 
 
 https://mp.weixin.qq.com/s/g-WzYF3wWAljok1XjPoo7w?
 -//-Android 平台 Native 代码的崩溃捕获机制及实现
 
 CountDownTimer.onFinish on a null()
 
 四、捕捉native crash-native crash-native
 native crash-native()-》
 3) /proc/self/maps:检查各个模块加载在内存的地址范围
 
 
 /proc/self/maps:检查各个模块加载在内存的地址范围()
 
 
 5.0以上:安卓系统中没有了libcorkscrew.so,使用自己编译的libunwind
 
 美团外卖Android Crash治理之路----
 https://mp.weixin.qq.com/s?__biz=MjM5NjQ5MTI5OA==&mid=2651748107&idx=1&sn=55dff1b286e92cfb6aaee776df8ec89e&chksm=bd12ae468a652750a7624c30eca56f6f83347b16cdfb9153b647c6e5229a822b16724a1bbd9d&scene=38#wechat_redirect
 
 
 
 notifyDataSetChanged() notifyDataSetChanged()
 
 
 常用的如JDK里的ArrayList和Android里的SparseArray、ArrayMap
 
 ArrayList  SparseArray
 自己维护通知, 同时也极大的避免了
 
 The content of the adapter has changed but ListView did not receive a notification
 
 ArrayList            通过Hook来解决,Hook分为Java Hook和Native Hook
 
 通过Hook来解决,Hook分为Java Hook和Native Hook。        Native Hook
 
 
 。我们在定位这个Crash的可疑点无果后决定通过Hook的方式解决
 
 AsyncTask$SerialExecutor()
 
 
 public static void setFinalStatic(Field field, Object newValue) throws Exception {
        field.setAccessible(true);
        Field artField = Field.class.getDeclaredField("artField");
        artField.setAccessible(true);
        Object artFieldValue = artField.get(field);
        Field accessFlagsFiled = artFieldValue.getClass().getDeclaredField("accessFlags");
        accessFlagsFiled.setAccessible(true);
        accessFlagsFiled.setInt(artFieldValue, field.getModifiers() & ~Modifier.FINAL);
        field.set(null, newValue);
    }
    
    Java反射之如何判断类或变量、方法的修饰符(Modifier解析)--https://blog.csdn.net/xiao__gui/article/details/8141216
    
 
 int   getModifiers()

          返回此类或接口以整数编码的 Java语言修饰符。


 
 getModifiers 
 
 需要用到java.lang.reflect.Modifier这个类
 
 
        System.out.println(Modifier.toString(field.getModifiers()));

 Java基础篇:反射机制详解---https://blog.csdn.net/a745233700/article/details/82893076
 
 
 Class.forName("com.mysql.jdbc.Driver"); // 动态加载mysql驱动
 
 
 
 Stude
 
 mysql
 
 通过class类的静态方法:forName(String className)(最常用)            
 
 forName
 
 public Constructor getConstructor(Class... parameterTypes)
 
     Constructor con = clazz.getConstructor(null);
 
 
 
        Class stuClass = Class.forName("fanshe.method.Student");

 、反射main方法:
 
 数组在Java里是比较特殊的一种类型,它可以赋值给一个Object Reference。
 
 
 
import java.lang.reflect.Method;
 
 
 10、反射方法的其他使用--通过反射越过泛型检查:
        Field field = Integer.class.getDeclaredField("value");
        
        
        
 
 
 55
 
 java.lang.reflect.Filed.class中setInt与set的区别
 
 set    
 设置字段的值:
 *         Field --> public void set(Object obj,Object value):
 *                     参数说明:
 *                     1.obj:要设置的字段所在的对象;
 *                     2.value:要为字段设置的值;

 要求用包装类型(wrapped type),不能传基本类型参数(primitive type)。
 
 Java Field.get()取得对象的Field属性值--http://www.51gjie.com/java/795.html
 
 java.lang.reflect.Filed.class中setInt与set的区别---https://www.cnblogs.com/buguge/p/13914947.html
 
 
     //拿到了Field类的实例后就可以调用其中的方法了
    //方法:get(Object obj) 返回指定对象obj上此 Field 表示的字段的值
    System.out.println("属性值:  " + field.get(obj));

    //方法: set(Object obj, Object value)  将指定对象变量上此 Field 对象表示的字段设置为指定的新值
 
 
 set     get Object obj
 
  
        67 field=A.class.getDeclaredField("fild");   
        Field field=A.class.getDeclaredField("fild");  
 
 artFieldValue.getClass().getDeclaredField("accessFlags");
 
 accessFlags = true;
 
 
     Object artFieldValue = artField.get(field);
        Field accessFlagsFiled = artFieldValue.getClass().getDeclaredField("accessFlags");
        accessFlagsFiled.setAccessible(true);
        
        accessFlagsFiled.setInt(artFieldValue,getModifiers() &)
        
 
         field.set(null, newValue);
         field.set(null,newValue);78
 
 我们发现原生系统上对应系统版本的AbsListView里并没有UpdateBottomFlagTask
 
 setFinalStatic
 
 s.AsyncTask$3.done
 
 
 .AbsListView$UpdateBottomFlagTask.doInBackground
 .AbsListView$UpdateBottomFlagTask(doInBackground)
 
 
  at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:231)
  
  Android ROM编译时会将framework、app、bin等目录打入system.img中
 
 ,文件格式一般为yaffs2或ext
 yaffs2 ext
 
 yaffs2 ext
 
 LeakCanary
 
 另外我们可以在debug下使用StrictMode来检查Activity的泄露、
 
 Closeable对象没有被关闭等问题。
 
 另外我们可以在debug下使用StrictMode来检查Activity的泄露、Closeable对象没有被关闭等问题。
 debug StrictMode(
 )
 
 closeable
 
 另外我们可以在debug下使用StrictMode来检查Activity的泄露、Closeable对象没有被关闭等问题。
 
 
 StrictMode Activity Closeable对象没有被关闭等问题。
 
 
 矢。Android Studio也提供了非常好用的Memory Profiler,堆转储和分配跟踪器功能可以帮我们迅速定位问题。
 
 
 AOP增强辅助
 
 AOP增强辅助
 
 Memory Profiler,堆转储和分配跟踪器功能可以帮我们迅速定位问题。
 
 AOP增强辅助  Android Gradle 
 
 ClassNotFoundException
 
 
 Intent#getStringExtra()->
 
 getStringExtra
 
 
 我们专门制作了一个Gradle插件,只需要配置一下参数就可以将某个特定方法的调用替换成另一个方法:
 
 Gradle
 
 
 WaimaiBytecodeManipulator {
     replacements(
         "android/content/Intent.getIntExtra(Ljava/lang/String;I)I=com/waimai/IntentUtil.getInt(Landroid/content/Intent;Ljava/lang/String;I)I",
         "android/content/Intent.getStringExtra(Ljava/lang/String;)Ljava/lang/String;=com/waimai/IntentUtil.getString(Landroid/content/Intent;Ljava/lang/String;)Ljava/lang/String;",
         "android/content/Intent.getBooleanExtra(Ljava/lang/String;Z)Z=com/waimai/IntentUtil.getBoolean(Landroid/content/Intent;Ljava/lang/String;Z)Z",
         ...)
    }
}
 IntentUtil.getInt Landroid/content/Intent
 
 getBooleanExtra()    Z=com
 
 Intent.getXXXExtra        IntentUtil->IntentUtil
 
 
 
  美团外卖Android Crash治理之路----
 https://mp.weixin.qq.com/s?__biz=MjM5NjQ5MTI5OA==&mid=2651748107&idx=1&sn=55dff1b286e92cfb6aaee776df8ec89e&chksm=bd12ae468a652750a7624c30eca56f6f83347b16cdfb9153b647c6e5229a822b16724a1bbd9d&scene=38#wechat_redirect
 
 
 
 
 
 依赖库的问题
 
 NoClassDefFoundError、NoSuchFieldError、NoSuchMethodError等异常。
 NoSuchFieldError、NoSuchMethodError等异常。()
 
 NoClassDefFoundError、NoSuchFieldError、NoSuchMethodError等异常。
 NoSuchFieldError、NoSuchMethodError等异常。 NoSuchMethodError等异常。
 
 NoClassDefFoundError、NoSuchFieldError、NoSuchMethodError等异常。
 
 ()
 
 
 Android App v6.0->        Android App
 
 
 Defersor    Defensor。
 
 导致热修复功能 -> Defensor。
 
 Defensor。
 
 Defensor在编译时通过DexTask获取到所有的输入文件(也就是被编译过的class文件)
 
 页面跳转路由统一处理页面跳转
 
 
Defensor。
 
 
 业务模块的划分
 
 对于这个问题,我们的做法是App的业务模块化。业务模块化后,每个业务都有都有唯一包名和对应的负责人
 
 
 
 业务模块化本身也是工程架构优先需要考虑的事情之一。
 
 
 业务模块化后,每个业务都有都有唯一包名和对应的负责人
 
 
 页面跳转路由统一处理页面跳转
 
 页面跳转路由统一处理页面跳转
 
 ActivityNotFoundException        scheme        scheme
 
 
 启动Activity的地方,几乎是随处可见,无法预测哪一处会造成ActivityNotFoundException。
 
 
 scheme(ActivityNotFoundException adapter){
 }
 
 
 网络层统一处理API脏数据
 
 网络层统一处理API脏数据
 
 的脏数据,特别容易引起线上大面积的崩溃。
 
 
 scheme->
 但是JSON解析Model这个过程可能存在问题,例如没有返回数据或者返回了类型不对的数据
 
 
 Crash治理之路----009
 
 大图监控

上面讲到大对象是导致OOM的主要原因之一,而Bitmap是App里最常见的大对象类型,因此对占用内存过大的Bitmap对象的监控就很有必要了。
 
 
 OOM        Bitmap()
 
 大图监控
 
 
 问题,不会影响到UI层,返回给UI层的都是校验成功的数据。这样改造后,我们发现这类的Crash率有了极大的改善。

大图监控

上面讲到大对象是导致OOM的主要原因之一,而Bitmap是App里最常见的大对象类型,

因此对占用内存过大的Bitmap对象的监控就很有必要了。

我们用AOP方式Hook了三种常见图片库的加载图片回调方法,同时监控图片库加载图片时的两个维度:

加载图片使用的URL。外卖App中除静态资源外,所有图片都要求发布到专用的图片CDN服务器上,
加载图片时使用正则表达式匹配URL,除了限定CDN域名之外还要求所有图片加载时都要添加对应的动态缩放参数。

最终加载出的图片结果(也就是Bitmap对象)。我们知道Bitmap对象所占内存和其分辨率大小成正比,
而一般情况下在ImageView上设置超过自身尺寸的图片是没有意义的,
所以我们要求显示在ImageView中的Bitmap分辨率不允许超过View自身的尺寸(为了降低误报率也可以设定一个报警阈值)。

开发过程中,在App里检测到不合规的图片时会立即高亮出错的ImageView所在的位置并弹出对话框提示ImageView所在的Activity、
XPath和加载图片使用的URL等信息,如下图,辅助开发同学定位并解决问题。在Release环境下可以将报警信息上报到服务器,
实时观察数据,有问题及时处理。
 
 AOP增强辅助 Hook()
 
 
 我们用AOP方式Hook了三种常见图片库的加载图片回调方法,同时监控图片库加载图片时的两个维度:


 765 = textsize.bold        
 
 Hook
 
 
 加载图片使用的URL。外卖App中除静态资源外
 
 
 
加载图片使用的URL。外卖App中除静态资源外(),


最终加载出的图片结果(也就是Bitmap对象)

CDN URL    Bitmap

开发过程中,在App里检测到不合规的图片时会立即高亮出错的ImageView所在的位置并弹出对话框

CDN (URL ->Bitmap)

Activity、XPath

Activity、XPath()Xpath

Lint检查


Lint规则已经实现了Crash预防、Bug预防、


Crash Bug预防、

NotSerializableException
NotSerializableException


Crash预防、Bug预防、提升性能/安全和代码规范检查这些功能
 
 NotSerializableException()->()
 
 
 Lint检查  Crash治理56----
 
 
 Crash预防、Bug预防、提升性能/安全和代码规范检查这些功能。
 
 
 Lint检查可以在多个阶段执行,包括在本地手动检查、编码实时检查、编译时检查、commit时检查,
 
 以及在CI系统中提Pull Request时检查、打包时检查等,如下图所示。
 
 更详细的内容可参考《美团外卖Android Lint代码检查实践》。
 
 Android Lint代码检查Lin》78》。
 
 Pull Request
 
 提代码    Review  
 
 打包提测
 
 
 美团外卖Android平台化架构演进实践---
 https://tech.meituan.com/2018/03/16/meituan-food-delivery-android-architecture-evolution.html
 
 
 我们需要快速将代码重构为支持平台化的多工程模式
 
 支持平台化的多工程模式
 
 
 如果两端的代码没有统一而直接做平台化业务拆库,必然会导致问题的复杂化。
 
 必然会导致问题的复杂化。
 
 
 在这样的背景下,可以看出我们面临的问题相较于其他平台型App更为特殊和复杂:既要解决外卖业务平台化的问题,又要解决外卖App和外卖频道两端代码复用的问题。
 
 在实施平台化和两端代码复用的道路上并非一帆风顺
 
 平台化 和两端代码复用的道路上
 
 搜索库拆分实践
 
 搜索库拆分实践
 

 两端代码复用        两端代码复用
 
 
 经过多次讨论后,团队发起了两端代码复用的技术方案尝试,
 
 然而两端的搜索模块代码底层差异很大,BaseActivity和BaseFragment不统一,
 UI样式不统一,数据Model不统一,图片、网络、埋点不统一,
 并且两端发版周期也不一致。针对这些问题的解决方案是:
 
 通过代理屏蔽Activity和Fragment基类不统一的问题;
两端主工程style覆盖搜索库的UI样式;
搜索库使用独立的数据Model,上层去做数据适配;
其他差异通通抛出接口让上层实现;
和PM沟通尽量使产品需求和发版周期一致。
 style    Fragment();
 style Fragment();
 
 Model00
 18
 
 0.32  1.8
 
 由于需求和版本发布周期的差异
 
 
 UI样式不统一,数据Model不统一,图片、网络、埋点不统一,
 
 UI
 CommonLogic
 
 FlatCallBack
 
 FlatCallBack        CommonLogic
 
 5
 为两端的差异性
 
 
 组件提供个性化配置满足两端差异需求,如果无法满足再通过代理抛到上层处理。
 
 页面组件化是一个良好的设计,但它主要适用于解决Activity巨大化的问题
 
 
  
 美团外卖Android平台化架构演进实践---
 https://tech.meituan.com/2018/03/16/meituan-food-delivery-android-architecture-evolution.html
 
 另一方面,页面组件化也没有为2端差异性预留可伸缩的空间。
 
 
 MVP分层复用实践
 
 借鉴Clean MVP架构,根据职责将代码拆分为Presenter,Data Repository,Use Case,View,Model等角色;
 
 
 Presenter        Data()
 
 Use    Case,View,Model等角色;
 
 中间层实践
 
 们当然有想过通过中间层设计屏蔽两端的基础库差异。
 
 我们曾经在Volley和Retrofit之上封装了一层网络框架,对外暴露统一的接口
 
 ,对外暴露统一的接口,上层可以切换底层依赖Volley或是Retrofit
 
 (平台化实践)
 
 
 而实际上外卖平台化正是解决两端代码复用的一剂良药
 
 
 外卖平台化正是解决代码复用的
 
 
 
 平台化架构
 
 
  美团外卖Android平台化架构演进实践---
 https://tech.meituan.com/2018/03/16/meituan-food-delivery-android-architecture-evolution.html
 
 
 从底层到高层依次为平台层、业务层和宿主层。()//
 
 
 宿主层的内容包括,Waimai App壳和美团外卖频道Waimai-channel壳,这一层用于Application的初始化、
 
 dex加载和其他各种必要的组件或基础库的初88。
 
 Waimai    debug
 
 业务库通信框架->Waimai-channel
 
 
 每个工程都可以独立编译、独立打包;
每个工程内部的修改,不会影响其他工程;
业务库工程可以快速拆分出来,集成到其他App中。
但工程隔离带来的另一个问题是,同层间的业务库需要通信怎么办?这时候就需要提供业务库通信框架来解决这个问题。
 
 
 ServiceLoaders sdk        sdk
 
 因此我们需要在架构中提供同层间的通信框架,
 
 scheme    ServiceLoaders        sdk
 
 :scheme路由和美团自建的ServiceLoaders sdk。scheme路由本质上是利用Android的scheme原理进行通信,
 
 ServiceLoader本质上是利用的Java反射机制进行通信。
 
 scheme路由本质上是利用Android的scheme原理进行通信,ServiceLoader本质上是利用的Java反射机制进行通信。
 
 
 最终效果:所有业务页面的跳转,都需要通过平台层的scheme路由去分发。
 
 通过scheme路由,所有业务都得到解耦,不再需要相互依赖而可以实现页面的跳转和基本数据类型的传递。

serviceloader的调用如图所示:
 
 
   美团外卖Android平台化架构演进实践---
 https://tech.meituan.com/2018/03/16/meituan-food-delivery-android-architecture-evolution.html
 
 
 提供方和使用方通过平台层的一个接口作为双方交互的约束。
 
 外卖内核模块设计
 
 
 
 在集成zxing二维码时遇到预览拉伸的问题,原因:预览尺寸和surface view不一致。
解决方式:
针对于扫描时,二维码拉伸变形的问题,是因为zxing默认是针对横屏扫描的,所以,我们只需要改变x,y的计算,即横纵轴即可。此时,我们需要找到CameraConfigurationManager类:
找到findBestPreviewSizeValue方法:
将newDiff的变量计算代码改成如下:
int newDiff=Math.abs(newY - screenResolution.x) + Math.abs(newX - screenResolution.y);
 
 而实际上外卖业务的子业务是否应该和垂类业务保持同层是一个目前无法确定的事情。
 
 
 
 
 
 在实践的过程中,我们也遇到业务本身上就不好划分层级边界的业务。大家可以从美团外卖三层架构图上,
 看出外卖业务库,像商家、订单等,是和外卖的垂类业务库是同级的。
 而实际上外卖业务的子业务是否应该和垂类业务保持同层是一个目前无法确定的事情。

目前,外卖接入的垂类业务商超业务,是隶属于外卖业务的子频道,它依然依赖着外卖的核心model、
核心服务,包括商品管理、订单管理、购物车管理等,因此目前它和外卖业务的商家、
订单这样的子业务库同层是没有问题的。但随着商超业务的发展,商超业务未来可能会建设自己的商品管理、
订单管理、购物车管理的服务,那么到时商超业务就会上升到和外卖业务一样同层的业务。这时候,
外卖核心管理服务,处在平台层,就会导致架构的层级边界变得不再清晰。

我们的解决办法是通过设计一个属于外卖业务的内核模块来适应未来的变化,内核模块的设计如图:

内圈为基础模型类,这些模型类构成了外卖核心业务(从门店→点菜→购物车→订单)的基础;
中间圈为依赖基础模型类构建的基础服务(CRUD);
最外圈为外卖的各维度业务,向内依赖基础模型圈和外卖基础服务圈。
如果未来确定外卖平台需要接入更多和外卖平级的业务,且最内圈都完全不一样,我们将把外卖内核模块上移,
在外卖业务子库下建立对内核模块的依赖;如果未来只是有更多的外卖子业务的接入,
那就继续保留我们现在的架构;如果未来接入的业务基础模型类一样,但自己的业务服务需要分化,
那么我们将对保留内核模块最核心的内圈,并抽象出服务层由外卖和商超上层自己实现真正的服务。

循环的wifi发送---https://blog.csdn.net/ningchao328/article/details/53023135

https://www.cnblogs.com/pythoncd/p/10367144.html ----Android反编译三件套 apktool 、dex2jar、jd-gui
1.还是老话下载三件套(点击下载)

adb shell "dumpsys window | grep mCurrentFocus"


标题-》添加摄像机
vstc.eye4zx.activity.AAddNetCameraActivity


声波方式->vstc.eye4zx.activity.tools.InputWifiActivity

一键配置WIFI->vstc.eye4zx.activity.wirelessConfiguration.WirelessSearchActivity


 view = new Intent(mContext, vstc/eye4zx/activity/tools/InputWifiActivity);
                view.putExtra("sendMac", sendMac);
                view.putExtra("currentBssid", currentBssid);
                view.putExtra("resultString", "");
                view.putExtra("intentFlag", 1);
                view.putExtra("isExist", 0);
                view.putExtra("camera_type", cameratype);
                view.putExtra("ipc_camera_type", ipc_camera);
                mContext.startActivity(view);

  case 2131296617: 
            view = new Intent(mContext, vstc/eye4zx/activity/AAddNetCameraActivity);
            view.putExtra("camera_type", 2);
            view.putExtra("ipc_camera_type", -1);
            startActivity(view);
            return;

        case 2131296614: 
            if(NetTypeUtils.isMobile(this))
            {
                view = new Intent(mContext, vstc/eye4zx/activity/AAddNetCameraActivity);
                view.putExtra("camera_type", 0);
                view.putExtra("ipc_camera_type", -1);
                startActivity(view);
                return;
            }
            if(reqPermission("location_auto"))
            {
                return;
            } else
            {
                view = new Intent(mContext, vstc/eye4zx/activity/AAddNetCameraActivity);
                view.putExtra("camera_type", 0);
                view.putExtra("ipc_camera_type", -1);
                startActivity(view);
                return;
            }


 https://juejin.cn/post/6844903972587716621--深入探索Android稳定性优化
 
 
 https://mp.weixin.qq.com/s/g-WzYF3wWAljok1XjPoo7w?
 -//-Android 平台 Native 代码的崩溃捕获机制及实现
 
 CountDownTimer.onFinish on a null()
 
 四、捕捉native crash-native crash-native
 native crash-native()-》
 3) /proc/self/maps:检查各个模块加载在内存的地址范围
 
 
 /proc/self/maps:检查各个模块加载在内存的地址范围()
 
 
 5.0以上:安卓系统中没有了libcorkscrew.so,使用自己编译的libunwind
 
 美团外卖Android Crash治理之路----
 https://mp.weixin.qq.com/s?__biz=MjM5NjQ5MTI5OA==&mid=2651748107&idx=1&sn=55dff1b286e92cfb6aaee776df8ec89e&chksm=bd12ae468a652750a7624c30eca56f6f83347b16cdfb9153b647c6e5229a822b16724a1bbd9d&scene=38#wechat_redirect
 
 
 
 notifyDataSetChanged() notifyDataSetChanged()
 
 
 常用的如JDK里的ArrayList和Android里的SparseArray、ArrayMap
 
 ArrayList  SparseArray
 自己维护通知, 同时也极大的避免了
 
 The content of the adapter has changed but ListView did not receive a notification
 
 ArrayList            通过Hook来解决,Hook分为Java Hook和Native Hook
 
 通过Hook来解决,Hook分为Java Hook和Native Hook。        Native Hook
 
 
 。我们在定位这个Crash的可疑点无果后决定通过Hook的方式解决
 
 AsyncTask$SerialExecutor()
 
 
 public static void setFinalStatic(Field field, Object newValue) throws Exception {
        field.setAccessible(true);
        Field artField = Field.class.getDeclaredField("artField");
        artField.setAccessible(true);
        Object artFieldValue = artField.get(field);
        Field accessFlagsFiled = artFieldValue.getClass().getDeclaredField("accessFlags");
        accessFlagsFiled.setAccessible(true);
        accessFlagsFiled.setInt(artFieldValue, field.getModifiers() & ~Modifier.FINAL);
        field.set(null, newValue);
    }
    
    Java反射之如何判断类或变量、方法的修饰符(Modifier解析)--https://blog.csdn.net/xiao__gui/article/details/8141216
    
 
 int   getModifiers()

          返回此类或接口以整数编码的 Java语言修饰符。


 
 getModifiers 
 
 需要用到java.lang.reflect.Modifier这个类
 
 
        System.out.println(Modifier.toString(field.getModifiers()));

 Java基础篇:反射机制详解---https://blog.csdn.net/a745233700/article/details/82893076
 
 
 Class.forName("com.mysql.jdbc.Driver"); // 动态加载mysql驱动
 
 
 
 Stude
 
 mysql
 
 通过class类的静态方法:forName(String className)(最常用)            
 
 forName
 
 public Constructor getConstructor(Class... parameterTypes)
 
     Constructor con = clazz.getConstructor(null);
 
 
 
        Class stuClass = Class.forName("fanshe.method.Student");

 、反射main方法:
 
 数组在Java里是比较特殊的一种类型,它可以赋值给一个Object Reference。
 
 
 
import java.lang.reflect.Method;
 
 
 10、反射方法的其他使用--通过反射越过泛型检查:
        Field field = Integer.class.getDeclaredField("value");
        
        
        
 
 
 55
 
 java.lang.reflect.Filed.class中setInt与set的区别
 
 set    
 设置字段的值:
 *         Field --> public void set(Object obj,Object value):
 *                     参数说明:
 *                     1.obj:要设置的字段所在的对象;
 *                     2.value:要为字段设置的值;

 要求用包装类型(wrapped type),不能传基本类型参数(primitive type)。
 
 Java Field.get()取得对象的Field属性值--http://www.51gjie.com/java/795.html
 
 java.lang.reflect.Filed.class中setInt与set的区别---https://www.cnblogs.com/buguge/p/13914947.html
 
 
     //拿到了Field类的实例后就可以调用其中的方法了
    //方法:get(Object obj) 返回指定对象obj上此 Field 表示的字段的值
    System.out.println("属性值:  " + field.get(obj));

    //方法: set(Object obj, Object value)  将指定对象变量上此 Field 对象表示的字段设置为指定的新值
 
 
 set     get Object obj
 
  
        67 field=A.class.getDeclaredField("fild");   
        Field field=A.class.getDeclaredField("fild");  
 
 artFieldValue.getClass().getDeclaredField("accessFlags");
 
 accessFlags = true;
 
 
     Object artFieldValue = artField.get(field);
        Field accessFlagsFiled = artFieldValue.getClass().getDeclaredField("accessFlags");
        accessFlagsFiled.setAccessible(true);
        
        accessFlagsFiled.setInt(artFieldValue,getModifiers() &)
        
 
         field.set(null, newValue);
         field.set(null,newValue);78
 
 我们发现原生系统上对应系统版本的AbsListView里并没有UpdateBottomFlagTask
 
 setFinalStatic
 
 s.AsyncTask$3.done
 
 
 .AbsListView$UpdateBottomFlagTask.doInBackground
 .AbsListView$UpdateBottomFlagTask(doInBackground)
 
 
  at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:231)
  
  Android ROM编译时会将framework、app、bin等目录打入system.img中
 
 ,文件格式一般为yaffs2或ext
 yaffs2 ext
 
 yaffs2 ext
 
 LeakCanary
 
 另外我们可以在debug下使用StrictMode来检查Activity的泄露、
 
 Closeable对象没有被关闭等问题。
 
 另外我们可以在debug下使用StrictMode来检查Activity的泄露、Closeable对象没有被关闭等问题。
 debug StrictMode(
 )
 
 closeable
 
 另外我们可以在debug下使用StrictMode来检查Activity的泄露、Closeable对象没有被关闭等问题。
 
 
 StrictMode Activity Closeable对象没有被关闭等问题。
 
 
 矢。Android Studio也提供了非常好用的Memory Profiler,堆转储和分配跟踪器功能可以帮我们迅速定位问题。
 
 
 AOP增强辅助
 
 AOP增强辅助
 
 Memory Profiler,堆转储和分配跟踪器功能可以帮我们迅速定位问题。
 
 AOP增强辅助  Android Gradle 
 
 ClassNotFoundException
 
 
 Intent#getStringExtra()->
 
 getStringExtra
 
 
 我们专门制作了一个Gradle插件,只需要配置一下参数就可以将某个特定方法的调用替换成另一个方法:
 
 Gradle
 
 
 WaimaiBytecodeManipulator {
     replacements(
         "android/content/Intent.getIntExtra(Ljava/lang/String;I)I=com/waimai/IntentUtil.getInt(Landroid/content/Intent;Ljava/lang/String;I)I",
         "android/content/Intent.getStringExtra(Ljava/lang/String;)Ljava/lang/String;=com/waimai/IntentUtil.getString(Landroid/content/Intent;Ljava/lang/String;)Ljava/lang/String;",
         "android/content/Intent.getBooleanExtra(Ljava/lang/String;Z)Z=com/waimai/IntentUtil.getBoolean(Landroid/content/Intent;Ljava/lang/String;Z)Z",
         ...)
    }
}
 IntentUtil.getInt Landroid/content/Intent
 
 getBooleanExtra()    Z=com
 
 Intent.getXXXExtra        IntentUtil->IntentUtil
 
 
 
  美团外卖Android Crash治理之路----
 https://mp.weixin.qq.com/s?__biz=MjM5NjQ5MTI5OA==&mid=2651748107&idx=1&sn=55dff1b286e92cfb6aaee776df8ec89e&chksm=bd12ae468a652750a7624c30eca56f6f83347b16cdfb9153b647c6e5229a822b16724a1bbd9d&scene=38#wechat_redirect
 
 
 
 
 
 依赖库的问题
 
 NoClassDefFoundError、NoSuchFieldError、NoSuchMethodError等异常。
 NoSuchFieldError、NoSuchMethodError等异常。()
 
 NoClassDefFoundError、NoSuchFieldError、NoSuchMethodError等异常。
 NoSuchFieldError、NoSuchMethodError等异常。 NoSuchMethodError等异常。
 
 NoClassDefFoundError、NoSuchFieldError、NoSuchMethodError等异常。
 
 ()
 
 
 Android App v6.0->        Android App
 
 
 Defersor    Defensor。
 
 导致热修复功能 -> Defensor。
 
 Defensor。
 
 Defensor在编译时通过DexTask获取到所有的输入文件(也就是被编译过的class文件)
 
 页面跳转路由统一处理页面跳转
 
 
Defensor。
 
 
 业务模块的划分
 
 对于这个问题,我们的做法是App的业务模块化。业务模块化后,每个业务都有都有唯一包名和对应的负责人
 
 
 
 业务模块化本身也是工程架构优先需要考虑的事情之一。
 
 
 业务模块化后,每个业务都有都有唯一包名和对应的负责人
 
 
 页面跳转路由统一处理页面跳转
 
 页面跳转路由统一处理页面跳转
 
 ActivityNotFoundException        scheme        scheme
 
 
 启动Activity的地方,几乎是随处可见,无法预测哪一处会造成ActivityNotFoundException。
 
 
 scheme(ActivityNotFoundException adapter){
 }
 
 
 网络层统一处理API脏数据
 
 网络层统一处理API脏数据
 
 的脏数据,特别容易引起线上大面积的崩溃。
 
 
 scheme->
 但是JSON解析Model这个过程可能存在问题,例如没有返回数据或者返回了类型不对的数据
 
 
 Crash治理之路----009
 
 大图监控

上面讲到大对象是导致OOM的主要原因之一,而Bitmap是App里最常见的大对象类型,因此对占用内存过大的Bitmap对象的监控就很有必要了。
 
 
 OOM        Bitmap()
 
 大图监控
 
 
 问题,不会影响到UI层,返回给UI层的都是校验成功的数据。这样改造后,我们发现这类的Crash率有了极大的改善。

大图监控

上面讲到大对象是导致OOM的主要原因之一,而Bitmap是App里最常见的大对象类型,

因此对占用内存过大的Bitmap对象的监控就很有必要了。

我们用AOP方式Hook了三种常见图片库的加载图片回调方法,同时监控图片库加载图片时的两个维度:

加载图片使用的URL。外卖App中除静态资源外,所有图片都要求发布到专用的图片CDN服务器上,
加载图片时使用正则表达式匹配URL,除了限定CDN域名之外还要求所有图片加载时都要添加对应的动态缩放参数。

最终加载出的图片结果(也就是Bitmap对象)。我们知道Bitmap对象所占内存和其分辨率大小成正比,
而一般情况下在ImageView上设置超过自身尺寸的图片是没有意义的,
所以我们要求显示在ImageView中的Bitmap分辨率不允许超过View自身的尺寸(为了降低误报率也可以设定一个报警阈值)。

开发过程中,在App里检测到不合规的图片时会立即高亮出错的ImageView所在的位置并弹出对话框提示ImageView所在的Activity、
XPath和加载图片使用的URL等信息,如下图,辅助开发同学定位并解决问题。在Release环境下可以将报警信息上报到服务器,
实时观察数据,有问题及时处理。
 
 AOP增强辅助 Hook()
 
 
 我们用AOP方式Hook了三种常见图片库的加载图片回调方法,同时监控图片库加载图片时的两个维度:


 765 = textsize.bold        
 
 Hook
 
 
 加载图片使用的URL。外卖App中除静态资源外
 
 
 
加载图片使用的URL。外卖App中除静态资源外(),


最终加载出的图片结果(也就是Bitmap对象)

CDN URL    Bitmap

开发过程中,在App里检测到不合规的图片时会立即高亮出错的ImageView所在的位置并弹出对话框

CDN (URL ->Bitmap)

Activity、XPath

Activity、XPath()Xpath

Lint检查


Lint规则已经实现了Crash预防、Bug预防、


Crash Bug预防、

NotSerializableException
NotSerializableException


Crash预防、Bug预防、提升性能/安全和代码规范检查这些功能
 
 NotSerializableException()->()
 
 
 Lint检查  Crash治理56----
 
 
 Crash预防、Bug预防、提升性能/安全和代码规范检查这些功能。
 
 
 Lint检查可以在多个阶段执行,包括在本地手动检查、编码实时检查、编译时检查、commit时检查,
 
 以及在CI系统中提Pull Request时检查、打包时检查等,如下图所示。
 
 更详细的内容可参考《美团外卖Android Lint代码检查实践》。
 
 Android Lint代码检查Lin》78》。
 
 Pull Request
 
 提代码    Review  
 
 打包提测
 
 
 美团外卖Android平台化架构演进实践---
 https://tech.meituan.com/2018/03/16/meituan-food-delivery-android-architecture-evolution.html
 
 
 我们需要快速将代码重构为支持平台化的多工程模式
 
 支持平台化的多工程模式
 
 
 如果两端的代码没有统一而直接做平台化业务拆库,必然会导致问题的复杂化。
 
 必然会导致问题的复杂化。
 
 
 在这样的背景下,可以看出我们面临的问题相较于其他平台型App更为特殊和复杂:既要解决外卖业务平台化的问题,又要解决外卖App和外卖频道两端代码复用的问题。
 
 在实施平台化和两端代码复用的道路上并非一帆风顺
 
 平台化 和两端代码复用的道路上
 
 搜索库拆分实践
 
 搜索库拆分实践
 

 两端代码复用        两端代码复用
 
 
 经过多次讨论后,团队发起了两端代码复用的技术方案尝试,
 
 然而两端的搜索模块代码底层差异很大,BaseActivity和BaseFragment不统一,
 UI样式不统一,数据Model不统一,图片、网络、埋点不统一,
 并且两端发版周期也不一致。针对这些问题的解决方案是:
 
 通过代理屏蔽Activity和Fragment基类不统一的问题;
两端主工程style覆盖搜索库的UI样式;
搜索库使用独立的数据Model,上层去做数据适配;
其他差异通通抛出接口让上层实现;
和PM沟通尽量使产品需求和发版周期一致。
 style    Fragment();
 style Fragment();
 
 Model00
 18
 
 0.32  1.8
 
 由于需求和版本发布周期的差异
 
 
 UI样式不统一,数据Model不统一,图片、网络、埋点不统一,
 
 UI
 CommonLogic
 
 FlatCallBack
 
 FlatCallBack        CommonLogic
 
 5
 为两端的差异性
 
 
 组件提供个性化配置满足两端差异需求,如果无法满足再通过代理抛到上层处理。
 
 页面组件化是一个良好的设计,但它主要适用于解决Activity巨大化的问题
 
 
  
 美团外卖Android平台化架构演进实践---
 https://tech.meituan.com/2018/03/16/meituan-food-delivery-android-architecture-evolution.html
 
 另一方面,页面组件化也没有为2端差异性预留可伸缩的空间。
 
 
 MVP分层复用实践
 
 借鉴Clean MVP架构,根据职责将代码拆分为Presenter,Data Repository,Use Case,View,Model等角色;
 
 
 Presenter        Data()
 
 Use    Case,View,Model等角色;
 
 中间层实践
 
 们当然有想过通过中间层设计屏蔽两端的基础库差异。
 
 我们曾经在Volley和Retrofit之上封装了一层网络框架,对外暴露统一的接口
 
 ,对外暴露统一的接口,上层可以切换底层依赖Volley或是Retrofit
 
 (平台化实践)
 
 
 而实际上外卖平台化正是解决两端代码复用的一剂良药
 
 
 外卖平台化正是解决代码复用的
 
 
 
 平台化架构
 
 
  美团外卖Android平台化架构演进实践---
 https://tech.meituan.com/2018/03/16/meituan-food-delivery-android-architecture-evolution.html
 
 
 从底层到高层依次为平台层、业务层和宿主层。()//
 
 
 宿主层的内容包括,Waimai App壳和美团外卖频道Waimai-channel壳,这一层用于Application的初始化、
 
 dex加载和其他各种必要的组件或基础库的初88。
 
 Waimai    debug
 
 业务库通信框架->Waimai-channel
 
 
 每个工程都可以独立编译、独立打包;
每个工程内部的修改,不会影响其他工程;
业务库工程可以快速拆分出来,集成到其他App中。
但工程隔离带来的另一个问题是,同层间的业务库需要通信怎么办?这时候就需要提供业务库通信框架来解决这个问题。
 
 
 ServiceLoaders sdk        sdk
 
 因此我们需要在架构中提供同层间的通信框架,
 
 scheme    ServiceLoaders        sdk
 
 :scheme路由和美团自建的ServiceLoaders sdk。scheme路由本质上是利用Android的scheme原理进行通信,
 
 ServiceLoader本质上是利用的Java反射机制进行通信。
 
 scheme路由本质上是利用Android的scheme原理进行通信,ServiceLoader本质上是利用的Java反射机制进行通信。
 
 
 最终效果:所有业务页面的跳转,都需要通过平台层的scheme路由去分发。
 
 通过scheme路由,所有业务都得到解耦,不再需要相互依赖而可以实现页面的跳转和基本数据类型的传递。

serviceloader的调用如图所示:
 
 
   美团外卖Android平台化架构演进实践---
 https://tech.meituan.com/2018/03/16/meituan-food-delivery-android-architecture-evolution.html
 
 
 提供方和使用方通过平台层的一个接口作为双方交互的约束。
 
 外卖内核模块设计
 
 
 
 在集成zxing二维码时遇到预览拉伸的问题,原因:预览尺寸和surface view不一致。
解决方式:
针对于扫描时,二维码拉伸变形的问题,是因为zxing默认是针对横屏扫描的,所以,我们只需要改变x,y的计算,即横纵轴即可。此时,我们需要找到CameraConfigurationManager类:
找到findBestPreviewSizeValue方法:
将newDiff的变量计算代码改成如下:
int newDiff=Math.abs(newY - screenResolution.x) + Math.abs(newX - screenResolution.y);
 
 而实际上外卖业务的子业务是否应该和垂类业务保持同层是一个目前无法确定的事情。
 
 
 
 
 
 在实践的过程中,我们也遇到业务本身上就不好划分层级边界的业务。大家可以从美团外卖三层架构图上,
 看出外卖业务库,像商家、订单等,是和外卖的垂类业务库是同级的。
 而实际上外卖业务的子业务是否应该和垂类业务保持同层是一个目前无法确定的事情。

目前,外卖接入的垂类业务商超业务,是隶属于外卖业务的子频道,它依然依赖着外卖的核心model、
核心服务,包括商品管理、订单管理、购物车管理等,因此目前它和外卖业务的商家、
订单这样的子业务库同层是没有问题的。但随着商超业务的发展,商超业务未来可能会建设自己的商品管理、
订单管理、购物车管理的服务,那么到时商超业务就会上升到和外卖业务一样同层的业务。这时候,
外卖核心管理服务,处在平台层,就会导致架构的层级边界变得不再清晰。

我们的解决办法是通过设计一个属于外卖业务的内核模块来适应未来的变化,内核模块的设计如图:

内圈为基础模型类,这些模型类构成了外卖核心业务(从门店→点菜→购物车→订单)的基础;
中间圈为依赖基础模型类构建的基础服务(CRUD);
最外圈为外卖的各维度业务,向内依赖基础模型圈和外卖基础服务圈。
如果未来确定外卖平台需要接入更多和外卖平级的业务,且最内圈都完全不一样,我们将把外卖内核模块上移,
在外卖业务子库下建立对内核模块的依赖;如果未来只是有更多的外卖子业务的接入,
那就继续保留我们现在的架构;如果未来接入的业务基础模型类一样,但自己的业务服务需要分化,
那么我们将对保留内核模块最核心的内圈,并抽象出服务层由外卖和商超上层自己实现真正的服务。

循环的wifi发送---https://blog.csdn.net/ningchao328/article/details/53023135

https://www.cnblogs.com/pythoncd/p/10367144.html ----Android反编译三件套 apktool 、dex2jar、jd-gui
1.还是老话下载三件套(点击下载)

adb shell "dumpsys window | grep mCurrentFocus"


标题-》添加摄像机
vstc.eye4zx.activity.AAddNetCameraActivity


声波方式->vstc.eye4zx.activity.tools.InputWifiActivity

一键配置WIFI->vstc.eye4zx.activity.wirelessConfiguration.WirelessSearchActivity


 view = new Intent(mContext, vstc/eye4zx/activity/tools/InputWifiActivity);
                view.putExtra("sendMac", sendMac);
                view.putExtra("currentBssid", currentBssid);
                view.putExtra("resultString", "");
                view.putExtra("intentFlag", 1);
                view.putExtra("isExist", 0);
                view.putExtra("camera_type", cameratype);
                view.putExtra("ipc_camera_type", ipc_camera);
                mContext.startActivity(view);

  case 2131296617: 
            view = new Intent(mContext, vstc/eye4zx/activity/AAddNetCameraActivity);
            view.putExtra("camera_type", 2);
            view.putExtra("ipc_camera_type", -1);
            startActivity(view);
            return;

        case 2131296614: 
            if(NetTypeUtils.isMobile(this))
            {
                view = new Intent(mContext, vstc/eye4zx/activity/AAddNetCameraActivity);
                view.putExtra("camera_type", 0);
                view.putExtra("ipc_camera_type", -1);
                startActivity(view);
                return;
            }
            if(reqPermission("location_auto"))
            {
                return;
            } else
            {
                view = new Intent(mContext, vstc/eye4zx/activity/AAddNetCameraActivity);
                view.putExtra("camera_type", 0);
                view.putExtra("ipc_camera_type", -1);
                startActivity(view);
                return;
            }

  美团外卖Android平台化架构演进实践---
 https://tech.meituan.com/2018/03/16/meituan-food-delivery-android-architecture-evolution.html
 
 
 工程内代码隔离
 
 那我们又想到规范代码,用包名去人为约定,但靠包名约束的代码,边界模糊,时不时的紧急需求,就把包名约定打破了,而且资源文件的摆放也是任意的,迁移成本高。
 
 
 微信Android模块化架构重构实践---(https://mp.weixin.qq.com/s/6Q818XA5FaHd7jJMFBG60w)
 
 独立的通信进程保持长连接的稳定性,独立的webview进程也阻隔了内存泄露导致的问题。
 
 相互独立的p_xxx工程中,这是微信第一次进行模块化架构的重构
 
 微信Android基本没有大的架构改动。配合gradle的编译,以及git的多分支并行开发,
 
 
 gradle git()
 
 快速跟进微信业务最新的支撑组件、协议、安全性、后台服务等能力,
 
 
gradle git() 我们大量适用Event事件总线作为模块间通信的方式,也基本是唯一的方式
 
 
 使用Event作为通信的媒介,自然要有定义它的地方,好让模块之间都能知道Event结构是怎样的
 
 Event
 
 就这样越来越多的代码很“自然的”被下沉到基础工程中。
 
 
 
 而现在不一AD03,再简单的逻辑堆积起来也会变复杂。此时,在模块生命周期外的逻辑基本上只能放主工程。


 67->
 
 
 重塑模块化
重塑模块化,我们分解为三个目标:

改变通信方式

重新设计模块

约束代码边界


 虑开发的便利性和协议的约束性。


 ,类似终端和服务器间的通信或restful这种55
 
 restful
 
 
 
所以我们想要简单点。经过权衡,我们决定用模块提供“SDK”的方式作为它与其他模块进行通信的手段。
 
 
 SDK”的方式作为它与其他模块进行通信的手段。
 
 
  微信Android模块化架构重构实践---(https://mp.weixin.qq.com/s/6Q818XA5FaHd7jJMFBG60w)
 
 
确定了方案,实现起来就很简单。我们的注册方式和接口访问都很简单。

用接口注册,再用接口访问,不暴露实现细节。如下图。

它们都通过基础组件访问网络、存储等服务,互相独立并行。

新的p工程架构支撑了微信更快速的业务发展,配合多分支开发模式的改进,

能够支持团队多分支多team的team。
 
 team(67,)
 
 plugin sdk
 
 此外当初为了平滑切换到gradle避免结构变化太大以及太多module,
 我们将所有工程都对接到一个module上。
 
 
 所以我们想要简单点。经过权衡,
 
 我们决定用模块提供“SDK”的方式作为它与其他模块进行通信的手段。
 
 
 空气检测器:
 空气质量:
 
 project plugin-messager-foundation
 
 include_with_api 
 
 
 
重新设计模块

要把模块重新设计,还要做好几件事。首先,消灭代码经常下沉的“三不管区域”——基础工程。这意味着原来的模块要把之前下沉的代码重新认领回去。
 
 
 为了巩固替代基础工程的mmkernel层,不被滥用为新的代码堆放处,
 
 顺便还要解决中心化问题。就必须强化它的职责和设计。
 
 mmkernel
 
 
 为何设计configure()和execute(),这可以理解为“收集任务”和“执行任务”的两个阶段
 
 约束代码边界
 

从之前的经验看,要想约束好代码的边界不被破坏,编译上的隔离是唯一法宝。

,我们实现了一种简单易用、粒度更细的工程组织结构——pins工程结构

pins工程结构
 

 pins工程能在module之内再次构建完整的多子工程结构,
 
 通过project.properties来指定编译依赖关系。
 
 
 project.properties来指定编译依赖关系。
 
 
 
避免的超量module的创建,轻量        module


module ->gradle

我们参考了很多业界开放和发表的架构设计。总的来说,

目前Android端App整体架设计上,除了聚焦在“大前端”之外,

基本上都在“插件化/应用沙盒”上面下功夫。
可以参考如atlas、small、DroidPlugin、DynamicApk等等方案,

不难发现让模块最终具备动态性是它们最核心的能力。

atlas small DroidPlugin、DynamicApk等等方案, DynamicApk等等方案,


 从几个成熟方案中都能看到hook Android框架、修改aapt、
 替换或包装android gradle plugin、代理组件等等设计
 
 微信Android模块化架构重构实践---(https://mp.weixin.qq.com/s/6Q818XA5FaHd7jJMFBG60w)
 
 hook Android aapt  android gradle plugin 
 
 hook Android aapt  android gradle plugin
 
 
 代码审查是由leader对申请回流主干的Merge Request进行review
 
 Merge Request(review)        Code Review
 
 aapt(android)
 
 代码审查率和模块认领率都在提高。Merge Request(review)
 
 代码审查是由leader对申请回流主干的Merge Request进行review,
 
 leader Merge Request review 
 
 
 可以考虑通过代码的审查进行监督,也可以通过开发简单的编译脚本,检查是否有不当依赖产生。
 
 
 模块的一般组织方式 

设计一个模块,我们有一个一般性的组织方式,可以将模块分成三个工程:

implementation工程、api工程、library工程。
 
 implementation工程、api工程、library工程。
 
 api工程、library工程。 library工程。
 
 
 implementation工程、api工程、library工程。
 
 implementation工程、api工程、library工程。 api工程、library工程。 library工程。
 
 
 能。例如,我们实现一个表情模块,library工程提供表情的资源、
 
 表情的渲染和播放能力,api工程提供了使用表情的服务接口,
 
 implementation工程则提供了api的实
 
 分析依赖关系的工具
 
 Analyze Dependencies
 
 以来微信的重构都是随着版本迭代进行“拆分”-> “灰度” -> “回流”的循环节奏。
 
 拆分”- 灰度、回流-》
 
 “设计系统的组织,其产生的设计和架构等价于组织间的沟通结构;
 
 ——用纯粹的模块化保持后续架构的灵活性和健壮性,重新强调依赖、
 
 强调应用状态和生命周期、强化代码的边界
 
 
 
 
 
 
 
 
 
 
 
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值