Code Monkey home page Code Monkey logo

blogandcode's People

Contributors

hankcoder avatar qmsggg avatar

Stargazers

 avatar

Watchers

 avatar  avatar  avatar

blogandcode's Issues

Android Touch事件分发机制系列一基本流程梳理

Touch事件分发中只有两个主角:ViewGroup和View。Activity的Touch事件事实上是调用它内部的ViewGroup的Touch事件,可以直接当成ViewGroup处理。

View在ViewGroup内,ViewGroup也可以在其他ViewGroup内,这时候把内部的ViewGroup当成View来分析。

ViewGroup的相关事件有三个:onInterceptTouchEvent、dispatchTouchEvent、onTouchEvent。View的相关事件只有两个:dispatchTouchEvent、onTouchEvent。

先分析ViewGroup的处理流程:首先得有个结构模型概念:ViewGroup和View组成了一棵树形结构,最顶层为Activity的ViewGroup,下面有若干的ViewGroup节点,每个节点之下又有若干的ViewGroup节点或者View节点,依次类推。如图:
image
当一个Touch事件(触摸事件为例)到达根节点,即Acitivty的ViewGroup时,它会依次下发,下发的过程是调用子View(ViewGroup)的dispatchTouchEvent方法实现的。简单来说,就是ViewGroup遍历它包含着的子View,调用每个View的dispatchTouchEvent方法,而当子View为ViewGroup时,又会通过调用ViwGroup的dispatchTouchEvent方法继续调用其内部的View的dispatchTouchEvent方法。上述例子中的消息下发顺序是这样的:①-②-⑤-⑥-⑦-③-④。dispatchTouchEvent方法只负责事件的分发,它拥有boolean类型的返回值,当返回为true时,顺序下发会中断。在上述例子中如果⑤的dispatchTouchEvent返回结果为true,那么⑥-⑦-③-④将都接收不到本次Touch事件。来个简单版的代码加深理解:

 /**
     * ViewGroup
     * @param ev
     * @return
     */
    public boolean dispatchTouchEvent(MotionEvent ev){
        ....//其他处理,在此不管
        View[] views=getChildView();
        for(int i=0;i<views.length;i++){
           //判断下Touch到屏幕上的点在该子View上面 
            if(...){
            if(views[i].dispatchTouchEvent(ev))
              return true;
             }
        }
        ...//其他处理,在此不管
    }
    /**
     * View
     * @param ev
     * @return
     */
    public boolean dispatchTouchEvent(MotionEvent ev){
        ....//其他处理,在此不管
        return false;
    }

在此可以看出,ViewGroup的dispatchTouchEvent是真正在执行“分发”工作,而View的dispatchTouchEvent方法,并不执行分发工作,或者说它分发的对象就是自己,决定是否把touch事件交给自己处理,而处理的方法,便是onTouchEvent事件,事实上子View的dispatchTouchEvent方法真正执行的代码是这样的

/**
     * View
     * @param ev
     * @return
     */
    public boolean dispatchTouchEvent(MotionEvent ev){
        ....//其他处理,在此不管
        return onTouchEvent(event);
    }

一般情况下,我们不该在普通View内重写dispatchTouchEvent方法,因为它并不执行分发逻辑。当Touch事件到达View时,我们该做的就是是否在onTouchEvent事件中处理它。

那么,ViewGroup的onTouchEvent事件是什么时候处理的呢?当ViewGroup所有的子View都返回false时,onTouchEvent事件便会执行。由于ViewGroup是继承于View的,它其实也是通过调用View的dispatchTouchEvent方法来执行onTouchEvent事件。

在目前的情况看来,似乎只要我们把所有的onTouchEvent都返回false,就能保证所有的子控件都响应本次Touch事件了。但必须要说明的是,这里的Touch事件,只限于Acition_Down事件,即触摸按下事件,而Aciton_UP和Action_MOVE却不会执行。事实上,一次完整的Touch事件,应该是由一个Down、一个Up和若干个Move组成的。Down方式通过dispatchTouchEvent分发,分发的目的是为了找到真正需要处理完整Touch请求的View。当某个View或者ViewGroup的onTouchEvent事件返回true时,便表示它是真正要处理这次请求的View,之后的Aciton_UP和Action_MOVE将由它处理。当所有子View的onTouchEvent都返回false时,这次的Touch请求就由根ViewGroup,即Activity自己处理了。

看看改进后的ViewGroup的dispatchTouchEvent方法

View mTarget=null;//保存捕获Touch事件处理的View
    public boolean dispatchTouchEvent(MotionEvent ev) {

        //....其他处理,在此不管
        
        if(ev.getAction()==KeyEvent.ACTION_DOWN){
            //每次Down事件,都置为Null

            if(!onInterceptTouchEvent()){
            mTarget=null;
            View[] views=getChildView();
            for(int i=0;i<views.length;i++){
                if(views[i].dispatchTouchEvent(ev))
                    mTarget=views[i];
                    return true;
            }
          }
        }
        //当子View没有捕获down事件时,ViewGroup自身处理。这里处理的Touch事件包含Down、Up和Move
        if(mTarget==null){
            return super.dispatchTouchEvent(ev);
        }
        //...其他处理,在此不管
        if(onInterceptTouchEvent()){
         //...其他处理,在此不管    
         }
//这一步在Action_Down中是不会执行到的,只有Move和UP才会执行到。
        return mTarget.dispatchTouchEvent(ev);

    }

