Open qmsggg opened 7 years ago
http://androidperformance.com/ 领导推荐的一个网址,里面有性能优化相关的内容
双缓冲:显示内容的数据内存,为什么要用双缓冲,我们知道在LInux上通常使用FrameBuffer来做显示输出,当用户进程跟新FrameBuffer中的数据后,显示驱动会把FrameBuffer中每个像素点的值更新到屏幕,但是这样会带来一个问题,如果上一帧的数据还没显示完,FrameBuffer中的数据又更新来,就会带来残影的问题,给用户直观的感觉就会有闪烁,所以普遍采用来双缓冲技术。双缓冲意味着要使用两个缓冲区(在SharedBUfferStack中),其中一个称为Front Buffer,另外一个称为BackBuffer。UI总是先在BackBUffer中绘制,然后再和Front Buffer交换,渲染到显示设备中。即只有当另一个buffer的数据准备好后,通过io_ctrl来通知显示设备切换Buffer。
应用层绘制一个页面(View),主要有三个过程:CPU准备数据->GPU从数据缓存列表获取数据->Display设备绘制。这个三个过程的耗时可以通过一个手机开发辅助工具查看:Profile GPU Rendering。PGR是从Android4.1系统开始提供的。 PGR功能特点如下:
不同颜色解释:
在实际的开发周宏,从图形上虽然可以看到绘制的时间,但对于不便进行数据分析,比如进入某一个页面,柱状图虽然实时绘制出来,但不能封号地分析,这里可以通过:adb shell dumpsys gfxinfo com..(包名)把具体的耗时输出到日志中来分析。 任何时候超过绿线(警戒线,对应时长16ms),就由可能丢失一帧的内容,虽然对于大部分应用来说,丢失几帧确实感觉不出来卡顿。在用GPR查看了有问题的页面后就可以用Hierarchy Viewer来优化布局。
TraceView是AndroidSDK自带的工具,用来分析函数调用过程,可以对Android的应用程序以及FrameWork层的代码进行性能分析。它是一个图形化的工具,最终会产生一个图表,用于对性能分析进行说明,可以分析到应用具体每一个方法的执行时间,使用可以非常直观简单,分析性能问题很方便。
2.TraceView视图说明
使用TraceView查看耗时时,主要关注Calls + Treur Galls / Total 和Cpu Time / Call这两个值,也就是关注调用次数多和耗时久的方法,然后优化这些方法的逻辑和调用次数,减少耗时。
RealTime与CpuTime区别为:因为RealTime包括来CPU的上下文切换,阻塞,GC等,所以RealTime方法的实际执行时间要比CPU Time稍微长一点。
减少层级
合理使用RelativeLayout和LinearLayout
合理使用Merge ReativeLayout也存在性能低的问题,原因是RelativeLayout会对子View做两次测量,在RelativeLayout中View的排列方式是基于彼此的依赖关系,因为这个依赖关系可能和布局中VIew的顺序并不同,在确定每个子VIew的位置时,需要先给所有子View做一次排序。如果在RelativeLayout中允许子View横向和纵向相互依赖,就需要横向,纵向分别进行一次排序测量。但如果在LinearLayout中有weight属性,也需要进行两次测量,因为没有更多的依赖关系,所以仍然会比RelativeLayout效率高,在布局上RelativeLayout不如LinearLayout快。 但是如果布局本身层次太深,还是推荐使用RelativeLayout减少布局本身层次,相较于测量两次,虽然会增加一些计算时间,但是在体验上影响不会太大,如果优化掉两层布局仅仅是增加一次测量,还是非常值得的,布局层次深会增加内存消耗,甚至引起栈溢出等问题,即使耗点时间,也不能让应用不可用。 根据以上分析,可以总结出一下几点布局原则:
尽量使用RelativeLayout和LinearLayout。
在布局层级相同的情况下,使用LinearLayout。
用LinearLayout有时会使嵌套层级变多,应该使用RelativeLayout,使用面尽量扁平化。
Merge的使用 使用Merge的场合主要有以下两种:
使用Merge需要注意:
使用VIEW.GONE等来隐藏View效率非常低,因为它还在布局当中。仍然会测试和解析这些布局。
ViewStub是一个轻量级的View,它是一个看不见的,并且不占用布局位置,占用资源非常消的视图对象,可以为ViewStub指定一个布局,加载布局是,只有ViewStub会被初始化,然后当ViewStub被设置为可见时,或者调用了ViewStub.inflate()时,Viewstub所指向的布局会被加载和实例化,然后ViewStub的布局属性都会传给它指向的布局
Android的布局复用通过
过度绘制(Overdraw)是在在屏幕上的某个像素在同一帧的时间内被绘制来多次。在多层次重叠的UI结构(如带背景的TextView)中,如果不可见的UI也在做绘制的操作,就会导致某些像素区被绘制来多次。
protected void onCreate(Bundle savedInstancestate) {
super.onCreate(savedInstanceState)
this.getWindow().setBackgroundDrawable(null);
}
针对ListView中的Avatar ImageView的设置,在getView的代码中,判断是否获取对应的Bitmap,获取Avatar的图像之后,把ImageView的Background设置为Transparent,只有当图像没有获取到时,才设置对应的Background占位图片。
Canvas.ClipRect
Android 应用程序的载体是APK文件,其中包括来组件和资源,APK文件可能运行在一个独立的进程中,也有可能产生多个进程,还可以多个APK运行在同一个进程中,可以通过不同的方式来实现。 但需要注意两点:
Application 是Android系统框架中的一个系统组件,Android程序启动时,系统会创建一个Application对象,用来存储系统的一些信息。Android系统会自动在每个程序运行时创建一个Application类的对象,并且只创建一个,可以理解为Application是一个单例类。 应用可以不指定一个具体的Application,系统会自动创建,但一般在开发中都会创建一个继承于系统Applicaton的类实现一些功能,比如一些数据库的创建,模块的初始化等。但这个派生类必须在AndroidManifest.xml中定义好,在application标签增加name属性,并添加自己的Application的类名。 启动Application时,系统会创建一个PID,即进程ID,所有的Activity都会在此进程上运行。在Application创建初始化全局变量,同一个应用的所有Activity都可以读取到这些全局变量的值,Application的生命周期是整个应用程序中最长的,它的生命周期等于这个应用程序的生命周期,因为它是全局的单例的,所以在不同的Activity或者Service中获得的对象都是同一个对象。因此在安卓中要避免使用静态变量来存储长久保存的值,可以用Application,但并不建议使用太多的全局变量。
public class LibApplication extends Application {
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
}
@Override
public void onCreate() {
super.onCreate();
}
@Override
public void onTerminate() {
super.onTerminate();
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
}
@Override
public void onLowMemory() {
super.onLowMemory();
}
@Override
public void onTrimMemory(int level) {
super.onTrimMemory(level);
}
}
在开发过程中,尽量使用Application中的Context实例,因为使用Activity中的Context可能会导致内存泄漏。也可以使用Activity的getApplicationContext方法。
启动->Applicaiton->attachBaseContext()->onCreate()->Activity生命周期。
1.adb shell am
2.代码打点
package com.errarehest.android.androidlib.utils.timemonitor;
import android.util.Log;
import java.util.HashMap;
import java.util.Iterator;
/**
* Created by luohao on 2017/11/7.
* Created date 2017/11/7
* 统计耗时的数据结构
*/
public class TimeMonitor {
private final String TAG = TimeMonitor.class.getSimpleName();
private int mMonitorId = -1;
// 保存一个耗时统计模块的各种耗时,tag对应某一个阶段的时间
private HashMap<String, Long> mTimeTag = new HashMap<>();
private long mStartTime = 0;
public TimeMonitor(int id) {
mMonitorId = id;
}
public int getMonitorId() {
return mMonitorId;
}
public void startMoniter() {
// 每次重新启动,都需要把前面的数据结构清除,避免统计到错误数据
if (mTimeTag.size() > 0) {
mTimeTag.clear();
}
mStartTime = System.currentTimeMillis();
}
// 打一次点,tag交线需要统计的上层自定义
public void recodingTimeTag(String tag) {
// 检查是否保存过相同的tag
if (mTimeTag.get(tag) != null) {
mTimeTag.remove(tag);
}
long time = System.currentTimeMillis() - mStartTime;
mTimeTag.put(tag, time);
}
public void end(String tag, boolean writeLog) {
recodingTimeTag(tag);
end(writeLog);
}
public void end(boolean writeLog) {
if (writeLog) {
// 写入文件
}
testShowData();
}
public void testShowData() {
if (mTimeTag.size() <= 0) {
return;
}
Iterator iterator = mTimeTag.keySet().iterator();
while (iterator.hasNext()) {
String tag = (String) iterator.next();
Log.d(TAG, tag + ":" + mTimeTag.get(tag));
}
}
public HashMap<String, Long> getTimeTag() {
return mTimeTag;
}
}
package com.errarehest.android.androidlib.utils.timemonitor;
/**
* Created by luohao on 2017/11/7.
* Created date 2017/11/7
*/
public class TimeMonitorConfig {
public static final int TIME_MONITOR_ID_APPLICATION_START = 1;
public static final int TIME_MONITOR_ID_RV_TEST = 2;
}
package com.errarehest.android.androidlib.utils.timemonitor;
import android.content.Context;
import java.util.HashMap;
/**
* Created by luohao on 2017/11/7.
* Created date 2017/11/7
*/
public class TimeMonitorManager {
private static TimeMonitorManager sTimeMonitorManager = null;
private static Context sContext = null;
private HashMap<Integer, TimeMonitor> mTimeMonitorList = null;
public synchronized static TimeMonitorManager getInstance() {
if (sTimeMonitorManager == null) {
sTimeMonitorManager = new TimeMonitorManager();
}
return sTimeMonitorManager;
}
public TimeMonitorManager() {
mTimeMonitorList = new HashMap<>();
}
public void resetTimeMonitor(int id) {
// Debug模式不需要计数
if (mTimeMonitorList.get(id) != null) {
mTimeMonitorList.remove(id);
}
getTimeMonitor(id);
}
public TimeMonitor getTimeMonitor(int id) {
TimeMonitor monitor = mTimeMonitorList.get(id);
if (monitor == null) {
monitor = new TimeMonitor(id);
mTimeMonitorList.put(id, monitor);
}
return monitor;
}
}
public class RvTest extends AppCompatActivity {
RecyclerView mRvTest;
Button mBtSet;
Button mBtAdd;
Button mBtClear;
TestAdapter mAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
TimeMonitorManager.getInstance().resetTimeMonitor(TimeMonitorConfig.TIME_MONITOR_ID_RV_TEST);
TimeMonitorManager.getInstance().getTimeMonitor(TimeMonitorConfig.TIME_MONITOR_ID_RV_TEST).startMoniter();
setContentView(R.layout.activity_rv_test);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
.setAction("Action", null).show();
}
});
mAdapter = new TestAdapter();
mRvTest = (RecyclerView) findViewById(R.id.rvTest);
mBtSet = (Button) findViewById(R.id.btSetData);
mBtAdd = (Button) findViewById(R.id.btAddData);
mBtClear = (Button) findViewById(R.id.btClearData);
mBtSet.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mAdapter.set(getData());
}
});
mBtAdd.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mAdapter.add(getData());
}
});
mBtClear.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mAdapter.notifyData(getData2());
}
});
mRvTest.setAdapter(mAdapter);
mRvTest.setLayoutManager(new MyLinearLayoutManager(this));
}
@Override
protected void onResume() {
super.onResume();
TimeMonitorManager.getInstance().getTimeMonitor(TimeMonitorConfig.TIME_MONITOR_ID_RV_TEST).recodingTimeTag("onResume:");
}
@Override
protected void onDestroy() {
TimeMonitorManager.getInstance().getTimeMonitor(TimeMonitorConfig.TIME_MONITOR_ID_RV_TEST).end(false);
super.onDestroy();
}
}
在列表滚动的时候停止图片加载, example:ImageLoader加载图片
在以下两个场景下可以采用局部刷新的方法来节省更多的资源:
一开始不明白,后来删掉这个属性之后发现会出现一个提示:
pick preview layout from the "Fragment Layout" context menu
原来tools:layout仅仅是告诉编辑器,Fragment在程序预览的时候该显示成什么样,并不会对apk产生实际作用,是为开发者设计的。
一般来说被xmlns:tools="http://schemas.android.com/tools" 声明的tools作为前缀的属性都不会被编译进去。这个跟上面代码中tools:context是一样的。
创建阶段(Created) 为对象分配存储空间 开始构造对象 从父类到子类对static成员进行初始化 父类成员变量按照顺序初始化,递归调用父类的构造方法 子类成员变量按照顺序初始化,子类构造方法调用 一旦对象被创建,并有某个引用指向它,这个对象的状态就切换到了应用阶段(In Use)
应用阶段(In Use) 对象至少被一个强引用持有并且对象在作用域内
不可见阶段(Invisible) 程序本身不再持有该对象的任何强引用,但是这些引用可能还存在着; 一般具体是指程序的执行已经超过该对象的作用域了
不可达阶段(Unreachable) 该对象不再被任何强引用所持有; 可能仍被JVM等系统下的某些已经装载的惊天变灵或者线程或JNI所持有,这些特殊的强引用被称为GC root,这种情况容易导致内存泄露,无法被回收
收集阶段(Collected) 对象不可达,并且GC已经准备好对该对象占用的内存空间重新分配的时候,处于手机阶段。 如果重写了finazlie()方法,则会去执行该方法。
尽量不要重写finazlie()方法,因为有可能影响JVM的对象分配与回收速度或者可能造成该对象的再次复活
终结阶段 当对象执行完finalize()方法之后,仍然处于不可达状态时,则该对象进入终结阶段。在这个阶段,内存空间等待GC进行回收
对象空间的重新分配 GC对该对象占有的内存空间进行回收或者再分配,该对象彻底消失
1.软引用可以加速虚拟机对垃圾内存的回收速度,更可靠地维护系统的运行安全,防止内存益出(Out Of Memory)等问题产生。 2.创建对象以后,在确定不需要使用该对象时,使对象置空,这样更符合垃圾回收标准,比如Object = null,可以提高内存使用效率,并且不要采用过深的继承层次。访问本地变量优于访问类中的变量。
资源性对象(比如Cursor,File文件等)往往都使用类一些缓冲,在不使用的时候,应该及时关闭它们,以便它们的缓存数据能够及时回收。它们的缓存不只是存在于Java虚拟机内,还存在于Java虚拟机外。如果仅仅是把它们引用设置为null,而不关闭它们,往往会造成内存泄漏。因为有些资源性对象,比如SQLite Curosr(在析构函数finalize()中,如果没有关闭它,它自己会调用close()关闭),如果我们没有关闭它,系统在回收它时也会关闭它,但是这样效率太低了。所以应该调用colse函数,将其关闭,然后设置为null。
Handler通过发送Message与主线程交互,Message发出之后存储在MessageQueue中,有些Message也不是马上就被处理到,在Message中存在一个target,它是Handler的一个引用,Message在Queue中存在的时间过长,就会导致Handler无法被回收。如果Handler非静态的,则会导致Activity或者Service不被回收。
如果对象的集合是一个static的,情况就严重了。
枚举的最大优点就是类型安全,但在Android平台上,枚举的内存开销是直接定义常量的3倍以上,所以尽量不使用枚举,目前提供了int型和String型两种注解方式:IntDef和StringDef,
public static final int UI_PERF_LEVEL_0 = 0;
public static final int UI_PERF_LEVEL_1 = 1;
@IntDef({UI_PERF_LEVEL_0, UI_PERF_LEVEL_1})
@Retention(RetentionPolicy.SOURCE)
public @interface PER_LEVEL {
}
public static int getLevel(@PER_LEVEL int level) {
...
}
使用IntDef和StringDef需要在Gradle配置中引入:compilecom.android.support:support-annotations:22.0.0
注:图片加载方案 #70
SharedPreferences实际上是对一个XML文件存储key-value键值对,每次的commit和apply操作都是一次I/O写操作。大家都知道I/O操作是最慢的操作之一,在主线程操作会导致主线程慢。SharedPreferences性能优化的主要两个方面: 1.IO性能 2.同步锁问题
A. 当SharedPreferences文件还没有被加载到内存的时候,调用getSharedPreferences方法会初始化文件并读入内存,这容易导致耗时增加。 B.Editor的commit或者apply方法每次执行时,同步写入磁盘耗时较长。
需要说明的是,editor的commit和apply方法的区别在于同步写入和异步写入,以及是否需要返回值。在不需要返回值的情况下,使用apply方法可以极大提高性能。
另一个方面就是同步锁的问题,SharedPreferences类中的commitToMemory()方法会锁定SharedPreferences对象,Put()和getEditor()方法会锁定Editor对象,在写入磁盘是更会锁定一个写入锁。 因此最好的优化方法就是避免频繁地读写SharedPreferences,减少无谓的调用,如下,为代码在同一生命周期内,SharedPreferences读一次即可。 if(开关未读 & 没有发生过变化) { int state = sp.getUserId(); } 而对于批量操作,最好先获取一个editor,进行批量操作,然后调用apply方法。
注:跨进程读写SharedPreferences需要用到ContentProvider方案支持,对所有SP操作套上了ContentProvider进行访问,耗时增加3倍左右,因此尽量避免跨进程进程操作。
SQLiteStatement只能插入具体的一个表中的数据,在插入之前记得先清除上一次的数据;
双缓冲机制:常用的放内存。
1.checkstyle 2.FindBugs 3.PMD 4.Android Lintn
下次正常后上传
ANR(Application Not Responding) 即应用无响应; 1.KeyDispatchTimeout (5s) 2.BroadcastTimeout(10s) 3.ServiceTimeout(20s)
原因: 自生和其它appCPU占用高。
1.网络连接(长连接心跳) 2.利用系统现有机制(AlarmReceiver,BootReceiver) 3.SyncAdapter 利用Android系统提供的账号同步机制实现进程优先级提高
FPS
FPS(Frames Per Second):表示每秒传递的帧数。在理想情况下,60FPS就感觉到不卡,这也就是说每个绘制时长应该是在16ms以内,即1000ms/60; 但是Android系统很有可能无法及时完成那些复杂的界面渲染操作。Android系统每隔16ms发出VSYNC信号,触发对UI进行渲染,如果每次渲染都成功,这样就能够达到流畅的画面所需的60FPS,即为了实现60FPS,就意味着程序的大多数绘制操作都必须在16ms内完成。