在我之前的博客如何优雅的检测主线程中的耗时方法中分析了:利用Android系统的消息机制原理去检测主线程中的耗时方法,其实对于执行方法引起的性能开销主要分两类:
对于这两类方法,可以使用工具Traceview(在Android Studio 3.0 版本和以下版本中使用)和CPU Profiler(Android Studio 3.1版本以上使用)进行分析。
由于Traceview已经被抛弃,所以这里不再做介绍。
关于CPU Profiler,官方提供了中文文档,清查看:使用 CPU Profiler 检查 CPU Activity 和函数跟踪。
例子:
模拟在Activity中执行时间长的方法和次数多的方法:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.activity_main_test1).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
method1();
}
});
findViewById(R.id.activity_main_test2).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
for (int i = 0; i < 20; i++) {
method2();
}
}
});
}
//执行次数多的方法
private void method2() {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 执行时间长的方法
private void method1() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
上面代码主要是测试点击按钮让主线程睡一秒的两种方法,使用CPU Profiler分析如下:
Call Chart 标签:
Call Chart 标签提供函数跟踪的图形表示形式,其中,水平轴表示函数调用(或调用方)的时间段和时间,并沿垂直轴显示其被调用者。 对系统 API 的函数调用显示为橙色,对应用自有函数的调用显示为绿色,对第三方 API(包括 Java 语言 API)的函数调用显示为蓝色。
通过上面图1中的Call Chart 标签,可以看到函数从上到下的栈调用轨迹:
com.android.internal.os.ZygoteLnit.main ->......-> android.app.ActivityThread,main -> ...... android.os.Looper.loop -> ...... -> MainActivity.onClick -> ...... -> MainActivity.method1 -> ......
Flame Chart 标签:
上面图2中显示了Flame Chart 标签,Flame Chart 标签提供一个倒置的调用图表,其汇总相同的调用堆栈。 即,收集共享相同调用方顺序的完全相同的函数,并在火焰图中用一个较长的横条表示它们(而不是将它们显示为多个较短的横条,如调用图表中所示)。 这样更方便您查看哪些函数消耗最多时间。 不过,这也意味着水平轴不再代表时间线,相反,它表示每个函数相对的执行时间。
调用Thread类方法的相同调用堆栈:
android.os.Handler.dispatchMessage -> ...... -> android.view.View.performClick -> MainActivity.onClick -> ...... -> MainActivity.method1 -> Thread.sleep -> ......
android.os.Handler.dispatchMessage -> ...... -> android.view.View.performClick -> MainActivity.onClick -> ...... -> MainActivity.method2 -> Thread.sleep -> ......
调用MainActivity类方法的相同调用堆栈:
android.os.Handler.dispatchMessage -> ...... -> android.view.View.performClick -> MainActivity.onClick -> ...... -> MainActivity.method1
android.os.Handler.dispatchMessage -> ...... -> android.view.View.performClick -> MainActivity.onClick -> ...... -> MainActivity.method2
Top Down 标签:
图3.Top Down 标签
上面图3显示Top Down 标签,Top Down 标签显示一个函数调用列表,在该列表中展开函数节点会显示函数的被调用方。
从上面可以看到method2()方法执行的时间是:1006692us。
另外:Top Down 标签还提供了在每个函数调用上所花费的 CPU 时间(时间也可以用线程总时间占所选时间范围的持续时间的百分比表示):
[外链图片转存失败(img-so20Ma6q-1562732652066)(https://developer.android.com/studio/images/profile/call_chart_1-2X.png)]
上面图4显示了Bottom Up标签, Bottom Up 标签显示一个函数调用列表,在该列表中展开函数节点将显示函数的调用方。
从上面可以看到函数method1的调用方。另外,Bottom Up 标签还用于按照消耗最多(最少)CPU 时间排序函数,上面函数列表从上到下,函数执行时间以此递减。
Top Down 标签和Bottom Up标签的区别:
图5. 一个“Top Down”树。
图6. 图 5 中函数 C 的“Bottom Up”树。
如图 5 所示,在“Top Down”标签中展开函数 A 的节点可显示它的被调用方,即函数 B 和 D。 然后,展开函数 D 的节点可显示它的被调用方,即函数 B 和 C 等等。 与 Flame chart 标签相似,“Top Down”树汇总共享相同调用堆栈的相同函数的跟踪信息。 也就是说,Flame chart 标签可提供Top down 标签的图形化表示形式。
图 6 为函数 C 提供了一个“Bottom Up”树。 在“Bottom Up”树中打开函数 C 的节点可显示它独有的调用方,即函数 B 和 D。 请注意,尽管 B 调用 C 两次,但在“Bottom Up”树中展开函数 C 的节点时,B 仅显示一次。 然后,展开 B 的节点显示其调用方,即函数 A 和 D。
执行时间长的方法,可以放在子线程中去执行。在Android中提供了IntentService,HandlerThread,AsyncTask类,在Java中也提供了ThreadPoolExecutor类来帮助执行时间长的方法。
用于启动具有looper的新线程的方便类。 然后可以使用looper来创建处理程序类。 请注意,仍然必须调用start()。
HandlerThread继承自Thread类,在 run方法中创建了一个当前线程的Looper并调用了消息循环loop()方法:
@Override
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}
HandlerThread可以做定时,延时或者无限循环的任务。
IntentService是服务的基类,可根据需要处理异步请求(表示为Intents)。 客户端通过Context.startService(Intent)调用发送请求; 服务根据需要启动,使用工作线程依次处理每个Intent,并在工作失败时自行停止。
这种“工作队列处理器”模式通常用于从应用程序的主线程卸载任务。 存在IntentService类以简化此模式并处理机制。 要使用它,请扩展IntentService并实现onHandleIntent(Intent)。 IntentService将接收Intents,启动工作线程,并根据需要停止服务。
所有请求都在一个工作线程上处理 - 它们可能需要多长时间(并且不会阻止应用程序的主循环),但一次只能处理一个请求。
由于IntentService是一个Service,所以需要在AndroidManifest.xml配置。
IntentService内部使用了HandlerThread:
private volatile Looper mServiceLooper;
private volatile ServiceHandler mServiceHandler;
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
onHandleIntent((Intent)msg.obj);
stopSelf(msg.arg1);
}
}
@Override
public void onCreate() {
// TODO: It would be nice to have an option to hold a partial wakelock
// during processing, and to have a static startService(Context, Intent)
// method that would launch the service & hand off a wakelock.
super.onCreate();
HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
thread.start();
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
}
@Override
public void onStart(@Nullable Intent intent, int startId) {
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
mServiceHandler.sendMessage(msg);
}
在HandlerThread类中的子线程中执行任务,任务来自Intent(必须是一个Inteng),执行任务后会停止自己。
IntentService适合执行时间长的耗时任务,比如下载,上传,复杂计算等。
AsyncTask可以正确,方便地使用UI线程。 此类允许您执行后台操作并在UI线程上发布结果,而无需操作线程和/或处理程序。
AsyncTask被设计为围绕Thread和Handler的助手类,并不构成通用的线程框架。 理想情况下,AsyncTasks应该用于短操作(最多几秒钟)。如果需要保持线程长时间运行,强烈建议您使用java.util.concurrent包提供的各种API,例如 Executor,ThreadPoolExecutor和FutureTask。
异步任务由在后台线程上运行的计算定义,其结果在UI线程上发布。 异步任务由3种泛型类型定义,称为Params,Progress和Result,以及4个步骤,称为onPreExecute,doInBackground,onProgressUpdate和onPostExecute。
AsyncTask内部实现是THREAD_POOL_EXECUTOR+SERIAL_EXECUTOR+Handler:
执行任务的THREAD_POOL_EXECUTOR:
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));//线程池大小:最少2个线程,最多4个线程。
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;//最大线程数
private static final int KEEP_ALIVE_SECONDS = 30;//线程闲置时,30秒后被回收
private static final BlockingQueue sPoolWorkQueue =
new LinkedBlockingQueue(128);//大小为128的有界队列
public static final Executor THREAD_POOL_EXECUTOR;
static {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
sPoolWorkQueue, sThreadFactory);
threadPoolExecutor.allowCoreThreadTimeOut(true);//核心线程闲置时,超时也会被回收
THREAD_POOL_EXECUTOR = threadPoolExecutor;
}
用于任务排队的SERIAL_EXECUTOR:
public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
private static class SerialExecutor implements Executor {
final ArrayDeque mTasks = new ArrayDeque();//用于任务排队的队列
Runnable mActive;
public synchronized void execute(final Runnable r) {
mTasks.offer(new Runnable() {
public void run() {
try {
r.run();//执行当前任务
} finally {
scheduleNext();//去执行下一个任务
}
}
});
if (mActive == null) {
scheduleNext();//去执行下一个任务
}
}
protected synchronized void scheduleNext() {
if ((mActive = mTasks.poll()) != null) {
THREAD_POOL_EXECUTOR.execute(mActive);
}
}
}
Executors提供的工厂方法:
创建线程池ThreadPoolExecutor:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
1.避免创建大量的对象
在自定义View时,通常会重写onMeasure,onLayout,onDraw方法,这些方法在View的生命周期过程中,通常会被调用多次,所以应该避免在方法中创建大量的对像,能够定义为全局对象的就定义为全局对象。
2.及时移除回调监听接口
ViewTreeObserver.OnGlobalLayoutListener监听器:
view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
//do somthing
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}else{
view.getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
}
});
RecyclerView.OnScrollListener监听器:
recyclerView.clearOnScrollListeners();
ViewPager.OnPageChangeListener监听器:
viewPager.clearOnPageChangeListeners();
3.不要做复杂的计算
在频繁的被回调的方法中不要做复杂的计算。
RecyclerView滑动的时候,RecyclerView.OnScrollListener监听器的onScrolled方法会被不断的回调:
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
}
});
输入文本时,TextWatcher监听器的方法会不断的被回调:
editText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
}
});