聊聊Android线程优化这件事,2024年最新二本学渣考研失败

public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
return new ThreadPoolDetectorMethodVisitor(api, mv);
}

class ThreadPoolDetectorMethodVisitor extends MethodVisitor {
public ThreadPoolDetectorMethodVisitor(int api, MethodVisitor methodVisitor) {
super(api, methodVisitor);
methodVisitor);
}

@Override
public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf)) {
if (opcode == Opcodes.INVOKESTATIC && owner.startsWith(“java/util/concurrent/Executors”)) {
System.out.println(“Detected creation of new ThreadPool!”);
}
super.visitMethodInsn(opcode, owner, name, desc desc, itf);
}
}
}

  1. 统计和分类扫描到的创建线程和线程池的类名
  • 扫描到的结果

  • 结果进行分类

  • 结果的用处
  1. 了解项目现状。
  2. 对后续优化可以设置白名单。
  3. 可以对线上设置的线程进行降级处理。
3. 线程和线程池优化

3.1 线程优化
  • 对于APP业务层和自研SDK,我们检查是否真的需要直接new thread,能否用线程池代替,如果必须创建单个线程,那我们创建的时候必须加上线程名,方便排查线程问题。
  • 对于三方SDK,那就可以通过插桩来重命名(名称必须少于16个字符),方便尽快知道该线程是来自哪个SDK。
3.2 线程池优化
  • 对于APP业务层,我们需要提供常用线程池,例如I/O、CPU、Single、Cache等等线程池,避免开发各自创建重复的线程池。
  • 对于自研SDK,我们尽量让架构组的开发同学提供可以设置自定义线程池的能力,方便我们代理到我们APP业务层的线程池。
  • 对于三方SDK,首先了解有没有提供设置我们自定义线程池的接口,有的话,那就直接设置我们APP业务层的线程池。如果没有这种能力,那我们就进行插桩来进行线程池收敛。在进行三方SDK插桩代理的时候,需要注意三点:
  1. 设置白名单,进行逐步代理。
  2. 针对不同的SDK,要区分是本地任务还是网络任务,这样能明确是代理到I/O线程池还是CPU线程池。
  3. 设置降级开关,方便线上有问题时,及时对单个SDK进行降级处理。
3.2.1 行业方案

(1)反射收敛,但是使用反射来收敛线程池的确有一些潜在的弊端:

  • 性能开销:反射在执行时需要进行一系列的检查和解析,这会比直接的Java方法方法调用带来更大的性能开销。
  • 安全问题:反射可以访问所有的字段和方法,包括私有有的和受保护的,这可能会破坏对象的封装性,导致安全问题。
  • 代码复杂性:使用反射的代码通常比直接的Java代码更复杂,更难理解和维护。

因此,虽然反射是一种强大的工具,但在使用时需要谨慎,尽量避免不必要的使用。

(2)代理收敛,但是使用代理设计模式来收敛线程池也有一些潜在的弊端:

  • 增加复杂性:代理方式会引入额外的类和对象,这会增加系统的复杂性。对于简单的问题,使用代理可能会显得过于复杂。
  • 代码可读性:由于代理方式涉及到额外的抽象层,这可能会对代码的可读性产生一定的影响。
  • 调试困难:由于代理模式的存在,错误可能会被掩盖或者难以定位,这可能会使得调试变得更加困难。

因此,虽然代理模式是一种强大的设计模式,但在使用时也需要考虑到这些潜在的问题。

(3)协程收敛,但是使用协程收敛线程池也有一些局限性和潜在的弊端:

  • 需要依赖Kotlin协程库:使用Kotlin协程需要依赖Kotlin协程库,如果应用程序中没有使用Kotlin语言,那么需要额外引入Kotlin库,增加了应用程序的体积。
  • 协程的执行时间不能过长:Kotlin协程的执行时间不能过长,否则会影响其他协程的执行。因此,在使用Kotlin协程进行线程收敛时,需要合理控制协程的执行时间。
  • 可能会导致内存泄漏:如果协程没有正确地取消,可能会导致内存泄漏。因此,在使用Kotlin协程时,需要注意正确地取消协程。

