当你懂了以下的技巧,优化创建的几百个线程不是问题( 三 )


object ProxyExecutors {@JvmStaticfun newFixedThreadPool(nThreads: Int): ExecutorService {// 这里使用BaseThreadPoolExecutor主要是为了避免上层代码把ExecutorService转型成ThreadPoolExecutor的问题,如果使用proxy方法动态代理,上层这么做会crashreturn BaseThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,LinkedBlockingQueue())// return proxy(Executors.newFixedThreadPool(nThreads))}ok,线程池搞定了 。但除了线程和线程池,Java和Android中还有很多封装了线程线程池的类 。比如HandlerThread,Timer,ASyncTask等,我们先处理这几个常用类吧 。
HandlerThread本质上就是Thread,采用和Thread类似的处理方式就好 。
Timer内部有个私有的TimerThread成员,本质也是个Thread,看源码可知Timer在构造方法中便启动了这个Thread,所以调用栈也就是Timer被初始化的栈 。我们可以新建BaseTimer,使用asm将代码中的Timer替换掉,然后构造方法中获取调用栈,再通过反射拿到内部Thread成员,获取其id等信息,与调用栈关联即可 。
ASyncTask这个相对来说难办一些,这里面主要有两个Executor:THREAD_POOL_EXECUTOR和SERIAL_EXECUTOR,其中主要干活的是THREAD_POOL_EXECUTOR,而SERIAL_EXECUTOR并不是真正意义上的线程池,只是实现了Executor接口而已 。SERIAL_EXECUTOR的execute()方法在向一个队列中添加任务,然后依次取出送入THREAD_POOL_EXECUTOR中执行,所以我们需要THREAD_POOL_EXECUTOR的线程池信息,关联SERIAL_EXECUTOR的execute()任务添加栈信息 。
而外界又可以直接用THREAD_POOL_EXECUTOR添加任务,这种情况下就又需要THREAD_POOL_EXECUTOR的任务添加栈信息了,所以这里需要一些特殊处理 。总体思路仍然是动态代理SERIAL_EXECUTOR和THREAD_POOL_EXECUTOR,然后把代理设置回ASyncTask,时机为App启动时 。由于ASyncTask中这两个线程池对象是final的,所以需要通过反射修改modifiers去掉final位 。这在5.0及以上系统没什么问题,但4.x的源码中modifiers是通过native获取的:
/*** Returns the modifiers for this field. The {@link Modifier} class should* be used to decode the result.** @return the modifiers for this field* @see Modifier*/public int getModifiers() {return getFieldModifiers(declaringClass, slot);}private native int getFieldModifiers(Class<?> declaringClass, int slot);对此暂未找到修改方式,所以如果要追踪ASyncTask,请使用Android 5.0及以上手机 。
至此,线程/线程池溯源基本完成了,后续会继续完善IntentService、ForkJoinPool和Java/Android源码中封装的其他不太常用的线程/线程池封装类 。
初版效果长这样:

当你懂了以下的技巧,优化创建的几百个线程不是问题

文章插图
 

当你懂了以下的技巧,优化创建的几百个线程不是问题

文章插图
 
可以看到界面中对用户代码做了高亮,帮助用户在迷乱的调用栈中一眼看到问题根源 。其原理大致是在asm扫描class过程中,根据获取到的项目信息来记录哪些是用户写的代码(不能直接根据是否为jar包来判断,如果项目存在多个module,可能这些module最后也是以jar包形式被asm扫描),然后把相关class包名记下来,过滤掉包含有“已有短包名”的“较长包名”,把这些包名list通过asm写入到指定Java类的指定ArrayList成员中 。在获取到调用栈后,可以和这些包名list对比,进行高亮 。
/ 总结 /最后,此文作为抛砖引玉,提供一种线程溯源的思路 。其实可以看出asm还是很强大的,能做什么就取决于大家的想象力了 。后面会逐渐完善新功能,比如记录线程运行时间,根据线程各维度状态筛选/排序等,统计线程所属包名或jar文件名,看看那些三方库在为非作歹(特别是某些广告sdk,简直 。。),甚至对线程/线程池运行状态可疑的给出警告和优化建议等等 。
最最后,作为asm初学者,很多用法还比较初级,如果有更优雅的实现方式,欢迎疯狂pull requests;另外,该项目虽然目前在咕咚这个比较庞大复杂的app上可正常运行,但难免还有考虑不周全的地方,毕竟替换字节码是比较危险的操作 。
以下是我整理的资料
Android核心知识点笔记github:https://github.com/AndroidCot/Android

【当你懂了以下的技巧,优化创建的几百个线程不是问题】


推荐阅读