Android移动性能实战 汇总

磁盘

写入放大

磁盘一页为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对用户的体验不好
  • 下载或网络请求,在失败重试的机制中必须要要有明确的结束条件,不能出现有无限重试的情况,否则会消耗大量流量和电量
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容