共计 19231 个字符,预计需要花费 49 分钟才能阅读完成。
什么是性能
快,稳,省,小,这四点很形象的代表了性能的四个方面,同时也让咱们晓得咱们 App 当初是否是款性能良好的 APP,如果有一项不达标,那么阐明咱们的利用有待优化。
很多时候咱们重视性能实现,保障能用,然而咱们会发现,这样的利用很难拿的出手,外面的槽点太多了,性能很差,然而又不晓得从哪里下手进行优化,那么咱们就一步一步来,看看咱们到底应该怎么优化咱们的 APP。
1、布局优化
和 UI 相干的首先就是布局,特地是在开发一些简单界面的时候,通常咱们都是采纳布局嵌套的办法,每个人的布局思路不太一样,写出的也不太一样,,所以就可能造成嵌套的层级过多。
官网 屏幕上的某个像素在同一帧的工夫内被绘制了屡次。在多层次的 UI 构造外面,如果不可见的 UI 也在做绘制的操作,这就会导致某些像素区域被绘制了屡次。这就节约大量的 CPU 以及 GPU 资源。
文言 显示一个布局就好比咱们盖一个房子,首先咱们要测量房子的大小,还要测量房间外面各个家具的大小,和地位,而后进行摆放同时也要对房子进行装修,如果咱们是一层,都在明面上,干起活来敞亮也轻松,可是有的人的房子,喜爱各种隔断,分成一个一个的大隔断间,每个大隔断间里还有小隔断间,小隔断间里有小小隔断间,还有小小小隔断间。。。N 层隔断间。
看到这些头皮发麻吧,而且是一个大隔断间外面所有的小隔断,小小隔断等等都测量完摆放好,能力换另外一个大隔断,天呢,太浪费时间了,不能都间接都放里面吗?也好摆放啊,这么搞我怎么摆,每个隔断间都要装修一遍,太浪费时间了啊。
咱们的 Android 虚拟机也会这么埋怨,咱们家原本就不富裕,什么都要省着用,你这么搞,必定运行有问题啊,那么多嵌套的小隔断间须要解决,都会占用 cpu 计算的工夫和 GPU 渲染的工夫。显示 GPU 适度绘制,分层如下如所示:
通过色彩咱们能够晓得咱们利用是否有多余档次的绘制,如果一路飘红,那么咱们就要相应的解决了。
所以咱们有了第一个优化版本:
优化 1.0
- 如果父控件有色彩,也是本人须要的色彩,那么就不用在子控件加背景色彩
- 如果每个自控件的色彩不太一样,而且能够齐全笼罩父控件,那么就不须要再父控件上加背景色彩
- 尽量减少不必要的嵌套
- 能用 LinearLayout 和 FrameLayout,就不要用 RelativeLayout,因为 RelativeLayout 控件绝对比较复杂,测绘也想要耗时。
做到了以上 4 点只能说祝贺你,入门级优化曾经实现了。
针对嵌套布局,谷歌也是陆续出了一些新的计划。对就是 include、merge 和ViewStub三兄弟。
include 能够进步布局的复用性,大大不便咱们的开发,有人说这个没有缩小布局的嵌套吧,对,include 的确没有,然而 include 和 merge 联手搭配,成果那是杠杠滴。
merge 的布局取决于父控件是哪个布局,应用 merge 相当于缩小了本身的一层布局,间接采纳父 include 的布局,当然间接在父布局外面应用意义不大,所以会和 include 配合应用,既减少了布局的复用性,用缩小了一层布局嵌套。
ViewStub 它能够按需加载,什么意思?用到他的时候喊他一下,再来加载,不须要的时候像空气一样,在一边静静的呆着,不吃你的米,也不花你家的钱。等须要的时候 ViewStub 中的布局才加载到内存,多勤俭持家啊。对于一些进度条,提示信息等等八百年才用一次的性能,应用 ViewStub 是极其适合的。这就是不必不晓得,一用戒不了。
咱们开始进化咱们的优化
优化 1.1
- 应用 include 和 merge 减少复用,缩小层级
- ViewStub 按需加载,更加轻便
可能又有人说了:背景复用了,嵌套曾经很精简了,再精简就实现了不了简单视图了,可是还是一路飘红,这个怎么办?面对这个问题谷歌给了咱们一个新的布局ConstraintLayout
。
ConstraintLayout 能够无效地解决布局嵌套过多的问题。ConstraintLayout 应用束缚的形式来指定各个控件的地位和关系的,它有点相似于 RelativeLayout,但远比 RelativeLayout 要更弱小(照抄隔壁 IOS 的束缚布局)。所以简略布局简略解决,简单布局 ConstraintLayout 很好使,晋升性能从布局做起。
再次进化:
优化 1.2
- 简单界面可抉择 ConstraintLayout,可无效缩小层级
2、绘制优化
咱们把布局优化了,然而和布局非亲非故的还有绘制,这是间接影响显示的两个基本因素。
其实布局优化了对于性能晋升影响不算很大,然而是咱们最容易下手,最间接接触的优化,所以不论能晋升多少,哪怕只有百分之一的晋升,咱们也要做,因为影响性能的中央太多了,每个局部都晋升一点,咱们利用就能够晋升很多了。
咱们平时感觉的卡顿问题最次要的起因之一是因为渲染性能,因为越来越简单的界面交互,其中可能增加了动画,或者图片等等。咱们心愿发明出越来越炫的交互界面,同时也心愿他能够晦涩显示,然而往往卡顿就产生在这里。
这个是 Android 的渲染机制造成的,Android 零碎每隔 16ms 收回 VSYNC 信号,触发对 UI 进行渲染,然而渲染未必胜利,如果胜利了那么代表一切顺利,然而失败了可能就要延误工夫,或者间接跳过去,给人视觉上的体现,就是要么卡了一会,要么跳帧。
View 的绘制频率保障 60fps 是最佳的,这就要求每帧绘制工夫不超过 16ms(16ms = 1000/60),尽管程序很难保障 16ms 这个工夫,然而尽量升高 onDraw 办法中的复杂度总是切实有效的。
这个失常状况下,每隔 16ms draw()一下,很参差,很晦涩,很完满。
往往会产生如下图的状况,有个便秘的家伙霸占着,一帧画面拉的工夫那么长,这一下可不就卡顿了嘛。把前面的工夫给占用了,前面只能延后,或者间接略过了。
既然问题找到了,那么咱们必定要有相应的解决办法,基本做法是 加重 onDraw()的累赘。
所以
第一点: onDraw 办法中不要做耗时的工作,也不做过多的循环操作,特地是嵌套循环,尽管每次循环耗时很小,然而大量的循环势必霸占 CPU 的工夫片,从而造成 View 的绘制过程不晦涩。
第二点: 除了循环之外,onDraw()中不要创立新的部分对象,因为 onDraw()办法个别都会频繁大量调用,就意味着会产生大量的零时对象,不进占用过的内存,而且会导致系统更加频繁的 GC,大大降低程序的执行速度和效率。
其实这两点在 android 的 UI 线程中都实用。
降级进化:
优化 2.0
- onDraw 中不要创立新的部分对象
- onDraw 办法中不要做耗时的工作
其实从渲染优化里咱们也牵扯出了另一个优化,那就是内存优化。
3、内存优化
内存透露指的是那些程序不再应用的对象无奈被 GC 辨认,这样就导致这个对象始终留在内存当中,占用了没来就不多的内存空间。
内存透露是一个迟缓积攒的过程,一点一点的给你,温水煮青蛙个别,咱们往往很难直观的看到,只能最初内存不够用了,程序奔溃了,才晓得外面有大量的透露,然而到底是那些中央?预计是狼烟遍地,千疮百孔,都不晓得如何下手。怎么办?最让人好受的是内存透露状况那么多,记不住,了解也不容易,要害是老会遗记。怎么办呢?老这么上来也不是事,总不能面试的时候突击,做我的项目的时候手足无措吧。所以肯定要记住理解 GC 原理,这样才能够更精确的了解内存透露的场景和起因。不懂 GC 原理的能够先看一下这个 JVM 初探:内存调配、GC 原理与垃圾收集器
原本 GC 的诞生是为了让 java 程序员更加轻松(这一点隔壁 C ++ 苦楚的一匹),java 虚构机会主动帮忙咱们回收那些不再须要的内存空间。通过援用计数法,可达性分析法等等办法,确认该对象是否没有援用,是否能够被回收。
有人会说真么强悍的性能看起来无懈可击啊,对,实践上能够达到打消内存透露,然而很多人不按常理出牌啊,往往很多时候,有的对象还放弃着援用,但逻辑上曾经不会再用到。就是这一类对象,游走于 GC 法律的边缘,我没用了,然而你又不晓得我没用了,就是这么赖着不走,空耗内存。
因为有内存透露,所以内存被占用越来越多,那么 GC 会更容易被触发,GC 会越来越频发,然而当 GC 的时候所有的线程都是暂停状态的,须要解决的对象数量越多耗时越长,所以这也会造成卡顿。
那么什么状况下会呈现这样的对象呢?根本能够分为以下四大类:1、汇合类透露 2、单例 / 动态变量造成的内存透露 3、匿名外部类 / 非动态外部类 4、资源未敞开造成的内存透露
1、汇合类透露
汇合类增加元素后,仍援用着汇合元素对象,导致该汇合中的元素对象无奈被回收,从而导致内存泄露。
举个栗子:
static List<Object> mList = new ArrayList<>();
for (int i = 0; i < 100; i++) {Object obj = new Object();
mList.add(obj);
obj = null;
}
当 mList 没用的时候,咱们如果不做解决的话,这就是典型的占着茅坑不拉屎,mList 外部持有者泛滥汇合元素的对象,不泄露天理难容啊。解决这个问题也超级简略。把 mList 清理掉,而后把它的援用也给开释掉。
mList.clear();
mList = null;
2、单例 / 动态变量造成的内存透露
单例模式具备其 动态个性,它的生命周期 等于应用程序的生命周期,正是因为这一点,往往很容易造成内存透露。先来一个小栗子:
public class SingleInstance {
private static SingleInstance mInstance;
private Context mContext;
private SingleInstance(Context context){this.mContext = context;}
public static SingleInstance newInstance(Context context){if(mInstance == null){mInstance = new SingleInstance(context);
}
return sInstance;
}
}
当咱们在 Activity 外面应用这个的时候,把咱们 Acitivty 的 context 传进去,那么,这个单例就持有这个 Activity 的援用,当这个 Activity 没有用了,须要销毁的时候,因为这个单例还持有 Activity 的援用,所以无奈 GC 回收,所以就呈现了内存透露,也就是生命周期长的持有了生命周期短的援用,造成了内存透露。
所以咱们要做的就是生命周期长的和生命周期长的玩,短的和短的玩。就好比你去商场,原本就是传个话的,话说完就要走了,忽然保安过去非要拉着你的手,说要和你山高水长。只有商场在一天,他就要陪你一天。天呢?太可怕了。叔叔咱们不约,我有我的小伙伴,我还要上学呢,你连忙找你的保洁阿姨去吧。你在商场的生命周期原本可能就是 1 分钟,而保安的生命周期那是要和商场开关门统一的,所以不同生命周期的最好别一起玩的好。
解决方案也很简略:
public class SingleInstance {
private static SingleInstance mInstance;
private Context mContext;
private SingleInstance(Context context){this.mContext = context.getApplicationContext();
}
public static SingleInstance newInstance(Context context){if(mInstance == null){mInstance = new SingleInstance(context);
}
return sInstance;
}
}
还有一个罕用的中央就是 Toast。你应该晓得和谁玩了吧。
3、匿名外部类 / 非动态外部类
这里有一张宝图:
非动态外部类他会持有他外部类的援用,从图咱们能够看到非动态外部类的生命周期可能比外部类更长,这就是二楼的状况统一了,如果非动态外部类的周明周期长于外部类,在加上主动持有外部类的强援用,我的乖乖,想不透露都难啊。
咱们再来举个栗子:
public class TestActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
new MyAscnyTask().execute();
}
class MyAscnyTask extends AsyncTask<Void, Integer, String>{
@Override
protected String doInBackground(Void... params) {
try {Thread.sleep(100000);
} catch (InterruptedException e) {e.printStackTrace();
}
return "";
}
}
}
咱们常常会用这个办法去异步加载,而后更新数据。貌似很平时,咱们开始学这个的时候就是这么写的,没发现有问题啊,然而你这么想一想,MyAscnyTask 是一个非动态外部类,如果他解决数据的工夫很长,极其点咱们用 sleep 100 秒,在这期间 Activity 可能早就敞开了,原本 Activity 的内存应该被回收的,然而咱们晓得非动态外部类会持有外部类的援用,所以 Activity 也须要陪着非动态外部类 MyAscnyTask 一起天荒地老。好了,内存透露就造成了。
怎么办呢?
既然 MyAscnyTask 的生命周期可能比拟长,那就把它变成动态,和 Application 玩去吧,这样 MyAscnyTask 就不会再持有外部类的援用了。两者也互相独立了。
public class TestActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
new MyAscnyTask().execute();
}
// 改了这里 留神一下 static
static class MyAscnyTask extends AsyncTask<Void, Integer, String>{
@Override
protected String doInBackground(Void... params) {
try {Thread.sleep(100000);
} catch (InterruptedException e) {e.printStackTrace();
}
return "";
}
}
}
说完非动态外部类,我再来看看匿名外部类,这个问题很常见,匿名外部类和非动态外部类有一个独特的中央,就是会只有外部类的强援用,所以这哥俩实质是一样的。然而解决办法有些不一样。然而思路相对一样。换汤不换药。
举个灰常相熟的栗子:
public class TestActivity extends Activity {
private TextView mText;
private Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {super.handleMessage(msg);
//do something
mText.setText("do someThing");
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
mText = findVIewById(R.id.mText);
// 匿名线程持有 Activity 的援用,进行耗时操作
new Thread(new Runnable() {
@Override
public void run() {
try {Thread.sleep(100000);
} catch (InterruptedException e) {e.printStackTrace();
}
}
}).start();
mHandler. sendEmptyMessageDelayed(0, 100000);
}
想必这两个办法是咱们常常用的吧,很相熟,也是这么学的,没感觉不对啊,老师就是这么教的,通过咱们下面的剖析,还这么想吗?要害是 耗时工夫过长,造成外部类的生命周期大于外部类,对弈非动态外部类,咱们能够动态化,至于匿名外部类怎么办呢?一样把它变成动态外部类,也就是说尽量不要用匿名外部类。完事了吗?很多人不留神这么一件事,如果咱们在 handleMessage 办法里进行 UI 的更新,这个 Handler 动态化了和 Activity 没啥关系了,然而比方这个 mText,怎么说?全写是 activity.mText,看到了吧,持有了 Activity 的援用,也就是说 Handler 吃力心理变成动态类,自认为不持有 Activity 的援用了,精确的说是不主动持有 Activity 的援用了,然而咱们要做 UI 更新的时候势必会持有 Activity 的援用,动态类持有非动态类的援用,咱们发现怎么又开始内存透露了呢?处处是坑啊,怎么办呢?咱们这里就要引出弱援用的概念了。
援用分为强援用,软援用,弱援用,虚援用,强度一次递加。
强援用
咱们平时不做非凡解决的个别都是强援用,如果一个对象具备强援用,GC 宁肯 OOM 也绝不会回收它。看出多强硬了吧。
软援用(SoftReference)
如果内存空间足够,GC 就不会回收它,如果内存空间有余了,就会回收这些对象的内存。
弱援用(WeakReference)
弱援用要比软援用, 更弱一个级别,内存不够要回收他,GC 的时候不论内存够不够也要回收他,几乎是弱的一匹。不过 GC 是一个优先级很低的线程,也不是太频繁进行,所以弱援用的生存还过得去,没那么胆战心惊。
虚援用
用的甚少,我没有用过,如果想理解的敌人,能够自行谷歌百度。
所以咱们用弱援用来润饰 Activity,这样 GC 的时候,该回收的也就回收了,不会再有内存透露了。很完满。
public class TestActivity extends Activity {
private TextView mText;
private MyHandler myHandler = new MyHandler(TestActivity.this);
private MyThread myThread = new MyThread();
private static class MyHandler extends Handler {
WeakReference<TestActivity> weakReference;
MyHandler(TestActivity testActivity) {this.weakReference = new WeakReference<TestActivity>(testActivity);
}
@Override
public void handleMessage(Message msg) {super.handleMessage(msg);
weakReference.get().mText.setText("do someThing");
}
}
private static class MyThread extends Thread {
@Override
public void run() {super.run();
try {sleep(100000);
} catch (InterruptedException e) {e.printStackTrace();
}
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
mText = findViewById(R.id.mText);
myHandler.sendEmptyMessageDelayed(0, 100000);
myThread.start();}
// 最初清空这些回调
@Override
protected void onDestroy() {super.onDestroy();
myHandler.removeCallbacksAndMessages(null);
}
4、资源未敞开造成的内存透露
- 网络、文件等流遗记敞开
- 手动注册播送时,退出时遗记 unregisterReceiver()
- Service 执行完后遗记 stopSelf()
- EventBus 等观察者模式的框架遗记手动解除注册
这些须要记住又开就无关,具体做法也很简略就不一一赘述了。给大家介绍几个很好用的工具:1、leakcanary 傻瓜式操作,哪里有透露主动给你显示进去,很间接很暴力。2、咱们平时也要多应用 Memory Monitor 进行内存监控,这个剖析就有些难度了,能够上网搜一下具体怎么应用。3、Android Lint 它能够帮忙咱们发现代码机构 / 品质问题,同时提供一些解决方案,内存泄露的会飘黄,用起来很不便,具体应用办法上网学习,这里不多做阐明了。
so
优化 3.0
-
解决各个状况下的内存透露,留神平时代码的标准。
4、启动速度优化
不晓得大家有没有仔细发现,咱们的利用启动要比别的大厂的要慢,要花费更多的工夫,明明他们的包体更大,app 更简单,怎么启动工夫反而比咱们的短呢?
然而这块的优化关注的人很少,因为 App 经常伴有闪屏页,所以这个问题看起来就不是问题了,然而一款好的利用是相对不容许这样的,我加闪屏页是我的事,启动速度慢相对不能够。
app 启动分为冷启动(Cold start)、热启动(Hot start)和温启动(Warm start)三种。
冷启动(Cold start)
冷启动是指应用程序从头开始:零碎的过程在此开始之前没有创立应用程序。冷启动产生在诸如自设施启动以来首次启动应用程序或自零碎终止应用程序以来。
在冷启动开始时,零碎有三个工作。这些工作是:1、加载并启动应用程序 2、启动后立刻显示应用程序的空白启动窗口 3、创立应用程序过程
当零碎为咱们创立了利用过程之后,开始创立应用程序对象。
1、启动主线程 2、创立主 Activity 3、加载布局 4、屏幕布局 5、执行初始绘制
应用程序过程实现第一次绘制后,零碎过程会替换以后显示的背景窗口,将其替换为主流动。此时,用户能够开始应用该应用程序。至此启动实现。
Application 创立
当 Application 启动时,空白的启动窗口将保留在屏幕上,直到零碎首次实现绘制应用程序。此时,零碎过程会替换应用程序的启动窗口,容许用户开始与应用程序进行交互。这就是为什么咱们的程序启动时会先呈现一段时间的黑屏(白屏)。
如果咱们有本人的 Application,零碎会 onCreate()
在咱们的 Application 对象上调用该办法。之后,应用程序会生成主线程(也称为 UI 线程),并通过创立次要流动来执行工作。
从这一点开始,App 就依照他的 应用程序生命周期阶段进行。
Activity 创立
应用程序过程创建活动后,流动将执行以下操作:
- 初始化值。
- 调用构造函数。
- 调用回调办法,例如 Activity.onCreate() “https://developer.android.com/reference/android/app/Activity.html#onCreate(android.os.Bundle)”),对应 Activity 的以后生命周期状态。
通常,该 onCreate() “https://developer.android.com/reference/android/app/Activity.html#onCreate(android.os.Bundle)”)办法对加载工夫的影响最大,因为它以最高的开销执行工作:加载和收缩视图,以及初始化流动运行所需的对象。
热启动(Hot start)
应用程序的热启动比冷启动要简略得多,开销也更低。在一个热启动中,零碎都会把你的 Activity 带到前台。如果应用程序的 Activity 依然驻留在内存中,那么应用程序能够防止反复对象初始化、布局加载和渲染。
热启动显示与冷启动计划雷同的屏幕行为:零碎过程显示空白屏幕,直到应用程序实现出现流动。
温启动(Warm start)
温启动蕴含了冷启动时产生的一些操作,与此同时,它示意的开销比热启动少,有许多潜在的状态能够被认为是和煦的开始。
场景:
- 用户退出您的利用,但随后重新启动它。该过程可能已持续运行,但应用程序必须通过调用从头开始从新创立 Activity 的 onCreate() “https://developer.android.com/reference/android/app/Activity.html#onCreate(android.os.Bundle)”)。
- 零碎将您的应用程序从内存中逐出,而后用户重新启动它。须要重新启动过程和流动,然而在调用 onCreate()的时候能够从 Bundle(savedInstanceState)获取数据。
理解完启动过程,咱们就晓得哪里会影响咱们启动的速度了。在创立应用程序和创立 Activity 期间都可能会呈现性能问题。
这里是慢的定义:
- 冷启动须要 5 秒或更长时间。
- 温启动须要 2 秒或更长时间。
- 热启动须要 1.5 秒或更长时间。
无论何种启动,咱们的优化点都是:Application、Activity 创立以及回调等过程
谷歌官网给的倡议是:1、利用提前展现进去的 Window,疾速展现进去一个界面,给用户疾速反馈的体验;2、防止在启动时做密集惨重的初始化(Heavy app initialization);3、防止 I / O 操作、反序列化、网络操作、布局嵌套等。
具体做法:
针对 1:利用提前展现进去的 Window,疾速展现进去一个界面
应用 Activity 的 windowBackground 主题属性来为启动的 Activity 提供一个简略的 drawable。
Layout XML file:
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" android:opacity="opaque">
<!-- The background color, preferably the same as your normal theme -->
<item android:drawable="@android:color/white"/>
<!-- Your product logo - 144dp color version of your app icon -->
<item>
<bitmap
android:src="@drawable/product_logo_144dp"
android:gravity="center"/>
</item>
</layer-list>
Manifest file:
<activity ...
android:theme="@style/AppTheme.Launcher" />
这样在启动的时候,会先展现一个界面,这个界面就是 Manifest 中设置的 Style,等 Activity 加载结束后,再去加载 Activity 的界面,而在 Activity 的界面中,咱们将主题从新设置为失常的主题,从而产生一种快的感觉。其实就是个障眼法而已,提前让你看到了假的界面。也算是一种不错的办法,然而治标不治本。
针对 2:防止在启动时做密集惨重的初始化
咱们扫视一下咱们的 MyApplication 外面的操作。初始化操作有友盟,百度,bugly,数据库,IM,神策,图片加载库,网络申请库,广告 sdk,地图,推送,等等,这么多须要初始化,Application 的工作太重了,启动不慢才怪呢。
怎么办呢?这些还都是必要的,不能不去初始化啊,那就只能异步加载了。然而并不是所有的都能够进行异步解决。这里分状况给出一些倡议:1、比方像友盟,bugly 这样的业务非必要的能够的异步加载。2、比方地图,推送等,非第一工夫须要的能够在主线程做延时启动。当程序曾经启动起来之后,在进行初始化。3、对于图片,网络申请框架必须在主线程里初始化了。
同时因为咱们个别会有闪屏页面,也能够把延时启动的地图,推动的启动在这个时间段里,这样合理安排工夫片的应用。极大的进步了启动速度。
针对 3:防止 I / O 操作、反序列化、网络操作、布局嵌套等。
这个不必多说了,大家应该晓得如何去做了,有些上文也有阐明。
so
优化 4.0
- 利用提前展现进去的 Window,疾速展现进去一个界面,给用户疾速反馈的体验;
- 防止在启动时做密集惨重的初始化(Heavy app initialization);
-
防止 I / O 操作、反序列化、网络操作、布局嵌套等。
5、包体优化
我做过两年的海内利用产品,深知包体大小对于产品新增的影响,包体小百分之五,可能新增就减少百分之五。如果产品基数很大,这个晋升就更可怕了。不管怎么说,咱们要减肥,要六块腹肌,不要九九归一的大肚子。
既然要瘦身,那么咱们必须晓得 APK 的文件形成,解压 apk:
assets 文件夹 寄存一些配置文件、资源文件,assets 不会主动生成对应的 ID,而是通过 AssetManager 类的接口获取。
res 目录 res 是 resource 的缩写,这个目录寄存资源文件,会主动生成对应的 ID 并映射到 .R 文件中,拜访间接应用资源 ID。
META-INF 保留利用的签名信息,签名信息能够验证 APK 文件的完整性。
AndroidManifest.xml 这个文件用来形容 Android 利用的配置信息,一些组件的注册信息、可应用权限等。
classes.dex Dalvik 字节码程序,让 Dalvik 虚拟机可执行,个别状况下,Android 利用在打包时通过 Android SDK 中的 dx 工具将 Java 字节码转换为 Dalvik 字节码。
resources.arsc 记录着资源文件和资源 ID 之间的映射关系,用来依据资源 ID 寻找资源。
咱们须要从代码和资源两个方面去缩小响应的大小。
1、首先咱们能够应用 lint
工具,如果有没有应用过的资源就会打印如下的信息(不会应用的敌人能够上网看一下)
res/layout/preferences.xml: Warning: The resource R.layout.preferences appears
to be unused [UnusedResources]
同时咱们能够开启资源压缩, 主动删除无用的资源
android {
...
buildTypes {
release {
shrinkResources true
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'),
'proguard-rules.pro'
}
}
无用的资源曾经被删除了,接下来哪里能够在瘦身呢?
2、咱们能够应用可绘制对象,某些图像不须要动态图像资源; 框架能够在运行时动静绘制图像。Drawable 对象(<shape>
以 XML 格局)能够占用 APK 中的大量空间。此外,XML Drawable 对象产生合乎资料设计准则的单色图像。
下面的话官网,简略说来就是,能本人用 XML 写 Drawable,就本人写,能不必公司的 UI 切图,就别和他们谈话,咱们本人造,做本人的 UI,美滋滋。而且这种图片占用空间会很小。
3、重用资源,比方一个三角按钮,点击前三角朝上代表收起的意思,点击后三角朝下,代表开展,个别状况下,咱们会用两张图来切换,咱们齐全能够用旋转的模式去扭转
<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/ic_thumb_up"
android:pivotX="50%"
android:pivotY="50%"
android:fromDegrees="180" />
比方同一图像的着色不同,咱们能够用 android:tint 和 tintMode 属性,低版本(5.0 以下)能够应用 ColorFilter。
4、压缩 PNG 和 JPEG 文件 您能够缩小 PNG 文件的大小,而不会失落应用工具如图像品质 pngcrush,pngquant,或 zopflipng。所有这些工具都能够缩小 PNG 文件的大小,同时放弃感知的图像品质。
5、应用 WebP 文件格式 能够应用图像的 WebP 文件格式,而不是应用 PNG 或 JPEG 文件。WebP 格局提供有损压缩(如 JPEG)以及透明度(如 PNG),但能够提供比 JPEG 或 PNG 更好的压缩。
能够应用 Android Studio 将现有的 BMP,JPG,PNG 或动态 GIF 图像转换为 WebP 格局。
6、应用矢量图形 能够应用矢量图形来创立与分辨率无关的图标和其余可伸缩 Image。应用这些图形能够大大减少 APK 大小。一个 100 字节的文件能够生成与屏幕大小相干的清晰图像。
然而,零碎渲染每个 VectorDrawable 对象须要破费大量工夫,而较大的图像须要更长的工夫能力显示在屏幕上。因而,请思考仅在显示小图像时应用这些矢量图形。
不要把 AnimationDrawable 用于创立逐帧动画,因为这样做须要为动画的每个帧蕴含一个独自的位图文件,这会大大增加 APK 的大小。
7、代码混同 应用 proGuard 代码混同器工具,它包含压缩、优化、混同等性能。这个大家太相熟了。不多说了。
android {
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile(‘proguard-android.txt'),'proguard-rules.pro'
}
}
8、插件化。比方功能模块放在服务器上,按需下载,能够缩小安装包大小。
so
优化 5.0
- 代码混同
- 插件化
- 资源优化
6、耗电优化
咱们可能对耗电优化不怎么感冒,没事,谷歌这方面做得也不咋地,5.0 之后才有像样的计划,讲瞎话这个优化的优先级没有后面几个那么高,然而咱们也要理解一些防止耗电的坑,至于更细的耗电剖析能够应用这个 Battery Historian。
Battery Historian 是由 Google 提供的 Android 零碎电量剖析工具,从手机中导出 bugreport 文件上传至页面,在网页中生成具体的图表数据来展现手机上各模块电量耗费过程,最初通过 App 数据的剖析制订出相干的电量优化的办法。
咱们来谈一下怎么躲避电老虎吧。
谷歌举荐应用 JobScheduler,来调整工作优先级等策略来达到升高损耗的目标。JobScheduler 能够防止频繁的唤醒硬件模块,造成不必要的电量耗费。防止在不适合的工夫 (例如低电量状况下、弱网络或者挪动网络状况下的) 执行过多的工作耗费电量。
具体性能:1、能够推延的非面向用户的工作(如定期数据库数据更新);2、当充电时才心愿执行的工作(如备份数据);3、须要拜访网络或 Wi-Fi 连贯的工作(如向服务器拉取配置数据);4、零散工作合并到一个批次去定期运行;5、当设施闲暇时启动某些工作;6、只有当条件失去满足, 零碎才会启动打算中的工作(充电、WIFI…);
同时谷歌针对耗电优化也提出了一个懈怠第一的法令:
缩小 你的应用程序能够删除冗余操作吗?例如,它是否能够缓存下载的数据而不是反复唤醒无线电以从新下载数据?
推延 利用是否须要立刻执行操作?例如,它能够等到设施充电能力将数据备份到云端吗?
合并 能够批处理工作,而不是屡次将设施置于活动状态吗?例如,几十个应用程序是否真的有必要在不同工夫关上收音机发送邮件?在一次唤醒收音机期间,是否能够传输音讯?
谷歌在耗电优化这方面的确显得有些有力,心愿当前能够退出更好的工具和解决方案,不然这方面的优化优先级还是很低。付出和回报所差太大。
so
优化 6.0
- 应用 JobScheduler 调度工作
- 应用懈怠法令
6、ListView 和 Bitmap 优化
针对 ListView 优化,次要是正当应用 ViewHolder。创立一个外部类 ViewHolder,外面的成员变量和 view 中所蕴含的组件个数、类型雷同,在 convertview 为 null 的时候,把 findviewbyId 找到的控件赋给 ViewHolder 中对应的变量,就相当于先把它们装进一个容器,下次要用的时候,间接从容器中获取。
当初咱们当初个别应用 RecyclerView,自带这个优化,不过还是要了解一下原理的好。而后能够对承受来的数据进行分段或者分页加载,也能够优化性能。
对于 Bitmap,这个咱们应用的就比拟多了,很容易呈现 OOM 的问题。
Bitmap 的优化套路很简略,粗犷,就是让压缩。三种压缩形式:1. 对图片品质进行压缩 2. 对图片尺寸进行压缩 3. 应用 libjpeg.so 库进行压缩
对图片品质进行压缩
public static Bitmap compressImage(Bitmap bitmap){ByteArrayOutputStream baos = new ByteArrayOutputStream();
// 品质压缩办法,这里 100 示意不压缩,把压缩后的数据寄存到 baos 中
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);
int options = 100;
// 循环判断如果压缩后图片是否大于 50kb, 大于持续压缩
while (baos.toByteArray().length / 1024>50) {
// 清空 baos
baos.reset();
bitmap.compress(Bitmap.CompressFormat.JPEG, options, baos);
options -= 10;// 每次都缩小 10
}
// 把压缩后的数据 baos 寄存到 ByteArrayInputStream 中
ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());
// 把 ByteArrayInputStream 数据生成图片
Bitmap newBitmap = BitmapFactory.decodeStream(isBm, null, null);
return newBitmap;
}
对图片尺寸进行压缩
/**
* 按图片尺寸压缩 参数是 bitmap
* @param bitmap
* @param pixelW
* @param pixelH
* @return
*/
public static Bitmap compressImageFromBitmap(Bitmap bitmap, int pixelW, int pixelH) {ByteArrayOutputStream os = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, os);
if(os.toByteArray().length / 1024>512) {// 判断如果图片大于 0.5M, 进行压缩防止在生成图片(BitmapFactory.decodeStream)时溢出
os.reset();
bitmap.compress(Bitmap.CompressFormat.JPEG, 50, os);// 这里压缩 50%,把压缩后的数据寄存到 baos 中
}
ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray());
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
options.inPreferredConfig = Bitmap.Config.RGB_565;
BitmapFactory.decodeStream(is, null, options);
options.inJustDecodeBounds = false;
options.inSampleSize = computeSampleSize(options , pixelH > pixelW ? pixelW : pixelH ,pixelW * pixelH);
is = new ByteArrayInputStream(os.toByteArray());
Bitmap newBitmap = BitmapFactory.decodeStream(is, null, options);
return newBitmap;
}
/**
* 动静计算出图片的 inSampleSize
* @param options
* @param minSideLength
* @param maxNumOfPixels
* @return
*/
public static int computeSampleSize(BitmapFactory.Options options, int minSideLength, int maxNumOfPixels) {int initialSize = computeInitialSampleSize(options, minSideLength, maxNumOfPixels);
int roundedSize;
if (initialSize <= 8) {
roundedSize = 1;
while (roundedSize < initialSize) {roundedSize <<= 1;}
} else {roundedSize = (initialSize + 7) / 8 * 8;
}
return roundedSize;
}
private static int computeInitialSampleSize(BitmapFactory.Options options, int minSideLength, int maxNumOfPixels) {
double w = options.outWidth;
double h = options.outHeight;
int lowerBound = (maxNumOfPixels == -1) ? 1 : (int) Math.ceil(Math.sqrt(w * h / maxNumOfPixels));
int upperBound = (minSideLength == -1) ? 128 :(int) Math.min(Math.floor(w / minSideLength), Math.floor(h / minSideLength));
if (upperBound < lowerBound) {return lowerBound;}
if ((maxNumOfPixels == -1) && (minSideLength == -1)) {return 1;} else if (minSideLength == -1) {return lowerBound;} else {return upperBound;}
}
应用 libjpeg.so 库进行压缩 能够参考这篇 Android 性能优化系列之 Bitmap 图片优化:https://blog.csdn.net/u012124…)
优化 7.0
- ListView 应用 ViewHolder,分段,分页加载
- 压缩 Bitmap
8、响应速度优化
影响响应速度的次要因素是主线程有耗时操作,影响了响应速度。所以响应速度优化的核心思想是防止在主线程中做耗时操作,把耗时操作异步解决。
9、线程优化
线程优化的思维是采纳线程池,防止在程序中存在大量的 Thread。线程池能够重用外部的线程,从而防止了现场的创立和销毁所带来的性能开销,同时线程池还能无效地控制线程池的最大并发数,防止大量的线程因相互抢占系统资源从而导致阻塞景象产生。
《Android 开发艺术摸索》对线程池的解说很具体,不相熟线程池的能够去理解一下。
- 长处:1、缩小在创立和销毁线程上所花的工夫以及系统资源的开销。2、如不应用线程池,有可能造成零碎创立大量线程而导致耗费完零碎内存以及”适度切换”。
- 须要留神的是:1、如果线程池中的数量为达到外围线程的数量,则间接会启动一个外围线程来执行工作。2、如果线程池中的数量曾经达到或超过外围线程的数量,则工作会被插入到工作队列中标期待执行。3、如果 (2) 中的工作无奈插入到工作队列中,因为工作队列已满,这时候如果线程数量未达到线程池规定最大值,则会启动一个非核心线程来执行工作。4、如果 (3) 中线程数量曾经达到线程池最大值,则会拒绝执行此工作,ThreadPoolExecutor 会调用 RejectedExecutionHandler 的 rejectedExecution 办法告诉调用者。
10、微优化
这些微优化能够在组合时进步整体应用程序性能,但这些更改不太可能导致显着的性能影响。抉择正确的算法和数据结构应始终是咱们的首要任务,以进步代码效率。
- 编写高效代码有两个根本规定 :1、
不要做你不须要做的工作
2、如果能够防止,请不要分配内存
1、防止创立不必要的对象 对象创立永远不是收费的,尽管每一个的代价不是很大,然而总归是代价的不是吗?能不创立何必要浪费资源呢?
2、首选动态(这里说的是特定情景)如果您不须要拜访对象的字段,请使您的办法放弃动态。调用速度将进步约 15%-20%。这也是很好的做法,因为你能够从办法签名中看出,调用办法不能扭转对象的状态
3、对常量应用 static final 此优化仅实用于根本类型和 String 常量,而不适用于 任意援用类型。尽管如此,static final
尽可能申明常量是一种好习惯。
4、应用加强的 for 循环语法 加强 for
循环(for-each)可用于实现 Iterable 接口和数组的汇合。对于汇合,调配一个迭代器来对 hasNext()
和进行接口调用next()
。应用一个 ArrayList,手写计数循环快约 3 倍,但对于其余汇合,加强的 for 循环语法将齐全等效于显式迭代器用法。
5、防止应用浮点数 依据教训,浮点数比 Android 设施上的整数慢约 2 倍
结尾
本文篇幅无限,性能优化的方面很多,每一项深刻上来,不写个几十万字是完结不了,所以很多都是浅尝辄止,心愿能够抛砖引玉,用我的高明的文章,给大家一些帮忙。性能优化须要走的路还很远,心愿能和各位同学一起前行,一起提高。
相干教程
Android 根底系列教程:
Android 根底课程 U - 小结_哔哩哔哩_bilibili
Android 根底课程 UI- 布局_哔哩哔哩_bilibili
Android 根底课程 UI- 控件_哔哩哔哩_bilibili
Android 根底课程 UI- 动画_哔哩哔哩_bilibili
Android 根底课程 -activity 的应用_哔哩哔哩_bilibili
Android 根底课程 -Fragment 应用办法_哔哩哔哩_bilibili
Android 根底课程 - 热修复 / 热更新技术原理_哔哩哔哩_bilibili
本文转自 https://juejin.cn/post/6844903641032163336,如有侵权,请分割删除。