Android开发之内存优化有效防止内存溢出OOM与内存泄漏

来源:互联网 发布:安装apache 编辑:程序博客网 时间:2024/05/20 18:41

   在进行Android开发的过程中,很多开发人员都出现过OOM的情况。

   什么叫做OOM其实就是OutOfMemoryError。相信Android开发对这个问题引起的程序异常,再熟悉不过了。一般会引起这些情况的原因包含:Bitmap的处理未关闭Cursor;长期周对象拥有短周期对象,导致短周期对象不能及时释放(如单例对象长期持有Context)

   现在就简单的例举一些出来,下面来进行一步步的处理:


Bitmap的处理

   比较常见,所以简单说一下处理思路即可,首先获取图片先关尺寸信息,然后对比需要显示的控件尺寸,根据BitmapFactory来对图片的尺寸进行裁剪,减少图片对内存的使用,这样避免发生OOM内存溢出,代码不再列出,其他博文有很多类似代码。

  

    Cursor的操作

示例错误代码

public void inidata() {Cursor cur = sqlliste.query(querysql, new String[] { userid, SystemCache.getCurrentUser().getAccDB() });if (cur == null)return;while (cur.moveToNext()) {userconfigcache.put(cur.getString(cur.getColumnIndex("config")), cur.getString(cur.getColumnIndex("value")));break;}}


上述代码首先对于游标的使用并未关闭,这是错误的写法,正确的应该如下

public void inidata() {Cursor cur = null;try {cur = sqlliste.query(querysql, new String[] { userid, SystemCache.getCurrentUser().getAccDB() });if (cur == null)return;while (cur.moveToNext()) {userconfigcache.put(cur.getString(cur.getColumnIndex("config")), cur.getString(cur.getColumnIndex("value")));break;}} catch (Exception e) {} finally {if (cur != null)cur.close();}}

这样即使sqllite发生了任何错误,也是保证Cursor是关闭的。

可以想像一下:在一个Activity中调用此方法,然后关闭,打开,关闭这个Activity,游标一直没有关闭,这样的结果肯定会导致内存泄漏。所以需要注意编码习惯的养成。

相似的还有BraodcastReceiverContentObserverFileStream,使用完一定要注意关闭。

 

 长期周对象拥有短周期对象


例如把Activity作为一个参数传递给一个方法函数体A,并且这个A方法的初始化如下;

A a=new A(Activity.this);

A函数方法体

Public class A{  Private  Static Context context;Public A(Context context){This.context=context;}}

这样即使这个Activity关闭了,但是这个Activity作为一个变量被A方法体内部的静态变量所持有,导致Activity的资源不能释放。重复打开、关闭操作,会发现内存会持续升高,因为Android对于一个app的内存分配是有限的,这样持续升高的内存最后的结果肯定是内存泄漏,导致App的崩溃。所以要避免这种写法。

修改代码如下:

//方法1:Public class A{  Private  Static Context context;Public A(Context context){This.context=context;}Public void release(){Context=null;//去掉对Android的关联引用}}//在Activity的Ondestory方法中调用a.release();进行资源释放就好了。//方法2:这种方法简单,推荐使用Public class A{  Private  Static Context context;Public A(Context context){This.context=context.getApplicationContext();//关联到全局的环境变量,这样不会影响对于Activity的资源释放}}


  其他情况

  单例情况1

  与上面类似:这样写代码

public class SystemCache {private static SystemCache localcache;private Context context;public static SystemCache getInstance(Context context) {if (localcache == null)localcache = new SystemCache(context);return localcache;}public SystemCache(Context context) {this.context = context.getApplicationContext();}}

单例情况2

public class TestManager {    public static final TestManager INSTANCE = new TestManager();    private List<TESTListener> listenerList;    public static TestManager getInstance() {        return INSTANCE;    }    public void registerListener(TESTListener listener) {        if (!mListenerList.contains(listener)) {            mListenerList.add(listener);        }    }    public void unregisterListener(MyListener listener) {        mListenerList.remove(listener);    }}interface TESTListener {    public void doSomeThing();}

Activity

public class TestActivity extends AppCompatActivity {    private TESTListener mTESTListener =new TESTListener() {        @Override        public void doSomeThing() {        }    };    private TestManager testManager=TestManager.getInstance();    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_test);        testManager.registerListener(mTESTListener );    }}

上面的代码看似很正常的一段代码,但是却很容易引起内存溢出,为什么呢?

非静态的内部类都会持有指向外部类对象的引用。因此我们创建的mTESTListener让单例所持有的时候,mTESTListener 

是引用了TestActivity对象的(只是这种实例化监听没有写出)。

从而导致TestActivity在关闭的时候,TestActivity对象是被单例长期持有,所以不能释放,从而出现了内存泄漏的现象。

修改代码:

TestActivity中重载onDestroy方法,在页面销毁的时候调用unregisterListenermTESTListener进行移除。

protected void onDestroy() {        testManager.unregisterListener(mMyListener);        super.onDestroy();}


AsyncTask的使用不当

示例代码

mAsyncTask=new AsyncTask<String,Void,Void>()        {            @Override            protected Void doInBackground(String... params) {                //doSamething                return null;            }        }.execute("mytask");

在主线程中用AsyncTask创建一个异步任务,那么AsyncTask就持有了对当前activity的引用了,如果这个activity关闭,

然后这个异步现成还在一直的进行长时间的后台循环运算,这样连续的对activity进行打开关闭操作,这样就会存在多个

activity的后台长时间循环运算,并且多个activity并没有被释放。这样的结果也是可想而知。

修改代码如下:

mAsyncTask=new AsyncTask<String,Void,Void>()        {            @Override            protected Void doInBackground(String... params) {                Boolean loop=true;                while (loop) {//此处为模拟长时间运算                    if(isCancelled())                        return null;                //doSomething..                    Log.d("test", "task is running");                }                return null;            }        }.execute("mytask");

然后在

protected void onDestroy() {        mAsyncTask.cancel(true);        super.onDestroy();}

修改的策略是在执行长时间或者循环时,进行查看当前线程是否有进行取消操作


adapter的使用

一定要注意定义一个Holder类来进行控件缓存优化,不过现在大家应该都在使用recycleview了吧

Handler的使用

内部可以创建WeakReference<Activity>来对资源进行弱引用,这样如果在回收资源的时候,不会影响Activity的资源回收操作。

有时候也可以进行如下代码:去掉引用关系

public void onDestroy() {    mHandler.removeMessages(message);    mHandler.removeCallbacks(Runnable);    mHandler.removeCallbacksAndMessages(null);}

Static的使用这个就不再类述

注意:千万不要使用Static来定义一个共享的全局变量信息(例如登录信息之类),即使是保存在ApplicationContext

中的静态变量同样会在资源紧缺的时候被回收,在手机资源不足的时候,系统会回收Static的变量值的,从而置为null

这种全局的变量信息建议使用单例保存,并且集合相关的一些本地保存措施。

我现在使用的是内存缓存,然后是本地二进制文件,内存为空取本地二进制文件进行对象还原


对于Context的引用,尽量应用到全局的ApplicationContext(APK同生命周期),这样不会影响资源的释放

 

在开发中经常遇到的也就大体这么多注意点,当然还有其他的,也应该用MAT工具来具体的分析了














 

阅读全文
0 0
原创粉丝点击