ViewGroup还有个onInterceptTouchEvent,看名字便知道这是个拦截事件。这个拦截事件需要分两种情况来说明:

1.假如我们在某个ViewGroup的onInterceptTouchEvent中,将Action为Down的Touch事件返回true,那便表示将该ViewGroup的所有下发操作拦截掉,这种情况下,mTarget会一直为null,因为mTarget是在Down事件中赋值的。由于mTarge为null,该ViewGroup的onTouchEvent事件被执行。这种情况下可以把这个ViewGroup直接当成View来对待。

2.假如我们在某个ViewGroup的onInterceptTouchEvent中,将Acion为Down的Touch事件都返回false,其他的都返回True,这种情况下,Down事件能正常分发,若子View都返回false,那mTarget还是为空,无影响。若某个子View返回了true,mTarget被赋值了,在Action_Move和Aciton_UP分发到该ViewGroup时,便会给mTarget分发一个Action_Delete的MotionEvent,同时清空mTarget的值,使得接下去的Action_Move(如果上一个操作不是UP)将由ViewGroup的onTouchEvent处理。

情况一用到的比较多,情况二个人还未找到使用场景。

从头到尾总结一下:

1.Touch事件分发中只有两个主角:ViewGroup和View。ViewGroup包含onInterceptTouchEvent、dispatchTouchEvent、onTouchEvent三个相关事件。View包含dispatchTouchEvent、onTouchEvent两个相关事件。其中ViewGroup又继承于View。

2.ViewGroup和View组成了一个树状结构,根节点为Activity内部包含的一个ViwGroup。

3.触摸事件由Action_Down、Action_Move、Aciton_UP组成,其中一次完整的触摸事件中,Down和Up都只有一个,Move有若干个,可以为0个。

4.当Acitivty接收到Touch事件时,将遍历子View进行Down事件的分发。ViewGroup的遍历可以看成是递归的。分发的目的是为了找到真正要处理本次完整触摸事件的View,这个View会在onTouchuEvent结果返回true。

5.当某个子View返回true时,会中止Down事件的分发,同时在ViewGroup中记录该子View。接下去的Move和Up事件将由该子View直接进行处理。由于子View是保存在ViewGroup中的,多层ViewGroup的节点结构时,上级ViewGroup保存的会是真实处理事件的View所在的ViewGroup对象:如ViewGroup0-ViewGroup1-TextView的结构中,TextView返回了true,它将被保存在ViewGroup1中,而ViewGroup1也会返回true,被保存在ViewGroup0中。当Move和UP事件来时,会先从ViewGroup0传递至ViewGroup1,再由ViewGroup1传递至TextView。

6.当ViewGroup中所有子View都不捕获Down事件时,将触发ViewGroup自身的onTouch事件。触发的方式是调用super.dispatchTouchEvent函数,即父类View的dispatchTouchEvent方法。在所有子View都不处理的情况下,触发Acitivity的onTouchEvent方法。

7.onInterceptTouchEvent有两个作用:1.拦截Down事件的分发。2.中止Up和Move事件向目标View传递,使得目标View所在的ViewGroup捕获Up和Move事件。

另外,上文所列出的代码并非真正的源码,只是概括了源码在事件分发处理中的核心处理流程,真正源码各位可以自己去看,包含了更丰富的内容。

补充:

“触摸事件由Action_Down、Action_Move、Aciton_UP组成,其中一次完整的触摸事件中,Down和Up都只有一个,Move有若干个,可以为0个。”,这里补充下其实UP事件是可能为0个的。

最近刚好在做一个手势放大缩小移动图片的Demo,对此有了更多的理解。对于onInterceptTouchEvent事件,它的应用场景在很多带scroll效果的ViewGroup中都有体现。设想一下再一个ViewPager中,每个Item都是个ImageView,我们需要对这些ImageView做Matrix操作,这不可避免要捕获掉Touch事件,但是我们又需要做到不影响ViewPager翻页效果,这又必须保证ViewPager能捕获到Move事件,于是,ViewPager的onInterceptTouchEvent会对Move事件做一个过滤,当适当条件的Move事件(持续若干事件或移动若干距离,这里我没读源码只是猜测)触发时,并会拦截掉,返回子View一个Action_Cancel事件。这个时候子View就没有Up事件了,很多需要在Up中处理的事物要转到Cancel中处理。

