Android app应用性能优化

久未见 提交于 2019-12-01 19:48:16

一、名词解释

内存溢出

实质应用程序不能及时释放内存或者加载到内存上的数据太大而导致的OOM问题

内存泄漏

目标类被其他类持有、导致无法销毁、从而导致目标类无法被调用也无法销毁。

窗体(内存)泄露

是指Activity或者Fragment在Destory的情况下、自身引用被其他对象或者线程持有,无法销毁。

栈内存溢出

‍StackOverflowError:应用程序调用中,导致栈空间无限延长,超过了虚拟机的承载能力‍

 

二、App内存回收机制

app使用内存超过3/4并且不是当前的app,那么系统会回收内存(android.app.ActivityThread)

Android系统内存不足时,组建回收顺序问题

http://www.2cto.com/kf/201512/453248.html

 

(空进程,死亡进程,后台进程)

省电方面

Android 应用开发的耗电量控制

使用StrictMode严格模式检测不良代码

三、代码优化

1>Handler的使方式和内存优化

消息队列遵循先进先出(First in First out)的原则来讲某些信息或者任务进行排队等待。android中有自己的消息队列,如Handler和Broadcastreceiver。

andriod提供了 Handler 和 Looper 来满足线程间的通信。Handler 先进先出原则。Looper类用来管理特定线程内对象之间的消息交换(Message Exchange)。
1)Looper: 一个线程可以产生一个Looper对象,由它来管理此线程里的Message Queue(消息队列)。
2)Handler: 你可以构造Handler对象来与Looper沟通,以便push新消息到Message Queue里;或者接收Looper从Message Queue取出)所送来的消息。
3) Message Queue(消息队列):用来存放线程放入的消息。
4)线程:UI thread 通常就是main thread,而Android启动程序时会替它建立一个Message Queue。

Handler的集中创建方式和用法

Handler mHandler = new Handler() {  
          public void handleMessage(Message msg) {   
               switch (msg.what) {   
                    case TestHandler.GUIUPDATEIDENTIFIER:   
                         myBounceView.invalidate();  
                         break;   
               }   
               super.handleMessage(msg);   
          }   
     };

在工作线程中创建(异步)Handler

class LooperThread extends Thread
{
public Handler mHandler;
public void run() 
{
    Looper.prepare();
    mHandler = new Handler() 
    {
            public void handleMessage(Message msg) 
            {
            
            }
        };
    Looper.loop();
}

使用HandlerThread创建(异步)Handler

HandlerThread handlerThread = new HandlerThread("my.handlerthread");
handlerThread.start(); 

Handler mHandler = new Handler(handlerThread.getLooper()){
   public void handleMessage(Message msg) 
   {
            
   }
};

 

Handler的用法很多,这里只贴出线程通信的用法,读者可以自行深入研究其他用法

class myThread implements Runnable {   
          public void run() {  
               while (!Thread.currentThread().isInterrupted()) {    
                    //这里可以使用 new,因为主线程中的消息队列只有一条
                    Message message = new Message(); 
                    message.what = 1024;   
                      
                    TmHandler.sendMessage(message);   
                    try {   
                         Thread.sleep(100);    
                    } catch (InterruptedException e) {   
                         Thread.currentThread().interrupt();   
                    }   
               }   
          }   
     }

1.1>Handler关于内存泄露

当我们这样写在一个Activity中时,Android Lint会提示我们这样一个 warning: In Android, Handler classes should be static or leaks might occur.。
意思说:在Android中,Handler 类应该是静态的否则可能发生泄漏

同样在Eclipse代码编辑区域的 Handler定义行也会出现类似的警告,一般都会加上supresslint的注解

先来看看,为什么会出现这种问题:

1.当Android程序第一次创建的时候,在主线程同时会创建一个Looper对象。Looper实现了一个简单的消息队列,一个接着一个处理Message对象。
程序框架所有主要的事件(例如:屏幕上的点击时间,Activity生命周期的方法等等)都包含在Message对象中,然后添加到Looper的消息队列中,
一个一个处理。主线程的Looper存在整个应用程序的生命周期内。
2.当一个Handler对象在主线程中创建的时候,它会关联到Looper的 message queue 。Message添加到消息队列中的时候Message
会持有当前Handler引用,当Looper处理到当前消息的时候,会调用Handler#handleMessage(Message).
3.在java中,no-static的内部类会 隐式的 持有当前类的一个引用。static的类则没有。

综上三点可知,这种泄露非常危险,Activity或者被持有者(宿主)不能及时释放内存,Looper也在不断循环,是导致内存泄露原因之一,

原因之二是 Activity未被回收,当Activity处于非活动状态时,如果handlerMessage的处理导致UI的改变,将会导致窗体泄露,比如弹框,UI改变等。

1.2定义静态类

  /** 
   * 使用静态的内部类,不会持有当前对象的引用 
   */ 
  private static class MyHandler extends Handler 
  { 
    private SoftReference<Activity> mActivityReference = null; 
 
    public MyHandler(SampleActivity activity) 
    { 
       mActivityReference = new SoftReference<Activity>(activity)
    } 
 
    @Override 
    public void handleMessage(Message msg) { 
      SampleActivity activity = (SampleActivity )mActivityReference.get(); 
      if (activity != null) { 
        // ... 
      } 
    } 
    
    public void release()
    {
       removeCallbacksAndMessages(null);
       mActivityReference.clear();

    }
  } 
 
  private final MyHandler mHandler = new MyHandler(this);

在Activity的OnDestroy中调用

