一:启动优化
应用在冷启动之前,要执行三个任务:
- 加载启动App;
- App启动之后立即展示出一个空白的Window;
- 创建App的进程;
作为普通应用,App进程的创建等环节我们是无法主动控制的,可以优化的也就是Application、Activity创建以及回调等过程。
Google也给出了启动加速的方向:
1: 利用提前展示出来的Window,快速展示出来一个界面,给用户快速反馈的体验;
2:避免在启动时做密集沉重的初始化(Heavy app initialization);
3: 位问题:避免I/O操作、反序列化、网络操作、布局嵌套等。
优化方案:
启动加速之主题切换
Activity的windowBackground主题属性来为启动的Activity提供一个简单的drawable。
Application启动优化
- 考虑异步初始化三方组件,不阻塞主线程;
- 延迟部分三方组件的初始化;
- 一些第三方的框架考虑用的时候在初始化
Trace工具分析代码执行时间
adb pull /storage/emulated/0/app1.trace把文件拉出来分析
把pull到电脑上的文件拖到AS中就可以分析了
Debug.startMethodTracing(filePath);
中间为需要统计执行时间的代码
Debug.stopMethodTracing();
查看trace文件

左侧为发生的具体线程,右侧为发生的时间轴,下面是发生的具体方法信息。注意两列:Real Time/Call(实际发生时间),Calls+RecurCalls/Total(发生次数);
查看activity启动时间:
adb shell am start -W com.lqr.wechat/packagename.SplashActivity
ThisTime:最后一个启动的Activity的启动耗时;
TotalTime:自己的所有Activity的启动耗时;
WaitTime: ActivityManagerService启动App的Activity时的总时间(包括当前Activity的onPause()和自己Activity的启动)。
二:过度绘制优化
过度绘制优化(主要减少GPU工作量)
- 查看方法
开发者选项-》Profile GPU rendering/调试GPU过度绘制 - 处理方案
- 1.减少背景重复
- 2.使用裁减减少控件之间的重合部分
- Android7.0之后系统做出的优化
invalidate()不再执行测量和布局动作
布局的优化(主要减少CPU工作量)
常用工具:
Device Monitor窗口中Hierarchy view
注意点:
1.能在一个平面显示的内容,尽量只用一个容器
2.尽可能把相同的容器合并merge
3.能复用的代码,用include处理,可以减少GPU重复工作
三:内存优化
分析内存的常用工具
Procstats
DDMS
MAT
LeakCanary
Profile
工具很多,掌握原理方法,工具随便找两个能用就行
内存分配
Java程序在运行的过程中会将其管理的内存分为若干个不同的数据区:
- 方法区:方法区存放的是类信息、常量、静态变量,所有线程共享区域。
- 虚拟机栈:每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息,线程私有区域。
- 本地方法栈:与虚拟机栈类似,区别是虚拟机栈为虚拟机执行Java方法服务,本地方法栈为虚拟机使用到的Native方法服务
- 堆:JVM管理的内存中最大的一块,所有线程共享;用来存放对象实例,几乎所有的对象实例都在堆上分配内存;此区域也是垃圾回收器(Garbage Collection)主要的作用区域,内存泄漏就发生在这个区域。
- 程序计数器:可看做是当前线程所执行的字节码的行号指示器;如果线程在执行Java方法,这个计数器记录的是正在执行的虚拟机字节码指令地址;如果执行的是Native方法,这个计数器的值为空(Undefined)。
GC垃圾回收器
GC如何确定内存回收??
- 引用计数算法
缺点:互相引用容易出现计算器永不为0
Object o1=new Object(); 计数+1=1
Object o2;
o2=o1; 计数+1=2
o1=null; 计数为1 o1和o2都不会回收
-
可达性分析算法
GC是需要2次扫描才回收对象,当GC 引用链断开的时候 即可回收
image.png
-
回收也和引用类型有关系
- 强引用
- 软引用SoftReference
内存不足时回收,存放一些重要性不是很强又不能随便让清除的对象,比如图片切换到后台不需要马上显示了 - 弱引用WeakReference
第一次扫到了,就标记下来,第二次扫到直接回收 - 虚引用PhantomReference
不对生存造成任何影响,用于跟踪GC的回收通知
内存回收算法
-
标记清除算法Mark-Sweep
标记清除算法
效率问题,标记和清除两个过程的效率都不高;
空间问题,标记清除之后会产生大量的不连续的内存碎片。
-
复制算法Copying
复制算法
优点:实现简单,运行高效;每次都是对整个半区进行内存回收,内存分配时也不需要考虑内存碎片等情况,只要移动堆顶指针,按顺序分配内存即可;
缺点:粗暴的将内存缩小为原来的一半,代价实在有点高。
-
标记压缩算法Mark-Compact
标记压缩算法
先标记需要回收的对象(标记过程与“标记-清除”算法一样),然后把所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。
这种方法的特点:
避免了内存碎片;
避免了“复制”算法50%的空间浪费;
主要针对对象存活率高的老年代。
- 分代收集算法
根据对象的存活周期的不同将内存划分为几块,一般是把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。在新生代中,每次垃圾收集时都会发现有大量对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用标记—清除算法或标记—整理算法来进行回收。
内存泄露
产生的原因:一个长生命周期的对象持有一个短生命周期对象的引用
通俗讲就是该回收的对象,因为引用问题没有被回收,最终会产生OOM
查看内存泄漏的工具
Android Profiler的使用
运行起来其 查看内存,选择一块内存区域查看hprof文件
- Allocations: 动态分配对象个数
- Deallocation:解除分配的对象个数
- Total count :对象的总数
- Shallow Size:对象本身占用的内存大小
- Retained Size:GC回收能收走的内存大小
优化内存的良好编码习惯
- 1.数据类型 不要使用比需求更占空间的基本数据类型
- 2.循环尽量用foreach,少用iterator, 自动装箱尽量少用
- 3.数据结构与算法的解度处理
- 4.枚举优化
- 5.static staticfinal的问题
- 6.字符串的连接尽量少用加号(+)
- 7.重复申请内存的问题
- 8.避免GC回收将来要重用的对象
- 9.Activity组件泄漏
- 10.尽量使用IntentService,而不是Service

