)
55 | // return c.compareTo(other)
56 | return c.compareTo(other.getReal())
57 | }
58 | return 0
59 | }
60 |
61 | fun getReal(): Any {
62 | return any
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/threadtracker/src/main/java/com/codoon/threadtracker/proxy/ProxyAsyncTaskExecutor.java:
--------------------------------------------------------------------------------
1 | package com.codoon.threadtracker.proxy;
2 |
3 | import com.codoon.threadtracker.ThreadInfoManager;
4 | import com.codoon.threadtracker.TrackerUtils;
5 | import com.codoon.threadtracker.bean.ThreadPoolInfo;
6 |
7 | import java.lang.reflect.InvocationHandler;
8 | import java.lang.reflect.Method;
9 | import java.util.concurrent.Executor;
10 |
11 | /**
12 | * 动态代理AsyncTask中THREAD_POOL_EXECUTOR、SERIAL_EXECUTOR
13 | *
14 | * AsyncTask中sDefaultExecutor并不是真正的线程池,
15 | * 任务是由sDefaultExecutor通过队列提交给THREAD_POOL_EXECUTOR
16 | * 所以poolName应该是THREAD_POOL_EXECUTOR的,但stack应该是sDefaultExecutor的
17 | * 于是sDefaultExecutor在execute时包装成poolRunnable转入stack,但poolName为空
18 | * 修改THREAD_POOL_EXECUTOR的threadFactory,使线程和poolName建立关系
19 | * 最后PoolRunnableAndOther把空的poolName更新成THREAD_POOL_EXECUTOR的poolName
20 | *
21 | * 使用java避免kotlin调用method.invoke args传null引起的问题
22 | */
23 | public class ProxyAsyncTaskExecutor implements InvocationHandler {
24 | final static int TYPE_THREAD_POOL_EXECUTOR = 0;
25 | final static int TYPE_SERIAL_EXECUTOR = 1;
26 |
27 | private Executor executor;
28 | private String poolName;
29 | private int type;
30 |
31 | ProxyAsyncTaskExecutor(Executor executor, int type) {
32 | this.executor = executor;
33 | this.type = type;
34 | poolName = TrackerUtils.toObjectString(executor);
35 |
36 | String createStack = TrackerUtils.getStackString(false);
37 | ThreadPoolInfo poolInfo = new ThreadPoolInfo();
38 | poolInfo.setPoolName(poolName);
39 | poolInfo.setCreateStack(createStack);
40 | poolInfo.setCreateThreadId(Thread.currentThread().getId());
41 | ThreadInfoManager.getINSTANCE().putThreadPoolInfo(poolName, poolInfo);
42 | }
43 |
44 | @Override
45 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
46 | // Log.d(LOG_TAG, "ProxyAsyncTaskExecutor type: " + type + " method:" + method.getName());
47 |
48 | if (method.getName().equals("execute") && args != null && args.length == 1 && args[0] instanceof Runnable) {
49 | String callStack = TrackerUtils.getStackString(true);
50 | if (type == TYPE_THREAD_POOL_EXECUTOR) {
51 | // 处理外部直接使用AsyncTask.THREAD_POOL_EXECUTOR.execute的情况,
52 | // 因AsyncTask中sDefaultExecutor把PoolRunnableAndOther又包装成Runnable提交到THREAD_POOL_EXECUTOR
53 | // 所以这里根据调用栈区分,如果是外部直接execute则包装成PoolRunnableAndOther,否则什么都不做
54 | // Log.d(LOG_TAG,"ProxyAsyncTaskExecutor callStack: "+ callStack);
55 | // android.os.AsyncTask$SerialExecutor.scheduleNext(AsyncTask.java:258)
56 | // android.os.AsyncTask$SerialExecutor.execute(AsyncTask.java:252)
57 | if (!callStack.contains("AsyncTask$SerialExecutor.scheduleNext")) {
58 | Runnable runnable = new PoolRunnableAndOther(args[0], callStack, poolName);
59 | args[0] = runnable;
60 | }
61 | } else if (type == TYPE_SERIAL_EXECUTOR) {
62 | // 需要callStack,不需要poolName,因为SERIAL_EXECUTOR并不是一个真正的线程池
63 | if (!(args[0] instanceof PoolRunnableAndOther)) {
64 | Runnable runnable = new PoolRunnableAndOther(args[0], callStack, null);
65 | args[0] = runnable;
66 | }
67 | }
68 | }
69 | return method.invoke(executor, args);
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/threadtracker/src/main/java/com/codoon/threadtracker/proxy/ProxyExecutorService.java:
--------------------------------------------------------------------------------
1 | package com.codoon.threadtracker.proxy;
2 |
3 | import com.codoon.threadtracker.ThreadInfoManager;
4 | import com.codoon.threadtracker.TrackerUtils;
5 | import com.codoon.threadtracker.bean.ThreadPoolInfo;
6 |
7 | import java.lang.reflect.InvocationHandler;
8 | import java.lang.reflect.Method;
9 | import java.util.ArrayList;
10 | import java.util.Collection;
11 | import java.util.Iterator;
12 | import java.util.concurrent.Callable;
13 | import java.util.concurrent.ExecutorService;
14 |
15 | /**
16 | * 对ExecutorService、ScheduledExecutorService接口进行代理
17 | * 不使用kotlin是因为以下方法第二个变长参数,如果java传null方法接收到的也是null,
18 | * 但如果kotlin传null,方法接收后参数会变成["null"],即有一个字符串的数组,对于不需要参数的方法来说就会报错
19 | * {@link java.lang.reflect.Method#invoke(java.lang.Object, java.lang.Object...)}
20 | */
21 | public class ProxyExecutorService implements InvocationHandler {
22 | private ExecutorService executor;
23 | private String poolName = null;
24 |
25 | ProxyExecutorService(ExecutorService executor) {
26 | this.executor = executor;
27 | poolName = TrackerUtils.toObjectString(executor);
28 | String createStack = TrackerUtils.getStackString(false);
29 | ThreadPoolInfo poolInfo = new ThreadPoolInfo();
30 | poolInfo.setPoolName(poolName);
31 | poolInfo.setCreateStack(createStack);
32 | poolInfo.setCreateThreadId(Thread.currentThread().getId());
33 | ThreadInfoManager.getINSTANCE().putThreadPoolInfo(poolName, poolInfo);
34 | }
35 |
36 | @Override
37 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
38 |
39 | // 因方法数众多,并且被代理的各类中方法也不一致
40 | // 所以被调用方法中只要含有Runnable、Callable类型的参数,都替换成PoolRunnableAndOther代理
41 | if (args != null) {
42 | String callStack = TrackerUtils.getStackString(true);
43 | for (int i = 0; i < args.length; i++) {
44 | Object arg = args[i];
45 | if ((arg instanceof Runnable || arg instanceof Callable) && !(arg instanceof PoolRunnableAndOther)) {
46 | PoolRunnableAndOther any = new PoolRunnableAndOther(arg, callStack, poolName);
47 | args[i] = any;
48 | } else if (arg instanceof Collection && !((Collection) arg).isEmpty()) {
49 | // invokeAny invokeAll 等情况
50 | Iterator iter = ((Collection) arg).iterator();
51 | ArrayList taskList = new ArrayList<>();
52 | boolean allOk = iter.hasNext();
53 | while (iter.hasNext()) {
54 | Object it = iter.next();
55 | if (it instanceof Runnable || it instanceof Callable) {
56 | if (it instanceof PoolRunnableAndOther) {
57 | taskList.add((PoolRunnableAndOther) it);
58 | } else {
59 | taskList.add(new PoolRunnableAndOther(it, callStack, poolName));
60 | }
61 | } else {
62 | allOk = false;
63 | break;
64 | }
65 | }
66 | if (allOk) {
67 | args[i] = taskList;
68 | }
69 | }
70 | }
71 | }
72 |
73 | if (method.getName().equals("shutdown") || method.getName().equals("shutdownNow")) {
74 | ThreadInfoManager.getINSTANCE().shutDownPool(poolName);
75 | }
76 | return method.invoke(executor, args);
77 | }
78 | }
--------------------------------------------------------------------------------
/threadtracker/src/main/java/com/codoon/threadtracker/proxy/ProxyExecutors.kt:
--------------------------------------------------------------------------------
1 | package com.codoon.threadtracker.proxy
2 |
3 | import android.os.Build
4 | import androidx.annotation.RequiresApi
5 | import com.codoon.threadtracker.TrackerUtils.toObjectString
6 | import java.lang.reflect.Proxy
7 | import java.util.concurrent.*
8 |
9 | /**
10 | * 针对Executors.xxx创建线程池的方式进行字节码替换,替换为ProxyExecutors.xxx
11 | * 注意要加 @JvmStatic 以便字节码替换成ProxyExecutors后调用正常(否则java层需要ProxyExecutors.INSTANCE.newXXX 的调用方式)
12 | */
13 | object ProxyExecutors {
14 |
15 | @JvmStatic
16 | fun newFixedThreadPool(nThreads: Int): ExecutorService {
17 | // 这里使用TBaseThreadPoolExecutor主要是为了避免上层代码把ExecutorService转型成ThreadPoolExecutor的问题,如果使用proxy方法动态代理,上层这么做会crash
18 | // 本类中其他不使用动态代理的原因也类似
19 | return TBaseThreadPoolExecutor(
20 | nThreads, nThreads,
21 | 0L, TimeUnit.MILLISECONDS,
22 | LinkedBlockingQueue()
23 | )
24 | // return proxy(Executors.newFixedThreadPool(nThreads))
25 | }
26 |
27 | @RequiresApi(api = Build.VERSION_CODES.N)
28 | @JvmStatic
29 | fun newWorkStealingPool(parallelism: Int): ExecutorService {
30 | // todo ForkJoinPool 暂时没有TBaseForkJoinPool
31 | return proxy(Executors.newWorkStealingPool(parallelism))
32 | }
33 |
34 | @RequiresApi(api = Build.VERSION_CODES.N)
35 | @JvmStatic
36 | fun newWorkStealingPool(): ExecutorService {
37 | return proxy(Executors.newWorkStealingPool())
38 | }
39 |
40 | @JvmStatic
41 | fun newFixedThreadPool(nThreads: Int, threadFactory: ThreadFactory?): ExecutorService {
42 | return TBaseThreadPoolExecutor(
43 | nThreads, nThreads,
44 | 0L, TimeUnit.MILLISECONDS,
45 | LinkedBlockingQueue(),
46 | threadFactory
47 | )
48 | }
49 |
50 | @JvmStatic
51 | fun newSingleThreadExecutor(): ExecutorService {
52 | return proxy(Executors.newSingleThreadExecutor())
53 | }
54 |
55 | @JvmStatic
56 | fun newSingleThreadExecutor(threadFactory: ThreadFactory?): ExecutorService {
57 | return proxy(Executors.newSingleThreadExecutor(threadFactory))
58 | }
59 |
60 | @JvmStatic
61 | fun newCachedThreadPool(): ExecutorService {
62 | return TBaseThreadPoolExecutor(
63 | 0, Int.MAX_VALUE,
64 | 60L, TimeUnit.SECONDS,
65 | SynchronousQueue()
66 | )
67 | }
68 |
69 | @JvmStatic
70 | fun newCachedThreadPool(threadFactory: ThreadFactory?): ExecutorService {
71 | return TBaseThreadPoolExecutor(
72 | 0, Int.MAX_VALUE,
73 | 60L, TimeUnit.SECONDS,
74 | SynchronousQueue(),
75 | threadFactory
76 | )
77 | }
78 |
79 | @JvmStatic
80 | fun newSingleThreadScheduledExecutor(): ScheduledExecutorService {
81 | return proxy(Executors.newSingleThreadScheduledExecutor())
82 | }
83 |
84 | @JvmStatic
85 | fun newSingleThreadScheduledExecutor(threadFactory: ThreadFactory?): ScheduledExecutorService {
86 | return proxy(Executors.newSingleThreadScheduledExecutor(threadFactory))
87 | }
88 |
89 | @JvmStatic
90 | fun newScheduledThreadPool(corePoolSize: Int): ScheduledExecutorService {
91 | return TBaseScheduledThreadPoolExecutor(corePoolSize)
92 | }
93 |
94 | @JvmStatic
95 | fun newScheduledThreadPool(
96 | corePoolSize: Int,
97 | threadFactory: ThreadFactory?
98 | ): ScheduledExecutorService {
99 | return TBaseScheduledThreadPoolExecutor(corePoolSize, threadFactory)
100 | }
101 |
102 | @JvmStatic
103 | fun unconfigurableExecutorService(executor: ExecutorService): ExecutorService {
104 | return proxy(Executors.unconfigurableExecutorService(executor))
105 | }
106 |
107 | @JvmStatic
108 | fun unconfigurableScheduledExecutorService(executor: ScheduledExecutorService): ScheduledExecutorService {
109 | return proxy(Executors.unconfigurableScheduledExecutorService(executor))
110 | }
111 |
112 | private fun proxy(executorService: ExecutorService): ExecutorService {
113 | if (executorService is ThreadPoolExecutor) {
114 | // 这里和TBaseThreadPoolExecutor一样,设置ThreadFactory为了尽早获取线程信息和线程池建立联系,而不用等到run时
115 | executorService.threadFactory = TBaseThreadFactory(
116 | executorService.threadFactory,
117 | toObjectString(executorService)
118 | )
119 | }
120 | val handler = ProxyExecutorService(executorService)
121 | return Proxy.newProxyInstance(
122 | executorService.javaClass.classLoader,
123 | AbstractExecutorService::class.java.interfaces,
124 | handler
125 | ) as ExecutorService
126 | }
127 |
128 | private fun proxy(executorService: ScheduledExecutorService): ScheduledExecutorService {
129 | if (executorService is ScheduledThreadPoolExecutor) {
130 | executorService.threadFactory = TBaseThreadFactory(
131 | executorService.threadFactory,
132 | toObjectString(executorService)
133 | )
134 | }
135 | val handler = ProxyExecutorService(executorService)
136 | return Proxy.newProxyInstance(
137 | executorService.javaClass.classLoader,
138 | ScheduledThreadPoolExecutor::class.java.interfaces,
139 | handler
140 | ) as ScheduledExecutorService
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/threadtracker/src/main/java/com/codoon/threadtracker/proxy/ProxyThreadPoolExecutor.kt:
--------------------------------------------------------------------------------
1 | package com.codoon.threadtracker.proxy
2 |
3 | import com.codoon.threadtracker.ThreadInfoManager.Companion.INSTANCE
4 | import com.codoon.threadtracker.TrackerUtils.getStackString
5 | import com.codoon.threadtracker.TrackerUtils.toObjectString
6 | import com.codoon.threadtracker.bean.ThreadPoolInfo
7 | import java.util.concurrent.*
8 |
9 | /**
10 | * 目前只是为了代理AsyncTask.THREAD_POOL_EXECUTOR
11 | * 不用动态代理主要是为了防止上层将AsyncTask.THREAD_POOL_EXECUTOR强转为ThreadPoolExecutor时崩溃
12 | * 除execute方法外其他比如remove、submit等暂未处理,因AsyncTask.THREAD_POOL_EXECUTOR只是Executor
13 | * !!!这个类只是防止crash,并未承担其他ThreadPoolExecutor的责任
14 | */
15 | open class ProxyThreadPoolExecutor(private val real: ThreadPoolExecutor) :
16 | ThreadPoolExecutor(1, 1, 1, TimeUnit.SECONDS, LinkedBlockingDeque()) {
17 |
18 | private val poolName = toObjectString(real)
19 |
20 | init {
21 | val createStack = getStackString(false)
22 | val poolInfo = ThreadPoolInfo()
23 | poolInfo.poolName = poolName
24 | poolInfo.createStack = createStack
25 | poolInfo.createThreadId = Thread.currentThread().id
26 | INSTANCE.putThreadPoolInfo(poolName, poolInfo)
27 | }
28 |
29 | override fun execute(command: Runnable) {
30 | val callStack = getStackString(true)
31 |
32 | // 处理外部直接使用AsyncTask.THREAD_POOL_EXECUTOR.execute的情况,
33 | // 因AsyncTask中sDefaultExecutor把PoolRunnableAndOther又包装成Runnable提交到THREAD_POOL_EXECUTOR
34 | // 所以这里根据调用栈区分,如果是外部直接execute则包装成PoolRunnableAndOther,否则什么都不做
35 | // Log.d(LOG_TAG,"ProxyAsyncTaskExecutor callStack: "+ callStack);
36 | // android.os.AsyncTask$SerialExecutor.scheduleNext(AsyncTask.java:258)
37 | // android.os.AsyncTask$SerialExecutor.execute(AsyncTask.java:252)
38 | val runnable: Runnable
39 | if (!callStack.contains("AsyncTask\$SerialExecutor.scheduleNext")) {
40 | runnable = PoolRunnableAndOther(command, callStack, poolName)
41 | } else {
42 | runnable = command
43 | }
44 | real.execute(runnable)
45 | }
46 |
47 |
48 | override fun submit(task: Runnable): Future<*> {
49 | return real.submit(task)
50 | }
51 |
52 | override fun submit(task: Runnable, result: T): Future {
53 | return real.submit(task, result)
54 | }
55 |
56 | override fun submit(task: Callable): Future {
57 | return real.submit(task)
58 | }
59 |
60 | override fun getCorePoolSize(): Int {
61 | return real.corePoolSize
62 | }
63 |
64 | override fun invokeAny(tasks: MutableCollection>): T {
65 | return real.invokeAny(tasks)
66 | }
67 |
68 | override fun invokeAny(
69 | tasks: MutableCollection>,
70 | timeout: Long,
71 | unit: TimeUnit
72 | ): T {
73 | return real.invokeAny(tasks, timeout, unit)
74 | }
75 |
76 | override fun prestartAllCoreThreads(): Int {
77 | return real.prestartAllCoreThreads()
78 | }
79 |
80 | override fun getCompletedTaskCount(): Long {
81 | return real.completedTaskCount
82 | }
83 |
84 | override fun getQueue(): BlockingQueue {
85 | return real.queue
86 | }
87 |
88 | override fun getPoolSize(): Int {
89 | return real.poolSize
90 | }
91 |
92 | override fun getRejectedExecutionHandler(): RejectedExecutionHandler {
93 | return real.rejectedExecutionHandler
94 | }
95 |
96 | override fun getTaskCount(): Long {
97 | return real.taskCount
98 | }
99 |
100 | override fun allowCoreThreadTimeOut(value: Boolean) {
101 | real.allowCoreThreadTimeOut(value)
102 | }
103 |
104 | override fun awaitTermination(timeout: Long, unit: TimeUnit): Boolean {
105 | return real.awaitTermination(timeout, unit)
106 | }
107 |
108 | override fun getThreadFactory(): ThreadFactory {
109 | return real.threadFactory
110 | }
111 |
112 | override fun setRejectedExecutionHandler(handler: RejectedExecutionHandler?) {
113 | real.rejectedExecutionHandler = handler
114 | }
115 |
116 | override fun getLargestPoolSize(): Int {
117 | return real.largestPoolSize
118 | }
119 |
120 | override fun setThreadFactory(threadFactory: ThreadFactory?) {
121 | real.threadFactory = threadFactory
122 | }
123 |
124 | override fun setCorePoolSize(corePoolSize: Int) {
125 | real.corePoolSize = corePoolSize
126 | }
127 |
128 | override fun toString(): String {
129 | return real.toString()
130 | }
131 |
132 | override fun remove(task: Runnable?): Boolean {
133 | return real.remove(task)
134 | }
135 |
136 | override fun isTerminated(): Boolean {
137 | return real.isTerminated
138 | }
139 |
140 | override fun getKeepAliveTime(unit: TimeUnit?): Long {
141 | return real.getKeepAliveTime(unit)
142 | }
143 |
144 | override fun setKeepAliveTime(time: Long, unit: TimeUnit?) {
145 | real.setKeepAliveTime(time, unit)
146 | }
147 |
148 | override fun prestartCoreThread(): Boolean {
149 | return real.prestartCoreThread()
150 | }
151 |
152 | override fun purge() {
153 | real.purge()
154 | }
155 |
156 | override fun shutdown() {
157 | real.shutdown()
158 | }
159 |
160 | override fun shutdownNow(): MutableList {
161 | return real.shutdownNow()
162 | }
163 |
164 | override fun isShutdown(): Boolean {
165 | return real.isShutdown
166 | }
167 |
168 | override fun setMaximumPoolSize(maximumPoolSize: Int) {
169 | real.maximumPoolSize = maximumPoolSize
170 | }
171 |
172 | override fun getMaximumPoolSize(): Int {
173 | return real.maximumPoolSize
174 | }
175 |
176 | override fun invokeAll(tasks: MutableCollection>): MutableList> {
177 | return real.invokeAll(tasks)
178 | }
179 |
180 | override fun invokeAll(
181 | tasks: MutableCollection>,
182 | timeout: Long,
183 | unit: TimeUnit
184 | ): MutableList> {
185 | return real.invokeAll(tasks, timeout, unit)
186 | }
187 |
188 | override fun getActiveCount(): Int {
189 | return real.activeCount
190 | }
191 |
192 | override fun isTerminating(): Boolean {
193 | return real.isTerminating
194 | }
195 |
196 | override fun allowsCoreThreadTimeOut(): Boolean {
197 | return real.allowsCoreThreadTimeOut()
198 | }
199 | }
--------------------------------------------------------------------------------
/threadtracker/src/main/java/com/codoon/threadtracker/proxy/TBaseHandlerThread.kt:
--------------------------------------------------------------------------------
1 | package com.codoon.threadtracker.proxy
2 |
3 | import android.os.HandlerThread
4 | import android.os.SystemClock
5 | import com.codoon.threadtracker.ThreadInfoManager
6 | import com.codoon.threadtracker.TrackerUtils
7 | import com.codoon.threadtracker.bean.ThreadInfo
8 |
9 | /**
10 | * 因HandlerThread继承自Thread,所以复制ProxyThread方法
11 | */
12 | open class TBaseHandlerThread : HandlerThread {
13 | constructor(name: String) : super(name)
14 |
15 | constructor(name: String, priority: Int) : super(name, priority)
16 |
17 | @Synchronized
18 | override fun start() {
19 | val callStack = TrackerUtils.getStackString()
20 | super.start()
21 |
22 | // 有则更新没有则新增
23 | val info = ThreadInfoManager.INSTANCE.getThreadInfoById(id)
24 | info?.also {
25 | it.id = id
26 | it.name = name
27 | it.state = state
28 | if (it.callStack.isEmpty()) { // 如果来自线程池,callStack意义为任务添加栈,可能已经有值了,不能更新为start调用栈
29 | it.callStack = callStack
30 | it.callThreadId = currentThread().id
31 | }
32 | } ?: apply {
33 | val newInfo = ThreadInfo()
34 | newInfo.id = id
35 | newInfo.name = name
36 | newInfo.callStack = callStack
37 | newInfo.callThreadId = currentThread().id
38 | newInfo.state = state
39 | newInfo.startTime = SystemClock.elapsedRealtime()
40 | ThreadInfoManager.INSTANCE.putThreadInfo(id, newInfo)
41 | }
42 | }
43 |
44 | override fun run() {
45 | super.run()
46 | ThreadInfoManager.INSTANCE.removeThreadInfo(id)
47 | }
48 | }
--------------------------------------------------------------------------------
/threadtracker/src/main/java/com/codoon/threadtracker/proxy/TBaseScheduledThreadPoolExecutor.kt:
--------------------------------------------------------------------------------
1 | package com.codoon.threadtracker.proxy
2 |
3 | import com.codoon.threadtracker.ThreadInfoManager
4 | import com.codoon.threadtracker.TrackerUtils
5 | import com.codoon.threadtracker.bean.ThreadPoolInfo
6 | import java.lang.ref.WeakReference
7 | import java.util.concurrent.*
8 |
9 | /**
10 | * ScheduledThreadPoolExecutor代理类
11 | * 参见TBaseThreadPoolExecutor注释
12 | *
13 | * 因ScheduledThreadPoolExecutor extends ThreadPoolExecutor
14 | * 但此继承关系在sdk中,asm无法改变,于是ThreadPoolExecutor中方法复制一份过来
15 | */
16 | open class TBaseScheduledThreadPoolExecutor : ScheduledThreadPoolExecutor {
17 | private val poolName = TrackerUtils.toObjectString(this)
18 | private val weakRunnableList = mutableListOf>()
19 |
20 | constructor(corePoolSize: Int) : super(corePoolSize)
21 | constructor(corePoolSize: Int, threadFactory: ThreadFactory?) : super(
22 | corePoolSize,
23 | threadFactory
24 | ) {
25 | init()
26 | }
27 |
28 | constructor(corePoolSize: Int, handler: RejectedExecutionHandler?) : super(
29 | corePoolSize,
30 | handler
31 | ) {
32 | init()
33 | }
34 |
35 | constructor(
36 | corePoolSize: Int,
37 | threadFactory: ThreadFactory?,
38 | handler: RejectedExecutionHandler?
39 | ) : super(corePoolSize, threadFactory, handler) {
40 | init()
41 | }
42 |
43 | init {
44 | val createStack = TrackerUtils.getStackString()
45 | val poolInfo = ThreadPoolInfo()
46 | poolInfo.poolName = poolName
47 | poolInfo.createStack = createStack
48 | poolInfo.createThreadId = Thread.currentThread().id
49 | ThreadInfoManager.INSTANCE.putThreadPoolInfo(poolName, poolInfo)
50 | }
51 |
52 | private fun init() {
53 | threadFactory = TBaseThreadFactory(threadFactory, poolName)
54 | }
55 |
56 | override fun schedule(
57 | callable: Callable,
58 | delay: Long,
59 | unit: TimeUnit
60 | ): ScheduledFuture {
61 |
62 | var task = callable
63 | if (task !is PoolRunnableAndOther) {
64 | val callStack = TrackerUtils.getStackString()
65 | task = PoolRunnableAndOther(task, callStack, poolName) as Callable
66 | }
67 | return super.schedule(task, delay, unit)
68 | }
69 |
70 | override fun schedule(command: Runnable, delay: Long, unit: TimeUnit): ScheduledFuture<*> {
71 | var runnable = command
72 | if (runnable !is PoolRunnableAndOther) {
73 | val callStack = TrackerUtils.getStackString()
74 | runnable = PoolRunnableAndOther(command, callStack, poolName)
75 | }
76 | return super.schedule(runnable, delay, unit)
77 | }
78 |
79 | override fun scheduleAtFixedRate(
80 | command: Runnable,
81 | initialDelay: Long,
82 | period: Long,
83 | unit: TimeUnit
84 | ): ScheduledFuture<*> {
85 | var runnable = command
86 | if (runnable !is PoolRunnableAndOther) {
87 | val callStack = TrackerUtils.getStackString()
88 | runnable = PoolRunnableAndOther(command, callStack, poolName)
89 | }
90 | return super.scheduleAtFixedRate(runnable, initialDelay, period, unit)
91 | }
92 |
93 | override fun scheduleWithFixedDelay(
94 | command: Runnable,
95 | initialDelay: Long,
96 | delay: Long,
97 | unit: TimeUnit
98 | ): ScheduledFuture<*> {
99 | var runnable = command
100 | if (runnable !is PoolRunnableAndOther) {
101 | val callStack = TrackerUtils.getStackString()
102 | runnable = PoolRunnableAndOther(command, callStack, poolName)
103 | }
104 | return super.scheduleWithFixedDelay(runnable, initialDelay, delay, unit)
105 | }
106 |
107 | override fun submit(task: Runnable): Future<*> {
108 | var runnable = task
109 | if (task !is PoolRunnableAndOther) {
110 | val callStack = TrackerUtils.getStackString()
111 | runnable = PoolRunnableAndOther(task, callStack, poolName)
112 | }
113 | return super.submit(runnable)
114 | }
115 |
116 | override fun submit(task: Callable): Future {
117 | var callable = task
118 | if (task !is PoolRunnableAndOther) {
119 | val callStack = TrackerUtils.getStackString()
120 | callable = PoolRunnableAndOther(task, callStack, poolName) as Callable
121 | }
122 | return super.submit(callable)
123 | }
124 |
125 | override fun submit(task: Runnable, result: T): Future {
126 | var runnable = task
127 | if (task !is PoolRunnableAndOther) {
128 | val callStack = TrackerUtils.getStackString()
129 | runnable = PoolRunnableAndOther(task, callStack, poolName)
130 | }
131 | return super.submit(runnable, result)
132 | }
133 |
134 | private fun proxyInvokeList(tasks: MutableCollection>): MutableList> {
135 | val callStack = TrackerUtils.getStackString()
136 | val proxyTasks = mutableListOf>()
137 | tasks.forEach { task ->
138 | if (task !is PoolRunnableAndOther) {
139 | val callable = PoolRunnableAndOther(task, callStack, poolName)
140 | proxyTasks.add(callable as Callable)
141 | } else {
142 | proxyTasks.add(task)
143 | }
144 | Unit
145 | }
146 | return proxyTasks
147 | }
148 |
149 | override fun invokeAny(tasks: MutableCollection>): T {
150 | return super.invokeAny(proxyInvokeList(tasks))
151 | }
152 |
153 | override fun invokeAny(
154 | tasks: MutableCollection>,
155 | timeout: Long,
156 | unit: TimeUnit
157 | ): T {
158 | return super.invokeAny(proxyInvokeList(tasks), timeout, unit)
159 | }
160 |
161 | override fun invokeAll(tasks: MutableCollection>): MutableList> {
162 | return super.invokeAll(proxyInvokeList(tasks))
163 | }
164 |
165 | override fun invokeAll(
166 | tasks: MutableCollection>,
167 | timeout: Long,
168 | unit: TimeUnit
169 | ): MutableList> {
170 | return super.invokeAll(proxyInvokeList(tasks), timeout, unit)
171 | }
172 |
173 | override fun execute(command: Runnable) {
174 | val callStack = TrackerUtils.getStackString()
175 | var runnable = command
176 | if (command !is PoolRunnableAndOther) { // 可能先进入队列后进入这里,此时已经是PoolRunnable
177 | runnable = PoolRunnableAndOther(command, callStack, poolName)
178 | weakRunnableList.add(WeakReference(runnable))
179 | }
180 | super.execute(runnable)
181 | }
182 |
183 | override fun setThreadFactory(threadFactory: ThreadFactory?) {
184 | threadFactory?.apply {
185 | super.setThreadFactory(TBaseThreadFactory(this, poolName))
186 | } ?: super.setThreadFactory(threadFactory)
187 | }
188 |
189 | override fun getThreadFactory(): ThreadFactory {
190 | if (super.getThreadFactory() is TBaseThreadFactory) {
191 | return (super.getThreadFactory() as TBaseThreadFactory).getReal()
192 | } else {
193 | return super.getThreadFactory()
194 | }
195 | }
196 |
197 | override fun remove(task: Runnable?): Boolean {
198 |
199 | for (i in 0 until weakRunnableList.size) {
200 | val runnable = weakRunnableList[i].get()
201 | if (runnable is PoolRunnableAndOther && runnable.getReal() == task) {
202 | weakRunnableList.removeAt(i)
203 | return super.remove(runnable)
204 | }
205 | }
206 | return super.remove(task)
207 | }
208 |
209 | override fun shutdown() {
210 | weakRunnableList.clear()
211 | super.shutdown()
212 | ThreadInfoManager.INSTANCE.shutDownPool(poolName)
213 | }
214 |
215 | override fun shutdownNow(): MutableList {
216 | val list = super.shutdownNow()
217 | for (i in 0 until list.size) {
218 | if (list[i] is PoolRunnableAndOther) {
219 | val real = (list[i] as PoolRunnableAndOther).getReal()
220 | if (real is Runnable) {
221 | list[i] = real
222 | }
223 | }
224 | }
225 | weakRunnableList.clear()
226 | ThreadInfoManager.INSTANCE.shutDownPool(poolName)
227 | return list
228 | }
229 | }
--------------------------------------------------------------------------------
/threadtracker/src/main/java/com/codoon/threadtracker/proxy/TBaseThread.kt:
--------------------------------------------------------------------------------
1 | package com.codoon.threadtracker.proxy
2 |
3 | import android.os.SystemClock
4 | import com.codoon.threadtracker.ThreadInfoManager
5 | import com.codoon.threadtracker.TrackerUtils.getStackString
6 | import com.codoon.threadtracker.bean.ThreadInfo
7 |
8 | /**
9 | * 如果有人自定义Thread,理论上一定会调用super,而继承通过字节码改成ProxyThread,因为调用了super所以这里也能调用到
10 | * start方法也可能是来自线程池,所以putThreadInfo时要先查找,以免用空的poolName覆盖原有poolName
11 | * 比如以下调用栈
12 | * com.codoon.threadtracker.proxy.ProxyThread.start(ProxyThread.kt:31)
13 | * java.util.concurrent.ThreadPoolExecutor.addWorker(ThreadPoolExecutor.java:970)
14 | * java.util.concurrent.ThreadPoolExecutor.ensurePrestart(ThreadPoolExecutor.java:1611)
15 | * java.util.concurrent.ScheduledThreadPoolExecutor.delayedExecute(ScheduledThreadPoolExecutor.java:342)
16 | * java.util.concurrent.ScheduledThreadPoolExecutor.scheduleWithFixedDelay(ScheduledThreadPoolExecutor.java:629)
17 | */
18 | open class TBaseThread : Thread {
19 | internal constructor() : super()
20 | internal constructor(runnable: Runnable?) : super(runnable)
21 | internal constructor(group: ThreadGroup?, target: Runnable?) : super(group, target)
22 | internal constructor(group: ThreadGroup?, name: String) : super(group, name)
23 | internal constructor(target: Runnable?, name: String) : super(target, name)
24 | internal constructor(group: ThreadGroup?, target: Runnable?, name: String) : super(
25 | group,
26 | target,
27 | name
28 | )
29 |
30 | internal constructor(
31 | group: ThreadGroup?,
32 | target: Runnable?,
33 | name: String,
34 | stackSize: Long
35 | ) : super(group, target, name, stackSize)
36 |
37 | @Synchronized
38 | override fun start() {
39 | val callStack = getStackString()
40 | super.start()
41 |
42 | // 有则更新没有则新增
43 | val info = ThreadInfoManager.INSTANCE.getThreadInfoById(id)
44 | info?.also {
45 | it.id = id
46 | it.name = name
47 | it.state = state
48 | if (it.callStack.isEmpty()) { // 如果来自线程池,callStack意义为任务添加栈,可能已经有值了,不能更新为start调用栈
49 | it.callStack = callStack
50 | it.callThreadId = currentThread().id
51 | }
52 | } ?: apply {
53 | val newInfo = ThreadInfo()
54 | newInfo.id = id
55 | newInfo.name = name
56 | newInfo.callStack = callStack
57 | newInfo.callThreadId = currentThread().id
58 | newInfo.state = state
59 | newInfo.startTime = SystemClock.elapsedRealtime()
60 | ThreadInfoManager.INSTANCE.putThreadInfo(id, newInfo)
61 | }
62 | }
63 |
64 | override fun run() {
65 | super.run()
66 | ThreadInfoManager.INSTANCE.removeThreadInfo(id)
67 | }
68 | }
--------------------------------------------------------------------------------
/threadtracker/src/main/java/com/codoon/threadtracker/proxy/TBaseThreadFactory.kt:
--------------------------------------------------------------------------------
1 | package com.codoon.threadtracker.proxy
2 |
3 | import com.codoon.threadtracker.ThreadInfoManager
4 | import com.codoon.threadtracker.bean.ThreadInfo
5 | import java.util.concurrent.ThreadFactory
6 |
7 |
8 | open class TBaseThreadFactory(
9 | private val threadFactory: ThreadFactory,
10 | private val poolName: String
11 | ) : ThreadFactory {
12 | override fun newThread(runnable: Runnable): Thread {
13 | // 注意这里面的runnable是被worker包装过的,已经不是用户传来的runnable
14 | val thread = threadFactory.newThread(runnable)
15 | addThreadInfo(thread)
16 | return thread
17 | }
18 |
19 | private fun addThreadInfo(thread: Thread) {
20 | var info = ThreadInfoManager.INSTANCE.getThreadInfoById(thread.id)
21 | info = (info ?: ThreadInfo()).also {
22 | it.id = thread.id
23 | it.name = thread.name
24 | it.state = thread.state
25 | it.poolName = poolName
26 | }
27 | ThreadInfoManager.INSTANCE.putThreadInfo(thread.id, info)
28 | }
29 |
30 | fun getReal(): ThreadFactory {
31 | return threadFactory
32 | }
33 | }
--------------------------------------------------------------------------------
/threadtracker/src/main/java/com/codoon/threadtracker/proxy/TBaseThreadPoolExecutor.kt:
--------------------------------------------------------------------------------
1 | package com.codoon.threadtracker.proxy
2 |
3 | import com.codoon.threadtracker.ThreadInfoManager
4 | import com.codoon.threadtracker.TrackerUtils
5 | import com.codoon.threadtracker.TrackerUtils.toObjectString
6 | import com.codoon.threadtracker.bean.ThreadPoolInfo
7 | import java.lang.ref.WeakReference
8 | import java.util.concurrent.*
9 |
10 | /**
11 | * ThreadPoolExecutor代理类
12 | * 修改后记得同步到TBaseScheduledThreadPoolExecutor
13 | *
14 | * 注意类似的base类要加open关键字!
15 | */
16 | open class TBaseThreadPoolExecutor : ThreadPoolExecutor {
17 | private val poolName = toObjectString(this)
18 |
19 | // 用于remove操作时,根据原始runnable寻找被代理包装后的runnable并移除
20 | private val weakRunnableList = mutableListOf>()
21 |
22 | init {
23 | val createStack = TrackerUtils.getStackString()
24 | val poolInfo = ThreadPoolInfo()
25 | poolInfo.poolName = poolName
26 | poolInfo.createStack = createStack
27 | poolInfo.createThreadId = Thread.currentThread().id
28 | ThreadInfoManager.INSTANCE.putThreadPoolInfo(poolName, poolInfo)
29 | }
30 |
31 | constructor(
32 | corePoolSize: Int,
33 | maximumPoolSize: Int,
34 | keepAliveTime: Long,
35 | unit: TimeUnit?,
36 | workQueue: BlockingQueue?
37 | ) : super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue) {
38 | init()
39 | }
40 |
41 | constructor(
42 | corePoolSize: Int,
43 | maximumPoolSize: Int,
44 | keepAliveTime: Long,
45 | unit: TimeUnit?,
46 | workQueue: BlockingQueue?,
47 | threadFactory: ThreadFactory?
48 | ) : super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory) {
49 | init()
50 | }
51 |
52 | constructor(
53 | corePoolSize: Int,
54 | maximumPoolSize: Int,
55 | keepAliveTime: Long,
56 | unit: TimeUnit?,
57 | workQueue: BlockingQueue?,
58 | handler: RejectedExecutionHandler?
59 | ) : super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler) {
60 | init()
61 | }
62 |
63 | constructor(
64 | corePoolSize: Int,
65 | maximumPoolSize: Int,
66 | keepAliveTime: Long,
67 | unit: TimeUnit?,
68 | workQueue: BlockingQueue?,
69 | threadFactory: ThreadFactory?,
70 | handler: RejectedExecutionHandler?
71 | ) : super(
72 | corePoolSize,
73 | maximumPoolSize,
74 | keepAliveTime,
75 | unit,
76 | workQueue,
77 | threadFactory,
78 | handler
79 | ) {
80 | init()
81 | }
82 |
83 | private fun init() {
84 | // 这样可以尽早将线程和线程池建立联系,而不用等到run时
85 | threadFactory = TBaseThreadFactory(threadFactory, poolName)
86 | }
87 |
88 | override fun setThreadFactory(threadFactory: ThreadFactory?) {
89 | threadFactory?.apply {
90 | super.setThreadFactory(TBaseThreadFactory(this, poolName))
91 | } ?: super.setThreadFactory(threadFactory)
92 | }
93 |
94 | // submit使得task被包了一层,无法直接remove了,不用存到weakRunnableList中
95 | override fun submit(task: Runnable): Future<*> {
96 | var runnable = task
97 | if (task !is PoolRunnableAndOther) {
98 | val callStack = TrackerUtils.getStackString()
99 | runnable = PoolRunnableAndOther(task, callStack, poolName)
100 | }
101 | return super.submit(runnable)
102 | }
103 |
104 | // callable不能通过remove移除,不用存到weakRunnableList中
105 | override fun submit(task: Callable): Future {
106 | var callable = task
107 | if (task !is PoolRunnableAndOther) {
108 | val callStack = TrackerUtils.getStackString()
109 | callable = PoolRunnableAndOther(task, callStack, poolName) as Callable
110 | }
111 | return super.submit(callable)
112 | }
113 |
114 | override fun submit(task: Runnable, result: T): Future {
115 | var runnable = task
116 | if (task !is PoolRunnableAndOther) {
117 | val callStack = TrackerUtils.getStackString()
118 | runnable = PoolRunnableAndOther(task, callStack, poolName)
119 | }
120 | return super.submit(runnable, result)
121 | }
122 |
123 | override fun invokeAny(tasks: MutableCollection>): T {
124 | return super.invokeAny(proxyInvokeList(tasks))
125 | }
126 |
127 | override fun invokeAny(
128 | tasks: MutableCollection>,
129 | timeout: Long,
130 | unit: TimeUnit
131 | ): T {
132 | return super.invokeAny(proxyInvokeList(tasks), timeout, unit)
133 | }
134 |
135 | override fun invokeAll(tasks: MutableCollection>): MutableList> {
136 | return super.invokeAll(proxyInvokeList(tasks))
137 | }
138 |
139 | override fun invokeAll(
140 | tasks: MutableCollection>,
141 | timeout: Long,
142 | unit: TimeUnit
143 | ): MutableList> {
144 | return super.invokeAll(proxyInvokeList(tasks), timeout, unit)
145 | }
146 |
147 | override fun execute(command: Runnable) {
148 | val callStack = TrackerUtils.getStackString()
149 | var runnable = command
150 | if (command !is PoolRunnableAndOther) { // 可能先进入队列后进入这里,此时已经是PoolRunnable
151 | runnable = PoolRunnableAndOther(command, callStack, poolName)
152 | weakRunnableList.add(WeakReference(runnable))
153 | }
154 | super.execute(runnable)
155 | }
156 |
157 | override fun getThreadFactory(): ThreadFactory {
158 | if (super.getThreadFactory() is TBaseThreadFactory) {
159 | // 防止上层自定义ThreadFactory,get后向下转型失败
160 | return (super.getThreadFactory() as TBaseThreadFactory).getReal()
161 | } else {
162 | return super.getThreadFactory()
163 | }
164 | }
165 |
166 | override fun remove(task: Runnable?): Boolean {
167 | for (i in 0 until weakRunnableList.size) {
168 | val runnable = weakRunnableList[i].get()
169 | if (runnable is PoolRunnableAndOther && runnable.getReal() == task) {
170 | weakRunnableList.removeAt(i)
171 | return super.remove(runnable)
172 | }
173 | }
174 | return super.remove(task)
175 | }
176 |
177 | override fun getQueue(): BlockingQueue {
178 | // 这里如果上层强转runnable会出错,暂未处理
179 | return super.getQueue()
180 | }
181 |
182 | override fun shutdown() {
183 | weakRunnableList.clear()
184 | super.shutdown()
185 | ThreadInfoManager.INSTANCE.shutDownPool(poolName)
186 | }
187 |
188 | override fun shutdownNow(): MutableList {
189 | val list = super.shutdownNow()
190 | for (i in 0 until list.size) {
191 | if (list[i] is PoolRunnableAndOther) {
192 | val real = (list[i] as PoolRunnableAndOther).getReal()
193 | if (real is Runnable) {
194 | list[i] = real
195 | }
196 | }
197 | }
198 | weakRunnableList.clear()
199 | ThreadInfoManager.INSTANCE.shutDownPool(poolName)
200 | return list
201 | }
202 |
203 | private fun proxyInvokeList(tasks: MutableCollection>): MutableList> {
204 | val callStack = TrackerUtils.getStackString()
205 | val proxyTasks = mutableListOf>()
206 | tasks.forEach { task ->
207 | if (task !is PoolRunnableAndOther) {
208 | val callable = PoolRunnableAndOther(task, callStack, poolName) as Callable
209 | proxyTasks.add(callable)
210 | } else {
211 | proxyTasks.add(task)
212 | }
213 | Unit
214 | }
215 | return proxyTasks
216 | }
217 |
218 | // 复写这个方法有可能导致本该马上回收的被延迟回收
219 | // override fun finalize() {
220 | // ThreadInfoManager.INSTANCE.removePool(poolName)
221 | // super.finalize()
222 | // }
223 | }
--------------------------------------------------------------------------------
/threadtracker/src/main/java/com/codoon/threadtracker/proxy/TBaseTimer.kt:
--------------------------------------------------------------------------------
1 | package com.codoon.threadtracker.proxy
2 |
3 | import android.os.SystemClock
4 | import android.util.Log
5 | import com.codoon.threadtracker.LOG_TAG
6 | import com.codoon.threadtracker.ThreadInfoManager
7 | import com.codoon.threadtracker.TrackerUtils
8 | import com.codoon.threadtracker.bean.ThreadInfo
9 | import java.util.*
10 |
11 | open class TBaseTimer : Timer {
12 | constructor() : super() {
13 | init()
14 | }
15 |
16 | constructor(isDaemon: Boolean) : super(isDaemon) {
17 | init()
18 | }
19 |
20 | constructor(name: String) : super(name) {
21 | init()
22 | }
23 |
24 | constructor(name: String, isDaemon: Boolean) : super(name, isDaemon) {
25 | init()
26 | }
27 |
28 | private fun init() {
29 | val callStack = TrackerUtils.getStackString()
30 | var hasProxy = false
31 | try {
32 | val fields = javaClass.superclass?.declaredFields
33 | fields?.forEach {
34 | it.isAccessible = true
35 | val any = it.get(this)
36 | if (any is Thread && any.isAlive) {
37 | hasProxy = true
38 | any.apply {
39 | val newInfo = ThreadInfo()
40 | newInfo.id = id
41 | newInfo.name = name
42 | newInfo.callStack = callStack
43 | newInfo.callThreadId = Thread.currentThread().id
44 | newInfo.state = state
45 | newInfo.startTime = SystemClock.elapsedRealtime()
46 | ThreadInfoManager.INSTANCE.putThreadInfo(id, newInfo)
47 | }
48 | }
49 | }
50 | } catch (e: Exception) {
51 | Log.e(LOG_TAG, "ProxyTimer err: ${e.message}")
52 | }
53 | if (!hasProxy) {
54 | Log.e(LOG_TAG, "ProxyTimer fail")
55 | }
56 | }
57 | }
--------------------------------------------------------------------------------
/threadtracker/src/main/java/com/codoon/threadtracker/ui/ThreadDetailsActivity.kt:
--------------------------------------------------------------------------------
1 | package com.codoon.threadtracker.ui
2 |
3 | import android.annotation.SuppressLint
4 | import android.app.Activity
5 | import android.content.Context
6 | import android.content.Intent
7 | import android.graphics.Color
8 | import android.graphics.Paint
9 | import android.graphics.Typeface
10 | import android.os.Bundle
11 | import android.os.Looper
12 | import android.text.SpannableString
13 | import android.text.Spanned
14 | import android.text.style.ForegroundColorSpan
15 | import android.text.style.RelativeSizeSpan
16 | import android.text.style.StyleSpan
17 | import android.util.Log
18 | import android.view.View
19 | import android.widget.Toast
20 | import com.codoon.threadthracker.R
21 | import com.codoon.threadtracker.LOG_TAG
22 | import com.codoon.threadtracker.ThreadInfoManager
23 | import com.codoon.threadtracker.TrackerUtils.setStatusBarColor
24 | import com.codoon.threadtracker.UserPackage
25 | import com.codoon.threadtracker.bean.ShowInfo
26 | import com.codoon.threadtracker.bean.ThreadInfo
27 | import com.codoon.threadtracker.bean.ThreadPoolInfo
28 | import kotlinx.android.synthetic.main.threadtracker_activity_details.*
29 |
30 |
31 | /**
32 | * 线程/线程池详情页,包括基础信息和调用栈
33 | */
34 | class ThreadDetailsActivity : Activity() {
35 | private val colorBlue = Color.argb(0xff, 0x22, 0x22, 0xee)
36 |
37 | companion object {
38 | fun startDetailsActivity(context: Context, showInfo: ShowInfo) {
39 | val intent = Intent()
40 | intent.setClass(context, ThreadDetailsActivity::class.java)
41 | intent.putExtra("threadId", showInfo.threadId)
42 | intent.putExtra("poolName", showInfo.poolName)
43 | context.startActivity(intent)
44 | }
45 | }
46 |
47 | override fun onCreate(savedInstanceState: Bundle?) {
48 | super.onCreate(savedInstanceState)
49 | setContentView(R.layout.threadtracker_activity_details)
50 | setStatusBarColor(window)
51 | backBtn.setOnClickListener { onBackPressed() }
52 | showDetails()
53 | }
54 |
55 | private fun showDetails() {
56 | val threadIdOut = intent.getLongExtra("threadId", -1)
57 | val poolNameOut = intent.getStringExtra("poolName")
58 | var threadInfo: ThreadInfo? = null
59 | var poolInfo: ThreadPoolInfo? = null
60 |
61 | if (threadIdOut == -1L && poolNameOut == null) {
62 | Toast.makeText(this, "not find", Toast.LENGTH_SHORT).show()
63 | finish()
64 | return
65 | }
66 |
67 | var type = ShowInfo.SINGLE_THREAD
68 |
69 | if (threadIdOut != -1L) {
70 | threadInfo = ThreadInfoManager.INSTANCE.getLastThreadInfoById(threadIdOut)
71 | threadInfo?.apply {
72 | if (poolName != null) {
73 | type = ShowInfo.POOL_THREAD
74 | } else {
75 | type = ShowInfo.SINGLE_THREAD
76 | }
77 | } ?: apply {
78 | Toast.makeText(this, "not find\nmaybe $threadIdOut has destroy", Toast.LENGTH_SHORT)
79 | .show()
80 | finish()
81 | return
82 | }
83 | } else if (poolNameOut != null) {
84 | poolInfo = ThreadInfoManager.INSTANCE.getLastThreadPoolInfoByName(poolNameOut)
85 | type = ShowInfo.POOL
86 | if (poolInfo == null) {
87 | Toast.makeText(this, "not find", Toast.LENGTH_SHORT).show()
88 | finish()
89 | return
90 | }
91 | }
92 | when (type) {
93 | ShowInfo.SINGLE_THREAD -> { // 展示独立线程详细信息
94 | Log.d(LOG_TAG, "details:${threadInfo}")
95 | showSingleThreadInfo(threadInfo)
96 | }
97 | ShowInfo.POOL -> { // 展示线程池详细信息
98 | Log.d(LOG_TAG, "details:${poolInfo}")
99 | showPoolInfo(poolInfo)
100 | }
101 | ShowInfo.POOL_THREAD -> { // 展示线程池中线程详细信息
102 | Log.d(LOG_TAG, "details:${threadInfo}")
103 | showPoolThreadInfo(threadInfo)
104 | }
105 | }
106 | }
107 |
108 | @SuppressLint("SetTextI18n")
109 | private fun showSingleThreadInfo(threadInfo: ThreadInfo?) {
110 | threadInfo?.apply {
111 | infoDetails.text =
112 | "id: ${id}\n\n" +
113 | "name: ${name}\n\n" +
114 | "state: $state"
115 | stack1Details.text = highlightStack(callStack)
116 | if (callStack.isEmpty()) {
117 | stack1Details.setTextColor(colorBlue)
118 | stack1Details.text = "unknown"
119 | stack1TipsLayout.visibility = View.GONE
120 | } else {
121 | if (callThreadId != Looper.getMainLooper().thread.id) {
122 | stack1Tips.text = "Call from thread $callThreadId"
123 | stack1Jump.paint.flags = Paint.UNDERLINE_TEXT_FLAG
124 | stack1Jump.visibility = View.VISIBLE
125 | stack1Jump.setOnClickListener {
126 | startDetailsActivity(
127 | this@ThreadDetailsActivity,
128 | ShowInfo(threadId = callThreadId)
129 | )
130 | }
131 | } else {
132 | stack1Tips.text = "Call from main thread"
133 | stack1Jump.visibility = View.GONE
134 | }
135 | }
136 | stack2Details.text = highlightStack(runningStack)
137 | }
138 | infoTitle.text = "Thread Info"
139 | stack1Title.text = "Call Stack" // start调用栈
140 | }
141 |
142 | @SuppressLint("SetTextI18n")
143 | private fun showPoolInfo(poolInfo: ThreadPoolInfo?) {
144 | poolInfo?.apply {
145 | infoDetails.text = "poolName: ${poolName}"
146 | stack1Details.text = highlightStack(createStack)
147 | if (createStack.isEmpty()) {
148 | stack1Details.setTextColor(colorBlue)
149 | stack1Details.text = "unknown"
150 | stack1TipsLayout.visibility = View.GONE
151 | } else {
152 | if (createThreadId != Looper.getMainLooper().thread.id) {
153 | stack1Tips.text = "Create from thread $createThreadId"
154 | stack1Jump.paint.flags = Paint.UNDERLINE_TEXT_FLAG
155 | stack1Jump.visibility = View.VISIBLE
156 | stack1Jump.setOnClickListener {
157 | startDetailsActivity(
158 | this@ThreadDetailsActivity,
159 | ShowInfo(threadId = createThreadId)
160 | )
161 | }
162 | } else {
163 | stack1Tips.text = "Create from main thread"
164 | stack1Jump.visibility = View.GONE
165 | }
166 | }
167 | }
168 | infoTitle.text = "ThreadPool Info"
169 | stack1Title.text = "Create Stack" // 线程池创建栈
170 |
171 | stack2Title.visibility = View.GONE
172 | stack2Details.visibility = View.GONE
173 | }
174 |
175 | @SuppressLint("SetTextI18n")
176 | private fun showPoolThreadInfo(threadInfo: ThreadInfo?) {
177 | threadInfo?.apply {
178 | infoDetails.text =
179 | "id: ${id}\n\n" +
180 | "name: ${name}\n\n" +
181 | "state: ${state}\n\n" +
182 | "pool: ${poolName}"
183 | stack1Details.text = highlightStack(callStack)
184 | if (callStack.isEmpty()) {
185 | stack1Details.setTextColor(colorBlue)
186 | stack1Details.text = "no task running"
187 | stack1TipsLayout.visibility = View.GONE
188 | } else {
189 | if (callThreadId != Looper.getMainLooper().thread.id) {
190 | stack1Tips.text = "Task add from thread $callThreadId"
191 | stack1Jump.paint.flags = Paint.UNDERLINE_TEXT_FLAG
192 | stack1Jump.visibility = View.VISIBLE
193 | stack1Jump.setOnClickListener {
194 | startDetailsActivity(
195 | this@ThreadDetailsActivity,
196 | ShowInfo(threadId = callThreadId)
197 | )
198 | }
199 | } else {
200 | stack1Tips.text = "Task add from main thread"
201 | stack1Jump.visibility = View.GONE
202 | }
203 | }
204 |
205 | infoTitleJump.paint.flags = Paint.UNDERLINE_TEXT_FLAG
206 | infoTitleJump.setOnClickListener {
207 | startDetailsActivity(
208 | this@ThreadDetailsActivity,
209 | ShowInfo(poolName = poolName)
210 | )
211 | }
212 | infoTitleTipsLayout.visibility = View.VISIBLE
213 |
214 | stack2Details.text = highlightStack(runningStack)
215 | }
216 | infoTitle.text = "Thread Info"
217 | stack1Title.text = "Task Add Stack" // 线程池中线程正在运行任务的添加栈
218 | }
219 |
220 | private fun highlightStack(stack0: String): SpannableString {
221 | val stack = stack0.replace("\n", "\n\n")
222 | var indexA = -1
223 | var indexZ = -1
224 | for (s in UserPackage.getPackageList()) {
225 | val indexTemp = stack.indexOf(s)
226 | if (indexTemp != -1) {
227 | if (indexA == -1 || (indexTemp < indexA)) {
228 | indexA = indexTemp
229 | indexZ = stack.indexOf("\n", startIndex = indexA)
230 | if (indexA == 0)
231 | break
232 | }
233 | }
234 | }
235 |
236 | val span = SpannableString(stack)
237 | if (indexA != -1) {
238 | val styleSpan = StyleSpan(Typeface.BOLD)
239 | span.setSpan(styleSpan, indexA, indexZ, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
240 |
241 | val relativeSizeSpan = RelativeSizeSpan(1.1f)
242 | span.setSpan(relativeSizeSpan, indexA, indexZ, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
243 |
244 | val foregroundColorSpan = ForegroundColorSpan(Color.parseColor("#FFF76347"))
245 | span.setSpan(foregroundColorSpan, indexA, indexZ, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
246 | }
247 | return span
248 | }
249 | }
--------------------------------------------------------------------------------
/threadtracker/src/main/java/com/codoon/threadtracker/ui/TrackerActivity.kt:
--------------------------------------------------------------------------------
1 | package com.codoon.threadtracker.ui
2 |
3 | import android.app.Activity
4 | import android.os.Bundle
5 | import android.os.Handler
6 | import android.os.HandlerThread
7 | import android.os.Message
8 | import android.view.View
9 | import android.widget.Toast
10 | import androidx.recyclerview.widget.LinearLayoutManager
11 | import com.codoon.threadthracker.R
12 | import com.codoon.threadtracker.ThreadInfoManager
13 | import com.codoon.threadtracker.TrackerUtils.setStatusBarColor
14 | import kotlinx.android.synthetic.main.threadtracker_activity_tracker.*
15 |
16 | /**
17 | * 线程/线程池列表
18 | */
19 | class TrackerActivity : Activity() {
20 |
21 | private val refreshHandlerThread = HandlerThread("ThreadTracker-Refresh").apply {
22 | start()
23 | }
24 | private val refreshHandler = object : Handler(refreshHandlerThread.looper) {
25 | override fun handleMessage(msg: Message) {
26 | refreshList(msg.what == 1)
27 | }
28 | }
29 |
30 | override fun onCreate(savedInstanceState: Bundle?) {
31 | super.onCreate(savedInstanceState)
32 | setContentView(R.layout.threadtracker_activity_tracker)
33 | setStatusBarColor(window)
34 |
35 | refreshBtn.setOnClickListener {
36 | refreshBtn.visibility = View.GONE
37 | refreshProgress.visibility = View.VISIBLE
38 | refreshHandler.sendEmptyMessage(1)
39 | }
40 | val adapter = TrackerAdapter(emptyList(), object : OnItemClickListener {
41 | override fun onItemClick(view: View) {
42 | val position: Int = recyclerView.getChildAdapterPosition(view)
43 | ThreadDetailsActivity.startDetailsActivity(
44 | this@TrackerActivity,
45 | (recyclerView.adapter as TrackerAdapter).getItemList()[position]
46 | )
47 | }
48 | })
49 | recyclerView.adapter = adapter
50 | recyclerView.layoutManager = LinearLayoutManager(this)
51 |
52 | refreshHandler.sendEmptyMessage(0)
53 | }
54 |
55 | private fun refreshList(toast: Boolean) {
56 | val infoResult = ThreadInfoManager.INSTANCE.buildAllThreadInfo()
57 | runOnUiThread {
58 | (recyclerView.adapter as TrackerAdapter).setItemList(infoResult.list)
59 | refreshBtn.visibility = View.VISIBLE
60 | refreshProgress.visibility = View.GONE
61 | // statisticsText.text = "total: ${infoResult.totalNum} system/unknown: ${infoResult.unknownNum}"
62 | if (toast) {
63 | Toast.makeText(
64 | this,
65 | "total: ${infoResult.totalNum} system/unknown: ${infoResult.unknownNum}",
66 | Toast.LENGTH_SHORT
67 | ).show()
68 | }
69 | }
70 | }
71 |
72 | override fun onDestroy() {
73 | refreshHandler.removeCallbacksAndMessages(null)
74 | refreshHandlerThread.quit()
75 | super.onDestroy()
76 | }
77 | }
--------------------------------------------------------------------------------
/threadtracker/src/main/java/com/codoon/threadtracker/ui/TrackerAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.codoon.threadtracker.ui
2 |
3 | import android.graphics.Color
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import android.view.ViewGroup
7 | import android.widget.RelativeLayout
8 | import android.widget.TextView
9 | import androidx.recyclerview.widget.RecyclerView
10 | import com.codoon.threadthracker.R
11 | import com.codoon.threadtracker.bean.ShowInfo
12 | import com.codoon.threadtracker.toPx
13 |
14 | class TrackerAdapter(private var list: List, private val listener: OnItemClickListener) :
15 | RecyclerView.Adapter() {
16 |
17 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
18 | val view = LayoutInflater.from(parent.context).inflate(R.layout.threadtracker_thread_item, parent, false)
19 | view.setOnClickListener {
20 | listener.onItemClick(it)
21 | }
22 | return ViewHolder(view)
23 | }
24 |
25 | override fun getItemCount(): Int {
26 | return list.size
27 | }
28 |
29 | fun getItemList(): List {
30 | return list
31 | }
32 |
33 | fun setItemList(list: List) {
34 | this.list = list
35 | notifyDataSetChanged()
36 | }
37 |
38 | override fun onBindViewHolder(holder: ViewHolder, position: Int) {
39 | val data = list[position]
40 | when (data.type) {
41 | ShowInfo.SINGLE_THREAD -> {
42 | holder.threadLayout.setBackgroundColor(Color.argb(0x00, 0x00, 0xbc, 0x71))
43 | holder.threadName.text = data.threadName
44 | holder.threadState.text = data.threadState.name
45 | holder.threadName.setPadding(0, 0, 0, 0)
46 | holder.threadState.visibility = View.VISIBLE
47 | }
48 | ShowInfo.POOL -> {
49 | holder.threadLayout.setBackgroundColor(Color.argb(0x20, 0x00, 0xbc, 0x71))
50 | holder.threadName.text = data.poolName
51 | holder.threadState.visibility = View.GONE
52 | holder.threadName.setPadding(0, 0, 0, 0)
53 | }
54 | ShowInfo.POOL_THREAD -> {
55 | holder.threadLayout.setBackgroundColor(Color.argb(0x00, 0x00, 0xbc, 0x71))
56 | holder.threadName.text = data.threadName
57 | holder.threadState.text = data.threadState.name
58 | holder.threadName.apply {
59 | setPadding(20.toPx(context), paddingTop, paddingRight, paddingBottom)
60 | }
61 | holder.threadState.visibility = View.VISIBLE
62 | }
63 | }
64 | }
65 |
66 |
67 | class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
68 | val threadLayout: RelativeLayout = itemView.findViewById(R.id.threadLayout)
69 | val threadName: TextView = itemView.findViewById(R.id.threadName)
70 | val threadState: TextView = itemView.findViewById(R.id.threadState)
71 | }
72 | }
73 |
74 | interface OnItemClickListener {
75 | fun onItemClick(view: View)
76 | }
77 |
--------------------------------------------------------------------------------
/threadtracker/src/main/res/drawable/threadtracker_refresh_progress.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 | -
18 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/threadtracker/src/main/res/drawable/threadtracker_refreshing.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codoon/ThreadTracker/9368e2a6ad7c4055116dcceb3d7c5bf575b7920e/threadtracker/src/main/res/drawable/threadtracker_refreshing.png
--------------------------------------------------------------------------------
/threadtracker/src/main/res/layout/threadtracker_activity_details.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
18 |
19 |
24 |
25 |
29 |
30 |
34 |
35 |
45 |
46 |
52 |
53 |
67 |
68 |
79 |
80 |
81 |
82 |
91 |
92 |
102 |
103 |
107 |
108 |
122 |
123 |
136 |
137 |
138 |
139 |
148 |
149 |
159 |
160 |
170 |
171 |
172 |
--------------------------------------------------------------------------------
/threadtracker/src/main/res/layout/threadtracker_activity_tracker.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
18 |
19 |
33 |
34 |
44 |
45 |
54 |
55 |
60 |
61 |
--------------------------------------------------------------------------------
/threadtracker/src/main/res/layout/threadtracker_thread_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
21 |
22 |
34 |
35 |
--------------------------------------------------------------------------------
/threadtracker/src/main/res/mipmap-xxhdpi/threadtracker_arrow_back.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codoon/ThreadTracker/9368e2a6ad7c4055116dcceb3d7c5bf575b7920e/threadtracker/src/main/res/mipmap-xxhdpi/threadtracker_arrow_back.png
--------------------------------------------------------------------------------
/threadtracker/src/main/res/mipmap-xxhdpi/threadtracker_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codoon/ThreadTracker/9368e2a6ad7c4055116dcceb3d7c5bf575b7920e/threadtracker/src/main/res/mipmap-xxhdpi/threadtracker_launcher.png
--------------------------------------------------------------------------------
/threadtracker/src/main/res/mipmap-xxhdpi/threadtracker_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codoon/ThreadTracker/9368e2a6ad7c4055116dcceb3d7c5bf575b7920e/threadtracker/src/main/res/mipmap-xxhdpi/threadtracker_launcher_round.png
--------------------------------------------------------------------------------
/threadtracker/src/main/res/mipmap-xxhdpi/threadtracker_refresh.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codoon/ThreadTracker/9368e2a6ad7c4055116dcceb3d7c5bf575b7920e/threadtracker/src/main/res/mipmap-xxhdpi/threadtracker_refresh.png
--------------------------------------------------------------------------------
/threadtracker/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/threadtracker/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/threadtracker/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
--------------------------------------------------------------------------------