目录
写在前面
一、Android线程调度原理解析
1.1、线程调度原理
1.2、线程调度模型
1.3、Android线程调度
二、Android异步方式
三、Android线程优化实战
3.1、线程使用准则
3.2、线程池优化实战
四、定位线程创建者
4.1、如何确定线程创建者
4.2、Epic实战
五、优雅实现线程收敛
5.1、线程收敛常规方案
5.2、基础库如何使用线程
5.3、基础库优雅使用线程
各位小伙伴们早上好,端午节即将到来,提前恭祝大家“端午安康”!
在上一篇中我们说到了Android平台卡顿优化的相关知识,还没了解的可以先去了解一波哦——《你想要知道的android卡顿优化》,
今天咱们继续Android性能优化专题的分析,来到了Android线程优化。
①、nice值
下面是android.os.Process类中定义的各个优先级:
②、cgroup
对于Android来说,只有nice值实际上并不能满足所有场景,比如某个应用有一个前台的UI线程,同时它还有10个后台线程,虽然后台线程的优先级比较低,但是数量较多,合起来这些后台线程对CPU的消耗也会影响到前台线程的性能,所以对于Android来说又引入了另外一套机制来处理这种特殊的情况——cgroup。
需要注意的问题
①、Thread:最简单、常见的异步方式
②、Handler Thread:自带消息循环的线程
③、Intent Service:继承自Service在内部创建Handler Thread
④、AsyncTask:Android提供的异步工具类,内部实现是基于线程池
⑤、线程池:Java提供的线程池
⑥、RxJava:由强大的Scheduler集合提供(这里只看线程调度功能)
总结:
接下来针对线程池的使用来做一个简单的实践,还是打开我们之前的项目,这里说一下每次实践的代码都是基于第一篇启动优化的那个案例上写的。
首先新建一个包async,然后在包中创建一个类ThreadPoolUtils,这里我们创建可重用且固定线程数的线程池,核心数为5,并且对外暴露一个get方法,然后我们可以在任何地方都能获取到这个全局的线程池:
public class ThreadPoolUtils {
//创建定长线程池,核心数为5
private static ExecutorService mService = Executors.newFixedThreadPool(5, new ThreadFactory() {
@Override
public Thread newThread(Runnable runnable) {
Thread thread = new Thread(runnable,"ThreadPoolUtils");//设置线程名
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); //设置线程优先级
return thread;
}
});
//获取全局的线程池
public static ExecutorService getService(){
return mService;
}
}
然后使用的时候就可以在你需要的地方直接调用了,并且你在使用的时候还可以修改线程的优先级以及线程名称:
//使用全局统一的线程池
ThreadPoolUtils.getService().execute(new Runnable() {
@Override
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT); //修改线程优先级
String oldName = Thread.currentThread().getName();
Thread.currentThread().setName("Jarchie"); //修改线程名称
Log.i("MainActivity","");
Thread.currentThread().setName(oldName); //将原有名称改回去
}
});
当你的项目做的越来越大的时候一般情况下线程都会变的非常多,最好是能够对整体的线程数进行收敛,那么问题来了,如何知道某个线程是在哪里创建的呢?不仅仅是你自己的项目源码,你依赖的第三方库、aar中都有线程的创建,如果单靠人眼review代码的方式,工作量很大而且你还不一定能找的全。并且你这次优化完了线程数,你还要考虑其他人新加的线程是否合理,所以就需要能够建立一套很好的监控预防手段。然后针对这些情况来做一个解决方案的总结分析,主要思想就是以下两点:
解决方案:
可以在构造函数中加上自己的逻辑,获取当前的调用栈信息,拿到调用栈信息之后,就可以分析看出某个线程是否使用的是统一的线程池,也可以知道某个线程具体属于哪个业务方。
Epic简介
Epic使用
代码中使用
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
//Hook Thread类的构造函数,两个参数:需要Hook的类,MethodHook的回调
DexposedBridge.hookAllConstructors(Thread.class, new XC_MethodHook() {
//afterHookedMethod是Hook此方法之后给我们的回调
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param); //Hook完成之后会回调到这里
//实现自己的逻辑,param.thisObject可以拿到线程对象
Thread thread = (Thread) param.thisObject;
//Log.getStackTraceString打印当前的调用栈信息
Log.i(thread.getName() + "stack", Log.getStackTraceString(new Throwable()));
}
});
}
如果你的手机支持的话,这个时候运行程序应该就可以看到线程打印出来的堆栈信息了,我的手机不支持,所以就随便扒了一张图给大家了:
举个栗子:比如这里有一个日志工具类,我们将它作为应用的日志基础库,假设它内部有一些异步操作,原始的情况下是它自己内部实现的,然后现在在它内部对外暴露一个API,如果外部注入了一个ExecutorService,那么我们就使用外部注入的这个,如果外部没有注入,那就使用它默认的,代码如下所示:
public class LogUtils {
private static ExecutorService mExecutorService;
public static void setExecutor(ExecutorService executorService){
mExecutorService = executorService;
}
public static final String TAG = "Jarchie";
public static void i(String msg){
if(Utils.isMainProcess(BaseApp.getApplication())){
Log.i(TAG,msg);
}
// 异步操作
if(mExecutorService != null){
mExecutorService.execute(() -> {
...
});
}else {
//使用原有的
...
}
}
}
统一线程库
举个栗子:根据上面的说明,可以做如下的设置:
//获取CPU的核心数
private int CPUCOUNT = Runtime.getRuntime().availableProcessors();
//cpu线程池,核心数大小需要和cpu核心数相关联,这里简单的将它们保持一致了
private ThreadPoolExecutor cpuExecutor = new ThreadPoolExecutor(CPUCOUNT, CPUCOUNT,
30, TimeUnit.SECONDS, new LinkedBlockingDeque<>(), sThreadFactory);
//IO线程池,核心数64,这个数量可以针对自身项目再确定
private ThreadPoolExecutor iOExecutor = new ThreadPoolExecutor(64, 64,
30, TimeUnit.SECONDS, new LinkedBlockingDeque<>(), sThreadFactory);
//这里面使用了一个count作为标记
private static final ThreadFactory sThreadFactory = new ThreadFactory() {
private final AtomicInteger mCount = new AtomicInteger(1);
public Thread newThread(Runnable runnable) {
return new Thread(runnable, "ThreadPoolUtils #" + mCount.getAndIncrement());
}
};
然后在你实际项目中需要区分具体的任务类型,针对性的选择相应的线程池进行使用。
以上就是对于Android线程优化方面的总结了,今天的内容还好不算多,觉得有用的朋友可以看看。
OK,废话就不多说了,今天就先到这里吧,各位下期再会!