一、背景

在日常开发APP的进程中,不免需求运用第二方库和第三方库来帮助开发者快速完成一些功用,进步开发效率。可是,这些库也或许会给线程带来一定的压力,首要表现在以下几个方面:

  • 线程数量增多:一些库或许会在后台发动一些线程来履行使命,这样会添加体系中线程的数量,然后导致体系资源的糟蹋。
  • 线程竞赛:一些库或许会在同一时间发动多个线程来履行使命,这样会导致线程之间的竞赛,然后影响程序的履行效率。
  • 线程堵塞:一些库或许会在履行使命时堵塞主线程,然后导致程序的卡顿和响应速度变慢。

二、全体思路

为了解决运用第二方库和第三方库署理的线程问题,我挑选用下面的思路来进行线程优化:

  1. 线程检测,评估优化空间。
  2. 线程计算,搜集优化规模。
  3. 线程和线程池优化,线程数收敛。
  4. 线程栈裁剪,削减线程内存。

三、详细计划

1. 线程检测

最常见的几种获取线程信息的办法如下

聊聊Android线程优化这件事

为了有完好的线程计算,而且能实时了解运转进程中线程数的改动,那咱们就挑选了读取伪文件体系里边线程信息的办法。

/**
 * 获取一切线程信息
 */
private fun getThreadInfoList(): List<ThreadInfo>? {
    //获取伪文件一切的线程信息文件
    val file = File("/proc/self/task")
    ...
    //遍历task文件目录下
    for (threadDir in listFile) {
        //读取每个目录下的status文件获取单个线程信息
        val statusFile = File(threadDir, "status")
        if (statusFile.exists()) {
            val threadInfo = ThreadInfo()
            try {
                BufferedReader(InputStreamReader(FileInputStream(statusFile))).use { reader ->
                    var line: String
                    hitFlag = 0
                    while (reader.readLine().also { line = it } != null) {
                        if (hitFlag > 2) {
                            break
                        }
                        //解析线程名
                        if (line.startsWith("Name")) {
                            val name =
                                line.substring("Name".length + 1).trim { it <= ' ' }
                            threadInfo.name = name
                            hitFlag++
                            continue
                        }
                        //解析线程Pid
                        if (line.startsWith("Pid")) {
                            val pid =
                                line.substring("Pid".length + 1).trim { it <= ' ' }
                            threadInfo.id = pid
                            hitFlag++
                            continue
                        }
                        //解析线程状态
                        if (line.startsWith("State")) {
                           ...
                            threadInfo.status = state
                            hitFlag++
                        }
                    }
                }
            } catch (e: Exception) {
                Log.e(LOG_TAG, e.toString())
            }
            threadInfoList.add(threadInfo)
        }
    }
    return threadInfoList
}

最终只需求在APP发动后就敞开轮询使命:1,获取伪文件。2,写入数据库。3,更新视图展示。

聊聊Android线程优化这件事

计算了运转时创立的线程、可用的线程、正在运转的线程。

抱负的状况便是可用的线程数应该和正在运转的线程数尽量挨近,实践发现差异巨大,所以优化的空间仍是蛮值得期待的。

2. 线程计算

聊聊Android线程优化这件事

  1. 了解创立线程和线程池的字节码

聊聊Android线程优化这件事

  1. 怎么扫描到创立的线程和线程池

经过插桩的办法,来查找创立线程池和线程的类名,并把这些类名一致输出到一份txt文档。插桩的框架,我挑选的是ASM,由于运用ASM进行插桩具有高效性、灵敏性、易用性、兼容性和社区活跃等长处,是一种比较优异的字节码操作框架,关于进步运用程序的功能和可保护性具有重要意义。

那么经过ASM是怎么扫描到的呢?

要扫描到创立线程池的类名,你需求运用ASM的访问者模式(Visitor Pattern)来遍历字节码中的办法和指令。在遍历进程中,当遇到创立线程的指令(如:new java/util/concurrent/ThreadPoolExecutor)时,就能够获取到创立线程的类名。

import org.objectweb.asm.*;
public class ThreadPoolDetectorClassVisitor extends ClassVisitor {
    public ThreadPoolDetectorClassVisitor(int api, ClassVisitor classVisitor) {
        super(api, classVisitor);
    }
    @Override
    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. 计算和分类扫描到的创立线程和线程池的类名

  • 扫描到的成果

聊聊Android线程优化这件事

  • 成果进行分类

聊聊Android线程优化这件事

  • 成果的用途
  1. 了解项目现状。
  2. 对后续优化能够设置白名单。
  3. 能够对线上设置的线程进行降级处理。

3. 线程和线程池优化

聊聊Android线程优化这件事

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 代码规划图

聊聊Android线程优化这件事

3.2.3 代码流程图

聊聊Android线程优化这件事

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

3.2.4 代码施行

