磁盘
写入放大
磁盘一页为4k,一块为128页有的是64页。
在ssd删除一页数据时,会将其标记为删除,并不会真正的删除,所以数据才有恢复一说。在下次写入到这页的时候才会进行删除,但是ssd的最小删除单位是块,所以就造成了写入放大。在一个新的ssd中是很小几率出现写入放大的,但使用时间长了,ssd中的页都被使用过了就很容易出现。
写入放大的过程
- 写入4k数据
- 检查到写入ssd的这一块数据满了但是其中有1页数据标记为删除
- 将整个块拷贝出来(512k)
- 修改数据(4k)
- ssd擦除整个块(512k)
- 将之前复制出来修改的数据写入ssd(512k)
在写入过程中,虽然只是写入4k数据,但是由于写入放大,多次操作了512k大小的数据(128倍)
压缩文件
Android中压缩解压zip有zipfile和zipinputstream
。
- 首先两这个实际都是调用到native层进行压缩解压的(zlib库)
- zipfile是在native层进行文件读取然后处理完返回给Java,一次处理的数据为1-64k
- zipinputstream是在Java层准备好数据,再传给native层,每次固定为512字节
zipfile读写文件次数更少,Java层与native层的交互次数更少
由此可见zipfile的效率高于zipinputstream,所以文件压缩操作首选使用zipfile,但是zipinputstream也有使用场景
- 需要边接收数据边解压,例如网络传输
- zip中的目录central directory损坏了,此时只能通过zipinputstream进行顺序解压
数据库的AUTOINCREMENT,主键的自增长
sqlite每行都有一个64位的行号,默认从1开始,插入数据时,如果没有指定行号,会使用当前最大行号+1,如果达到了最大值,会回去找没有使用的行号,找不到就SQLITE_FULL,所以普通的行号是不一定保证递增的。但如果添加AUTOINCREMENT,SQLite会保证当前的行号一定是递增的,如果达到最大值就会直接SQLITE_FULL,AUTOINCREMENT的实现是新建了一个sqlite_sequence表进行最大行号记录的,所以更新插入的操作都需要去维护这个表,导致一次操作读取多个表进行了多次的文件操作
所以尽量减少使用AUTOINCREMENT的使用,这个在SQLite官网也是这么建议的
bitmap的decode
bitmap的decode我们经常使用的有decodeFile,decodeStream,decodeResource,decodeResourceStream等,这些方法最终都是掉到native层的dodecode方法进行解码,但是他们在java层有不一样的操作。(android4.3及之前没什么差别)
public static Bitmap decodeFile(String pathName, Options opts) {
...
stream = new FileInputStream(pathName);
bm = decodeStream(stream, null, opts);
...
}
从decodeFile代码可以看出,在生成一个FileInputStream后就调用了decodeStream方法。问题就是处在了FileInputStream,这个流是一个字节流,读取是按字节读取,所以每次读的少导致了读的次数增加和很多。而我们在使用decodeStream时都会传入一个BufferInputStream,这个流有每次读取会经可能的读多一些,默认是8k,这回使读取的次数大大的减少提高读取效率。
前面也说到4.3的android不会有此问题,因为在decodeStream中如果不是buffer会生成一个buffer
public static Bitmap decodeStream(InputStream is, Rect outPadding, Options opts) {
...
if (!is.markSupported()) {
is = new BufferedInputStream(is, DECODE_BUFFER_SIZE);
}
...
}
所以开发中建议使用decodeStream,同理建议使用decodeResourceStream
同理在读取文件时尽量使用带有缓冲的流,除非有特殊的操作
其他建议
- 尽量减少文件操作,能一次做完的不要分两次
- 尽量不要在主线程做文件读写操作
-
读写文件时我们一般会用一个buffer来装一次读取的数据,而这个buffer大小应该为4k,或者8k,Java默认为8k
-数据库打开是耗时的操作,尽量打开一次后不要关闭
内存
Java内部类的泄漏
class a{
class b{
}
}
内部类b中,我们是可以直接使用this来访问a类中的属性,所以b类中会有对a类的持有,所以内部类最好加个静态static或者在a类释放的时候将b的也释放了
系统服务
Android开发中我们经常会是使用到一些系统服务,例如audiomanager wifimanager等。使用这些服务时我们都会使用getSystemService方法来获取,这个方法在context接口中,如果我们使用activity的来获取时,由于内部的实现会默认将这个context传给service,当服务中某些不确定情况下,service内部出现异常,或hold住传入的context,此情况不是必现,在很多次调用中才会偶尔出现。所以建议获取服务的context使用applicationContext,当然少数service是需要使用activity来获取的
activity.getSystemService
activity.getApplicationContext().getSystemService
postDelay
在activity中,我们经常会使用到postDelay的达到延时执行的效果,但是postDelay之后,activity有可能退出或者销毁,此时会出现内存泄露。所以使用post之类的方法时,在activity销毁的时候需要将post的任务都取消掉
bitmap像素数据格式
一般使用的像素数据格式:
- ARGB_8888:每像素4字节
- RGB_565:每像素2字节,但是不支持透明
在生成bitmap中,android默认使用的是ARGB_8888,这会使得bitmap占用内存增大,所以如果图片是没有透明通道的可以使用rgb565
另外在api26中,android新增了一种模式HARDWARE,这种模式的bitmap内存会存储在显存中不占用java堆栈内存,但是使用这个模式的bitmap生成了之后不可以在这上面画东西
bitmap的重用
在BitmapFactory.Options中有一个inBitmap属性,这个属性的类型为Bitmap,是用于位图重用的,当传入了inBitmap,在decode的时候会将解码后的数据放到inBitmap中并将inBitmap中之前的数据清除掉,这样就可以重复利用一个bitmap内存了。在使用时,需要注意inBitmap需要是可变的,且在解码的图片大于inbitmap时,图片会被裁剪。所以在bitmap可重复利用的情况下,最好使用inbitmap来重用,例如列表
资源目录
Android工程中的资源目录经常会有x,xx,xxx的标识,这些表示是用来放置不同分辨率下的资源,例如xx目录下存放了xx分辨率的图片,在xx分辨率的手机下就会在这个目录下找图片,找不到再去x和xxx中找并进行相应的缩放,以此达到不同分辨率的适配,如果资源放错了有可能会导致内存使用的增大,若将xx的图片放到x中,当xx在x目录下找图片时会将图片放大x倍导致占用内存增大
资源文件要根据分辨率放到相应的文件夹中
其他建议
- 减少大内存的使用,避免oom
- 减少对象的频繁创建,避免过多gc
- 减少常驻内存,避免内存泄漏
- 有些业务需要context,但是这些业务在activity销毁后有可能还在执行,此时可以使用弱引用持有。个人认为这可以作为临时做法,因为这做法太过隐晦了
网络
图片格式
在网络传输中,我们经常会遇到加载图片,而图片一般来说都比较大所以也更加占用网络资源。一般使用到的图片格式有jpg,png,webp,其中,webp的体积最小支持透明通道,png体积最大色彩最丰富,jpg不支持透明通道,所以在在正常情况下我们可以使用webp来代替png和jpg,以减小网络传输的成本。这里需要注意的是,ios不兼容webp(可以使用其他库来实现兼容),webp的体积虽然小但是其压缩和解压比jpg慢了4-8倍。所以图片格式的替换需要根据实际的使用场景来定。
下载分片大小
我们下载资源一般都会进行分片下载,而每次分片的大小不宜太低,专项组对qq进行分析时,发现其分片大小为8K,此时和微信进行对比,其网络传输速度低于微信,在将分片大小设置成32K是,QQ的传输速度远高于微信。个人认为,对于网络传输的分片最好不要太小但也不要太大,太小了就无法充分使用网络的带宽,太大了占用的内存就大了,所以最理想的是根据机器来定分片的大小,此处的32K只是来验证增大分片可以是网络传输变快,但个人认为这不是一个实际使用的值。
其他建议
- 前台网络的IO最好小于60K/s,大于60对用户的体验不好
- 下载或网络请求,在失败重试的机制中必须要要有明确的结束条件,不能出现有无限重试的情况,否则会消耗大量流量和电量