Android移动性能实战读书笔记

1. 磁盘IO优化

1.1 检测工具

开启严格模式,在ApplicationonCreate方法中增加以下代码

if (BuildConfig.DEBUG) {
    //设置线程策略检查的事件类型,以及发生该事件的处理措施
    StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
            .detectDiskReads()
            .detectDiskWrites()
            .detectNetwork()   // or .detectAll() for all detectable problems
            .penaltyLog()
            .build());
    //设置虚拟机进程的检查策略
    StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
            .detectLeakedSqlLiteObjects()
            .detectLeakedClosableObjects()
            .penaltyLog()
            .penaltyDeath()
            .build());
}

1.2 优化措施

  1. 对于SharePrefrencecommit操作,当需要提交多个数据时,只需要保留最后一个commit即可,减少不必要的磁盘io,使用apply代替commit.

  2. 不要重复打开数据库连接,建议调用SqliteHelper.getWriteableDatabase后保存连接,不必调用close方法,让其生命周期和应用的生命周期保持一致.

  3. 使用ObjectOutputStreamObjectInputStream时,建议配合使用缓冲ByteArrayOutputStream或者ByteArrayInputStream,以减少不必要的IO操作.

    public static boolean saveObject(Serializable object) {
        try {
            File cacheDir = InterviewApp.self().getExternalCacheDir();
            FileOutputStream fos = new FileOutputStream(new File(cacheDir,"text.txt"));
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            //先写入到字节缓冲流
            oos.writeObject(object);
            //通过字节缓冲流再写到磁盘
            bos.writeTo(fos);
            fos.flush();
            oos.close();
            fos.close();
            return true;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return false;
    }
    
  4. 数据库建表时,这个AUTOINCREMENT关键词会增加CPU,内存,磁盘空间和磁盘I/O的负担,所以尽量不要用,除非必需。

  5. 数据库查询时,减少使用select*.

  6. 避免主线程操作数据库和文件.

  7. 文件读写操作时,尽量使用8kb的buffer进行读写.

  8. 批量更新数据库使用事务.

2. 内存优化

2.1 优化工具

  • DDMS: Dalvik Debug Monitor Server,dalvik虚拟机调试监控服务.
  • MAT: 内存分析工具
  • LeakCanary: 开源工具,能够自动检测内存泄漏

2.2 场景

activity泄漏
  1. 匿名内部类持有外部Activity的引用

    解决方案:

    使用静态内部类搭配弱引用,或者在Activity的onDestroy中停止任务,避免内部类的生命周期溢出,超过Activity的生命周期

  2. 集合类或其他单例类持有Activity的引用

    解决方案:

    在使用完后及时释放引用,在能使用Application Context代替的情况下,使用ApplicationContext

  3. 过渡界面存在于回退栈中,没有被回收

    解决方案:

    手动调用Activity的finish方法

  4. getSystemService造成系统服务持有Activity的Context的引用

    解决方案:

    使用ApplicationContext

    这里使用一个例子来分析一下引用链

        public static boolean isWifiEnabled(Context context) {
            WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
            if (wifiManager != null) {
                return wifiManager.isWifiEnabled();
            }
            return false;
        }
    

    我们可能会使用如上的方法来判断当前是不是处于wifi环境,如果此时的Context传的是Activity,那么就会发生内存泄漏.

    首先看一下context.getSystemService,我们知道Context唯一的实现类是ContextImpl,直接看一下ContextImpl.getSystemService的实现.

     @Override
        public Object getSystemService(String name) {
            return SystemServiceRegistry.getSystemService(this, name);
        }
    
        @Override
        public String getSystemServiceName(Class<?> serviceClass) {
            return SystemServiceRegistry.getSystemServiceName(serviceClass);
        }
    
    

    它并没有具体实现,而是调用了SystemServiceRegistry.getSystemService

       /**
         * Gets a system service from a given context.
         */
        public static Object getSystemService(ContextImpl ctx, String name) {
            ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
            return fetcher != null ? fetcher.getService(ctx) : null;
        }
    

    SYSTEM_SERVICE_FETCHERS是一个HashMap,存的key就是服务的名称,value是是服务的获取类提供了一个缓存及懒加载的效果.

     static abstract class CachedServiceFetcher<T> implements ServiceFetcher<T> {
            private final int mCacheIndex;
    
            public CachedServiceFetcher() {
                mCacheIndex = sServiceCacheSize++;
            }
    
            @Override
            @SuppressWarnings("unchecked")
            public final T getService(ContextImpl ctx) {
                final Object[] cache = ctx.mServiceCache;
                synchronized (cache) {
                    // Fetch or create the service.
                    Object service = cache[mCacheIndex];
                    if (service == null) {
                        try {
                            service = createService(ctx);
                            cache[mCacheIndex] = service;
                        } catch (ServiceNotFoundException e) {
                            onServiceNotFound(e);
                        }
                    }
                    return (T)service;
                }
            }
    
            public abstract T createService(ContextImpl ctx) throws ServiceNotFoundException;
        }
    

    先在缓存数组中查找,有就直接返回,没有就调用createService创建一个并加入缓存,因此系统服务实例都是以这样一种单例的形式存在,只要用到了,它的生命周期就和应用一样长.

    接下来看一下WifiManager的注册过程

      registerService(Context.WIFI_SERVICE, WifiManager.class,
                    new CachedServiceFetcher<WifiManager>() {
                @Override
                public WifiManager createService(ContextImpl ctx) throws ServiceNotFoundException {
                    IBinder b = ServiceManager.getServiceOrThrow(Context.WIFI_SERVICE);
                    IWifiManager service = IWifiManager.Stub.asInterface(b);
                    return new WifiManager(ctx.getOuterContext(), service,
                            ConnectivityThread.getInstanceLooper());
                }});
    

    createService实际上创建了一个WifiManager的实例,并传入了outerContext以及远程服务的代理对象,这里我们可以知道WifiManager大部分的功能都是通过跨进程调用服务端的方法实现的.

    其实这个outerContext就是我们熟悉的Activity或Service实例,具体为什么这里不分析了,感兴趣可以自己看源码.

    再看一下WifiManager的构造方法

      public WifiManager(Context context, IWifiManager service, Looper looper) {
            mContext = context;
            mService = service;
            mLooper = looper;
            mTargetSdkVersion = context.getApplicationInfo().targetSdkVersion;
        }
    
    

    这里将Context作为成员变量保存起来了,内存泄漏就产生了.这个其实和之前说的单例持有Activity引用差不多,只不过这是系统服务的单例.

    改成下面这样就行了

     public static boolean isWifiEnabled(Context context) {
            WifiManager wifiManager = (WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
            if (wifiManager != null) {
                return wifiManager.isWifiEnabled();
            }
            return false;
        }
    
  5. Timer引发的

解决方案:

在onDestroy中及时取消Timer任务

WebView泄漏

解决方案:

开启独立进程,指定某个Activity的process属性,让其在单独的进程运行

Drawable目录下资源不匹配

Android默认的资源加载机制是根据当前设备的屏幕密度取对应drawable-xxx目录下的资源文件,如果没找到,就按照就近原则取其他drawable目录下的同名资源,如果你的资源放在drawable-mdpi下面,而你的设备密度为480dpi,那么加载的图片将放大3倍,这是相当可怕的.

解决方案:

抓不准该放到那个目录的图片,就尽量问设计人员要高品质图片然后往高密度目录下放,这样在低密屏上“放大倍数”是小于1的,在保证画质的前提下,内存也是可控的。
拿不准的图片,使用Drawable.createFromStream替换getResources().getDrawable来加载,这样就可以绕过Android以上的这套默认适配法则。

Bitmap占用内存过大,导致OOM

解决方案:

是用InSampleSize对图片进行压缩

posted @ 2018-11-28 19:58  静致远  阅读(200)  评论(0编辑  收藏  举报