我们知道,Dalvik虚拟机实则也算是一个Java虚拟机,只不过它执行的不是class文件,而是dex文件。虽然Android 4.4发布了一个ART运行时,准备用来替换掉之前一直使用的Dalvik虚拟机,希望籍此解决饱受诟病的性能问题。但是这里我们先通过罗升阳的Dalvik虚拟机简要介绍和学习计划了解Dalvik虚拟机的内存管理与垃圾回收,对我们分析Android的内存优化会有很多的启示的。
public class HomeActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.act_home);
findViewById(R.id.btn_click).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
startAsyncTask();
}
});
}
/*关于隐式引用----内部类可以直接去调用外部类的成员(属性和方法),
如果没有持有外部类的引用,内部类是没办法去调用外部类的成员,
但是内部类又没有显示的去指定声明引用,所以称之为隐式引用。*/
/*AsyncTask是一个匿名的内部类,隐式的持有外部类(MainActivity)的引用,
当activity被销毁的时候,如果AsyncTask(代码sleep 20秒,模拟了一个耗时操作)
没有执行完成,则MainActivity将会泄漏*/
void startAsyncTask() {
// This async task is an anonymous class and therefore has a hidden reference to the outer
// class MainActivity. If the activity gets destroyed before the task finishes (e.g. rotation),
// the activity instance will leak.
new AsyncTask() {
@Override
protected Void doInBackground(Void... params) {
// Do some slow work in background
SystemClock.sleep(20000);
return null;
}
}.execute();
}
}
示例二
package com.example.guoliuya.memorytest;
import android.content.Context;
/**
* Created by idea on 2016/12/30.
* 单例模式持有context 对象引发内存泄漏示例
*/
public class MyInstanceTest {
private Context context;
private static MyInstanceTest mInstance;
public static MyInstanceTest getInstance(Context context) {
if (mInstance == null) {
synchronized (MyInstanceTest.class) {
if (mInstance == null)
mInstance = new MyInstanceTest(context);
//解决方法 把context的引用替换成ApplicationContext的引用
mInstance = new MyInstanceTest(context.getApplicationContext());
}
}
return mInstance;
}
private MyInstanceTest(Context context) {
this.context = context;
}
}
示例三
public class HomeActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.act_home);
test();
}
//加上static,变成静态匿名内部类
public static void test() {
//匿名内部类会引用其外围实例HomeActivity.this,所以会导致内存泄漏
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
//解决方法
public class HomeActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.act_home);
test();
}
//加上static,变成静态匿名内部类
public static void test() {
//匿名内部类会引用其外围实例HomeActivity.this,所以会导致内存泄漏
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
}
示例四
public class HomeActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.act_home);
findViewById(R.id.btn_click).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
loadData();
}
});
}
private Handler mHandler = new Handler() {
public void handleMessage(android.os.Message msg) {
switch (msg.what) {
case 0:
// 刷新数据
break;
default:
break;
}
};
};
private void loadData() {
//获取数据
mHandler.sendEmptyMessage(0);
}
}
//解决方法
//第一步,将Handler改成静态内部类。
private static class MyHandler extends Handler {
//第二步,将需要引用Activity的地方,改成弱引用。
private WeakReference homaActivityInstance;
public MyHandler(HomeActivity hai) {
this.homaActivityInstance = new WeakReference(hai);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
HomeActivity aty = homaActivityInstance == null ? null : homaActivityInstance.get();
//如果Activity被释放回收了,则不处理这些消息
if (aty == null||aty.isFinishing()) {
return;
}
}
}
private void loadData() {
// 获取数据
myHandler.sendEmptyMessage(0);
}
@Override
protected void onDestroy() {
//第三步,在Activity退出的时候移除回调
super.onDestroy();
myHandler.removeCallbacksAndMessages(null);
}
2.Cursor游标结果集,I/O流,数据库,网络的连接用完及时关闭。
资源性对象比如(Cursor,File文件等)往往都用了一些缓冲,我们在不使用的时候,应该及时关闭它们,以便它们的缓冲及时回收内存。它们的缓冲不仅存在于 java虚拟机内,还存在于java虚拟机外。如果我们仅仅是把它的引用设置为null,而不关闭它们,往往会造成内存泄漏。因为有些资源性对象,比如 SQLiteCursor(在析构函数finalize(),如果我们没有关闭它,它自己会调close()关闭),如果我们没有关闭它,系统在回收它时也会关闭它,但是这样的效率太低了。因此对于资源性对象在不使用的时候,应该调用它的close()函数,将其关闭掉,然后才置为null.在我们的程序退出时一定要确保我们的资源性对象已经关闭。
3.在Android程序里面存在很多需要register 和 unregister的监听器,我们需要确保及时unregister监听器
4.使用ArrayMap/SparseArray来代替HashMap,ArrayMap/SparseArray是专门为移动设备设计的高效的数据结构。
5.不要轻易使用Enum
这点在Google的Android官方培训课程提到过,具体可以参考胡凯前辈的《 Android性能优化典范(三)》
6.避免创建不必要的对象
最好能重用对象而不是在每次需要的时候就创建一个相同功能的新对象
String s = new String("hello world"); //don't do this!
使用单例模式
当心无意识的自动装箱
public static void main(String[] args) {
Long sum = 0L;
for (long i = 0; i < Integer.MAX_VALUE; i++) {
sum += i;
}
System.out.println(sum);
}
7.资源文件需要选择合适的文件夹进行存放
hdpi/xhdpi/xxhdpi等等不同dpi的文件夹下的图片在不同的设备上会经过scale的处理。例如我们只在hdpi的目录下放置了一张100100的图片,那么根据换算关系,xxhdpi的手机去引用那张图片就会被拉伸到200200。需要注意到在这种情况下,内存占用是会显著提高的。对于不希望被拉伸的图片,需要放到assets或者nodpi的目录下。
8.谨慎使用static对象
static对象的生命周期过长,应该谨慎使用
9.不要使用String进行字符串拼接
严格的讲,String拼接只能归结到内存抖动中,因为产生的String副本能够被GC,不会造成内存泄露。
频繁的字符串拼接,使用StringBuffer(不建议使用)或者StringBuilder代替String,可以在一定程度上避免OOM和内存抖动。
10.非静态内部类内存泄露
在Activity中创建非静态内部类,非静态内部类会持有Activity的隐式引用,若内部类生命周期长于Activity,会导致Activity实例无法被回收。(屏幕旋转后会重新创建Activity实例,如果内部类持有引用,将会导致旋转前的实例无法被回收)。解决方案:如果一定要使用内部类,就改用static内部类,在内部类中通过WeakReference的方式引用外界资源.
正确的代码示例:
static class ImageDownloadTask extends AsyncTask {
private String url;
private WeakReference photoAdapter;
public ImageDownloadTask(PhotoAdapter photoAdapter) {
this.photoAdapter = new WeakReference(photoAdapter);
}
@Override
protected Bitmap doInBackground(String... params) {
//在后台开始下载图片
url = params[0];
Bitmap bitmap = photoAdapter.get().loadBitmap(url);
if (bitmap != null) {
//把下载好的图片放入LruCache中
String key = MD5Tools.decodeString(url);
photoAdapter.get().put(key, bitmap);
}
return bitmap;
}
@Override
protected void onPostExecute(Bitmap bitmap) {
super.onPostExecute(bitmap);
//把下载好的图片显示出来
ImageView mImageView = (ImageView) photoAdapter.get().mGridView.get().findViewWithTag(MD5Tools.decodeString(url));
if (mImageView != null && bitmap != null) {
mImageView.setImageBitmap(bitmap);
photoAdapter.get().mDownloadTaskList.remove(this);//把下载好的任务移除
}
}
}
11.匿名内部类内存泄漏
跟非静态内部类一样,匿名内部类也会持有外部类的隐式引用,比较常见的情况有,耗时Handler,耗时Thread,都会造成内存泄漏,解决方式也是static+WeakReference。
12.webview对象没有及时的destroy
一般情况下我们在activity的destory()方法里面会调用webView.destory(),但注意在android5.1之后,这样做会有引起内存泄漏的风险。具体可参考Android 5.1 WebView内存泄漏分析
13.慎用Services
service用于在后台执行一些耗时操作,只用当它执行任务的时候才开启,否则其他时刻都应该不工作,service完成任务之后要主动停止,否则如果用户发现有常驻后台行为的应用并且可能卸载它甚至引起内存泄漏。
当你开启一个service,系统会倾向为了保留这个service而一直保留service所在的进程。这使得进程的运行代价很高,因为系统没有办法把service所占用的RAM空间腾出来让给其他组件。
推荐使用IntentService, 它会在工作线程处理完交代给它的intent任务之后自动停止。
IntentService是Service类的子类,用来处理异步请求。客户端可以通过startService(Intent)
方法传递请求给IntentService。IntentService在onCreate()函数中通过HandlerThread单独
开启一个线程来处理所有Intent请求对象(通过startService的方式发送过来的)所对应的任务,这
样以免事务处理阻塞主线程。执行完所一个Intent请求对象所对应的工作之后,如果没有新的Intent
请求达到,则自动停止Service;否则执行下一个Intent请求所对应的任务。
IntentService在处理事务时,还是采用的Handler方式,创建一个名叫ServiceHandler的内部
Handler,并把它直接绑定到HandlerThread所对应的子线程。 ServiceHandler把处理一个intent
所对应的事务都封装到叫做onHandleIntent的虚函数;因此我们直接实现虚函数onHandleIntent,再
在里面根据Intent的不同进行不同的事务处理就可以了。
另外,IntentService默认实现了Onbind()方法,返回值为null。
使用IntentService需要两个步骤:
1、写构造函数
2、实现虚函数onHandleIntent,并在里面根据Intent的不同进行不同的事务处理就可以了。
好处:处理异步请求的时候可以减少写代码的工作量,比较轻松地实现项目的需求
注意:IntentService的构造函数一定是参数为空的构造函数,然后再在其中调用super("name")这种形式的构造函数。
因为Service的实例化是系统来完成的,而且系统是用参数为空的构造函数来实例化Service的