在 Java 中非静态内部类和匿名内部类会持有他们所属外部类对象的引用,如果这个非静态内部类对象或者匿名内部类对象被一个耗时的线程(或者其他 GC Root)直接或者间接的引用,甚至这些内部类对象本身就在做一些耗时操作,这样就会导致这个内部类对象直接或者间接无法释放,内部类对象无法释放,外部类的对象也就无法释放造成内存泄漏,而且如果无法释放的对象积累起来就会造成 OOM。解决方案:使用静态内部类
由于静态变量的生命周期和应用一样长,所以如果静态变量持有 Activity 或者 Activity 中 View 对象的引用,就会导致该静态变量一直直接或者间接持有 Activity 的引用,导致该 Activity即使退出了也无法释放内存,从而引发内存泄漏。 解决方案:让静态变量直接或者间接持有 Activity 的强引用,可以将其修改为 soft reference 或者 weak reference ;或者将 Activity Context 更换为 Application Context
资源性对象比如(Cursor,File 文件等)往往都用了一些缓冲,我们在不使用的时候应该及时关闭它们,以便它们的缓冲对象被及时回收。对于资源性对象在不使用的时候,应该调用它的 close() 函数,将其关闭掉,然后再置为 null,在我们的程序退出时一定要确保我们的资源性对象已经关闭。
如果在一个对象使用结束之后未将该对象从该容器中移除掉,就会造成该对象不能被正确回收,从而造成内存泄漏,解决办法是
在使用完之后将该对象从容器中移除。示例如下:
ArrayList list = new ArrayList<>();
list.clear();
list = null;
StrictMode可以检测是否存在内存泄漏,示例如下:
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
.detectLeakedSqlLiteObjects()
.detectLeakedClosableObjects().detectActivityLeaks()
.penaltyLog()
.penaltyDeath()
.build());
对于缩略图这种用户对图片质量要求不高的图片,使用RGB_565进行解码,相比于ARGB_8888能节省一半的内存。同理对于常驻内存的图片,比如微信、QQ的聊天背景,用户对其图片质量要求不高,也可以用RGB_565进行解码,节省一半的内存。
如果有需要拼接的字符串,那么可以优先考虑使用StringBuffer或者StringBuilder来进行拼接,而不是加号连接符,因为使用加号连接符会创建多余的对象,拼接的字符串越长,加号连接符的性能越低。 避免创建短命的临时对象,减少对象的创建就能降低垃圾收集概率,进而减少对用户体验的影响。
每次打开、关闭或者读/写文件,操作系统都需要从用户态切换到内核态,这种状态切换本身是很消耗性能的,所以为了提高文
件的读/写效率,就需要尽量减少用户态和内核态的切换。使用缓存可以避免重复读/写,对于需要多次访问的数据,在第一次取出数据时,将数据放到缓存中,下次再访问这些数据时,就可以从缓存中取出来。
SharedPreferences操作的是xml文件,读写数据都是对磁盘的IO操作,commit提交是同步的,commit返回以后才能执行下面的操作,commit有返回值(Boolean)。Apply提交是异步的,apply调用以后会立即执行下面的操作,apply没有返回值。如果关注提交成功与否使用commit,如果不关注提交成功与否或者在主线程中记录数据的话,最好使用apply。
SharedPreferences是对磁盘的IO操作,不要频繁调用getBoolean(xxx)等get方法取值,最好只调用一次get方法,然后将数据保存在内存中,以后使用内存中的数据。
在读一个文件我们一般会设置一个buffer。即先把文件读到buffer中,然后再读取buffer的数据,所以真正对文件的读取次数 = 文件大小 / buffer大小 。 如果buffer比较小的话,那么读取文件的次数会非常多,当然在写文件时buffer是一样的道理。如果设置1KB的buffer,byte buffer[] = new byte[1024],要读取的文件有20KB, 那么根据这个buffer的大小,这个文件要被读取20次才能读完。buffer的大小可以取值文件所挂载的目录的区块大小block size,一般sd卡的区块大小为4KB。
获取block size如下:
public static long getSdCardBlockSize() {
File sdCardRoot = Environment.getExternalStorageDirectory();
StatFs sdCardStatFs = new StatFs(sdCardRoot.getPath());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
return sdCardStatFs.getBlockSizeLong();
} else {
return sdCardStatFs.getBlockSize();
}
}
public static long getDataBlockSize() {
StatFs sdCardStatFs = new StatFs("/data/data");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
return sdCardStatFs.getBlockSizeLong();
} else {
return sdCardStatFs.getBlockSize();
}
}
使用ObjectOutputStream在序列化对象时,每个数据成员都会带来一次I/O操作,效率非常低下。解决方案:在ObjectOutputStream上面再封装一个输出流ByteArrayOutputStream或者BufferedOutputStream,先将对象序列化后的信息写到缓存区中,然后再一次性地写到磁盘上,这样只需要一次I/O操作。同理,反序列化时ObjectInputStream上面再封装一个输入流ByteArrayInputStream或者BufferedInputStream,只需要从磁盘读取一次即可。
SQLiteDatabase的源码getWriteableDatabase()方法的注释说明:一旦打开数据库,该连接就会被缓存,以供下次使用,只有当真正不需要时,调用close关闭即可。 每次打开数据库都会伴随了I/O操作,getWriteableDatabase()会比较耗时,该操作不能在主线程中进行。 解决方案:数据库在打开后,先不要关闭,在应用程序退出时再关闭。
主键最好不要自增长(INTEGER PRIMARY KEY后面最好不要跟上AUTOINCREMENT),主键最好手工插入唯一的值。 SQLite创建一个叫sqlite_sequence的内部表来记录数据库表使用的最大的行号。如果指定使用AUTOINCREMENT来创建表,则sqlite_sequence也随之创建。UPDATE、INSERT和DELETE语句可能会修改sqlite_sequence的内容。因为维护sqlite_sequence表带来的额外开销将会导致INSERT的效率降低。 AUTO INCREMENT可以保证主键的严格递增,但AUTO INCREMENT关键词会增加CPU,内存,磁盘I/O的负担,所以尽量不要用,除非必需。
Android4.4以及以后的系统版本尽量使用BitmapFactory.decodeStream 而不是bitmapfactory.decodeResource或者bitmapfactory.decodeFile。decodeFile源码中使用的是FileInputStream,这需要多次读取磁盘,效率很低。decodeResource同样存在这个问题,建议使用decodeResourceStream。
StrictMode可以发现并定位在主线程中读写SD卡的操作,示例如下:
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectDiskReads()
.detectDiskWrites()
.detectNetwork().detectCustomSlowCalls()
.penaltyLog()
.build());
1、 能用int就不用long,能用float就不用double,减轻CPU运算压力
2、 设计算法要考虑时间复杂度,减少CPU运算时间
3、 解码图片时尽量使用cache,最好只解码一次,避免重复解码
网络优化主要关注三个方向,网络请求成功率、网络延迟、宽带成本(流量消耗)。
典型的网络差的两种场景,弱信号网络(进电梯、隧道)+拥塞网络(演唱会时分享图片)。弱信号场景,App能做的就是在应用层做重试,因为很可能这个弱信号是一时的。拥塞网络场景,如果App不断重试只会使得拥塞更为严重,App要做的就是少发送网络请求,核心业务发送少量的网络请求,非核心业务就不要发送网络请求了。
网络时延原因 | 解决方案 |
DNS解析耗时(200-2000ms不等) | IP直连 |
TCP三次握手耗时,短连接每次请求都会进行TCP连接(Keep-Alive除外) | 使用长连接,只进行一次TCP连接 |
TCP拥塞控制逻辑 | 长连接可以绕过拥塞策略中的慢启动 |
图片、视频、文件压缩后上传。
大量创建Thread或Runnable的弊端:
内存方面考虑:大量的线程的创建和销毁很容易导致GC执行,从而发生内存抖动现象,造成页面卡顿;
时间方面考虑:线程的创建和销毁都需要时间,当有大量的线程创建和销毁时,耗时会比较明显。
用线程池的益处:
一、 重用线程池中的线程,降低系统资源消耗,避免频繁创建和销毁线程带来内存和CPU上的损耗
二、 提高系统响应速度,当有任务到达时,无需等待新线程的创建便能立即执行
三、 方便线程并发量的控制,线程若是无限制的创建,不仅会消耗大量的系统资源,而且可能造成线程溢出(华为手机限制在400多个线程,很容易出现线程溢出)