一、什么是性能?
以上图片形象的描述了什么是性能。如果我们开发出来的APP没有达到以上的快、稳、省、小。那说明我们的应用是有待优化的,就是我们说的性能优化。
二、性能优化
1、布局优化
(1)避免不必要的多层级嵌套
屏幕某个像素在同一帧的时间内被绘制了多次,会浪费CPU资源。
(2)如果父控件已经有颜色,也是子控件需要的颜色,就没必要再子控件设置颜色。
(3)能用LinearLayout和FrameLayout,尽量不用RelativeLayout。因为RelativeLayout相对复杂,绘制更耗时
(4)使用ViewStub,按需加载,需要的时候才绘制。
(5)复杂的界面可以用ConstraintLayout,可以有效减少层级(本人不怎么使用,使用比较麻烦)
2、绘制的优化
(1)不要在onDraw方法中做耗时操作,会导致View绘制不流畅
(2)不要在onDraw方法中创建新的局部对象,因为onDraw方法会频繁的调用,这样就会创建很多局部对象,不仅仅暂用内存,也会导致系统频繁的GC操作。大大的降低了程序的运行效率。
3、内存的优化
3.1、内存泄露的危害
我们知道JAVA中特有的GC让JAVA程序员更加的轻松,也就是JAVA虚拟机自动识别那些没用的对象,然后进行回收,释放内存。但是往往有的时候,有的对象逻辑上已经没用了,但是还保持着引用,想当于赖在内存中不走,空耗内存。这样就导致了内存的泄露。因为有了内存的泄露,那么内存被占用就越来越多,这样就更加容易触发GC,而每一次GC操作,所有的线程都是停止的状态,如果过多频繁的GC,那么线程停止和恢复就越频繁,也就会容易造成卡顿。
简单的来说
1、内存泄露会导致内存被空耗。
2、内存泄露过多会造成频繁的GC,造成卡顿。
3.2、造成内存泄露的情况
上面我们说了内存泄露的危害,下面我们来了解下内存泄露的主要常见情况。
1、集合类泄露
static List
以上的静态集合变量持有了众多个对象,当我们不用该集合又不做处理的时候,就会造成内存泄露。处理也比较简单。先把集合清理掉,然后把它的引用也释放。
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;
}
}
我们看到构造方法SingleInstance需要一个Context对象,如果我们把Activity的Context传给它,当我们的Activity需要被销毁的时候,而静态变量SingleInstance mInstance还持有Activity的引用,会导致GC无法回收。所以就会出现了内存泄露,也就是生命周期长的持有生命周期短的引用,导致生命周期短的无法被回收。我们来看下下具体的解决办法。
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;
}
}
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{
@Override
protected String doInBackground(Void... params) {
try {
Thread.sleep(100000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "";
}
}
}
在TestActivity 中有一个 非静态内部类MyAscnyTask ,如果MyAscnyTask 中的doInBackground操作耗时较长,而TestActivity 早就被关闭了,而MyAscnyTask 还持有TestActivity 的引用,就回导致TestActivity 无法被GC。解决办法,我们只需要把非静态内部类改成静态的内部类即可:
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{
@Override
protected String doInBackground(Void... params) {
try {
Thread.sleep(100000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "";
}
}
}
说完非静态内部类,我们来说匿名内部类,其实匿名内部类和非静态内部类的性质是一样的,都是因为持有
外部类的引用,导致无法GC。下面我们来看个例子
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);
}
你们线程持有了外部类TestActivity 的引用,当TestActivity 销毁的时候,由于匿名线程还持有TestActivity的引用,导致无法GC。以上我们第一反应我们就会想到,我们只需把这个匿名内部类改成静态的内部类,这样就不会持有外部类的引用了,这样问题不就是解决了吗?对于以上,我们只需要把Thread和Handler 用一个静态的内部类实现,这就不会持有TestActivity 的引用了。
是的,这样做只是解决问题第一步,我们观察
mText.setText(" do someThing");
我们把Handler改成了静态的,但是我们在handleMessage方法中做UI操作,mText.setText(" do someThing");而mText肯定是持有TestActivity 的引用,由于Handler是静态的,也就说生命周期大于TestActivity,这一来又会导致生命周期长的持有生命周期短的引用,无法GC。怎么解决呢?除了我们要把匿名内部类改成静态的内部类之外,我们还需要通过弱引用的方式去解决。我们先来了解一下JAVA中的几个引用。
(1)强引用
我们平时不做特殊处理的情况下,一般都是属于强引用。当无法GC的时候,就算内存溢出OOM了也不会回收。
(2)软引用
当内存空间足够的情况下不会回收,只有内存空间不足的时候才会强制回收
(3)弱引用
GC的时候,不管内存够不够都要回收他。由于GC是一个优先级很低的线程,不会频繁的操作,所以我们使用的时候,不用担心对象被盲目的GC掉。
(4)虚引用
没用过
所以针对以上的问题,我们可以使用弱引用,要就是说,当TestActivity被关闭的时候,JVM会进行GC回收。这个时候即使其他的类还持有TestActivity的引用,由于我们使用的是弱引用,所以会进行强制的回收。具体解决办法
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 weakReference;
MyHandler(TestActivity testActivity) {
this.weakReference = new WeakReference(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、资源未关闭造成内存泄露
网络、文件等流忘记关闭
手动注册广播,忘记取消注册
Service执行完忘记stopSelf
EventBus 等观察者模式框架忘记手动解除注册
平时注意代码规范。
工具:leakcanary可以检测内存泄露
4、启动速度的优化
在介绍APP启动速度的优化的时候我们先来了解APP启动的三种方式:
冷启动、热启动、温启动。
冷启动
冷启动指的是APP应用从头开始启动,通常是APP的第一次启动。启动的时候系统有三个任务:
(1)加载并启动应用程序
(2)启动后立即显示窗口(也就是我们平常看到的空白窗口)
(3)创建应用进程
只有创建应用进程完之后,才会创建我们的应用程序对象,主要由以下步骤:
(1)启动主线程
(2)创建主Activity
(3)加载布局
(4)屏幕布局(在屏幕中布置布局)
(5)初始化绘制
当绘制完成之后,系统才会交换当前显示的窗口,将其替换为主活动,此时用户可以使用该应用程序,也就是我们的系统界面可以看到了。在绘制完成之前,一直都是属于空白页。
Application的创建
我们知道Application初始化完之后,才会初始化我们的主Activity,然后加载布局,绘制操作,最后将窗口交换为当前Activity的界面。在显示主Activity界面之前窗口都是属于空白页。
热启动
热启动比冷启动开销较小,在热启动中,当我们需要启动的Activity已经在内存中,则不会创建新的Activity,而是直接把Activity带到前台,避免了创建Activity加载布局,绘制等操作。
温启动
没啥要讲的
以上小结:冷启动和热启动
当应用程序的Activity退出的时候,进程没有被杀死,重新启动Acivity的时候,会在原来的进程中创建Activity
,或者如果内存中已有需要启动的Activity将把该Activity移到前台。这样的启动方式叫做热启动。
假如打开Activity的时候没有进程,则去创建一个新的进程,在该进程中创建Activity。这样的启动方式叫做任冷启动。
不管是冷启动还是热启动都会经历空白页。所以我们针对以上进程优化:
1、利用提前展示出来的Window,快速展示出一个界面来代替空白页。
2、避免在Applicaion中做大量的初始化操作,如果能异步就异步。尽快加载主Activity。
5、包体积的优化
1、lint检测无用的代码、资源。
2、包的体积主要在于图片。能自己手写shape就不要用图片
3、适当压缩图片
4、代码混淆,使用proGuard 代码混淆器工具,它包括压缩、优化、混淆等功
6、电量的优化
Battery Historian电量分析工具
1、使用JobScheduler调度任务
在合适的场景调度相应的任务。
2、懒人做法。缓存数据、推迟执行等等
7、相应速度的优化
异步任务
8、线程的优化
使用线程池,因为大量的线程的创建和销毁会给性能带来一定的开销。线程池还能有效的控制并发数,因为如果有大量的线程相互想占资源,会造成堵塞现象。