[笔记]Android性能优化 上
[笔记]Android性能优化 中
[笔记]Android性能优化 下
说明
这篇文章是将很久以来看过的文章,包括自己写的一些测试代码的总结.属于笔记的性质,没有面面俱到,一些自己相对熟悉的点可能会略过.
最开始看到的性能优化的文章,就是胡凯的优化典范系列,后来又陆续看过一些人写的,个人觉得anly_jun和胡凯的质量最好.
文章大的框架也是先把优化典范过一遍,记录个人认为重要的点,然后是anly_jun的系列,将之前未覆盖的补充进去,也包括HenCoder的一些课程相关内容.
当然除了上面几位,还有很多其他大神的文章,时间久了也记不太清,在此一并谢过.
笔记内容引用来源
- 胡凯
- anly_jun
- HenCoder
1.Android性能优化之渲染篇
1.VSYNC
- 帧率:GPU在1秒内绘制操作的帧数.如60fps.
- 我们通常都会提到60fps与16ms,这是因为人眼与大脑之间的协作无法感知超过60fps的画面更新.
- 开发app的性能目标就是保持60fps,这意味着每一帧只有16ms=1000/60的时间来处理所有的任务
- 刷新率:屏幕在1秒内刷新屏幕的次数.如60Hz,每16ms刷新1次屏幕.
- GPU获取图形数据进行渲染,然后屏幕将渲染后的内容展示在屏幕上.
- 大多数手机屏幕的刷新率是60Hz,如果GPU渲染1帧的时间低于1000/60=16ms,那么在屏幕刷新时候都有最新帧可显示.如果GPU渲染某1帧 f 的时间超过16ms,在屏幕刷新时候,f并没有被GPU渲染完成则无法展示,屏幕只能继续展示f的上1帧的内容.这就是掉帧,造成了UI界面的卡顿.
下面展示了帧率正常和帧率低于刷新率(掉帧)的情形
2.GPU渲染:GPU渲染依赖2个组件:CPU和GPU
- CPU负责Measure,Layout,Record,Execute操作.
- GPU负责Rasterization(栅格化)操作.
- Resterization栅格化是绘制那些Button,Shape,Path,String,Bitmap等组件最基础的操作.它把组件拆分到不同的像素上进行显示.这是一个很费时的操作.
- CPU负责把UI组件计算成Polygons(多边形),Texture(纹理),然后交给GPU进行栅格化渲染.
- 为了App流畅,我们需要确保在16ms内完成所有CPU和GPU的工作.
3.过度绘制
Overdraw过度绘制是指屏幕上的某个像素在同一帧的时间内被绘制了多次.过度绘制会大量浪费CPU及GPU资源/占用CPU和GPU的处理时间
- 过度绘制的原因
- UI布局存在大量重叠
- 非必须的背景重叠.
- 如Activity有背景,Layout又有背景,子View又有背景.仅仅移除非必要背景就可以显著提升性能.
- 子View在onDraw中存在重叠部分绘制的情况,比如Bitmap重叠绘制
4.如何提升渲染性能
- 移除XML布局文件中非必要的Background
- 保持布局扁平化,尽量避免布局嵌套
- 在任何时候都避免调用requestLayout(),调用requestLayout会导致该layout的所有父节点都发生重新layout的操作
- 在自定义View的onDraw中避免过度绘制.
代码实例:
public class OverdrawView extends View {
public OverdrawView(Context context) {
super(context);
init();
}
public OverdrawView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public OverdrawView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
private Bitmap bitmap1,bitmap2,bitmap3;
private void init(){
paint.setStyle(Paint.Style.FILL);
bitmap1 = BitmapFactory.decodeResource(getResources(),R.mipmap.png1);
bitmap2 = BitmapFactory.decodeResource(getResources(),R.mipmap.png2);
bitmap3 = BitmapFactory.decodeResource(getResources(),R.mipmap.png3);
}
int w,h;
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
w = getMeasuredWidth();
h = getMeasuredHeight();
}
private boolean Overdraw = true;
@Override
protected void onDraw(Canvas canvas) {
if(Overdraw){
//默认会出现过度绘制
canvas.drawBitmap(bitmap1,0,0,paint);
canvas.drawBitmap(bitmap2,w/3,0,paint);
canvas.drawBitmap(bitmap3,w*2/3,0,paint);
}else{
//使用Canvas.clipRect避免过度绘制
canvas.save();
canvas.clipRect(0,0,w/3,h);
canvas.drawBitmap(bitmap1,0,0,paint);
canvas.restore();
canvas.save();
canvas.clipRect(w/3,0,w*2/3,h);
canvas.drawBitmap(bitmap2,w/3,0,paint);
canvas.restore();
canvas.save();
canvas.clipRect(w*2/3,0,w,h);
canvas.drawBitmap(bitmap3,w*2/3,0,paint);
canvas.restore();
}
}
//切换是否避免过度绘制
public void toggleOverdraw(){
Overdraw = !Overdraw;
invalidate();
}
}
效果图:
2.Android性能优化之内存篇
1.Android虚拟机的 分代堆内存/Generational Heap Memory模型
- 和JVM不同:Android的堆内存多了1个永久代/Permanent Generation.
- 和JVM类似:
- 新创建的对象存储在新生代/Young Generation
- GC所占用的时间和它是哪一个Generation有关,Young Generation的每次GC操作时间是最短的,Old Generation其次,Permanent Generation最长
- 无论哪一代,触发GC后,所有非垃圾回收线程暂停,GC结束后所有线程恢复执行
- 如果短时间内进行过多GC,多次暂停线程进行垃圾回收的累积时间就会增大.占用过多的帧间隔时间/16ms,导致CPU和GPU用于计算渲染的时间不足,导致卡顿/掉帧.
2.内存泄漏和内存溢出
内存泄漏就是无用对象占据的内存空间没有及时释放,导致内存空间浪费的情况.memory leak.
内存溢出是App为1个对象申请内存空间,内存空间不足的情况.out of memory.
内存泄漏数量足够大,就会引起内存溢出.或者说内存泄漏是内存溢出的原因之一.
3.Android性能优化典范-第2季
1.提升动画性能
-
Bitmap的缩放,旋转,裁剪比较耗性能.例如在一个圆形的钟表图上,我们把时钟的指针抠出来当做单独的图片进行旋转会比旋转一张完整的圆形图性能好.
- 尽量减少每次重绘的元素可以极大提升性能.可以把复杂的View拆分会更小的View进行组合,在需要刷新界面时候仅对指定View进行重绘.
- 假如钟表界面上有很多组件,可以把这些组件做拆分,背景图片单独拎出来设置为一个独立的View,通过setLayerType()方法使得这个View强制用Hardware来进行渲染.至于界面上哪些元素需要做拆分,他们各自的更新频率是多少,需要有针对性的单独讨论
2.对象池
- 短时间内大量对象被创建然后很快被销毁,会多次触发Android虚拟机在Young generation进行GC,使用AS查看内存曲线,会看到内存曲线剧烈起伏,称为"内存抖动".
- GC会暂停其他线程,短时间多次GC/内存抖动会引起CPU和GPU在16ms内无法完成当前帧的渲染,引起界面卡顿.
- 避免内存抖动,可以使用对象池
- 对象池的作用:减少频繁创建和销毁对象带来的成本,实现对象的缓存和复用
- 1
2
3 4
- 实例
public class User { public String id; public String name; //对象池实例 private static final SynchronizedPool
sPool = new SynchronizedPool (10); public static User obtain() { User instance = sPool.acquire(); return (instance != null) ? instance : new User(); } public void recycle() { sPool.release(this); } }
3.for index,for simple,iterator三种遍历性能比较
public class ForTest {
public static void main(String[] args) {
Vector v = new Vector<>();
ArrayList a = new ArrayList<>();
LinkedList l = new LinkedList<>();
int time = 1000000;
for(int i = 0; i< time; i++){
Integer item = new Random().nextInt(time);
v.add(item);
a.add(item);
l.add(item);
}
//测试3种遍历性能
long start = System.currentTimeMillis();
for(int i = 0;i
- 不要用for index去遍历链表,因为LinkedList在get任何一个位置的数据的时候,都会把前面的数据走一遍.应该使用Iterator去遍历
- get(0),直接拿到0位的Node0的地址,拿到Node0里面的数据
- get(1),直接拿到0位的Node0的地址,从0位的Node0中找到下一个1位的Node1的地址,找到Node1,拿到Node1里面的数据
- get(2),直接拿到0位的Node0的地址,从0位的Node0中找到下一个1位的Node1的地址,找到Node1,从1位的Node1中找到下一个2位的Node2的地址,找到Node2,拿到Node2里面的数据
- Vector和ArrayList,使用for index遍历效率较高
4.Merge:通过Merge减少1个View层级
可以将merge当做1个ViewGroup v,如果v的类型和v的父控件的类型一致,那么v其实没必要存在,因为白白增加了布局的深度.所以merge使用时必须保证merge中子控件所应该在的ViewGroup类型和merge所在的父控件类型一致.
-
Merge的使用场景有2个:
- Activity的布局文件的根布局是FrameLayout,则将FrameLayout替换为merge
- 因为setContentView本质就是将布局文件inflate后加载到了id为android.id.content的FrameLayout上.
- merge作为根布局的布局文件通过include标签被引入其他布局文件中.这时候include所在的父控件,必须和merge所在的布局文件"原本根布局"一致.
- Activity的布局文件的根布局是FrameLayout,则将FrameLayout替换为merge
-
代码示例
merge作为根布局的布局文件,用于Activity的setContentView:activity_merge.xml
merge作为根布局的布局文件,被include标签引入其他布局文件中:activity_merge_include.xml
5.使用.9.png作为背景
- 典型场景是1个ImageView需要添加1个背景图作为边框.这样边框所在矩形的中间部分和实际显示的图片就好重叠发生Overdraw.
- 可以将背景图制作成.9.png.和前景图重叠部分设置为透明.Android的2D渲染器会优化.9.png的透明区域.
6.减少透明区域对性能的影响
- 不透明的View,显示它只需要渲染一次;如果View设置了alpha值,会至少需要渲染两次,性能不好
- 设置透明度setAlpha的时候,会把当前view绘制到offscreen buffer中,然后再显示出来.offscreen buffer是 一个临时缓冲区,把View放进来并做透明度的转化,然后显示到屏幕上,这个过程性能差,所以应该尽量避免这个过程
- 如何避免使用offscreen buffer
- 对于不存在过度绘制的View,如没有背景的TextView,就可以直接设置文字颜色;ImageView设置图片透明度setImageAlpha;自定义View设置绘制时的paint的透明度
- 如果是自定义View,确定不存在过度绘制,可以重写hasOverlappingRendering返回false即可.这样设置alpha时android会自动优化,避免使用offscreen buffer.
@Override public boolean hasOverlappingRendering() { return false; }
- 如果不是1,2两种情况,要设置View的透明度,则需要让GPU来渲染指定View,然后再设置透明度.
View v = findViewById(R.id.root); //通过setLayerType的方法来指定View应该如何进行渲染 //开启硬件加速 v.setLayerType(View.LAYER_TYPE_HARDWARE,null); v.setAlpha(0.60F); //透明度设置完毕后关闭硬件加速 v.setLayerType(View.LAYER_TYPE_NONE,null);
4.Android性能优化典范-第3季
1.避免使用枚举,用注解进行替代
- 枚举的问题
- 每个枚举值都是1个对象,相比较Integer和String常量,枚举的内存开销至少是其2倍.
- 过多枚举会增加dex大小及其中的方法数量,增加App占用的空间及引发65536几率
- 如何替代枚举:使用注解
- android.support.annotation中的@IntDef,@StringDef来包装Integer和String常量.
- 3个步骤
- 首先定义常量
- 然后自定义注解,设置取值范围就是刚刚定义的常量,并设置自定义注解的保留范围为源码时/SOURCE
- 位指定的属性及方法添加自定义注解.
- 代码实例
public class MainActivity extends Activity { //1:首先定义常量 public static final int MALE = 0; public static final int FEMALE = 1; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main_activity); Person person = new Person(); person.setSex(MALE); ((Button) findViewById(R.id.test)).setText(person.getSexDes()); } class Person { //3.为指定的属性及方法添加自定义注解 @SEX private int sex; //3.为指定的属性及方法添加自定义注解 public void setSex(@SEX int sex) { this.sex = sex; } //3.为指定的属性及方法添加自定义注解 @SEX public int getSex() { return sex; } public String getSexDes() { if (sex == MALE) { return "男"; } else { return "女"; } } } //2:然后创建自定义注解,设置取值范围就是刚刚定义的常量,并设置自定义注解的保留范围是源码时 @IntDef({MALE, FEMALE}) @Retention(RetentionPolicy.SOURCE) public @interface SEX { } }
5.Android内存优化之OOM
如何避免OOM:
- 减小对象的内存占用
- 内存对象复用防止重建
- 避免内存泄漏
- 内存使用策略优化
1.减小对象的内存占用
- 避免使用枚举,用注解替代
- 减小创建的Bitmap的内存,使用合适的缩放比例及解码格式
- inSampleSize:缩放比例
- decode format:解码格式
- 现在很多图片资源的URL都可以添加图片尺寸作为参数.在通过网络获取图片时选择合适的尺寸,减小网络流量消耗,并减小生成的Bitmap的大小.
2.内存对象的重复利用
- 对象池技术:减少频繁创建和销毁对象带来的成本,实现对象的缓存和复用
- 尽量使用Android系统内置资源,可降低APK大小,在一定程度降低内存开销
- ConvertView的复用
- LRU的机制实现Bitmap的缓存(图片加载框架的必备机制)
- 在for循环中,用StringBuilder代替String实现字符串拼接
3.避免内存泄漏
- 在App中使用leakcanary检测内存泄漏:leakcanary
- Activity的内存泄漏
- Handler引起Activity内存泄漏
- 原因:Handler作为Activity的1个非静态内部类实例,持有Activity实例的引用.若Activity退出后Handler依然有待接收的Message,这时候发生GC,Message-Handler-Activity的引用链导致Activity无法被回收.
- 2种解决方法
-
在onDestroy调用Handler.removeCallbacksAndMessages(null)移除该Handler关联的所有Message及Runnable.再发生GC,Message已经不存在,就可以顺利的回收Handler及Activity
@Override protected void onDestroy() { super.onDestroy(); m.removeCallbacksAndMessages(null); }
-
自定义静态内部类继承Handler,静态内部类实例不持有外部Activity的引用.在自定义Handler中定义外部Activity的弱引用,只有弱引用关联的外部Activity实例未被回收的情况下才继续执行handleMessage.自定义Handler持有外部Activity的弱引用,发生GC时不耽误Activity被回收.
static class M extends Handler{ WeakReference
mWeakReference; public M(Activity activity) { mWeakReference=new WeakReference (activity); } @Override public void handleMessage(Message msg) { if(mWeakReference != null){ Activity activity=mWeakReference.get(); if(activity != null){ if(msg.what == 15){ Toast.makeText(activity,"M:15",Toast.LENGTH_SHORT).show(); } if(msg.what == 5){ Toast.makeText(activity,"M:5",Toast.LENGTH_SHORT).show(); } } } } } private M m; @Override protected void onResume() { super.onResume(); m = new M(this); m.sendMessageDelayed(m.obtainMessage(15),15000); m.sendMessageDelayed(m.obtainMessage(5),5000); } 在避免内存泄漏的前提下,如果要求Activity退出就不执行后续动作,用方法1.如果要求后续动作在GC发生前继续执行,使用方法2
-
- Handler引起Activity内存泄漏
- Context:尽量使用Application Context而不是Activity Context,避免不经意的内存泄漏
- 资源对象要及时关闭
4.内存使用策略优化
- 图片选择合适的文件夹进行存放
- hdpi/xhdpi/xxhdpi等等不同dpi的文件夹下的图片在不同的设备上会经过scale的处理。例如我们只在hdpi的目录下放置了一张100100的图片,那么根据换算关系,xxhdpi的手机去引用那张图片就会被拉伸到200200。需要注意到在这种情况下,内存占用是会显著提高的。对于不希望被拉伸的图片,需要放到assets或者nodpi的目录下
- 谨慎使用依赖注入框架.依赖注入框架会扫描代码,需要大量的内存空间映射代码.
- 混淆可以减少不必要的代码,类,方法等.降低映射代码所需的内存空间
- onLowMemory()与onTrimMemory():没想到应该怎么用
- onLowMemory
- 当所有的background应用都被kill掉的时候,forground应用会收到onLowMemory()的回调.在这种情况下,需要尽快释放当前应用的非必须的内存资源,从而确保系统能够继续稳定运行
- onTrimMemory(int level)
- 当系统内存达到某些条件的时候,所有正在运行的应用都会收到这个回调,同时在这个回调里面会传递以下的参数,代表不同的内存使用情况,收到onTrimMemory()回调的时候,需要根据传递的参数类型进行判断,合理的选择释放自身的一些内存占用,一方面可以提高系统的整体运行流畅度,另外也可以避免自己被系统判断为优先需要杀掉的应用
- onLowMemory
6.Android开发最佳实践
1.注意对隐式Intent的运行时检查保护
- 类似打开相机等隐式Intent,不一定能够在所有的Android设备上都正常运行.
- 例如系统相机应用被关闭或者不存在相机应用,或者某些权限被关闭都可能导致抛出ActivityNotFoundException的异常.
- 预防这个问题的最佳解决方案是在发出这个隐式Intent之前调用resolveActivity做检查
- 代码实例
public class IntentCheckActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_intent_check); } public void openSBTest(View view) { // 跳转到"傻逼"软件 Intent sbIntent = new Intent("android.media.action.IMAGE_GO_SB"); if (sbIntent.resolveActivity(getPackageManager()) != null) { startActivity(sbIntent); } else { //会弹出这个提示 Toast.makeText(this,"设备木有傻逼!",Toast.LENGTH_SHORT).show(); } } public void openCameraTest(View view) { // 跳转到系统照相机 Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); if (cameraIntent.resolveActivity(getPackageManager()) != null) { startActivity(cameraIntent); //正常设备会进入相机并弹出提示 Toast.makeText(this,"设备有相机!",Toast.LENGTH_LONG).show(); } else { Toast.makeText(this,"设备木有相机!",Toast.LENGTH_SHORT).show(); } } }
2.Android 6.0的权限
3.MD新控件的使用:Toolbar替代ActionBar,AppBarLayout,Navigation Drawer, DrawerLayout, NavigationView等
7.Android性能优化典范-第4季
1.网络数据的缓存.okHttp,Picasso都支持网络缓存
okHttp Picasso
MVP架构实现的Github客户端(4-加入网络缓存)
2.代码混淆
2.1.AS中生成keystore.jks应用于APK打包
-
1:生成keystore.jks
2:查看.jks文件的SHA1安全码
在AS的Terminal中输入:
keytool -list -v -keystore C:\Users\Administrator\Desktop\key.jks
keytool -list -v -keystore .jks文件详细路径
回车后,输入密钥库口令/就是.jks的密码,输入过程不可见,输入完毕回车即可!
2.2.proguard-rules关键字及部分通配符含义
- keep 完整类名{*;}, 可以对指定类进行完全保留,不混淆类名,变量名,方法名.
-keep public class a.b.c.TestItem{ *; }
- 在App中,我们会定义很多实体bean.往往涉及到bean实例和json字符串间互相转换.部分json库会通过反射调用bean的set和get方法.因而实体bean的set,get方法不能被混淆,或者说我们自己写的方法,如果会被第三方库或其他地方通过反射调用,则指定方法要keep避免混淆.
-keep public class a.**.Bean.**{ public void set*(***); public *** get*(); # 对应获取boolean类型属性的方法 public *** is*(); }
- 我们自己写的使用了反射功能的类,必须keep
#保留单个包含反射代码的类 -keep public class a.b.c.ReflectUtils{ *; } #保留所有包含反射代码的类,比如所有涉及反射代码的类都在a.b.reflectpackage包及其子包下 -keep class a.b.reflectpackage.**{ *; }
- 如果我们要保留继承了指定类的子类,或者实现了指定接口的类
-keep class * extends a.b.c.Parent{*;} -keep class * implements a.b.c.OneInterface{*;}
2.3.proguard-rules.pro通用模板
#####################基本指令##############################################
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
-keepclassmembers class fqcn.of.javascript.interface.for.webview {
public *;
}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
-renamesourcefileattribute SourceFile
#代码混淆压缩比,在0~7之间,默认为5,一般不需要改
-optimizationpasses 5
#混淆时不使用大小写混合,混淆后的类名为小写
-dontusemixedcaseclassnames
#指定不去忽略非公共的库的类
-dontskipnonpubliclibraryclasses
#指定不去忽略非公共的库的类的成员
-dontskipnonpubliclibraryclassmembers
#不做预校验,preverify是proguard的4个步骤之一
#Android不需要preverify,去掉这一步可加快混淆速度
-dontpreverify
#有了verbose这句话,混淆后就会生成映射文件
#包含有类名->混淆后类名的映射关系
-verbose
#然后使用printmapping指定映射文件的名称
-printmapping mapping.txt
#指定混淆时采用的算法,后面的参数是一个过滤器,这个过滤器是谷歌推荐的算法,一般不改变
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
#保护代码中的Annotation不被混淆,这在JSON实体映射时非常重要(保留注解参数)
-keepattributes *Annotation*
#避免混淆泛型,这在JSON实体映射时非常重要
-keepattributes Signature
#抛出异常时保留代码行号
-keepattributes SourceFile,LineNumberTable
#忽略所有警告
-ignorewarnings
###################需要保留的东西########################################
#保留反射的方法和类不被混淆================================================
#手动启用support keep注解
#http://tools.android.com/tech-docs/support-annotations
-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 (...);
}
#==========================================================================================
#保留所有的本地native方法不被混淆
-keepclasseswithmembernames class * {
native ;
}
#保留了继承自Activity、Application这些类的子类
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Fragment
-keep public class * extends android.support.v4.app.Fragment
-keep public class * extends android.app.Application
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.app.backup.BackupAgentHelper
-keep public class * extends android.preference.Preference
-keep public class * extends android.view.View
-keep public class * extends com.android.vending.licensing.ILicensingService
-keep class android.support.** {*;}
#保留在Activity中的方法参数是view的方法,从而我们在layout里面便携onClick就不会受影响
-keepclassmembers class * extends android.app.Activity{
public void *(android.view.View);
}
#枚举类不能被混淆
-keepclassmembers enum *{
public static **[] values();
public static ** valueOf(java.lang.String);
}
#保留自定义控件不被混淆
-keep public class * extends android.view.View {
public (android.content.Context);
public (android.content.Context, android.util.AttributeSet);
public (android.content.Context, android.util.AttributeSet, int);
void set*(***);
*** get*();
}
#保留Parcelable序列化的类不被混淆
-keep class * implements android.os.Paracelable{
public static final android.os.Paracelable$Creator *;
}
#保留Serializable序列化的类的如下成员不被混淆
-keepclassmembers class * implements java.io.Serializable {
static final long serialVersionUID;
private static final java.io.ObjectStreamField[] serialPersistentFields;
!static !transient ;
!private ;
!private ;
private void writeObject(java.io.ObjectOutputStream);
private void readObject(java.io.ObjectInputStream);
java.lang.Object writeReplace();
java.lang.Object readResolve();
}
#对于R(资源)下的所有类及其方法,都不能被混淆
-keep class **.R$*{
*;
}
#R文件中的所有记录资源id的静态字段
-keepclassmembers class **.R$* {
public static ;
}
#对于带有回调函数onXXEvent的,不能被混淆
-keepclassmembers class * {
void *(**On*Event);
}
#============================针对app的量身定制=============================================
# webView处理,项目中没有使用到webView忽略即可
-keepclassmembers class * extends android.webkit.webViewClient {
public void *(android.webkit.WebView, java.lang.String, android.graphics.Bitmap);
public boolean *(android.webkit.WebView, java.lang.String);
}
-keepclassmembers class * extends android.webkit.webViewClient {
public void *(android.webkit.webView, java.lang.String);
}
2.4.混淆jar包
郭霖大神博客有介绍,自己没试过
2.5.几条实用的Proguard rules
在上面提供的通用模板上继续添加下面几行:
-repackageclasses com
-obfuscationdictionary dict.txt
-classobfuscationdictionary dict.txt
-packageobfuscationdictionary dict.txt
-assumenosideeffects class android.util.Log {
public static boolean isLoggable(java.lang.String, int);
public static int v(...);
public static int i(...);
public static int w(...);
public static int d(...);
public static int e(...);
}
- repackageclasses:除了keep的类,会把我们自己写的所有类以及所使用到的各种第三方库代码统统移动到我们指定的单个包下.
- 比如一些比较敏感的被keep的类在包a.b.min下,我们可以使用 -repackageclasses a.b.min,这样就有成千上万的被混淆的类和未被混淆的敏感的类在a.b.min下面,正常人根本就找不到关键类.尤其是keep的类也只是保留关键方法,名字也被混淆过.
- -obfuscationdictionary,-classobfuscationdictionary和-packageobfuscationdictionary分别指定变量/方法名,类名,包名混淆后的字符串集.
- 默认我们的代码命名会被混淆成字母组合,使用这些配置可以用乱码或中文内容进行命名.中文命名可以破坏部分反编译软件的正常工作,乱码则极大加大了查看代码的难度.
- dict.txt:需要放到和app模块的proguard-rules.pro同级目录.dict.txt具体内容可以自己写,参考开源项目:一种生成阅读极其困难的proguard字典的算法
- -assumenosideeffects class android.util.Log是在编译成 APK 之前把日志代码全部删掉.
- Androidstudio 混淆去掉日志 assumenosideeffects 不起作用
- 因为默认情况下,使用的是proguard-android.txt的混淆规则.proguard-android.txt中包含-dontoptimize.-dontoptimize导致日志语句不会被优化掉.所以我们提供的模板不包含-dontoptimize这一句.
- Androidstudio 混淆去掉日志 assumenosideeffects 不起作用
2.6.字符串硬编码
- 对于反编译者来说,最简单的入手点就是字符串搜索.硬编码留在代码里的字符串值都会在反编译过程中被原样恢复,不要使用硬编码.
- 如果一定要使用硬编码
- 新建1个存储硬编码的常量类,静态存放字符串常量,即使找到了常量类,反编译者很难搜索到哪里用了这些字符串.
- 常量类中的静态常量字符串,用名称作为真正内容,而值用难以理解的编码表示.
//1:新建常量类,用于存放字符串常量 public class HardStrings { //2:名称是真正内容,值是难以理解的编码. //这样即使是必须保存的Log,被反编译者看到的也只是难以理解的值,搞不清意义 public static final String MaxMemory = "001"; public static final String M = "002"; public static final String MemoryClass = "003"; public static final String LargeMemoryClass = "004"; public static final String 系统总内存 = "005"; public static final String 系统剩余内存 = "006"; public static final String 系统是否处于低内存运行 = "007"; public static final String 系统剩余内存低于 = "008"; public static final String M时为低内存运行 = "009"; }
2.7.res资源混淆及多渠道打包
简单讲,使用腾讯的2个gradle插件来实现res资源混淆及多渠道打包.
res资源混淆:AndResGuard
多渠道打包:VasDolly
多渠道打包原理+VasDolly和其他多渠道打包方案对比
具体流程:
AndResGuard使用了chaychan的方法,单独创建gradle文件
- 项目根目录下build.gradle中,添加插件的依赖,具体如下
buildscript { repositories { google() jcenter() } dependencies { classpath 'com.android.tools.build:gradle:3.1.4' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files //添加AndResGuard classpath 'com.tencent.mm:AndResGuard-gradle-plugin:1.2.12' //添加VasDolly classpath 'com.leon.channel:plugin:2.0.1' } }
- 在app目录下单独创建gradle文件and_res_guard.gradle.内容如下
apply plugin: 'AndResGuard' andResGuard { mappingFile = null use7zip = true useSign = true keepRoot = false compressFilePattern = [ "*.png", "*.jpg", "*.jpeg", "*.gif", "resources.arsc" ] whiteList = [ // // your icon // "R.drawable.icon", // // for fabric // "R.string.com.crashlytics.*", // // for umeng update // "R.string.tb_*", // "R.layout.tb_*", // "R.drawable.tb_*", // "R.drawable.u1*", // "R.drawable.u2*", // "R.color.tb_*", // // umeng share for sina // "R.drawable.sina*", // // for google-services.json // "R.string.google_app_id", // "R.string.gcm_defaultSenderId", // "R.string.default_web_client_id", // "R.string.ga_trackingId", // "R.string.firebase_database_url", // "R.string.google_api_key", // "R.string.google_crash_reporting_api_key", // // //友盟 // "R.string.umeng*", // "R.string.UM*", // "R.layout.umeng*", // "R.drawable.umeng*", // "R.id.umeng*", // "R.anim.umeng*", // "R.color.umeng*", // "R.style.*UM*", // "R.style.umeng*", // // //融云 // "R.drawable.u*", // "R.drawable.rc_*", // "R.string.rc_*", // "R.layout.rc_*", // "R.color.rc_*", // "R.id.rc_*", // "R.style.rc_*", // "R.dimen.rc_*", // "R.array.rc_*" ] sevenzip { artifact = 'com.tencent.mm:SevenZip:1.2.12' //path = "/usr/local/bin/7za" } }
- 模块app下的build.gradle文件添加依赖,具体如下
apply plugin: 'com.android.application' //引入刚刚创建的and_res_guard.gradle apply from: 'and_res_guard.gradle' //依赖VasDolly apply plugin: 'channel' channel{ //指定渠道文件 channelFile = file("channel.txt") //多渠道包的输出目录,默认为new File(project.buildDir,"channel") baseOutputDir = new File(project.buildDir,"channel") //多渠道包的命名规则,默认为:${appName}-${versionName}-${versionCode}-${flavorName}-${buildType} apkNameFormat ='${appName}-${versionName}-${versionCode}-${flavorName}-${buildType}' //快速模式:生成渠道包时不进行校验(速度可以提升10倍以上,默认为false) isFastMode = true //buildTime的时间格式,默认格式:yyyyMMdd-HHmmss buildTimeDateFormat = 'yyyyMMdd-HH:mm:ss' //低内存模式(仅针对V2签名,默认为false):只把签名块、中央目录和EOCD读取到内存,不把最大头的内容块读取到内存,在手机上合成APK时,可以使用该模式 lowMemory = false } rebuildChannel { //指定渠道文件 channelFile = file("channel.txt") // baseDebugApk = new File(project.projectDir, "app-release_7zip_aligned_signed.apk") baseReleaseApk = new File(project.projectDir, "app-release_7zip_aligned_signed.apk") //默认为new File(project.buildDir, "rebuildChannel/debug") // debugOutputDir = new File(project.buildDir, "rebuildChannel/debug") //默认为new File(project.buildDir, "rebuildChannel/release") releaseOutputDir = new File(project.buildDir, "rebuildChannel/release") //快速模式:生成渠道包时不进行校验(速度可以提升10倍以上,默认为false) isFastMode = false //低内存模式(仅针对V2签名,默认为false):只把签名块、中央目录和EOCD读取到内存,不把最大头的内容块读取到内存,在手机上合成APK时,可以使用该模式 lowMemory = false } android { signingConfigs { tcl { keyAlias 'qinghailongxin' keyPassword 'huanhailiuxin' storeFile file('C:/Users/Administrator/Desktop/key.jks') storePassword 'huanhailiuxin' } } compileSdkVersion 28 defaultConfig { applicationId "com.example.administrator.proguardapp" minSdkVersion 15 targetSdkVersion 28 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" vectorDrawables.useSupportLibrary = true } buildTypes { release { minifyEnabled true shrinkResources true zipAlignEnabled true pseudoLocalesEnabled true proguardFiles 'proguard-rules.pro' signingConfig signingConfigs.tcl } debug { signingConfig signingConfigs.tcl minifyEnabled true pseudoLocalesEnabled true zipAlignEnabled true } } } dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') implementation 'com.android.support:appcompat-v7:28.0.0' implementation 'com.android.support.constraint:constraint-layout:1.1.3' implementation 'com.android.support:design:28.0.0' implementation 'com.android.support:support-vector-drawable:28.0.0' testImplementation 'junit:junit:4.12' androidTestImplementation 'com.android.support.test:runner:1.0.2' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' implementation files('libs/litepal-2.0.0.jar') //依赖VasDolly api 'com.leon.channel:helper:2.0.1' }
- 首先使用AndResGuard实现资源混淆,再使用VasDolly实现多渠道打包
-
在Gradle界面中,找到app模块下andresguard的task.
- 如果想打debug包,则执行resguardDebug指令;
- 如果想打release包,则执行resguardRelease指令.
- 此处我们双击执行resguardRelease指令,在app目录下的/build/output/apk/release/AndResGuard_{apk_name}/ 文件夹中找到混淆后的Apk,其中app-release_aligned_signed.apk为进行混淆并签名过的apk.
-
我们查看app-release_aligned_signed.apk,res文件夹更名为r,里面的目录名称以及xml文件已经被混淆.
-
将app-release_aligned_signed.apk放到app模块下,在Gradle界面中,找到app模块下channel的task,执行reBuildChannel指令.
-
双击执行reBuildChannel指令,几秒钟就生成了20个通过app-release_aligned_signed.apk的多渠道apk.
- 通过helper类库中的ChannelReaderUtil类读取渠道信息
String channel = ChannelReaderUtil.getChannel(getApplicationContext());
-
-