因此,虽然Kotlin协程可以通过使用协程调度器来实现线程收敛,但是也存在一些弊端,需要开发者根据具体情况来选择是否使用。

(4)插桩收敛,虽然插桩也有一些不足之处:

  • 可能影响程序行为:如果插桩代码改变了程序的状态或者影响了线程的线程的调度,那么它可能会改变程序的行为。
  • 可能引入错误:如果插桩代码桩代码本身存在错误,那么它可能会引入新的错误到程序中。

但是这些缺点在线程池收敛的时候还是可控的,相比于上面的反射收敛、代理收敛和协程收敛来说,还有许多优点:

  • 直接性:插桩直接在代码中插入额外的逻辑,不需要通过代理或反射射间接地操作对象,这使得插桩更直接,更易于理解和控制。
  • 灵活活性:插桩可以在任何位置插入代码,,这提供了很大的灵活性。而代理和反射通常只能操作公开的接口和方法。
  • 无需修改原始代码:插桩通常常不需要常不需要修改原始的线程池代码,这使得它可以在不影响原始代码的情况下收集信息。
  • 颗粒度控制:可以对某个方法或某段代码进行线程收敛,而不是整个应用程序。

综上所述,我就选择了更加通用、灵活、精确的方式来收敛二方和三方的线程池—插桩代理

3.2.2 代码设计图

3.2.3 代码流程图

暂时无法在飞书文档外展示此内容

3.2.4 代码实施
  1. 创建NewThreadTrackerPlugin,在插件里主要是获取到需要进行代理的线程池白名单以及注册ThreadTrackerTransform。

class NewThreadTrackerPlugin implements Plugin {

@Override
void apply(Project project) {
System.out.println(“ThreadTracker:start ThreadTrackerPlugin”)
project.getRootProject().getSubprojects().each { subProject ->
PluginUtils.addProjectName(subProject.name)
PluginUtils.projectPathList.add(subProject.projectDir.toString())
}
org.gradle.api.plugins.ExtraPropertiesExtension ext = project.getRootProject().getExtensions().getExtraProperties()
//通过配置来设置是否需要输出所有创建线程池的txt文件,文件名为"thread_tracker_XXX.txt"
if (ext.has(“scanProject”)) {
boolean scan = ext.get(“scanProject”)
PluginUtils.setScanProject(scan)
System.out.println(“ThreadTracker:需要扫描项目吗?” + scan)
}
//通过配置来获取需要进行插桩代理的白名单
if(ext.has(“whiteList”)){
List list = ext.get(“whiteList”)
PluginUtils.addWhiteList(list)
}else {
System.out.println(“ThreadTracker:请创建thread_tracker.gradle文件,设置whiteList白名单”)
}
//注册ThreadTrackerTransform。
//Gradle Transform 是 Android 官方提供给开发者在项目构建阶段,即由 .class 到 .dex 转换期间修改 .class 文件的一套 API。目前比较经典的应用是字节码插桩、代码注入技术。
AppExtension appExtension = (AppExtension) project.getProperties().get(“android”)
appExtension.registerTransform(new ThreadTrackerTransform(), Collections.EMPTY_LIST)
}

}

  1. 创建 ThreadTrackerTransform,重写ThreadTrackerTransform的transform方法,在该方法里面来遍历文件目录下和Jar包中的class文件,并让ClassReader接受的是我们自定义的ThreadTrackerClassVisitor。

/**

  • transform 方法来处理中间转换过程,主要逻辑在该方法中实现。我们可以在 transform 方法中,实现对字节码的修改、处理等操作。
  • @param transformInvocation
    */
    @Override
    void transform(@NonNull TransformInvocation transformInvocation) {

    //对于一个.class文件进行Class Transformation操作,整体思路是这样的:
    // ClassReader --> ClassVisitor(1) --> … --> ClassVisitor(N) --> ClassWriter
    ClassReader classReader = new ClassReader(file.bytes)
    ClassWriter classWriter = new ClassWriter(classReader, ClassWriter.COMPUTE_MAXS)
    ClassVisitor cv = new ThreadTrackerClassVisitor(classWriter, null)
    classReader.accept(cv, EXPAND_FRAMES)
    byte[] code = classWriter.toByteArray()
    FileOutputStream fos = new FileOutputStream(
    file.parentFile.absolutePath + File.separator + name)
    fos.write(code)
    fos.close()

    }
  1. 创建ThreadTrackerClassVisitor,重写visitMethod来返回自定义的MethodVisitor,通过这个对象来访问方法的详细信息。