Android 技术关注(每天必看)

  • Android 开发工程师进阶指南,推荐一些值得订阅的 Android 技术专栏
    Android 的入门资料在互联网上可以用泛滥来形容。如果你有计算机基础,想快速学习 Android,做几个界面很轻易就能做到。然而移动创业热潮退去后,对 Android 开发者的能力要求也越来越高。当一个已经入门的开发者,想要成为一个更好的 Android 开发者的时候,就会发现互联网的资料太琐碎,而且资料的好坏也难辨。常常都会困惑我要如何提高自己,哪里有好的学习资料。
    今天给大家推荐一些值得订阅的 Android 技术专栏,具体订阅方式查看文末。

    • 1、《解读 Android 系统架构》小米系统工程师 Gityuan 的专栏,从源码角度,带领大家一睹Android系统架构;从App到framework,native,乃至Linux内核;从上至下地深度解读Android架构设计。
    • 2、《Android 开源库的源码导读》 包含 Retrofit 、 Okio、OkHttp、RxJava 原理剖析。作者 Piasy 清华大学计算机系,目前就职于 YOLO,带领安卓团队。同时作者还有其他专题技术专栏《Android 架构系列》 、《Piasy 的 WebRTC 专栏》
    • 3、《Android 插件化原理解析》 以DroidPlugin为例讲解插件框架的原理,揭开插件化的神秘面纱;同时还能帮助深入理解Android Framewrok;作者田维术,前360,现蚂蚁金服Android工程师。
    • 4、《安卓自定义 View 教程》作者GcsSloop, 一名生活在2.5次元的魔法师,我一直在致力于研究基础魔法,并且始终坚信利用这些基础魔法,可以创造出华丽而又强大的高级魔法。以下为专栏内容简介。
    • 5、《进阶全栈工程师之路》主要面向想往全栈进阶的中高级移动端开发者,虽然很多内容可能对所有端的工程师都是通用的,但更多还是从移动端开发者的立场去展开。
    • 6、《张鑫的技术团队管理心得》关于作者:美团点评·终端技术Leader;;关于专栏内容:互联网团队管理,项目管理,终端技术方面的思考。
    • 7、Android开发书籍推荐:从入门到精通系列学习路线书籍介绍,整理收集开发大牛的学习经验,以便让我们少走弯路,更快速成长。希望这个系列可以成为大家手头应对新手的好答案。
    • 8、一个老鸟发的公司内部整理的 Android 学习路线图 ,一个将近十年的技术团队leader分享的他给公司内部小伙伴整理的路线图。
    • 9、中文技术文档的写作规范,阮一峰总结的一个中文技术写作规,分享给爱写技术文章的各位同学,记得在写作时刻不忘记格式规范。一个格式正确的文章会让读者有更加愉悦的阅读体验,也会让自己的文字显得更加专业。
    • 10、Google Tech Dev Guide,Google 出品的技术开发指南:和 Google 一起成长,提升自己的技术技能。 页面一如既往的精美哈。
    • 11、《Android 开发工程师面试指南》这份指南包含了大部分Android开发的基础、进阶知识,不仅可以帮助准备面试的同学,也可以帮助正在学习和工作的同学梳理自己的知识点。同时还有来自百度、小米、乐视、美团、58、猎豹、360、新浪、搜狐内部题库。
  • Android技术周报

  • Android Google Developer

  • Android学习文档和工具

Android 混淆使用手册

转-学习使用,自己逐步增加

综述

毫无疑问,混淆是打包过程中最重要的流程之一,在没有特殊原因的情况下,所有 app 都应该开启混淆。

首先,这里说的的混淆其实是包括了代码压缩、代码混淆以及资源压缩等的优化过程。依靠 ProGuard,混淆流程将主项目以及依赖库中未被使用的类、类成员、方法、属性移除,这有助于规避64K方法数的瓶颈;同时,将类、类成员、方法重命名为无意义的简短名称,增加了逆向工程的难度。而依靠 Gradle 的 Android 插件,我们将移除未被使用的资源,可以有效减小 apk 安装包大小。

本文由两部分构成,第一部分给出混淆的最佳实践,力求让零基础的新手都可以直接使用混淆;第二部分会介绍一下混淆的整体、自定义混淆规则的语法与实践、自定义资源保持的规则等。

一、Android混淆最佳实践

1. 混淆配置

一般情况下,app module 的 build.gradle 文件默认会有如下结构:

