本文繼續分析booster的實現原理。java
對於這個組件官方是這樣描述的: 對於開發者來講,線程管理一直是個頭疼的問題,特別是第三方 SDK 中的線程,過多的線程可能會致使內存不足,然而幸運的是,這些問題都能經過 Booster 來解決。node
那booster是如何解決的呢? 其實實現思路相似上一篇文章booster分析-修復系統bug。即經過gradle transform
在編譯時作字節碼插樁,來實現代碼的動態替換(整個booster框架大部分功能的實現都是這個原理)。android
那麼來看一下booster
到底如何優化了多線程管理吧。git
通常一個比較成熟的應用在運行時後臺都會有不少線程在跑,爲了便於線程的管理。 booster在編譯時強制給應用程序運行時的線程(不管是本身開啓的仍是三方庫開啓的)都起了一個名字。這樣不管是bug的定位,仍是線程的調試都方便了很多。github
booster
在編譯時經過修改字節碼文件動態給HandlerThread
、Timer
、AsyncTask
、Thread
、ThreadPoolExecutor
等開啓的線程都起了名字。bash
好比,全部的Thread
在start()
以前:多線程
ThreadTransformer.javaapp
private fun MethodInsnNode.transformInvokeVirtual(context: TransformContext, klass: ClassNode, method: MethodNode) {
if (context.klassPool.get("java/lang/Thread").isAssignableFrom(this.owner)) {
when ("${this.name}${this.desc}") {
"start()V" -> {
method.instructions.insertBefore(this, LdcInsnNode(makeThreadName(klass.className)))
method.instructions.insertBefore(this, MethodInsnNode(Opcodes.INVOKESTATIC, SHADOW_THREAD, "setThreadName", "(Ljava/lang/Thread;Ljava/lang/String;)Ljava/lang/Thread;", false))
}
}
}
}
複製代碼
即在Thread.start()
以前調用了ShadowThread.setThreadName()
:框架
public class ShadowThread {
public static Thread setThreadName(final Thread t, final String prefix) {
t.setName(makeThreadName(t.getName(), prefix));
return t;
}
public static String makeThreadName(final String name, final String prefix) {
return name == null ? prefix : (name.startsWith(MARK) ? name : (prefix + "#" + name));
}
}
複製代碼
舉個例子:ide
class ThreadTest {
private val TAG = "Booster-Thread"
fun test(){
Thread({
Log.d(TAG, "thread name : ${Thread.currentThread().name}")
}).start()
}
}
複製代碼
上面代碼通過booster
優化後log輸入的線程名爲:
D/Booster-Thread: thread name : com.susion.boostertest.thread.ThreadTest
複製代碼
即線程的名字改成 : 包+類名, 而沒有優化前名稱是:
D/Booster-Thread: thread name : Thread-2
複製代碼
通過booster
優化後確實更容易識別線程了。 咱們寫多線程代碼時仍是給線程起個規範點的名字比較好
allowCoreThreadTimeOut(true)
先來回顧一下線程池的構造函數:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue)
複製代碼
keepAliveTime參數
是用來設置線程的超時時長,當線程的閒置時長超過這個時間時線程就會被回收,可是這個策略並不會應用在覈心線程上。
但當對線程池設置allowCoreThreadTimeOut(true)
時,核心線程也會被回收退出。
booster
經過對字節碼的動態替換,對應用中全部的線程池都設置了allowCoreThreadTimeOut(true)
, 這樣能夠保證線程資源及時回收, 好比:
ThreadTransformer.java
private fun MethodInsnNode.transformInvokeStatic(context: TransformContext, klass: ClassNode, method: MethodNode) {
when (this.owner) {
"java/util/concurrent/Executors" -> {
when (this.name) {
"newCachedThreadPool",
"newFixedThreadPool",
"newSingleThreadExecutor",
"newSingleThreadScheduledExecutor",
"newScheduledThreadPool" -> {
val r = this.desc.lastIndexOf(')')
val name = this.name.replace("new", "newOptimized")
val desc = "${this.desc.substring(0, r)}Ljava/lang/String;${this.desc.substring(r)}"
this.owner = 「com/didiglobal/booster/instrument/ShadowExecutors」
this.name = name
this.desc = desc
method.instructions.insertBefore(this, LdcInsnNode(makeThreadName(klass.className)))
}
}
}
}
}
複製代碼
即將Executors.newCachedThreadPool()
等方法都替換爲ShadowExecutors.newOptimizedXXX
方法:
ShadowExecutors.java
public static ExecutorService newOptimizedCachedThreadPool() {
final ThreadPoolExecutor executor = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), Executors.defaultThreadFactory());
executor.allowCoreThreadTimeOut(true);
return executor;
}
複製代碼
這個組件經過編譯時的lint檢查來找出一些在應用中調用可能阻塞UI線程的API,如:I/O API 等。主要有兩個特色:
首先指定一些受檢查的方法:
LintTransformer.java
class LintTransformer : ClassTransformer {
override fun onPreTransform(context: TransformContext) {
...
val parser = SAXParserFactory.newInstance().newSAXParser()
context.artifacts.get(ArtifactManager.MERGED_MANIFESTS).forEach { manifest ->
val handler = ComponentHandler()
parser.parse(manifest, handler)
// Attach component entry points to graph ROOT
mapOf(
Pair(handler.applications, APPLICATION_ENTRY_POINTS),
Pair(handler.activities, ACTIVITY_ENTRY_POINTS),
Pair(handler.services, SERVICE_ENTRY_POINTS),
Pair(handler.receivers, RECEIVER_ENTRY_POINTS),
Pair(handler.providers, PROVIDER_ENTRY_POINTS)
).forEach { components, entryPoints ->
components.forEach { component ->
entryPoints.map {
CallGraph.Node(component.replace('.', '/'), it.name, it.desc)
}.forEach {
globalBuilder.addEdge(CallGraph.ROOT, it) // 全部的分析點
}
}
}
}
}
}
複製代碼
它檢查的方法有:
constant.kt
/**
* Main/UI thread entry point of Application
*/
internal val APPLICATION_ENTRY_POINTS = arrayOf(
"onConfigurationChanged(Landroid/content/res/Configuration;)V",
"onCreate()V",
"onLowMemory()V",
"onTerminate()V",
"onTrimMemory(I)V"
).map(EntryPoint.Companion::valueOf).toSet()
/**
* Main/UI thread entry point of Activity
*/
internal val ACTIVITY_ENTRY_POINTS = arrayOf(
// <editor-fold desc="public methods of Activity">
"onActionModeFinished(Landroid/view/ActionMode;)V",
"onActionModeStarted(Landroid/view/ActionMode;)V",
"onActivityReenter(ILandroid/content/Intent;)V",
"onAttachFragment(Landroid/app/Fragment;)V",
"onAttachedToWindow()V",
....
複製代碼
即Aplication/Activity/Service/BroadcastReceiver/ContentProvider
等相關API。
override fun onPostTransform(context: TransformContext) {
val graph = globalBuilder.build()
...
// Analyse global call graph and separate each chain to individual graph
graph[CallGraph.ROOT].forEach { node ->
graph.analyse(context, node, listOf(CallGraph.ROOT, node), lints) { chain ->
val builder = graphBuilders.getOrPut(node.type) {
CallGraph.Builder().setTitle(node.type.replace('/', '.'))
}
chain.toEdges().forEach { edge ->
builder.addEdges(edge)
}
}
}
}
複製代碼
analyse()
用來檢查當前API是否命中了Lint檢查列表。若是命中就會加入到dot文件繪圖節點中。
booster
默認定義的敏感API有:
/**
* Sensitive APIs
*/
internal val LINT_APIS = setOf(
...
"android/graphics/BitmapFactory.decodeFile(Ljava/lang/String;)Landroid/graphics/Bitmap;",
"android/graphics/BitmapFactory.decodeFileDescriptor(Ljava/io/FileDescriptor;)Landroid/graphics/Bitmap;",
"android/graphics/BitmapFactory.decodeFileDescriptor(Ljava/io/FileDescriptor;Landroid/graphics/Rect;Landroid/graphics/BitmapFactory\$Options;)Landroid/graphics/Bitmap;",
"android/graphics/BitmapFactory.decodeResource(Landroid/content/res/Resources;I)Landroid/graphics/Bitmap;",
"android/graphics/BitmapFactory.decodeResource(Landroid/content/res/Resources;ILandroid/graphics/BitmapFactory\$Options;)Landroid/graphics/Bitmap;",
"android/graphics/BitmapFactory.decodeResourceStream(Landroid/content/res/Resources;Landroid/util/TypedValue;Ljava/io/InputStream;Landroid/graphics/Rect;Landroid/graphics/BitmapFactory\$Options;)Landroid/graphics/Bitmap;",
"android/graphics/BitmapFactory.decodeStream(Ljava/io/InputStream;)Landroid/graphics/Bitmap;",
"android/graphics/BitmapFactory.decodeStream(Ljava/io/InputStream;Landroid/graphics/Rect;Landroid/graphics/BitmapFactory\$Options;)Landroid/graphics/Bitmap;"
// </editor-fold>
).map(Node.Companion::valueOf).toSet()
複製代碼
即主要是一些可能致使主線程卡頓的API。
好比有下面這個類:
class LabThreadTestActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_lab_thread_test)
val test1 = getSharedPreferences("default", Context.MODE_PRIVATE).edit().putBoolean("test",true).commit()
// val test2 = getSharedPreferences("default", Context.MODE_PRIVATE).edit().putBoolean("test",true).apply()
}
}
複製代碼
通過booster-transform-lint
檢查後會生成com.susion.boostertest.thread.LabThreadTestActivity.dot
文件。這個文件轉爲png圖片以下:
更多分享見AdvancedAndroid