IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Android内存优化,常见内存泄漏及优化方案 -> 正文阅读

[移动开发]Android内存优化,常见内存泄漏及优化方案

前言

在安卓开发中,一些不好的编程习惯会导致应用出现内存泄漏的情况。

1. 单例导致的内存泄漏

单例模式在开发中是非常常见的,但因为单例模式的静态特性使得其生命周期同应用生命周期一样长,如果一个对象没有用处了,但单例还持有它的引用,那么在整个生命周期中都不会被回收,就可能导致内存泄漏。如下代码:

public class Singleton { 
	private static Singleton singleton = null; 
	private Context mContext; 
	public Singleton(Context mContext) { 
		this.mContext = mContext; 
	} 
	public static Singleton getSingleton(Context context){ 
		if (null == singleton){ 
		singleton = new Singleton(context); 
		} 
		return singleton; 
	}
}

1.1 问题阐述

如果我们调用getInstance(Context context)方法的时候传入context参数是Activity、Service等上下文,就到导致内存泄漏。原因如下:

当我们退出Activity时,该Activity就没有用了,但是因为singleton作为静态单例(在应用整个生命周期都会存在)会继续持有这个Activity的引用,导致这个Activity对象无法被回收释放,就可能导致内存泄漏。

1.2 优化意见

为了避免这种单例导致的内存泄漏,我们可以将context参数改为全局的上下文,如:

public Singleton(Context mContext) { 
	this.mContext = mContext.getApplicationContext(); 
}

全局的上下文Application Context就是应用程序的上下文,和单例的生命周期一样长,这样就能避免内存泄漏。单例模式对应应用的生命周期,所以我们在构造单例的时候尽量避免使用Activit的上下文,而是用Application的上下文。

2. 非静态内部类导致的内存泄漏

2.1 问题阐述

非静态内部类(包括匿名内部类)默认就会持有外部类的应用,当非静态内部类对象的生命周期比外部类对象的生命周期长时,就会导致内存泄漏。

非静态内部类导致的内存泄漏在Android开发中有一种典型的场景就是使用Handler,很多开发者在使用Hander是这样写的:

public class MainActivity extends AppCompatActivity { 

	@Override protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState); 
		setContentView(R.layout.activity_main); start(); 
		}
	
	 private void start() { 
	 	Message msg = Message.obtain(); 
	 	msg.what = 1; mHandler.sendMessage(msg); 
	 } 
		
	private Handler mHandler = new Handler() { 
		@Override public void handleMessage(Message msg) { 
			if (msg.what == 1) { 
				// 做相应逻辑 
			}
		} 
	};
}

当Activity退出后,msg仍然可能存在于消息队列MessageQueue中未处理或者正在处理,那么这样就会导致Activity无法被回收,从而导致Activity的内存泄漏。

2.2 优化意见

通常在Android开发中如果要使用内部类,但又要规避内存泄漏,一般都会采用“静态内部类+弱应用”的方式。

public class MainActivity extends AppCompatActivity { 
	private Handler mHandler; 
	@Override 
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState); 
		setContentView(R.layout.activity_main); 
		mHandler = new MyHandler(this); 
		start(); 
	} 

	private void start() { 
		Message msg = Message.obtain(); 
		msg.what = 1; 
		mHandler.sendMessage(msg); 
	} 

	private static class MyHandler extends Handler { 
		private WeakReference activityWeakReference; 
		public MyHandler(MainActivity activity) { 
			activityWeakReference = new WeakReference<>(activity); 
		} 

	@Override 
	public void handleMessage(Message msg) { 
		MainActivity activity = activityWeakReference.get(); 
		if (activity != null) { 
			if (msg.what == 1) { 
				// 做相应逻辑 
			} 
		} 
	} 
}

mHandler通过若引用的方式持有Activity,当GC执行垃圾回收时,遇到Activity就会回收并释放所占有的内存单元,这样就不会发生内存泄漏了。

