一个良好的App是经过严格的性能优化和内存优化给用户带来良好的操作。今天就说一下内存优化。
Java四种引用
Java的四种引用方式。
- 强引用 无论内存充足与否,系统都不会销毁对象实例。
- 弱引用 只要产生了GC(垃圾回收器),弱引用实例对象容易被销毁。
- 虚引用 检测对象是否已经回收
- 软引用 只要内存不足,就会被释放
通过代码来演示一下效果。
public static void main(String[] args){
//强引用 内存无论怎么样系统都不会释放
String str=new String("String");
//软引用 只要内存不足就释放掉
SoftReference<String> softReference=new SoftReference<String>(str);
//弱引用 只要系统产生了GC(垃圾回收)它引用的对象就会被释放掉
WeakReference<String> weakReference=new WeakReference<String>(str);
//虚引用 判断对象是否已被回收 很少用得上
PhantomReference phantomReference=new PhantomReference<String>(str)
System.out.println("强引用"+str);
System.out.println("软引用"+softReference.get());
System.out.println("弱引用"+weakReference.get());
}
当点击运行时候,输出的结果。
强引用String
软引用String
弱引用String
那么如何手动去销毁其对象实例呢?
强引用可以手动将对象置为null。
弱引用和软引用可以手动调用clear()的方法,弱引用也可以调用gc进行回收,代码如下。
public static void main(String[] args){
//强引用 内存无论怎么样系统都不会释放
String str=new String("String");
//软引用 内存不足就释放掉
SoftReference<String> softReference=new SoftReference<String>(str);
//弱引用 只要系统产生了GC(垃圾回收)它引用的对象就会被释放掉
WeakReference<String> weakReference=new WeakReference<String>(str);
//虚引用 判断对象是否已被回收
PhantomReference phantomReference=new PhantomReference<String>(str)
str=null;
softReference.clear();
System.out.println("强引用"+str);
System.out.println("软引用"+softReference.get());
System.gc();
//这种方式也是可以的
weakReference.clear();
System.out.println("弱引用"+weakReference.get());
}
运行输出效果
强引用null
软引用null
弱引用null
什么是内存泄漏?
举一个例子,如果一个Activity启动的时候执行了长时间的耗时操作,当该耗时操作并未完全结束时,用户点击了back回退,此时的系统产生的Activity页面消失,后台耗时操作仍然运行并持有Activity的对象实例,这样就会导致内存泄漏。也就是说,无用对象仍然被引用而导致内存泄漏。
为了提高开发的稳定性,使用相关的工具进行查找内存泄漏的原因。
LeakCannary
这是第三方工具,可以方便得运用到项目中并快速的找到app是否存在内存泄漏的隐患。
1.首先是添加依赖
dependencies {
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5' releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5' testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
}
2.创建一个类继承自Application,在onCreate()方法中添加如下代码:
public class ExampleApplication extends Application {
@Override public void onCreate() {
super.onCreate();
if (LeakCanary.isInAnalyzerProcess(this)) {
// This process is dedicated to LeakCanary for heap analysis.
// You should not init your app in this process.
return;
}
LeakCanary.install(this);
// Normal app init code...
}
}
这样就能使用了。贴代码。
MainActivity类
public class MainActivity extends Activity implements View.OnClickListener {
private Button btn_press;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn_press= (Button) findViewById(R.id.btn_press);
btn_press.setOnClickListener(this);
}
@Override
public void onClick(View view) {
MyThread myThread = new MyThread();
myThread.start();
}
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
MyApplication类
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
if (LeakCanary.isInAnalyzerProcess(this)) {
// This process is dedicated to LeakCanary for heap analysis.
// You should not init your app in this process.
return;
}
LeakCanary.install(this);
// Normal app init code...
}
}
添加一个按钮
<Button
android:id="@+id/btn_press"
android:text="Press"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
运行效果。当点击按钮是按back回退到主界面。过一会在导航栏的位置就会出现相应的泄漏信息。
常见的内存泄漏
内部类隐式持有外部类的引用导致的内存泄漏。
好比上面演示的例子。当Activity创建的实例被系统销毁时,创建的MyThread类依旧持有activity的对象所以报错。
解决的办法有三种,分别是在
- 将MyThread类变成静态类,
- 在包下重新创建一个MyThread类。然后在MainActivity调用就行了。
- 采用弱引用的方式
这里演示一下弱引用的方式防止内存泄漏。贴上代码。修改上面的代码。
public class MainActivity extends Activity implements View.OnClickListener {
private Button btn_press;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn_press= (Button) findViewById(R.id.btn_press);
btn_press.setOnClickListener(this);
}
@Override
public void onClick(View view) {
MyThread myThread = new MyThread(MainActivity.this);
myThread.start();
}
public static class MyThread extends Thread{
//创建一个弱引用对象
private WeakReference<MainActivity> mReference=null;
//在构造函数初始化弱引用的对象
public MyThread(MainActivity activity){
this.mReference=new WeakReference<MainActivity>(activity);
}
@Override
public void run() {
//取得弱引用的对象并和activity关联
MainActivity activity = mReference.get();
for (int i = 0; i < 20; i++) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
这样工具就不会提示出错了。
Handler内存泄漏
Handler经常存在内存溢出的隐患,因为Handler发送消息过程中,可能存在长时间的耗时操作。为了避免这种情况,我们同样适用弱引用来防止内存泄漏。
代码演示一下,
public class MainActivity extends Activity implements View.OnClickListener {
private Button btn_press;
private Handler mhandler=new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case 1:
btn_press.setText("接收消息,修改文本");
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn_press= (Button) findViewById(R.id.btn_press);
btn_press.setOnClickListener(this);
}
@Override
public void onClick(View view) {
StartHandler();
}
private void StartHandler() {
Message message=Message.obtain();
message.what=1;
message.obj="发送延迟任务";
mhandler.sendMessageDelayed(message,20000);
}
}
虽然按钮的文本确实改变了,但是工具依旧会报错,这是因为,Handler中的Looper类,最后需要从MessageQueue中取出消息,如果我们在发送消息的工程共需要20s的延迟时间,那么Looper这个类就一直处于等待的状态所以导致了内存泄漏。
同样,解决的办法是使用弱引用的方法。
贴代码
public class MainActivity extends Activity implements View.OnClickListener {
private Button btn_press;
//将MainActivity 传进去
private MyHandler myHandler=new MyHandler(MainActivity.this);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn_press= (Button) findViewById(R.id.btn_press);
btn_press.setOnClickListener(this);
}
@Override
public void onClick(View view) {
StartHandler();
}
private void StartHandler() {
Message message=Message.obtain();
message.what=1;
message.obj="发送延迟任务";
mhandler.sendMessageDelayed(message,20000);
}
private static class MyHandler extends Handler{
private WeakReference<MainActivity> mReference;
public MyHandler(MainActivity activity){
this.mReference=new WeakReference<MainActivity>(activity);
}
@Override
public void handleMessage(Message msg) {
MainActivity mainActivity = mReference.get();
if(mainActivity==null){
return ;
}
switch (msg.what){
case 1:
//通过软引用中的对象可以拿到外部非静态的Button对象
mainActivity.btn_press.setText("修改了按钮文本");
break;
}
}
}
}
Fragment如何检测内存泄漏?
对于Fragment的内存泄漏,它有特殊的方法。
需要在MyApplication创建一个RefWatcher观察者对象。
public class MyApplication extends Application {
public static RefWatcher mRefWatcher;
@Override public void onCreate() {
super.onCreate();
//...
mRefWatcher = LeakCanary.install(this);
// Normal app init code...
}
}
在Fragment的onDestroy()方法中添加
MyApplication.mRefWatcher.watch(this);
同理方法案例和上面相同。读者自己摸索。