在visitMethod方法方法中,我们可以插入自己的代码,以修改或替换原有的方法声明声明。例如,我们可以改变方法的访问权限、改变方法的参数、改变方法的返回值,甚至可以完全替换原有的方法声明。

@Override
public MethodVisitor visitMethod(int access0, String name0, String desc0, String signature0, String[] exceptions) {
MethodVisitor mv = cv.visitMethod(access0, name0, desc0, signature0, exceptions);
if (filterClass(className)) {
return mv;
}
return new ProxyThreadPoolMethodVisitor(ASM6, mv, className);
}

/**
*。 过滤掉不需要插桩的类,比如这个插桩代码模块、自定义的线程池等等
**/
private boolean filterClass(String className) {
return className.contains(“com/lalamove/threadtracker/”) || className.contains(“com/lalamove/plugins/thread”) || className.contains(“com/tencent/tinker/loader”) || className.contains(“com/lalamove/huolala/client/asm/HllPrivacyManager”);
}

  1. 创建ProxyThreadPoolMethodVisitor,并重写它的visitMethodInsn方法来真实插桩自己的线程池。

在visitMethodInsn方法中,我们可以插入自己的代码,以修改或替换原有的方法调用。

@Override
public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {
//如果配置中是需要扫描App,则把创建线程池的类名全部都写在"thread_tracker_XXX.txt"里面,供开发者统计、分类、设置白名单和降级处理
if (PluginUtils.getScanProject()) {
if (owner.equals(O_ThreadPoolExecutor) && name.equalsIgnoreCase(“”)) {
PluginUtils.writeClassNameToFile(“创建ThreadPoolExecutor的类:” + className);
}
}
//如果配置中是需要插桩代理线程池,则把原本的类 “java/util/concurrent/ThreadPoolExecutor"换成了我们自定义的类"com/lalamove/threadtracker/proxy/BaseProxyThreadPoolExecutor”
//mClassProxy只是一个总开关,是否开启代理;具体某个类是否需要代理,在创建线程池的具体地方会根据类名来判断
if (mClassProxy) {
if (owner.equals(O_ThreadPoolExecutor) && name.equalsIgnoreCase(“”)) {
if (“(IIJLjava/util/concurrent/TimeUnit;Ljava/util/concurrent/BlockingQueue;)V”.equalsIgnoreCase(descriptor)) {
mv.visitLdcInsn(className);
mv.visitMethodInsn(opcode, O_BaseProxyThreadPoolExecutor, name, “(IIJLjava/util/concurrent/TimeUnit;Ljava/util/concurrent/BlockingQueue;Ljava/lang/String;)V”, false);
} else if (“(IIJLjava/util/concurrent/TimeUnit;Ljava/util/concurrent/BlockingQueue;Ljava/util/concurrent/ThreadFactory;)V”.equalsIgnoreCase(descriptor)) {
mv.visitLdcInsn(className);
mv.visitMethodInsn(opcode, O_BaseProxyThreadPoolExecutor, name, “(IIJLjava/util/concurrent/TimeUnit;Ljava/util/concurrent/BlockingQueue;Ljava/util/concurrent/ThreadFactory;Ljava/lang/String;)V”, false);
} else if (“(IIJLjava/util/concurrent/TimeUnit;Ljava/util/concurrent/BlockingQueue;Ljava/util/concurrent/RejectedExecutionHandler;)V”.equalsIgnoreCase(descriptor)) {
mv.visitLdcInsn(className);
mv.visitMethodInsn(opcode, O_BaseProxyThreadPoolExecutor, name, “(IIJLjava/util/concurrent/TimeUnit;Ljava/util/concurrent/BlockingQueue;Ljava/util/concurrent/RejectedExecutionHandler;Ljava/lang/String;)V”, false);
} else if (“(IIJLjava/util/concurrent/TimeUnit;Ljava/util/concurrent/BlockingQueue;Ljava/util/concurrent/ThreadFactory;Ljava/util/concurrent/RejectedExecutionHandler;)V”.equalsIgnoreCase(descriptor)) {
mv.visitLdcInsn(className);
mv.visitMethodInsn(opcode, O_BaseProxyThreadPoolExecutor, name, “(IIJLjava/util/concurrent/TimeUnit;Ljava/util/concurrent/BlockingQueue;Ljava/util/concurrent/ThreadFactory;Ljava/util/concurrent/RejectedExecutionHandler;Ljava/lang/String;)V”, false);
} else {
mv.visitMethodInsn(opcode, O_BaseProxyThreadPoolExecutor, name, descriptor, false);
}
return;
}
}
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
}