上面的做法确实避免了Activity导致的内存泄漏,发送的msg不再持有Activity的引用了,但msg还是有可能存在消息队列MessageQueue中,所以更好的是在Activity销毁时就将mHandler的回调和发送的消息移除:

@Override 
protected void onDestroy() { 
	super.onDestroy(); 
	mHandler.removeCallbacksAndMessages(null); 
}

情况2

非静态内部类造成内存泄漏还有一种情况就是使用Thread或者AsyncTask。
比如在Activity中直接new一个子线程Thread:

public class MainActivity extends AppCompatActivity { 
	@Override 
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState); 
		setContentView(R.layout.activity_main); 
		new Thread(new Runnable() { 
			@Override 
			public void run() { 
				// 模拟相应耗时逻辑 
				try { 
					Thread.sleep(2000); 
				} catch (InterruptedException e) { 
					e.printStackTrace();
			} } }).start(); 
		}
}

或者直接新建AsyncTask异步任务:

public class MainActivity extends AppCompatActivity { 
	@Override 
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState); 
		setContentView(R.layout.activity_main); 
		new AsyncTask() { 
			@Override 
			protected Void doInBackground(Void... params) { 
				// 模拟相应耗时逻辑 
				try {
					Thread.sleep(2000); 
				} catch (InterruptedException e) { 
					e.printStackTrace(); 
				} 
				return null; 
				} 
			}.execute(); 
	}
}

这种方式新建的子线程Thread和AsyncTask都是匿名内部类对象,默认就隐式的持有外部Activity的引用,导致Activity内存泄漏。要避免内存泄漏的话就要像上面handler一样使用“静态内部类+弱引用”的方式(写法参考上面handler的正确写法)

3. 未取消注册或回调导致内存泄漏

比如我们在Activity中注册广播,如果在Activity销毁后不取消注册,那么这个广播会一直存在系统中,同上面所说的非静态内部类一样持有Activity引用,会导致内部泄漏。因为注册广播后,一定要在Activity销毁后取消注册。

public class MainActivity extends AppCompatActivity { 
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState); 
		setContentView(R.layout.activity_main); 
		this.registerReceiver(mReceiver, new IntentFilter()); 
	} 

	private BroadcastReceiver mReceiver = new BroadcastReceiver() { 
		@Override 
		public void onReceive(Context context, Intent intent) { 
			// 接收到广播需要做的逻辑 
		} 
	}; 

	@Override protected void onDestroy() { 
		super.onDestroy(); 
		this.unregisterReceiver(mReceiver); 
	}
}

在注册观察者模式时,如果不及时取消也会造成内存泄漏。比如,使用“Retrofit+RxJava”注册网络请求的观察者模式回调,同样作为匿名内部类持有外部引用,所以一定要记得在不用或者销毁时取消注册。

4. Timer和TimerTask导致的内存泄漏

Timer和TimerTask在Android中通常会被用来做一些计时或循环任务,比如实现无线轮播的ViewPager:

public class MainActivity extends AppCompatActivity { 
	private ViewPager mViewPager; 
	private PagerAdapter mAdapter; 
	private Timer mTimer; 
	private TimerTask mTimerTask; 
	
	@Override 
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState); 
		setContentView(R.layout.activity_main); 
		init(); 
		mTimer.schedule(mTimerTask, 3000, 3000); 
	} 

	private void init() { 
		mViewPager = (ViewPager) findViewById(R.id.view_pager); 
		mAdapter = new ViewPagerAdapter(); 
		mViewPager.setAdapter(mAdapter); 
		mTimer = new Timer(); 
		mTimerTask = new TimerTask() { 
		@Override 
		public void run() { 
			MainActivity.this.runOnUiThread(new Runnable() { 
				@Override 
				public void run() { 
					loopViewpager(); 
					} 
				}); 
			} };
		 } 

	private void loopViewpager() { 
		if (mAdapter.getCount() > 0) { 
			int curPos = mViewPager.getCurrentItem(); 
			curPos = (++curPos) % mAdapter.getCount();
			mViewPager.setCurrentItem(curPos); 
		} 
	}
	
	private void stopLoopViewPager() { 
		if (mTimer != null) { 
			mTimer.cancel(); 
			mTimer.purge(); 
			mTimer = null; 
		} 
		if (mTimerTask != null) { 
			mTimerTask.cancel(); 
			mTimerTask = null; 
		} 
	}
 
 	@Override 
 	protected void onDestroy() { 
 		super.onDestroy(); 
 		stopLoopViewPager(); 
 	}
}

