Android移动性能实战读书笔记
1. 磁盘IO优化
1.1 检测工具
开启严格模式,在Application
的onCreate
方法中增加以下代码
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 优化措施
-
对于
SharePrefrence
的commit
操作,当需要提交多个数据时,只需要保留最后一个commit
即可,减少不必要的磁盘io
,使用apply
代替commit
. -
不要重复打开数据库连接,建议调用
SqliteHelper.getWriteableDatabase
后保存连接,不必调用close方法,让其生命周期和应用的生命周期保持一致. -
使用
ObjectOutputStream
或ObjectInputStream
时,建议配合使用缓冲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; }
-
数据库建表时,这个
AUTOINCREMENT
关键词会增加CPU,内存,磁盘空间和磁盘I/O的负担,所以尽量不要用,除非必需。 -
数据库查询时,减少使用
select*
. -
避免主线程操作数据库和文件.
-
文件读写操作时,尽量使用
8kb
的buffer进行读写. -
批量更新数据库使用事务.
2. 内存优化
2.1 优化工具
- DDMS:
Dalvik Debug Monitor Server
,dalvik
虚拟机调试监控服务. - MAT: 内存分析工具
- LeakCanary: 开源工具,能够自动检测内存泄漏
2.2 场景
activity泄漏
-
匿名内部类持有外部
Activity
的引用解决方案:
使用静态内部类搭配弱引用,或者在Activity的onDestroy中停止任务,避免内部类的生命周期溢出,超过Activity的生命周期
-
集合类或其他单例类持有Activity的引用
解决方案:
在使用完后及时释放引用,在能使用Application Context代替的情况下,使用ApplicationContext
-
过渡界面存在于回退栈中,没有被回收
解决方案:
手动调用Activity的finish方法
-
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; }
-
Timer引发的
解决方案:
在onDestroy中及时取消Timer任务
WebView泄漏
解决方案:
开启独立进程,指定某个Activity的process属性,让其在单独的进程运行
Drawable目录下资源不匹配
Android默认的资源加载机制是根据当前设备的屏幕密度取对应drawable-xxx
目录下的资源文件,如果没找到,就按照就近原则取其他drawable目录下的同名资源,如果你的资源放在drawable-mdpi
下面,而你的设备密度为480dpi
,那么加载的图片将放大3倍,这是相当可怕的.
解决方案:
抓不准该放到那个目录的图片,就尽量问设计人员要高品质图片然后往高密度目录下放,这样在低密屏上“放大倍数”是小于1的,在保证画质的前提下,内存也是可控的。
拿不准的图片,使用Drawable.createFromStream替换getResources().getDrawable来加载,这样就可以绕过Android以上的这套默认适配法则。
Bitmap占用内存过大,导致OOM
解决方案:
是用InSampleSize对图片进行压缩