上述使用到的一些常量定义如下,也引入到了我们自己自定义的线程池。

class ClassConstant {
//Java里面创建线程池的类名
static final String O_ThreadPoolExecutor = “java/util/concurrent/ThreadPoolExecutor”;

//自定义创建线程池的类名
static final String O_BaseProxyThreadPoolExecutor = “com/lalamove/threadtracker/proxy/BaseProxyThreadPoolExecutor”;

}

  1. 创建BaseProxyThreadPoolExecutor,重写了创建线程池的所有构造方法,也通过传入的类名判断了该类里面的线程池是否需要代理,以及代理的是的CPU密集型线程池还是IO密集型线程池。

package com.lalamove.threadtracker.proxy

import android.util.Log

import com.lalamove.threadtracker.TrackerUtils
import java.util.concurrent.*

/**

  • ThreadPoolExecutor代理类
    */
    open class BaseProxyThreadPoolExecutor : ThreadPoolExecutor {

var mProxy = true

//App层自定义的IO线程池
private var threadPoolExecutor: ThreadPoolExecutor =
TrackerUtils.getProxyNetThreadPool()

constructor(
corePoolSize: Int,
maximumPoolSize: Int,
keepAliveTime: Long,
unit: TimeUnit?,
workQueue: BlockingQueue?,
className: String?,
) : super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue) {
init(corePoolSize,
maximumPoolSize,
keepAliveTime, className)
}

private fun init(
corePoolSize: Int,
maximumPoolSize: Int,
keepAliveTime: Long,
className: String?,
) {
//判断className下创建的线程池是否要被插桩代理
if (className != null) {
mProxy = TrackerUtils.isProxy(className)
}
//单线程暂不代理
if (corePoolSize == 1 || (corePoolSize == 0 && maximumPoolSize == 1)) {
mProxy = false
}
if (!mProxy) {
return
}
//设置核心线程超时允许销毁
if (keepAliveTime <= 0) {
setKeepAliveTime(10L, TimeUnit.MILLISECONDS)
}
allowCoreThreadTimeOut(true)
//设置className的线程池被代理为CPU线程池
if (className != null && TrackerUtils.proxyCpuClass(className)) {
threadPoolExecutor = TrackerUtils.getProxyCpuThreadPool()
}
}

override fun submit(task: Runnable): Future<*> {
return if (mProxy) threadPoolExecutor.submit(task) else super.submit(task)
}

override fun execute(command: Runnable) {
if (mProxy) threadPoolExecutor.execute(command) else super.execute(command)
}

//注意:不能关闭,否则影响其他被代理的线程池
override fun shutdown() {
if (!mProxy) {
super.shutdown()
}
}
//注意:不能关闭,否则影响其他被代理的线程池
override fun shutdownNow(): MutableList {
val list = if (mProxy) mutableListOf() else super.shutdownNow()

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数HarmonyOS鸿蒙开发工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年HarmonyOS鸿蒙开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上HarmonyOS鸿蒙开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新

如果你觉得这些内容对你有帮助,可以添加VX:vip204888 (备注鸿蒙获取)
img

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

)]
[外链图片转存中…(img-yx3TdMua-1712791888294)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上HarmonyOS鸿蒙开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新

如果你觉得这些内容对你有帮助,可以添加VX:vip204888 (备注鸿蒙获取)
[外链图片转存中…(img-nxpbPszg-1712791888294)]

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值