一个Android程序不可能无限制的使用内存和CPU资源,过多的使用内存会导致程序内存溢出,也就是OOM。过多的使用CPU资源,做一些大量的耗时任务会导致手机卡顿或者无法响应,也就是ANR。性能优化的主要内容包括布局优化、绘制优化、内存泄漏优化、响应速度优化、ListView优化、Bitmap优化、线程优化。
布局优化
布局优化主要思想当然是减少布局文件的层级,尽可能少用ViewGroup,尽可能选用性能高的ViewGroup,比如能使用LinearLayout代替RelativeLayout就使用LinearLayout,因为RelativeLayout相对来说要复杂一些。当然能使用一个ViewGroup就使用一个,尽量少的去嵌套。
使用<include>标签、<merge>标签和ViewStub。<include>标签主要用于布局的重用,相信都使用过。<merge>标签一般和<include>配合使用,减少布局的层级,ViewStub实现了按需加载,当需要的时候才进行加载,下面看看具体的使用。
<include>标签
<include>可以把指定的布局加载到当前布局中,使用<include>标签可以提高复用性
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.lzy.test.MainActivity">
<include
android:id="@+id/include"
layout="@layout/message"
android:layout_weight="1"
android:layout_width="match_parent"
android:layout_height="40dp" />
</LinearLayout>
在这里用<include>标签添加了一个message布局,这样就不需要再把布局重写一遍。在<include>标签里面只支持android:layout_开头的属性和id属性,也就是说不能使用类似android:background这样的属性。还有一点需要注意,当<include>标签中使用了以android:layout_开头的属性,那么必须去指定android:layout_height和android:layout_width,如上所示。
<merge>标签
<merge>标签一般和<include>标签一起使用从而减少布局的层级。所有Activity的根节点都是FrameLayout,如果我们在写布局文件时,根节点也需要一个FrameLayout怎么办?如果直接再写一个那不是两个FrameLayout嵌套在一起,完全没有必要,所以这时根节点写成<merge>就Ok了。
又比如上面的<include>标签,如果include进来的布局里面还是一个LinearLayout,外面<include>标签的根节点也是一个LinearLayout,那么这时include布局的LinearLayout就可以使用<merge>标签代替,如下所示:
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="button1" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="button2" />
</merge>
ViewStub的使用
ViewStub继承于View,宽高都为0,,本身不会参与任何的布局绘制过程,最佳用途就是实现View的延迟加载,避免资源浪费,在需要的时候才加载View,需要注意的是,加载view之后,viewstub本身就会被新加载进来的view替换掉。比如说一个加载失败的界面,是没有必要在初始化的时候就把它加载进来,这时通过ViewStub就可以做到在使用的时候再进行加载,提高了程序的性能。看看下面的例子:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.lzy.test.MainActivity">
<ViewStub
android:id="@+id/viewStub"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout="@layout/tv" />
</LinearLayout>
加载进来的布局
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="40dp"
android:gravity="center"
android:textSize="18sp"></TextView>
final ViewStub viewStub = (ViewStub) findViewById(R.id.viewStub);
handler.postDelayed(new Runnable() {
@Override
public void run() {
TextView view = (TextView) viewStub.inflate();
view.setText("加载进来的TextView");
}
}, 2000);
这里延迟两秒后调用ViewStub的inflate方法把ViewStub替换成要加载的布局,这时ViewStub就不存在了,因为被替换掉了,这个方法返回的正是加载进来的View,所以不需要采用findViewById的方法来找到它,我们加载进来的View是个TextView所以直接强转为TextView就OK,除了inflate方法也可以采用setVisibility方法也可以。
绘制优化
绘制优化是指在onDraw方法中应该要避免执行大量的操作,主要体现在两个方面:
首先,onDraw中不要创建新的局部变量,因为onDraw方法是会频繁的被调用的,这样每次调用后都会产生临时对象,这样不仅会占用过多的内存还会引起频繁的GC。
另一方面的话,在onDraw中不应该做耗时的任务,也不能执行千万次的循环操作,否则会造成绘制过程的不流畅,降低了用户体验。
内存泄漏优化
一个程序中,已经不再使用某个对象,但是因为仍然有引用指向它,垃圾回收器就无法回收它,当然该对象占用的内存就无法被使用,这就造成了内存泄露。下面看看几种常见场景的内存泄漏
静态变量引起的内存泄漏
public class MainActivity extends AppCompatActivity {
private static final String TAG = "lzy";
private static Drawable sDrawable;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
TextView textView = new TextView(this);
sDrawable = getDrawable(R.mipmap.ic_launcher);
textView.setBackground(sDrawable);
setContentView(textView);
}
}
上述代码中sDrawable是一个静态变量,它持有一个textView的引用,而textView又持有这个Activity的引用,也就是说sDrawable持有了Activity的引用,如果销毁这个Activity,由于sDrawable是一个静态变量它不会被销毁,也就说无法释放Activity的引用,所以这个Activity也无法释放,于是导致了内存泄漏。
单例模式引起的内存泄漏
因为单例的静态特性使得单例的生命周期和应用的生命周期一样长,这就说明了如果一个对象已经不需要使用了,而单例对象还持有该对象的引用,那么这个对象将不能被正常回收,这就导致了内存泄漏。
public class AppManager {
private static AppManager instance;
private Context context;
private AppManager(Context context) {
this.context = context;
}
public static AppManager getInstance(Context context) {
if (instance != null) {
instance = new AppManager(context);
}
return instance;
}
}
如果Activity中使用这个单例,传入的Context为Activity,那么这个单例就持有了Activity的引用,将不会释放,所以这个周期应该和Application的周期一样,所以这里AppManager的Context应该这样传,context.getApplicationContext(),这样就可以得到全局的Context。
非静态内部类创建静态实例造成的内存泄漏
有的时候我们可能会在启动频繁的Activity中,为了避免重复创建相同的数据资源,会出现这种写法:
public class MainActivity extends AppCompatActivity {
private static TestResource mResource = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if(mManager == null){
mManager = new TestResource();
}
//...
}
class TestResource {
//...
}
}
这样就在Activity内部创建了一个非静态内部类的单例,每次启动Activity时都会使用该单例的数据,这样虽然避免了资源的重复创建,不过这种写法却会造成内存泄漏,因为非静态内部类默认会持有外部类的引用,而又使用了该非静态内部类创建了一个静态的实例,该实例的生命周期和应用的一样长,这就导致了该静态实例一直会持有该Activity的引用,导致Activity的内存资源不能正常回收。正确的做法为:
将该内部类设为静态内部类或将该内部类抽取出来封装成一个单例,如果需要使用Context,请使用ApplicationContext 。
Handler造成的内存泄漏
Handler的使用造成的内存泄漏问题应该说最为常见了,平时在处理网络任务或者封装一些请求回调等api都应该会借助Handler来处理,对于Handler的使用代码编写一不规范即有可能造成内存泄漏,如下示例:
public class MainActivity extends AppCompatActivity {
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
//...
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
loadData();
}
private void loadData(){
//...request
Message message = Message.obtain();
mHandler.sendMessage(message);
}
}
这种创建Handler的方式会造成内存泄漏,由于mHandler是Handler的非静态匿名内部类的实例,所以它持有外部类Activity的引用,我们知道消息队列是在一个Looper线程中不断轮询处理消息,那么当这个Activity退出时消息队列中还有未处理的消息或者正在处理消息,而消息队列中的Message持有mHandler实例的引用,mHandler又持有Activity的引用,所以导致该Activity的内存资源无法及时回收,引发内存泄漏,所以另外一种做法为:
public class MainActivity extends AppCompatActivity {
private MyHandler mHandler = new MyHandler(this);
private TextView mTextView ;
private static class MyHandler extends Handler {
private WeakReference<Context> reference;
public MyHandler(Context context) {
reference = new WeakReference<>(context);
}
@Override
public void handleMessage(Message msg) {
MainActivity activity = (MainActivity) reference.get();
if(activity != null){
activity.mTextView.setText("");
}
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = (TextView)findViewById(R.id.textview);
loadData();
}
private void loadData() {
//...request
Message message = Message.obtain();
mHandler.sendMessage(message);
}
}
创建一个静态Handler内部类,然后对Handler持有的对象使用弱引用,这样在回收时也可以回收Handler持有的对象,这样虽然避免了Activity泄漏,不过Looper线程的消息队列中还是可能会有待处理的消息,所以我们在Activity的Destroy时或者Stop时应该移除消息队列中的消息,使用mHandler.removeCallbacksAndMessages(null);是移除消息队列中所有消息和所有的Runnable。当然也可以使用mHandler.removeCallbacks();或mHandler.removeMessages();来移除指定的Runnable和Message。
线程优化
线程优化的思想是采用线程池,避免程序中存在大量的Thread。线程池可以重用内部的线程,避免线程的创建的销毁带来的开销,同时还能有效的控制线程池的最大并发数量。
一些优化建议
1.避免创建过多的对象。
2.不要过多使用枚举,它占用的空间比整型大
3.常亮使用static final修饰
当使用了BraodcastReceiver、Cursor、Bitmap等资源时,当不需要使用时,需要及时释放掉,若没有释放,则会引起内存泄漏。