Bitmap的优化
-
Bitmap内存模型
Android 2.3.3(API10)之前,Bitmap的像素数据存放在Native内存,而Bitmap对象本身则存放在Dalvik Heap中。而在Android3.0之后,Bitmap的像素数据也被放在了Dalvik Heap中。而在Android 8.0 之后,Bitmap的像素数据又被放到 Native 内存中。 -
Bitmap的内存回收
1:在Android2.3.3之前推荐使用Bitmap.recycle()方法进行Bitmap的内存回收;
2: 在Android3.0之后更注重对Bitmap的复用; -
Bitmap占有多少内存?
getByteCount()方法是在API12加入的,代表存储Bitmap的色素需要的最少内存。API19开始getAllocationByteCount()方法代替了getByteCount()。 -
计算Bitmap在内存中的大小( width * height 知道是原始图片的宽高)
加载一张本地资源图片,那么它占用的内存 = width * height * nTargetDensity/inDensity * nTargetDensity/inDensity * 一个像素所占的内存。- nTargetDensity 手机系统密度
- inDensity是drawable对应的文件夹的密度
-
图片的压缩
-
质量压缩
质量压缩并不会改变图片在内存中的大小,仅仅会减小图片所占用的磁盘空间的大小,因为质量压缩不会改变图片的分辨率,而图片在内存中的大小是根据widthheight一个像素的所占用的字节数计算的,宽高没变,在内存中占用的大小自然不会变,质量压缩的原理是通过改变图片的位深和透明度来减小图片占用的磁盘空间大小,所以不适合作为缩略图,可以用于想保持图片质量的同时减小图片所占用的磁盘空间大小。另外,由于png是无损压缩,所以设置quality无效,以下是实现方式 -
采样率压缩
采样率压缩是通过设置BitmapFactory.Options.inSampleSize,来减小图片的分辨率,进而减小图片所占用的磁盘空间和内存大小。 -
缩放压缩/ 尺寸压缩
通过减少图片的像素来降低图片的磁盘空间大小和内存大小,可以用于缓存缩略图 -
JNI调用JEPG库(哈夫曼编码)
基本原理是根据数据中元素的使用频率,调整元素的编码长度,以得到更高的压缩比。
image.png
-

- bitmap内存复用
如何复用: - 使用LruCache和DiskLruCache做内存和磁盘缓存;
- 使用Bitmap复用,同时针对版本进行兼容。
大图跟长图的优化方案