android {
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

因为开启混淆会使编译时间变长,所以debug模式下不应该开启。我们需要做的是:

将release下 minifyEnabled的值改为true,打开混淆;

加上shrinkResources true,打开资源压缩。

修改后文件内容如下:

android {
    buildTypes {
        release {
            minifyEnabled true
            shrinkResources true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

2. 自定义混淆规则

在 app module 下默认生成了项目的自定义混淆规则文件 proguard-rules.pro,多方调研后,一份适用于大部分项目的混淆规则最佳实践如下:

#指定压缩级别
-optimizationpasses 5

#不跳过非公共的库的类成员
-dontskipnonpubliclibraryclassmembers

#混淆时采用的算法
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*

#把混淆类中的方法名也混淆了
-useuniqueclassmembernames

#优化时允许访问并修改有修饰符的类和类的成员 
-allowaccessmodification

#将文件来源重命名为“SourceFile”字符串
-renamesourcefileattribute SourceFile
#保留行号
-keepattributes SourceFile,LineNumberTable

#保持所有实现 Serializable 接口的类成员
-keepclassmembers class * implements java.io.Serializable {
    static final long serialVersionUID;
    private static final java.io.ObjectStreamField[] serialPersistentFields;
    private void writeObject(java.io.ObjectOutputStream);
    private void readObject(java.io.ObjectInputStream);
    java.lang.Object writeReplace();
    java.lang.Object readResolve();
}

#Fragment不需要在AndroidManifest.xml中注册,需要额外保护下
-keep public class * extends android.support.v4.app.Fragment
-keep public class * extends android.app.Fragment

# 保持测试相关的代码
-dontnote junit.framework.**
-dontnote junit.runner.**
-dontwarn android.test.**
-dontwarn android.support.test.**
-dontwarn org.junit.**

真正通用的、需要添加的就是上面这些,除此之外,需要每个项目根据自身的需求添加一些混淆规则:

第三方库所需的混淆规则。正规的第三方库一般都会在接入文档中写好所需混淆规则,使用时注意添加。

在运行时动态改变的代码,例如反射。比较典型的例子就是会与 json 相互转换的实体类。假如项目命名规范要求实体类都要放在model包下的话,可以添加类似这样的代码把所有实体类都保持住: -keep public class .Model. {*;}

  • JNI中调用的类。
  • WebView中 JavaScript调用的方法
  • Layout布局使用的 View构造函数、android:onClick等。

3. 检查混淆结果

混淆过的包必须进行检查,避免因混淆引入的bug。

一方面,需要从代码层面检查。使用上文的配置进行混淆打包后在 /build/outputs/mapping/release/ 目录下会输出以下文件:

  • dump.txt描述APK文件中所有类的内部结构

  • mapping.txt提供混淆前后类、方法、类成员等的对照表

  • seeds.txt列出没有被混淆的类和成员

  • usage.txt列出被移除的代码

我们可以根据 seeds.txt 文件检查未被混淆的类和成员中是否已包含所有期望保留的,再根据 usage.txt 文件查看是否有被误移除的代码。

另一方面,需要从测试方面检查。将混淆过的包进行全方面测试,检查是否有 bug 产生。

4. 解出混淆栈

混淆后的类、方法名等等难以阅读,这固然会增加逆向工程的难度,但对追踪线上 crash 也造成了阻碍。我们拿到 crash 的堆栈信息后会发现很难定位,这时需要将混淆反解。

在 /tools/proguard/ 路径下有附带的的反解工具(Window 系统为 proguardgui.bat,Mac 或 Linux 系统为 proguardgui.sh)。

这里以 Window 平台为例。双击运行 proguardgui.bat 后,可以看到左侧的一行菜单。点击 ReTrace,选择该混淆包对应的 mapping 文件(混淆后在 /build/outputs/mapping/release/ 路径下会生成 mapping.txt 文件,它的作用是提供混淆前后类、方法、类成员等的对照表),再将 crash 的 stack trace 黏贴进输入框中,点击右下角的 ReTrace ,混淆后的堆栈信息就显示出来了。

以上使用 GUI 程序进行操作,另一种方式是利用该路径下的 retrace 工具通过命令行进行反解,命令是

retrace.bat|retrace.sh [-verbose] mapping.txt [<stacktrace_file>]
例如:

retrace.bat -verbose mapping.txt obfuscated_trace.txt
注意事项:

  1. 所有在 AndroidManifest.xml 涉及到的类已经自动被保持,因此不用特意去添加这块混淆规则。(很多老的混淆文件里会加,现在已经没必要)

  2. proguard-android.txt 已经存在一些默认混淆规则,没必要在 proguard-rules.pro 重复添加,该文件具体规则见附录1:

二、混淆简介

Android中的“混淆”可以分为两部分,一部分是 Java 代码的优化与混淆,依靠 proguard 混淆器来实现;另一部分是资源压缩,将移除项目及依赖的库中未被使用的资源(资源压缩严格意义上跟混淆没啥关系,但一般我们都会放一起讲)。

1. 代码压缩

代码混淆是包含了代码压缩、优化、混淆等一系列行为的过程。如上图所示,混淆过程会有如下几个功能:

压缩。移除无效的类、类成员、方法、属性等;

优化。分析和优化方法的二进制代码;根据proguard-android-optimize.txt中的描述,优化可能会造成一些潜在风险,不能保证在所有版本的Dalvik上都正常运行。

混淆。把类名、属性名、方法名替换为简短且无意义的名称;

预校验。添加预校验信息。这个预校验是作用在Java平台上的,Android平台上不需要这项功能,去掉之后还可以加快混淆速度。

这四个流程默认开启。

在 Android 项目中我们可以选择将“优化”和“预校验”关闭,对应命令是-dontoptimize、 -dontpreverify(当然,默认的 proguard-android.txt 文件已包含这两条混淆命令,不需要开发者额外配置)。

2. 资源压缩

资源压缩将移除项目及依赖的库中未被使用的资源,这在减少 apk 包体积上会有不错的效果,一般建议开启。具体做法是在 build.grade 文件中,将 shrinkResources 属性设置为 true。需要注意的是,只有在用minifyEnabled true开启了代码压缩后,资源压缩才会生效。

资源压缩包含了“合并资源”和“移除资源”两个流程。

“合并资源”流程中,名称相同的资源被视为重复资源会被合并。需要注意的是,这一流程不受shrinkResources属性控制,也无法被禁止, gradle 必然会做这项工作,因为假如不同项目中存在相同名称的资源将导致错误。gradle 在四处地方寻找重复资源:

src/main/res/ 路径

不同的构建类型(debug、release等等)

不同的构建渠道

项目依赖的第三方库

合并资源时按照如下优先级顺序:

依赖 -> main -> 渠道 -> 构建类型
举个例子,假如重复资源同时存在于main文件夹和不同渠道中,gradle 会选择保留渠道中的资源。

同时,如果重复资源在同一层次出现,比如src/main/res/ 和 src/main/res/,则 gradle 无法完成资源合并,这时会报资源合并错误。

“移除资源”流程则见名知意,需要注意的是,类似代码,混淆资源移除也可以定义哪些资源需要被保留,这点在下文给出

三、自定义混淆规则

在上文“混淆配置”中有这样一行代码

proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
这行代码定义了混淆规则由两部分构成:位于 SDK 的 tools/proguard/ 文件夹中的 proguard-android.txt 的内容以及默认放置于模块根目录的 proguard-rules.pro 的内容。前者是 SDK 提供的默认混淆文件(内容见附录1),后者是开发者自定义混淆规则的地方。

1. 常见混淆命令:

  • optimizationpasses
  • dontoptimize
  • dontusemixedcaseclassnames
  • dontskipnonpubliclibraryclasses
  • dontpreverify
  • dontwarn
  • verbose
  • optimizations
  • keep
  • keepnames
  • keepclassmembers
  • keepclassmembernames
  • keepclasseswithmembers
  • keepclasseswithmembernames

在第一部分 Android 混淆最佳实践中已介绍部分需要使用到的混淆命令,这里不再赘述,详情请查阅官网。需要特别介绍的是与保持相关元素不参与混淆的规则相关的几种命令:
命令 作用
-keep 防止类和成员被移除或者被重命名
-keepnames 防止类和成员被重命名
-keepclassmembers 防止成员被移除或者被重命名
-keepnames 防止成员被重命名
-keepclasseswithmembers 防止拥有该成员的类和成员被移除或者被重命名
-keepclasseswithmembernames 防止拥有该成员的类和成员被重命名

2. 保持元素不参与混淆的规则

形如:

[保持命令] [类] {
    [成员] 
}

“类”代表类相关的限定条件,它将最终定位到某些符合该限定条件的类。它的内容可以使用:

具体的类

访问修饰符(public、 protected、private)

通配符*,匹配任意长度字符,但不含包名分隔符(.)

通配符**,匹配任意长度字符,并且包含包名分隔符(.)

extends,即可以指定类的基类

implement,匹配实现了某接口的类

$,内部类

“成员”代表类成员相关的限定条件,它将最终定位到某些符合该限定条件的类成员。它的内容可以使用:

匹配所有构造器
匹配所有域
匹配所有方法
通配符*,匹配任意长度字符,但不含包名分隔符(.)

通配符**,匹配任意长度字符,并且包含包名分隔符(.)

通配符***,匹配任意参数类型

…,匹配任意长度的任意类型参数。比如void test(…)就能匹配任意 void test(String a) 或者是 void test(int a, String b) 这些方法。

访问修饰符(public、 protected、private)

举个例子,假如需要将name.huihui.test包下所有继承 Activity的public类及其构造函数都保持住,可以这样写:

-keep public class name.huihui.test.** extends Android.app.Activity {

}

3. 常用的自定义混淆规则

不混淆某个类

-keep public class name.huihui.example.Test { *; }
不混淆某个包所有的类

-keep class name.huihui.test.** { *; }
不混淆某个类的子类

-keep public class * extends name.huihui.example.Test { *; }
不混淆所有类名中包含了“model”的类及其成员

-keep public class .model. {*;}
不混淆某个接口的实现

-keep class * implements name.huihui.example.TestInterface { *; }
不混淆某个类的构造方法

-keepclassmembers class name.huihui.example.Test {
public ();
}
不混淆某个类的特定的方法

-keepclassmembers class name.huihui.example.Test {
public void test(java.lang.String);
}
四、自定义资源保持规则

  1. keep.xml

用shrinkResources true开启资源压缩后,所有未被使用的资源默认被移除。假如你需要定义哪些资源必须被保留,在 res/raw/ 路径下创建一个 xml 文件,例如 keep.xml。

通过一些属性的设置可以实现定义资源保持的需求,可配置的属性有:

tools:keep 定义哪些资源需要被保留(资源之间用“,”隔开)

tools:discard 定义哪些资源需要被移除(资源之间用“,”隔开)

tools:shrinkMode 开启严格模式

当代码中通过 Resources.getIdentifier() 用动态的字符串来获取并使用资源时,普通的资源引用检查就可能会有问题。例如,如下代码会导致所有以“img_”开头的资源都被标记为已使用。

String name = String.format("img_%1d", angle + 1);
res = getResources().getIdentifier(name, "drawable", getPackageName());
我们可以设置 tools:shrinkMode 为 strict 来开启严格模式,使只有确实被使用的资源被保留。

以上就是自定义资源保持规则相关的配置,举个例子:


2. 移除替代资源

一些替代资源,例如多语言支持的 strings.xml,多分辨率支持的 layout.xml 等,在我们不需要使用又不想删除掉时,可以使用资源压缩将它们移除。

我们使用 resConfig 属性来指定需要支持的属性,例如

android {
defaultConfig {
...
resConfigs "en", "fr"
}
}
其他未显式声明的语言资源将被移除。

参考资料

Shrink Your Code and Resources

proguard

Android安全攻防战,反编译与混淆技术完全解析(下)

Android混淆从入门到精通

Android代码混淆之ProGuard

附录

proguard-android.txt文件内容

#包名不混合大小写
-dontusemixedcaseclassnames

#不跳过非公共的库的类
-dontskipnonpubliclibraryclasses

#混淆时记录日志
-verbose

#关闭预校验
-dontpreverify

#不优化输入的类文件
-dontoptimize

#保护注解
-keepattributes Annotation

#保持所有拥有本地方法的类名及本地方法名
-keepclasseswithmembernames class * {
native ;
}

#保持自定义View的get和set相关方法
-keepclassmembers public class * extends android.view.View {
void set*(**);
*** get
();
}

#保持Activity中View及其子类入参的方法
-keepclassmembers class * extends android.app.Activity {
public void *(android.view.View);
}

#枚举
-keepclassmembers enum * {
**[] $VALUES;
public *;
}

#Parcelable
-keepclassmembers class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator CREATOR;
}

#R文件的静态成员
-keepclassmembers class *.R$ {
public static ;
}

-dontwarn android.support.**

#keep相关注解
-keep class android.support.annotation.Keep

-keep @android.support.annotation.Keep class * {*;}

-keepclasseswithmembers class * {
@android.support.annotation.Keep ;
}

-keepclasseswithmembers class * {
@android.support.annotation.Keep ;
}

-keepclasseswithmembers class * {
@android.support.annotation.Keep (...);
}
写在最后

十分感谢你能看到最后,因本人水平有限,如有错漏还请不吝赐教,可以直接回复公众号或者去博客底下评论,在此先行谢过。

同时,假如有这方面的困惑或者讨论,也十分欢迎联系我。

Android插件化从入门到放弃-最强合集

Android插件化从入门到放弃-最强合集

本人最近研究插件化, 偶然发现此合集, 按照部分链接的文章实际简单写了些demo,受益良多, 觉得确实不错,特转载过来,给需要的人。
插件化涉及的东西很多,所以我们需要多个维度去学习。大概分为5个部分:预备知识、入门、进阶、系列、类库。一步一步深入了解插件的原理。

基础

  1. Java 类加载器
    类加载器(class loader)是 Java中的一个很重要的概念。类加载器负责加载 Java 类的字节代码到 Java 虚拟机中。本文首先详细介绍了 Java 类加载器的基本概念,包括代理模式、加载类的具体过程和线程上下文类加载器等,接着介绍如何开发自己的类加载器,最后介绍了类加载器在 Web 容器和 OSGi中的应用。

2.反射原理
Java 提供的反射機制允許您於執行時期動態載入類別、檢視類別資訊、生成物件或操作生成的物件,要舉反射機制的一個應用實例,就是在整合式開發環境中所提供的方法提示或是類別檢視工具,另外像 JSP 中的 JavaBean 自動收集請求資訊也使用到反射,而一些軟體開發框架(Framework)也常見到反射機制的使用,以達到動態載入使用者自訂類別的目的。

3.代理模式及Java实现动态代理
定义:给某个对象提供一个代理对象,并由代理对象控制对于原对象的访问,即客户不直接操控原对象,而是通过代理对象间接地操控原对象。

入门
1.Android动态加载dex技术初探
Android使用Dalvik虚拟机加载可执行程序,所以不能直接加载基于class的jar,而是需要将class转化为dex字节码,从而执行代码。优化后的字节码文件可以存在一个.jar中,只要其内部存放的是.dex即可使用。

2.Android插件化入门
开发者将插件代码封装成Jar或者APK。宿主下载或者从本地加载Jar或者APK到宿主中。将宿主调用插件中的算法或者Android特定的Class(如Activity)

3.插件化开发—动态加载技术加载已安装和未安装的apk
动态加载技术就是使用类加载器加载相应的apk、dex、jar(必须含有dex文件),再通过反射获得该apk、dex、jar内部的资源(class、图片、color等等)进而供宿主app使用。

4.Android动态加载技术三个关键问题详解
动态加载技术(也叫插件化技术)在技术驱动型的公司中扮演着相当重要的角色,当项目越来越庞大的时候,需要通过插件化来减轻应用的内存和CPU占用,还可以实现热插拔,即在不发布新版本的情况下更新某些模块。

进阶
1.携程Android App插件化和动态加载实践
携程Android App的插件化和动态加载框架已上线半年,经历了初期的探索和持续的打磨优化,新框架和工程配置经受住了生产实践的考验。本文将详细介绍Android平台插件式开发和动态加载技术的原理和实现细节,回顾携程Android App的架构演化过程,期望我们的经验能帮助到更多的Android工程师。

2.动态加载APK原理分享
被加载的apk称之为插件,因为机制类似于生物学的"寄生",加载了插件的应用也被称为宿主。 往往不是所有的apk都可作为插件被加载,往往需要遵循一定的"开发规范",还需要插件项目引入某种api类库,业界通常都是这么做的。

3.Android插件化的一种实现
Android的插件化已经是老生常谈的话题了,插件化的好处有很多:解除代码耦合,插件支持热插拔,静默升级,从根本上解决65K属性和方法的bug等等。下面给大家介绍一下我们正在用的差价化框架。本片主要以类图的方式向大家介绍插件话框架的实现。

4.蘑菇街 App 的组件化之路
随着我街业务的蓬勃发展,产品和运营随时上新功能新活动的需求越来越强烈,经常可以听到“有个功能我想周x上,行不行”。行么?当然是不行啦,上新功能得发新版本啊,到时候费时费力打乱开发节奏不说,覆盖率也是个问题。

5.DynamicLoadApk 源码解析
DynamicLoadApk 是一个开源的 Android 插件化框架。插件化的优点包括:(1) 模块解耦,(2) 动态升级,(3) 高效并行开发(编译速度更快) (4) 按需加载,内存占用更低等等DynamicLoadApk 提供了 3 种开发方式,让开发者在无需理解其工作原理的情况下快速的集成插件化功能。

6.Android apk动态加载机制的研究
问题是这样的:我们知道,apk必须安装才能运行,如果不安装要是也能运行该多好啊,事实上,这不是完全不可能的,尽管它比较难实现。在理论层面上,我们可以通过一个宿主程序来运行一些未安装的apk,当然,实践层面上也能实现,不过这对未安装的apk有要求。我们的想法是这样的,首先要明白apk未安装是不能被直接调起来.

7.美团Android DEX自动拆包及动态加载简介
作为一个android开发者,在开发应用时,随着业务规模发展到一定程度,不断地加入新功能、添加新的类库,代码在急剧的膨胀,相应的apk包的大小也急剧增加, 那么终有一天,你会不幸遇到这个错误.

8.途牛原创|途牛Android App的插件实现
途牛的插件化是基于dynamic-load-apk(github)实现的。定义了宿主和插件的通信方式,使得两者能够互起对方的页面,调用彼此的功能。同时对activity的启动方式singletask等进行了模式实现,并增加了对Service的支持等。总之使得插件开发最大限度的保持着原有的Android开发习惯。

  1. Android apk资源加载和activity生命周期管理
    博主分析了Android中apk的动态加载机制,并在文章的最后指出需要解决的两个复杂问题:资源的访问和activity生命周期的管理,而本文将会分析这两个复杂问题的解决方法。

10.APK动态加载框架(DL)解析
首先要说的是动态加载技术(或者说插件化)在技术驱动型的公司中扮演这相当重要的角色,当项目越来越庞大的时候,需要通过插件化来减轻应用的内存和cpu占用,还可以实现热插拔,即在不发布新版本的情况下更新某些模块。

系列
1.Kaedea---Android动态加载技术 简单易懂的介绍
我们很早开始就在Android项目中采用了动态加载技术,主要目的是为了达到让用户不用重新安装APK就能升级应用的功能(特别是 SDK项目),这样一来不但可以大大提高应用新版本的覆盖率,也减少了服务器对旧版本接口兼容的压力,同时如果也可以快速修复一些线上的BUG。

2.Kaedea---Android动态加载基础 ClassLoader的工作机制
早期使用过Eclipse等Java编写的软件的同学可能比较熟悉,Eclipse可以加载许多第三方的插件(或者叫扩展),这就是动态加载。这些插件大多是一些Jar包,而使用插件其实就是动态加载Jar包里的Class进行工作。

3.Kaedea---Android动态加载补充 加载SD卡的SO库
Android中JNI的使用其实就包含了动态加载,APP运行时动态加载.so库并通过JNI调用其封装好的方法。后者一般是使用NDK工具从C/C++代码编译而成,运行在Native层,效率会比执行在虚拟机的Java代码高很多,所以Android中经常通过动态加载.so库来完成一些对性能比较有需求的工作(比如T9搜索、或者Bitmap的解码、图片高斯模糊处理等)。

4.Kaedea---Android动态加载入门 简单加载模式
Java程序中,JVM虚拟机是通过类加载器ClassLoader加载.jar文件里面的类的。Android也类似,不过Android用的是Dalvik/ART虚拟机,不是JVM,也不能直接加载.jar文件,而是加载dex文件。

5.Kaedea---Android动态加载进阶 代理Activity模式
简单模式中,使用ClassLoader加载外部的Dex或Apk文件,可以加载一些本地APP不存在的类,从而执行一些新的代码逻辑。但是使用这种方法却不能直接启动插件里的Activity。

6.Kaedea---Android动态加载黑科技 动态创建Activity模式
还记得我们在代理Activity模式里谈到启动插件APK里的Activity的两个难题吗,由于插件里的Activity没在主项目的Manifest里面注册,所以无法经历系统Framework层级的一系列初始化过程,最终导致获得的Activity实例并没有生命周期和无法使用res资源。

7.尼古拉斯---插件开发基础篇:动态加载技术解读
在目前的软硬件环境下,Native App与Web App在用户体验上有着明显的优势,但在实际项目中有些会因为业务的频繁变更而频繁的升级客户端,造成较差的用户体验,而这也恰恰是Web App的优势。本文对网上Android动态加载jar的资料进行梳理和实践在这里与大家一起分享,试图改善频繁升级这一弊病。

8.尼古拉斯---插件开发开篇:类加载器分析
这篇文章主要介绍了Android中主要的两个类加载器:PathClassLoader和DexClassLoader,他们的区别,联系,用法等问题,以及我们在制作插件的过程中会遇到哪些常见的问题。这篇文章也是后续两篇文章的基础,因为如果不了解这两个类的话,我们将无法进行后续的操作。

9.尼古拉斯---插件开发中篇:资源加载问题(换肤原理解析)
这篇文章主要通过现在一些应用自带的换肤技术的解读来看看,在开发插件的过程中如何解决一些资源加载上的问题,这个问题为何要单独拿出来解释,就是因为他涉及的知识很多,也是后面一篇文章的基础,我们在需要加载插件中的资源文件的时候。

10.尼古拉斯---插件开发终极篇:动态加载Activity(免安装运行程序)
这篇文章主要是讲解了如何加载插件中的Activity。从而实现免安装运行程序,同时这篇文章也是对前三篇文章知识的综合使用。下载很多应用都会使用到插件技术,因为包的大小和一些功能的优先级来决定哪些模块可以制作成插件。

11.Weishu---Android插件化原理解析——概要
类的加载可以使用Java的ClassLoader机制,但是对于Android来说,并不是说类加载进来就可以用了,很多组件都是有“生命”的;因此对于这些有血有肉的类,必须给它们注入活力,也就是所谓的组件生命周期管理.

12.Weishu---Android插件化原理解析——Hook机制之动态代理
使用代理机制进行API Hook进而达到方法增强是框架的常用手段,比如J2EE框架Spring通过动态代理优雅地实现了AOP编程,极大地提升了Web开发效率;同样,插件框架也广泛使用了代理机制来增强系统API从而达到插件化的目的.

13.Weishu---Android插件化原理解析——Hook机制之Binder Hook
Android系统通过Binder机制给应用程序提供了一系列的系统服务,诸如ActivityManagerService,ClipboardManager, AudioManager等;这些广泛存在系统服务给应用程序提供了诸如任务管理,音频,视频等异常强大的功能。

14.Weishu---Android 插件化原理解析——Hook机制之AMS&PMS
在前面的文章中我们介绍了DroidPlugin的Hook机制,也就是代理方式和Binder Hook;插件框架通过AOP实现了插件使用和开发的透明性。在讲述DroidPlugin如何实现四大组件的插件化之前,有必要说明一下它对AMS以及PMS的Hook方式。

15.Weishu---Android 插件化原理解析——Activity生命周期管理
之前的 Android插件化原理解析 系列文章揭开了Hook机制的神秘面纱,现在我们手握倚天屠龙,那么如何通过这种技术完成插件化方案呢?具体来说,插件中的Activity,Service等组件如何在Android系统上运行起来?

16.Weishu---Android 插件化原理解析——插件加载机制
上文 Activity生命周期管理 中我们地完成了『启动没有在AndroidManifest.xml中显式声明的Activity』的任务;通过Hook AMS和拦截ActivityThread中H类对于组件调度我们成功地绕过了AndroidMAnifest.xml的限制。

17.Weishu---Android插件化原理解析——广播的管理
在Activity生命周期管理 以及 插件加载机制 中我们详细讲述了插件化过程中对于Activity组件的处理方式,为了实现Activity的插件化我们付出了相当多的努力;那么Android系统的其他组件,比如BroadcastReceiver,Service还有ContentProvider,它们又该如何处理呢?

18.Weishu---Android 插件化原理解析——Service的插件化
在 Activity生命周期管理 以及 广播的管理 中我们详细探讨了Android系统中的Activity、BroadcastReceiver组件的工作原理以及它们的插件化方案,相信读者已经对Android Framework和插件化技术有了一定的了解;
19. 美团App插件化实践
类库
1.DroidPlugin
是360手机助手在Android系统上实现了一种新的插件机制
2.Android-Plugin-Framework
此项目是Android插件开发框架完整源码及示例。用来通过动态加载的方式在宿主程序中运行插件APK。
3.Small
世界那么大,组件那么小。Small,做最轻巧的跨平台插件化框架。里面有很详细的文档
4.dynamic-load-apk
Android 使用动态加载框架DL进行插件化开发
5.AndroidDynamicLoader
Android 动态加载框架,他不是用代理 Activity 的方式实现而是用 Fragment 以及 Schema 的方式实现
6.DynamicAPK
实现Android App多apk插件化和动态加载,支持资源分包和热修复.携程App的插件化和动态加载框架.
7.ACDD
非代理Android动态部署框架
8.android-pluginmgr
不需要插件规范的apk动态加载框架。
9. VirtualAPK
VirtualAPK是滴滴出行自研的一款优秀的插件化框架。
10.android-pluginmgr
不需要插件规范的apk动态加载框架。
11.RePlugin
RePlugin是一套完整的、稳定的、适合全面使用的,占坑类插件化方案,由360手机卫士的RePlugin Team研发,也是业内首个提出”全面插件化“(全面特性、全面兼容、全面使用)的方案。

参考视频
1.DroidPlugin的实现原理及其应用
Droid Plugin是360手机助手在2015年初研发的一个全新的基于Android平台的插件机制.
2.android插件化及动态部署
阿里技术沙龙第十六期《android插件化及动态部署》视频

Android 开源库学习

    • https://github.com/huangweicai/OkLibDemo
      声明:oklib库所有的源码都是项目开发常用代码,库内容包括作者整理的项目资源及开源资源,未经作
      者允许不得以营销为手段用作商业用途,另外如库中涉及到读者源码可联系作者标明出处。本库将持续更
      新完善,也欢迎志同道合的朋友一起努力,为开源世界贡献一点力量。
      功能:一个专注于让项目开发更简单的框架,集成了主流的开发框架及常用的工具类,让项目开发更加统
      一规范,减少功能及方法测试时间,助力于项目稳定、快速、高效开发

Android 书籍阅读

  • Android群英传
    • 第一章Android体系和系统架构
    • 第二章ADB命令使用
    • 第三章Android控件架构
    • 第三章绘制View和ViewGroup
    • 第三章自定义View
    • 第三章事件分发机制
    • 第四章ListView常用优化
    • 第四章ListView滑动监听
    • 第四章具有弹性的ListView
    • 第四章自动显示隐藏ActionBar/Toolbar的ListView
    • 第四章动态改变ListView的布局
    • 第五章常用触控事件MotionEvent与获取View坐标长度的各种方法
    • 第五章实现滑动的7种方法(一二三)
    • 第五章实现滑动的7种方法(四)scrollTo和scrollBy
    • 第五章实现滑动的7种方法(五)Scroller
    • 第五章实现滑动的7种方式(六七)ViewDragHelper自定义侧滑菜单
    • 第六章屏幕尺寸信息
    • 第六章Canvas绘图技巧
    • 第六章Canvas的Layer图层
    • 第六章SurfaceView
    • 第七章View的Animation
    • 第七章Animator属性动画
    • 第七章布局动画
    • 第七章自定义动画
    • 第九章系统信息的获取
    • 第九章使用PackageManager获取应用包信息
    • 第九章ActivityManager获取信息
    • 第九章packages.xml的作用
    • 第九章五道安全防线与系统隐患

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.