  1. 创立NewThreadTrackerPlugin,在插件里首要是获取到需求进行署理的线程池白名单以及注册ThreadTrackerTransform。
class NewThreadTrackerPlugin implements Plugin<Project> {
    @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<String> 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("<init>")) {
                PluginUtils.writeClassNameToFile("创立ThreadPoolExecutor的类:" + className);
            } 
        }
        //假如装备中是需求插桩署理线程池,则把原本的类 "java/util/concurrent/ThreadPoolExecutor"换成了咱们自界说的类"com/lalamove/threadtracker/proxy/BaseProxyThreadPoolExecutor"
        //mClassProxy只是一个总开关,是否敞开署理;详细某个类是否需求署理,在创立线程池的详细当地会依据类名来判断
        if (mClassProxy) {
        if (owner.equals(O_ThreadPoolExecutor) && name.equalsIgnoreCase("<init>")) {
                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<Runnable>?,
        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<Runnable> {
        val list = if (mProxy) mutableListOf<Runnable>() else super.shutdownNow()
        return list
    }
}

3.2.5 施行署理

  1. 在工程最外层创立thread_tracker.gradle,里边能够设置需求署理的线程池白名单。

聊聊Android线程优化这件事

  1. 经过打印日志就能看出白名单里边的线程池是否被署理成功。

聊聊Android线程优化这件事

  1. 设置降级开关

(1)设置每个SDK里边细分类名对应的code

聊聊Android线程优化这件事

(2)在装备体系上设置需求关闭SDK,设置上面对应的code码即可。

聊聊Android线程优化这件事

(3)在APP初始化的时分尽或许早的获取装备体系上的code字符串

聊聊Android线程优化这件事

(4)在进行署理的时分,会匹配code字符串,来决定详细的线程池是否进行署理。

聊聊Android线程优化这件事

3.2.6 署理后的收益

  • 累计削减了大约40条线程的开支

聊聊Android线程优化这件事

聊聊Android线程优化这件事

4. 线程栈裁剪

4.1 裁剪办法

创立线程的时分,线程默许的栈空间巨细为 1M 左右,经过测验大部分状况下线程内履行的逻辑并不需求这么大的空间,因而线程栈空间减小,能够对内存这块有显着的优化。

聊聊Android线程优化这件事
接下来咱们来看下函数FixStackSize源码,是怎么设置线程栈默许为1M的?

static size_t FixStackSize(size_t stack_size) {
    //参数是java层中thread 的stack_size默许0
    if (stack_size == 0) {
      stack_size = Runtime::Current()->GetDefaultStackSize();
    }
    // 默许栈巨细是 1M
    stack_size += 1 * MB;
    //...
    if (Runtime::Current()->ExplicitStackOverflowChecks()) {
      stack_size += GetStackOverflowReservedBytes(kRuntimeISA);
    } else {
      8k+8K
      stack_size += Thread::kStackOverflowImplicitCheckSize +
          GetStackOverflowReservedBytes(kRuntimeISA);
    }
    //...
    return stack_size;
  }

发现函数的源码完成便是经过 stack_size += 1 * MB 来设置 stack_size 的: 假如咱们传入的 stack_size 为 0 时,默许巨细便是 1 M ; 假如咱们传入的 stack_size 为 -512KB 时,stack_size 就会变成 512KB(1M – 512KB)。 那咱们是不是只用带有 stack_size 入参的构造函数去创立线程,而且设置 stack_size 为 -512KB 就行了呢? 运用中创立线程的当地太多很难一一修正,前面咱们现已将运用中的线程部分收敛到自界说的线程池中去了,所以只需求修正自界说线程池中创立的线程办法即可。在咱们自界说的 ThreadFactory 中,创立 stack_size 为 – 512 KB 的线程,这么一个简略的操作就能削减线程所占用的虚拟内存。

package com.lalamove.threadtracker.proxy
import java.util.concurrent.ThreadFactory
import java.util.concurrent.atomic.AtomicInteger
open class ProxyThreadFactory : ThreadFactory {
    override fun newThread(runnable: Runnable): Thread {
        val mAtomicInteger = AtomicInteger(1)
        return Thread(null, runnable, "Thread-" + mAtomicInteger.getAndIncrement(), -512 * 1024)
    }
}

需求留意是线程栈巨细的设置需求依据详细的运用场景来进行调整。 假如线程栈巨细设置得过小,或许会导致栈溢出等问题; 假如设置得过大,或许会糟蹋过多的内存资源。 因而,在进行线程栈巨细设置时,我这边会设置一个动态的裁剪值,即便有线上问题,咱们也能够进行适当的调整,以确保程序的正常运转。

4.2 裁剪后的收益

  • 经过火山引擎的APP功能分析平台比照发现,内存均匀值削减了20M

聊聊Android线程优化这件事

  • 经过Profiler实测,发现和火山引擎检测成果相近
办法 Total(单位:M) Java(单位:M) Native(单位:M) Graphics(单位:M) Stack(单位:M) Code(单位:M) Others(单位:M)
关闭署理 492.4 61.1 181.6 57.9 0.2 144.7 46.9
敞开署理 464.3 58.2 158.6 64.5 0.1 139 43.8

聊聊Android线程优化这件事

聊聊Android线程优化这件事

四、收益和踩坑

1. 收益

  • 优化之前,线程数为197条;优化之后,线程数为152条;线程数削减了大约40条
  • 优化之前,内存运用了470.93M;优化之后,内存运用了450.24M;内存削减了大约20M
  • 优化之前,体系CPU运用率为34.83%;优化之后,体系CPU运用率为31.51%;体系CPU运用率下降了3%

聊聊Android线程优化这件事

  • APP运用的流畅性:优化之前,每秒改写23.36帧;优化之后,每秒改写36.3帧;帧率均匀每秒添加了13帧。

聊聊Android线程优化这件事

综上所述:经过插桩署理线程池进行收敛,能有用削减线程数(削减了40条),然后削减内存的运用(削减了20M)、下降CPU运用率(下降了3%)、使得APP运用的流畅性更高(每秒均匀多改写13帧),符合优化预期。

2. 踩坑

  • 网络使命线程和本地使命线程要分隔,防止网络不好的时分网络使命堵塞了本地使命
  • 要相互依靠的线程池需求分隔署理或许某些不署理,防止出现由于使命排队和相互依靠导致类似“死锁”现象
  • 中心线程数等于1的不要署理,由于不仅优化效果有限,还或许把占用1个线程变成占用多个线程,然后导致部分使命会常驻,占用中心线程