  public void onDestroy()
  {
       mHandler.release();
  }

1.3 Handler替代方案

有些时候,Handler无需自己定义,我们可以使用View自身提供的Handler进行操作

myView.post()

myView.postDelay()

myView.getHandler()

runOnUiThread()

.............................

LocalBroadcastReceiver

BroadcastReceiver

或者使用开源方案EventBus

 

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

2>避免(static)全局化或被全局对象持有,严格按照Android注册/解除机制

2.1 避免全局static化

对于广播,或者一些Runnable,CallBacks甚至普通内部类,我们要避免全局化,导致变量不能被释放,从而对象也不能释放

这里所说的全局化是被静态对象持有,比如单例,全局静态List,Map等

2.2全局化往往是容易持有对象,因此我们必须学会

add——remove

addAll——clearAll

register——unregister

bind——unbind

 

这里相关的主要有View,ViewTreeObserver,ContentObserver,static Map,static List, static SparseArray,singleObject如

View.addOnAttachStateChangeListener

View.addOnLayoutChangeListener

ViewTreeObserver.add

registerBroadcast

bindService

registerContentObserver

........

2.3如果全局化对象非要持有Context对象

建议使其持有ApplicationContext对象,而不是Service,BroadCastReceiver或者Activity

举个栗子

android.support.v4.content.LocalBroadcastManager的getInstance

   private static LocalBroadcastManager mInstance;
   
  public static LocalBroadcastManager getInstance(Context context)
  {
        if(mInstance == null)
            mInstance = new LocalBroadcastManager(context.getApplicationContext());
        return mInstance;
       
    }

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

3>Bitmap内存优化

Android中图片有四种属性,分别是:
ALPHA_8:每个像素占用1byte内存
ARGB_4444:每个像素占用2byte内存
ARGB_8888:每个像素占用4byte内存 (默认)
RGB_565:每个像素占用2byte内存
Android默认的颜色模式为ARGB_8888,这个颜色模式色彩最细腻,显示质量最高。但同样的,占用的内存也最大。 所以在对图片效果不是特别高的情况下使用RGB_565(565没有透明度属性)

publicstaticBitmapreadBitMap(Contextcontext, intresId) {
    BitmapFactory.Optionsopt = newBitmapFactory.Options();
    opt.inPreferredConfig = Bitmap.Config.RGB_565;
    opt.inPurgeable = true;
    opt.inInputShareable = true;
    //获取资源图片 
    InputStreamis = context.getResources().openRawResource(resId);
    returnBitmapFactory.decodeStream(is, null, opt);
}
// 先判断是否已经回收
if(bitmap != null && !bitmap.isRecycled()){
    // 回收并且置为null
    bitmap.recycle();
    bitmap = null;
}

3.1Bitmap调用的时候有个方法是recycle,用来释放内存

3.2使用SoftReference<Bitmap>,WeakReference<Bitmap>进行小内存引用管理

3.3建立磁盘缓存LruDisk,LruCache,具体需要Http 304文件校验,Http 206断点续传等方面知识

3.4进行图片压缩,具体参考http://my.oschina.net/ososchina/blog/495861

 

4>使用Android中高效的数据结构

具体用法请参考http://my.oschina.net/ososchina/blog/355721

 

5>关闭IO流,关闭Cursor、关闭Formatter、按时结束线程

 

6>.普通内部类(非静态内部类)

问题一,主类释放问题:内部类隐式持有主类的当前对象,对于内部类需不需要和Handler一样进行静态化,完全取决于内部类会不会被全局(static/单例)对象引用,如果被全局(static/单例)对象引用可能造成无法释放,否则完全不要过于担心,因为内部类对主类对象是强引用。

问题二.循环引用问题:

(所谓循环引用是2个货2个以上的相互关联的类的对象互相引用,造成了一种引用闭环问题,这种引用造成的问题是闭环内的所有对象都无法及时销毁)

Java的虚拟机机制避免了此类问题,但是为了App性能,我们需要特别注意此类问题,尽可能提前释放内存(release memory)。

 class MyAdapter extends BaseAdapter {
    
            private ListView listView;//用于接收传递过来的Context对象
            public MyImgAdapter(ListView listView) {
                super();
                this.listView = listView;
                this.listView.setAdapter(this);  //从这里开始出现循环引用
            }

            @Override
            public int getCount() {
                return imgs.length;
            }

            @Override
            public Object getItem(int position) {
                return position;
            }

            
            @Override
            public long getItemId(int position) {
                return position;
            }

            @Override
            public View getView(int position, View convertView, ViewGroup parent) {
           
                return convertView;
            }

            public void release()
            {
            //以下2句选择一种即可解除循环引用,但是为了效率,都用吧
              this.listView.setAdapter(null); 
              this.listView = null; 
            }
        }

7>判断Activity的状态机制,在Activity被压入栈中时阻止某些设计UI的API操作,防止窗体泄露

参考:Android postDelay+Dialog引起的窗体泄露

8>对Adapter中的View进行缓存

findViewById本身不需要缓存,因为每次都是树形遍历,而LayoutInflater每次都是创建一个新的View,所以必要时使用ViewHolder或者ViewModel进行缓存

 

9>不要过于使用枚举,枚举在内存中所占字节是2倍的静态变量所占的字节

 

10.提前释放某些不再用或者急需要减少内存的变量

this.field = null;
---------------------------------------------------------------------------------------------------
ViewGroup  container= getWindow().getDecorView().findViewById(android.R.id.content);
container.removeAllViews();
---------------------------------------------------------------------------------------------------
Iterator it = mImageList.iterator();
while(it.hasNext())
{
   Bitmap bmp = it.next();
    bmp.recycle();
     it.remove();
}
---------------------------------------------------------------------------------------------------
mList.clear();

 

11.避免AsyncTask

无法清空队列中的任务、并且不能超过128个任务

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!