黑色代表屏幕,蓝色代表需要加在的内存区、红色代表图片的大小
package com.example.administrator.lsn_8_demo;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapRegionDecoder;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Scroller;
import java.io.IOException;
import java.io.InputStream;
public class BigView extends View implements GestureDetector.OnGestureListener,View.OnTouchListener{
private Rect mRect;
private BitmapFactory.Options mOptions;
private GestureDetector mGestureDetector;
private Scroller mScroller;
private int mImageWidth;
private int mImageHeight;
private BitmapRegionDecoder mDecoder;
private int mViewWidth;
private int mViewHeight;
private float mScale;
private Bitmap bitmap;
public BigView(Context context) {
this(context,null,0);
}
public BigView(Context context, @Nullable AttributeSet attrs) {
this(context,attrs,0);
}
public BigView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
//指定要加载的区域
mRect =new Rect();
//需要复用
mOptions=new BitmapFactory.Options();
//手势识别类
mGestureDetector=new GestureDetector(context,this);
//设置onTouchListener
setOnTouchListener(this);
//滑动帮助
mScroller=new Scroller(context);
}
/**
* 由使用者输入一张图片
*/
public void setImage(InputStream is){
//先读取原图片的信息 高,宽
mOptions.inJustDecodeBounds=true;
BitmapFactory.decodeStream(is,null,mOptions);
mImageWidth=mOptions.outWidth;
mImageHeight=mOptions.outHeight;
//开启复用
mOptions.inMutable=true;
//设置格式成RGB_565
mOptions.inPreferredConfig=Bitmap.Config.RGB_565;
mOptions.inJustDecodeBounds=false;
//创建一个区域解码器
try {
mDecoder=BitmapRegionDecoder.newInstance(is,false);
} catch (IOException e) {
e.printStackTrace();
}
requestLayout();
}
/**
* 在测量的时候把我们需要的内存区域获取到 存入到mRect中
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//获取测量的view的大小
mViewWidth=getMeasuredWidth();
mViewHeight=getMeasuredHeight();
//确定要加载的图片的区域
mRect.left=0;
mRect.top=0;
mRect.right=mImageWidth;
//获取一个缩放因子
mScale=mViewWidth/(float)mImageWidth;
//高度就根据缩放比进行获取
mRect.bottom=(int)(mViewHeight/mScale);
}
/**
* 画出内容
*/
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//如果解码器拿不到,表示没有设置过要显示的图片
if(null==mDecoder){
return;
}
//复用上一张bitmap
mOptions.inBitmap=bitmap;
//解码指定的区域
bitmap=mDecoder.decodeRegion(mRect,mOptions);
//把得到的矩阵大小的内存进行缩放 得到view的大小
Matrix matrix=new Matrix();
matrix.setScale(mScale,mScale);
//画出来
canvas.drawBitmap(bitmap,matrix,null);
}
@Override
public boolean onTouch(View v, MotionEvent event) {
//交给手势处理
return mGestureDetector.onTouchEvent(event);
}
/**
* 手按下的回调
* @param e
* @return
*/
@Override
public boolean onDown(MotionEvent e) {
//如果移动还没有停止,强制停止
if(!mScroller.isFinished()){
mScroller.forceFinished(true);
}
//继续接收后续事件
return true;
}
/**
*
* @param e1 接下
* @param e2 移动
* @param distanceX 左右移动时的距离
* @param distanceY 上下移动时的距离
* @return
*/
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
//上下移动的时候,需要改变显示区域 改mRect
mRect.offset(0,(int)distanceY);
//处理移动时已经移到了两个顶端的问题
if(mRect.bottom>mImageHeight){
mRect.bottom=mImageHeight;
mRect.top=mImageHeight-(int)(mViewHeight/mScale);
}
if(mRect.top<0){
mRect.top=0;
mRect.bottom=(int)(mViewHeight/mScale);
}
invalidate();
return false;
}
/**
* 处理惯性问题
* @param e1
* @param e2
* @param velocityX 每秒移动的x点
* @param velocityY
* @return
*/
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
//做计算
mScroller.fling(0,mRect.top,
0,(int)-velocityY,
0,0,
0,mImageHeight-(int)(mViewHeight/mScale));
return false;
}
/*
使用上一个接口的计算结果
*/
@Override
public void computeScroll() {
if(mScroller.isFinished()){
return;
}
//true 表示当前滑动还没有结束
if(mScroller.computeScrollOffset()){
mRect.top=mScroller.getCurrY();
mRect.bottom=mRect.top+(int)(mViewHeight/mScale);
invalidate();
}
}
@Override
public void onShowPress(MotionEvent e) {
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
return false;
}
@Override
public void onLongPress(MotionEvent e) {
}
}
网友评论