标签:
在分享会上听小伙伴对这部分内容做了讲解,发觉在平时的编程中确实有很多问题没有注意到,故记录下来分享给各位,也欢迎各位不吝赐教纠正文中不足之处。
内存泄漏与内存溢出:
内存溢出简单讲就是程序运行要求的内存大于虚拟机能提供的最大内存,会导致程序崩溃,也就是我们常见的Out Of Memory错误。
内存泄露指程序未能释放已经不再使用的内存。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于程序设计的失误,导致在释放该段内存之前就失去了对该段内存的控制,从而造成了内存的浪费。少量的内存泄漏并不会影响到程序的运行,但长时间的积累会消耗越来越多的内存,最终导致内存溢出。
内存的分配是由程序完成的,而内存的释放有 GC(垃圾回收机制)完成。GC 为了能够正确的释放对象, 必须监控每一个对象的运行状态,包括对象的申请,引用,被引用,赋值等。当检测到一个对象完全无用时,便可以对这个对象进行回收。
常见内存泄漏:
理解了以上概念,我们将列举几种在我们平时容易导致内存泄漏的不好编程习惯,并利用 Android Studio 自带的内存分析工具进行检测内存泄漏。
①单例造成内存泄漏:
单例由于其静态的特性使得其生命周期跟应用一样长,处理不当极易导致内存泄漏。
例如:我们经常在程序的开屏页初始化一些,资源读取,网络请求等单例方法。我们现在来模拟一个这样的场景:
创建一个用来读取 drawable 资源的工具类,并采用单例的设计模式。
public class ResourceReader {
//单例对象
private static ResourceReader mInstance = null;
private Context context;
//通过 Context 上下文来创建对象
private ResourceReader(Context context){
this.context = context;
}
public static ResourceReader getInstance(Context context){
if(mInstance == null){
mInstance = new ResourceReader(context);
}
return mInstance;
}
public Drawable getDrawable(int drawableId){
return ContextCompat.getDrawable(context, drawableId);
}
}
在 WelcomeActivity 中,通过工具类读取一张图片,在按钮点击后跳转 MainActivity 并将 WelcomeActivity finish 掉。
public class WelcomeActivity extends AppCompatActivity {
private ImageView imageView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_welcome);
this.imageView = (ImageView) findViewById(R.id.welcome_image);
//通过工具类读取一个 Drawable 资源
Drawable drawable = ResourceReader.getInstance(this).getDrawable(R.drawable.welcome);
imageView.setImageDrawable(drawable);
}
public void onClick(View view){
Intent intent = new Intent(this, MainActivity.class);
startActivity(intent);
finish();//关闭该Activity
}
}
在 MainActivity 中依然用工具类加载一个 drawable 资源。
public class MainActivity extends AppCompatActivity {
private ImageView imageView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Drawable drawable = ResourceReader.getInstance(this).getDrawable(R.drawable.main);
imageView = (ImageView) findViewById(R.id.main_image);
imageView.setImageDrawable(drawable);
}
}
运行效果如下:
现在我们分析一下 WelcomeActivity 内存泄漏的原因:
我们在WelcomeActivity 中调用 drawable 加载工具类时传入了其自身上下文使得ResourceReader 的静态对象拥有了对 WelcomeActivity 的引用,在我们使用完 WelcomeActivity 并调用 finish() 方法时,我们以为其已经销毁并且内存可以被回收,其实不然,由于其被引用GC 系统无法回收这段内存而且我们也失去对这段内存的控制,这便导致了WelcomeActivity 的内存泄漏。
解决办法:
①我们在 Activity 被销毁时消除单例对它的引用。这种方法有所限制,并不是所有的情况都适合。例如类中存在非静态属性,则在不同时间调用可能导致其值错乱。
在 ResourceReader 工具类中加入以下代码:
public void reset(){
if(mInstance != null){
mInstance = null;
context = null;
}
}
在 Activity 的销毁方法中调用上边新加入的方法:
@Override
protected void onDestroy() {
ResourceReader.getInstance(this).reset();
super.onDestroy();
}
②在单例中我们尽可能的引用生命周期较长的对象,如 Application(推荐)改动也较少,只需要将 Context 改为 ApplicationContext。
public static ResourceReader getInstance(Context context){
if(mInstance == null){
//将 Context 改为 ApplicationContext
mInstance = new ResourceReader(context.getApplicationContext());
}
return mInstance;
}
修改完毕之后我们再次运行程序看看结果:
public class GatherActivity extends AppCompatActivity {
private static final List<Bitmap> bitmapList = new ArrayList<>();
private TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_gather);
textView = (TextView) findViewById(R.id.list_numer);
}
public void onClick(View view){
if(view.getId() == R.id.btn_add){
//加入Bitmap
bitmapList.add(BitmapFactory.decodeResource(getResources(), R.drawable.welcome));
}
textView.setText(String.format("共有 %d 个元素", bitmapList.size()));
}
@Override
protected void onDestroy() {
Log.v("ygl","Gather Activity On Destroy");
super.onDestroy();
}
}
解决办法:
①对于 final static 修饰符一定要慎用。
②对于集合一定要在特定的时机进行删除元素,或清空,或转储本地,避免集合所占内存无限制增长。
③非静态内部类造成内存泄漏:
这是经常被我们忽略的一点,非静态内部类默认会持有对外部类的引用,而该非静态内部类有创建了一个静态实例,如果没有合理释放则会造成内存泄露。
public class NearActivity extends AppCompatActivity {
//非静态内部类 User 创建的静态实例 mUser
private static User mUser = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_near);
mUser = new User();
}
@Override
protected void onDestroy() {
Log.v("ygl","Near Activity On Destroy");
super.onDestroy();
}
class User{
private String name;
private int age;
}
}
我们通过上边的分析方法可以得出,在 NearActivity 销毁后其内存并没有被回收。
解决办法:
①在合适的时机将内部类的静态对象进行销毁,如:
@Override
protected void onDestroy() {
Log.v("ygl","Near Activity On Destroy");
if(mUser != null){
mUser = null;
}
super.onDestroy();
}
②将内部类定义为静态内部类,使其不与外部类建立关系。
④匿名内部类/异步线程导致内存泄漏:
匿名内部类会持有一个外部类的引用,如果再将该引用传入异步线程,此线程与外部类的生命周期不再相同,就可能导致外部类对象内存泄漏。
举一个我们经常用到的场景:
public class SyncActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sync);
//创建匿名内部类
new Thread(new Runnable() {
@Override
public void run() {
for(int i=0;i<1000;i++){
try{
Thread.sleep(1000);
Log.v("ygl","i="+i);
}catch (Exception e){}
}
}
}).start();
//销毁Activity
finish();
}
@Override
protected void onDestroy() {
Log.v("ygl","Sync Activity On Destroy");
super.onDestroy();
}
}
大家可能已经猜到了,由于子线程在 Activity 销毁后依然会继续执行,而内部类 new Runnable() {};拥有对外部类 Activity的引用导致了Activity所占内存无法被回收。
解决办法:
①如果是在 Activity 结束后已没有必要运行的线程,在 onDestroy 中中断子线程的运行。
②可以考虑用全局的线程池代替在类中声明子线程。
⑤Handler 造成内存泄漏:
Handler 生命周期和 Activity 是不一致的,当 Activity 被 finish 时,延迟执行任务的 Message 还会继续执行存在于主线程中,它持有 Activity 的 Handler 引用,导致 Activity 无法被回收。
public class SampleActivity extends Activity {
private final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
...
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
...
}
}, 1000 * 60 * 10);
finish();
}
}
解决办法:
①在 Activity 结束时可以清空不必要的 Message 消息
②采用静态内部类和弱引用结合的方式
总结:
标签:
原文地址:http://blog.csdn.net/ygl_smile/article/details/52241286