当我们Activity销毁时,有可能Timer还在继续等待执行TimerTask,它持有Activity的引用不能被回收,因此当我们Activity销毁时要立即cancel掉Timer和TimerTask,以避免发生内存泄漏。

5. 集合中的对象未清理造成内存泄漏

如果一个对象放入到ArrayList、HashMap等集合中,这个集合就会持有该对象的引用。当我们不再需要这个对象时,也并没有将它从集合中移除,这样只要集合还在使用(而此时对象已经无用了),这个对象就造成了内存泄漏。并且如果集合被静态引用的话,集合里面哪些没有用的对象更会造成静态引用了。
所以如果在使用集合时要及时将不用的对象从集合remove,或者clear集合,以免内存泄漏。

6. 资源未关闭或释放导致内存泄漏

在使用IO、File流或者Sqlite、Cursor等资源时要及时关闭。这些资源在进行读写操作时通常都使用了缓冲,如果及时不关闭,这些缓冲对象就会一直被占用而得不到释放,以致发生内存泄露。因此我们在不需要使用它们的时候就及时关闭,以便缓冲能及时得到释放,从而避免内存泄露。

7. 属性动画造成内存泄漏

动画同样是一个耗时任务,比如在Activity中启动了属性动画(ObjectAnimator),但是在销毁的时候,没有调用cancle方法,虽然我们看不到动画了,但是这个动画依然会不断地播放下去,动画引用所在的控件,所在的控件引用Activity,这就造成Activity无法正常释放。因此同样要在Activity销毁的时候cancel掉属性动画,避免发生内存泄漏。

@Override
protected void onDestroy() { 
	super.onDestroy(); 
	mAnimator.cancel();
}

WebView造成内存泄露:

关于WebView的内存泄露,因为WebView在加载网页后会长期占用内存而不能被释放,因此我们在Activity销毁后要调用它的destory()方法来销毁它以释放内存。
解决方案:
在销毁WebView之前需要先将WebView从父容器中移除,然后再销毁WebView。

@Override
protected void onDestroy() { 
	super.onDestroy(); 
	mWebViewContainer.removeView(mWebView); 	// 先从父控件中移除WebView
	mWebView.stopLoading(); 
	mWebView.getSettings().setJavaScriptEnabled(false); 
	mWebView.clearHistory(); 
	mWebView.removeAllViews(); 
	mWebView.destroy();
}

总结

内存泄漏在Android内存优化是一个比较重要的一个方面,很多时候程序中发生了内存泄漏我们不一定就能注意到,所以在编码过程中一定要养成良好的习惯。
总结下来只要做到以下几点就能避免大多数情况的内存泄漏:

  1. 构造单例的时候别用Activity的引用;
  2. 静态引用时注意引用对象的置空或者少用静态变量;
  3. 使用静态内部类+软引用代替非静态内部类;
  4. 及时取消广播或者观察者注册;
  5. 耗时任务、属性动画在activity销毁时记得cancel
  6. 文件流、Cursor等资源及时关闭;
  7. activity销毁时WebView的移除和销毁。

说明

本文是翻译的一个大佬的文章,该文章写的是真的好,但大原文章的代码块过于混乱,故将代码块做此整理,并将文章按序排列。
原文地址:https://www.huaweicloud.com/articles/12589272.html

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章           查看所有文章
加:2021-08-19 12:09:56  更:2021-08-19 12:12:34 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 10:15:33-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码