微信tinker导致冷启动变慢的问题优化
微信tinker導致冷啟動變慢的問題優化
- 1. Android S用戶反饋微信啟動慢
- 2. 抓取微信systrace查看一下
- 3. tinker對冷啟動時間的影響
- 4. 修改方案
- 5. Open Dex是什么時候觸發的?其中傳入的location又是那里來的?
- 6. 斷點看一下帶tinker和不帶tinker的ClassLoader
- 7. 微信內部tinker加載的流程
- 8. Android S優化tinker導致啟動慢的方案
1. Android S用戶反饋微信啟動慢
首先第一個想到的就是dex的狀態問題
2. 抓取微信systrace查看一下
=> 可以看到果然出現了tinker
OpenDexFilesFromOat(/data/user/0/com.tencent.mm/tinker/patch-66e50d2a/dex/tinker_classN.apk)
=> 查看一下/data/user/0/com.tencent.mm/tinker/目錄,發現是有很多tinker的內容
$ adb shell ls -al /data/user/0/com.tencent.mm/tinker/
drwx------ 3 u0_a211 u0_a211 3452 2021-12-21 16:27 .
drwx------ 46 u0_a211 u0_a211 3452 2021-12-22 08:56 …
-rw------- 1 u0_a211 u0_a211 0 2021-12-22 08:57 info.lock
drwx------ 6 u0_a211 u0_a211 3452 2021-12-21 15:57 patch-66e50d2a
-rw-rw-rw- 1 u0_a211 u0_a211 359 2021-12-21 16:25 patch.info
-rw------- 1 u0_a211 u0_a211 42 2021-12-22 08:57 safemode_count_rec_com.tencent.mm
-rw------- 1 u0_a211 u0_a211 42 2021-12-21 17:32 safemode_count_rec_com.tencent.mm:appbrand0
-rw------- 1 u0_a211 u0_a211 42 2021-12-22 08:57 safemode_count_rec_com.tencent.mm:appbrand1
-rw------- 1 u0_a211 u0_a211 42 2021-12-21 16:26 safemode_count_rec_com.tencent.mm:cuploader
-rw------- 1 u0_a211 u0_a211 42 2021-12-22 08:56 safemode_count_rec_com.tencent.mm:push
-rw------- 1 u0_a211 u0_a211 42 2021-12-21 16:27 safemode_count_rec_com.tencent.mm:recovery
-rw------- 1 u0_a211 u0_a211 42 2021-12-21 17:32 safemode_count_rec_com.tencent.mm:sandbox
=> 里面的內容包含tinker_classN.apk還有odex/vdex/art/so等文件,目前tinker已經是微信優化過后的了,
不過由于文件比較大還是會導致,相當于加載2次dex文件,針對低配置的手機影響還是很容易看出來的。(相當于2次冷啟動)
0 /data/user/0/com.tencent.mm/tinker/info.lock
8.4M /data/user/0/com.tencent.mm/tinker/patch-66e50d2a/patch-66e50d2a.apk
36K /data/user/0/com.tencent.mm/tinker/patch-66e50d2a/dex/oat/tinker_classN.apk.cur.prof
2.6M /data/user/0/com.tencent.mm/tinker/patch-66e50d2a/dex/oat/arm/tinker_classN.vdex
8.1M /data/user/0/com.tencent.mm/tinker/patch-66e50d2a/dex/oat/arm/tinker_classN.odex
2.8M /data/user/0/com.tencent.mm/tinker/patch-66e50d2a/dex/oat/arm/tinker_classN.art
14M /data/user/0/com.tencent.mm/tinker/patch-66e50d2a/dex/oat/arm
64K /data/user/0/com.tencent.mm/tinker/patch-66e50d2a/dex/oat/tinker_classN.apk.prof
14M /data/user/0/com.tencent.mm/tinker/patch-66e50d2a/dex/oat
132M /data/user/0/com.tencent.mm/tinker/patch-66e50d2a/dex/tinker_classN.apk
146M /data/user/0/com.tencent.mm/tinker/patch-66e50d2a/dex
3.5K /data/user/0/com.tencent.mm/tinker/patch-66e50d2a/odex
3.7M /data/user/0/com.tencent.mm/tinker/patch-66e50d2a/lib/lib/armeabi-v7a/libliteavsdk.so
9.4M /data/user/0/com.tencent.mm/tinker/patch-66e50d2a/lib/lib/armeabi-v7a/libapp.so
6.3M /data/user/0/com.tencent.mm/tinker/patch-66e50d2a/lib/lib/armeabi-v7a/libflutter.so
1.1M /data/user/0/com.tencent.mm/tinker/patch-66e50d2a/lib/lib/armeabi-v7a/libwechatlv.so
21M /data/user/0/com.tencent.mm/tinker/patch-66e50d2a/lib/lib/armeabi-v7a
21M /data/user/0/com.tencent.mm/tinker/patch-66e50d2a/lib/lib
21M /data/user/0/com.tencent.mm/tinker/patch-66e50d2a/lib
68M /data/user/0/com.tencent.mm/tinker/patch-66e50d2a/res/resources.apk
68M /data/user/0/com.tencent.mm/tinker/patch-66e50d2a/res
243M /data/user/0/com.tencent.mm/tinker/patch-66e50d2a
4.0K /data/user/0/com.tencent.mm/tinker/patch.info
4.0K /data/user/0/com.tencent.mm/tinker/safemode_count_rec_com.tencent.mm
4.0K /data/user/0/com.tencent.mm/tinker/safemode_count_rec_com.tencent.mm:push
4.0K /data/user/0/com.tencent.mm/tinker/safemode_count_rec_com.tencent.mm:appbrand1
4.0K /data/user/0/com.tencent.mm/tinker/safemode_count_rec_com.tencent.mm:appbrand0
4.0K /data/user/0/com.tencent.mm/tinker/safemode_count_rec_com.tencent.mm:sandbox
4.0K /data/user/0/com.tencent.mm/tinker/safemode_count_rec_com.tencent.mm:cuploader
4.0K /data/user/0/com.tencent.mm/tinker/safemode_count_rec_com.tencent.mm:recovery
243M /data/user/0/com.tencent.mm/tinker/
ps:
之前舊版本的tinker(2020年6月),tinker目錄里面只有一個熱更新的apk,會更加慢
$ tinker$ du -ah
5.1M ./patch-66490a13/patch-66490a13.apk
4.0K ./patch-66490a13/odex
3.9M ./patch-66490a13/lib/lib/armeabi-v7a/libmagicbrush.so
3.4M ./patch-66490a13/lib/lib/armeabi-v7a/libliteavsdk.so
476K ./patch-66490a13/lib/lib/armeabi-v7a/libwechatsight_v7a.so
11M ./patch-66490a13/lib/lib/armeabi-v7a/libapp.so
19M ./patch-66490a13/lib/lib/armeabi-v7a
19M ./patch-66490a13/lib/lib
19M ./patch-66490a13/lib
83M ./patch-66490a13/dex/tinker_classN.apk
44K ./patch-66490a13/dex/oat/tinker_classN.apk.cur.prof
48K ./patch-66490a13/dex/oat
83M ./patch-66490a13/dex
48M ./patch-66490a13/res/resources.apk
48M ./patch-66490a13/res
154M ./patch-66490a13
0 ./info.lock
4.0K ./patch.info
154M .
3. tinker對冷啟動時間的影響
=> 帶有tinker,驗證一下wm_activity_launch_time這個時間,大概在4s左右
I wm_activity_launch_time: [0,263586177,com.tencent.mm/.app.WeChatSplashActivity,4071]
I wm_activity_launch_time: [0,225687646,com.tencent.mm/.app.WeChatSplashActivity,4032]
=> 手動刪除整個tinker,驗證時間明顯減少,那么Android S上微信還是會導致啟動時間變慢的問題
1317 1408 I wm_activity_launch_time: [0,244922921,com.tencent.mm/.app.WeChatSplashActivity,2158]
1317 1408 I wm_activity_launch_time: [0,216957026,com.tencent.mm/.app.WeChatSplashActivity,2108]
4. 修改方案
1、加載dex流程中阻斷,如在systrace中的OpenDexFilesFromOat,如果不加載/data/user/0/com.tencent.mm/tinker/patch-66e50d2a/dex/tinker_classN.apk,
如果在art中修改
OatFileManager::OpenDexFilesFromOat或者更下面的ArtDexFileLoader::OpenZip/ArtDexFileLoader::OpenAllDexFilesFromZip都是可以阻斷其打開流程
識別出tinker直接跳過,如下是在OpenAllDexFilesFromZip中跳過(這個方案只在Android S之前有效,Android S的正常android版本中art已經給mainline,使用的是gms里面的art)
//art/libdexfile/dex/art_dex_file_loader.cc bool ArtDexFileLoader::OpenAllDexFilesFromZip(const ZipArchive& zip_archive,const std::string& location,bool verify,bool verify_checksum,std::string* error_msg,std::vector<std::unique_ptr<const DexFile>>* dex_files) const {ScopedTrace trace("Dex file open from Zip " + std::string(location)); //...//識別location是否包含tinkerif (hasTinker) {//包含則跳過return false;} //... }2、那么Android S現在art修改方案無效,我們怎么做呢?
還是那句話:先調查清楚,再來動筆
5. Open Dex是什么時候觸發的?其中傳入的location又是那里來的?
1、從OatFileManager::OpenDexFilesFromOat往上找
//
2、這里上一級目錄在libcore中libcore/dalvik/src/main/java/dalvik/system/DexFile.java,
在創建DexFile對象的時候就會打開dex file
3、搜索new DexFile,只有DexPathList.java、DexFile.java才new了DexFile對象
libcore/dalvik$ grep -rn “new DexFile” .
./src/main/java/dalvik/system/DexPathList.java:268: DexFile dex = new DexFile(dexFiles, definingContext, null_elements);
./src/main/java/dalvik/system/DexPathList.java:347: DexFile dex = new DexFile(new ByteBuffer[] { buf }, /* classLoader */ null,
./src/main/java/dalvik/system/DexPathList.java:442: return new DexFile(file, loader, elements);
./src/main/java/dalvik/system/DexFile.java:216: return new DexFile(sourcePathName, outputPathName, flags, loader, elements);
4、往上找關聯流程
這里就直接找到LoadedApk.java,這里是App加載apk的地方,流程從這里往第3點找
//frameworks/base/core/java/android/app/LoadedApk.javapublic ClassLoader getClassLoader() {synchronized (mLock) {if (mClassLoader == null) {createOrUpdateClassLoaderLocked(null /*addedPaths*/);}return mClassLoader;}}private void createOrUpdateClassLoaderLocked(List<String> addedPaths) {//..//mApplicationInfo.sourceDir就是/data/app/***/com.tencent.mm***/base.apkmakePaths(mActivityThread, isBundledApp, mApplicationInfo, zipPaths, libPaths);//...final String zip = (zipPaths.size() == 1) ? zipPaths.get(0) :TextUtils.join(File.pathSeparator, zipPaths);//zip就是/data/app/***/com.tencent.mm***/base.apk//...if (mDefaultClassLoader == null) {//...//創建mDefaultClassLoadermDefaultClassLoader = ApplicationLoaders.getDefault().getClassLoaderWithSharedLibraries(zip, mApplicationInfo.targetSdkVersion, isBundledApp, librarySearchPath,libraryPermittedPath, mBaseClassLoader,mApplicationInfo.classLoaderName, sharedLibraries, nativeSharedLibraries);//微信的mAppComponentFactory = androidx.core.app.CoreComponentFactorymAppComponentFactory = createAppFactory(mApplicationInfo, mDefaultClassLoader);//...}//...if (mClassLoader == null) {//通過mAppComponentFactory創建mClassLoadermClassLoader = mAppComponentFactory.instantiateClassLoader(mDefaultClassLoader,new ApplicationInfo(mApplicationInfo));}}繼續看一下ApplicationLoaders.getDefault().getClassLoaderWithSharedLibraries
//frameworks/base/core/java/android/app/ApplicationLoaders.javaClassLoader getClassLoaderWithSharedLibraries(String zip, int targetSdkVersion, boolean isBundled,String librarySearchPath, String libraryPermittedPath,ClassLoader parent, String classLoaderName,List<ClassLoader> sharedLibraries, List<String> nativeSharedLibraries) {// For normal usage the cache key used is the same as the zip path.return getClassLoader(zip, targetSdkVersion, isBundled, librarySearchPath,libraryPermittedPath, parent, zip, classLoaderName, sharedLibraries,nativeSharedLibraries);}private ClassLoader getClassLoader(String zip, int targetSdkVersion, boolean isBundled,String librarySearchPath, String libraryPermittedPath,ClassLoader parent, String cacheKey,String classLoaderName, List<ClassLoader> sharedLibraries,List<String> nativeSharedLibraries) {//...ClassLoader classloader = ClassLoaderFactory.createClassLoader(zip, librarySearchPath, libraryPermittedPath, parent,targetSdkVersion, isBundled, classLoaderName, sharedLibraries,nativeSharedLibraries);//注意傳入的參數zip即可//...}//frameworks/base/core/java/com/android/internal/os/ClassLoaderFactory.java public static ClassLoader createClassLoader(String dexPath,String librarySearchPath, String libraryPermittedPath, ClassLoader parent,int targetSdkVersion, boolean isNamespaceShared, String classLoaderName,List<ClassLoader> sharedLibraries, List<String> nativeSharedLibraries) {final ClassLoader classLoader = createClassLoader(dexPath, librarySearchPath, parent,classLoaderName, sharedLibraries);//...}public static ClassLoader createClassLoader(String dexPath,String librarySearchPath, ClassLoader parent, String classloaderName,List<ClassLoader> sharedLibraries) {ClassLoader[] arrayOfSharedLibraries = (sharedLibraries == null)? null: sharedLibraries.toArray(new ClassLoader[sharedLibraries.size()]);ClassLoader result = null;//一般由于mApplicationInfo.classLoaderName沒有設置,故默認創建的都是PathClassLoaderif (isPathClassLoaderName(classloaderName)) {return new PathClassLoader(dexPath, librarySearchPath, parent, arrayOfSharedLibraries);} else if (isDelegateLastClassLoaderName(classloaderName)) {//如果有設置classloaderName = "dalvik.system.DelegateLastClassLoader"則進入這里return new DelegateLastClassLoader(dexPath, librarySearchPath, parent, arrayOfSharedLibraries);}throw new AssertionError("Invalid classLoaderName: " + classloaderName);}5、我們到了另外代碼文件目錄libcore/dalvik/,繼續查看libcore/dalvik/src/main/java/dalvik/system/PathClassLoader.java
//libcore/dalvik/src/main/java/dalvik/system/PathClassLoader.javapublic PathClassLoader(@NonNull String dexPath, @Nullable String librarySearchPath, @Nullable ClassLoader parent,@Nullable ClassLoader[] sharedLibraryLoaders) {super(dexPath, librarySearchPath, parent, sharedLibraryLoaders);}//libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.javapublic BaseDexClassLoader(String dexPath,String librarySearchPath, ClassLoader parent, ClassLoader[] libraries) {this(dexPath, librarySearchPath, parent, libraries, false);}public BaseDexClassLoader(String dexPath,String librarySearchPath, ClassLoader parent, ClassLoader[] sharedLibraryLoaders,boolean isTrusted) {super(parent);// Setup shared libraries before creating the path list. ART relies on the class loader// hierarchy being finalized before loading dex files.this.sharedLibraryLoaders = sharedLibraryLoaders == null? null: Arrays.copyOf(sharedLibraryLoaders, sharedLibraryLoaders.length);//注意此處開始構建new DexPathList//dexPath就是/data/app/***/com.tencent.mm***/base.apkthis.pathList = new DexPathList(this, dexPath, librarySearchPath, null, isTrusted);// Run background verification after having set 'pathList'.this.pathList.maybeRunBackgroundVerification(this);reportClassLoaderChain();}//libcore/dalvik/src/main/java/dalvik/system/DexPathList.java//這里就回到了這個章節的第3點DexPathList(ClassLoader definingContext, String dexPath,String librarySearchPath, File optimizedDirectory, boolean isTrusted) {//...// save dexPath for BaseDexClassLoaderthis.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,suppressedExceptions, definingContext, isTrusted);//...}6、到這里基本上可以理清楚
6. 斷點看一下帶tinker和不帶tinker的ClassLoader
打開的dex文件就是/data/app/***/com.tencent.mm***/base.apk,微信默認安裝的apk
mClassLoader = {PathClassLoader@33282} “dalvik.system.PathClassLoader[DexPathList[[zip file “/data/app/~~IhVsaxjwrzj_c6zzMznrww==/com.tencent.mm-hX9AatQT4nETNnGgbug_GA==/base.apk”],nativeLibraryDirectories=[/data/app/~~IhVsaxjwrzj_c6zzMznrww==/com.tencent.mm-hX9AatQT4nETNnGgbug_GA==/lib/arm, /data/app/~~IhVsaxjwrzj_c6zzMznrww==/com.tencent.mm-hX9AatQT4nETNnGgbug_GA==/base.apk!/lib/armeabi-v7a, /system/lib, /system/system_ext/lib]]]”
可以看到打開的dex文件就是/data/user/0/com.tencent.mm/tinker/patch-***/dex/tinker_classN.apk,這個就是微信熱更新里面的tinker文件,而不是我們一開始安裝的文件
mClassLoader = {DelegateLastClassLoader@15795} “dalvik.system.DelegateLastClassLoader[DexPathList[[zip file “/data/user/0/com.tencent.mm/tinker/patch-66e50d2a/dex/tinker_classN.apk”],nativeLibraryDirectories=[/data/user/0/com.tencent.mm/tinker/patch-66e50d2a/lib/lib/armeabi-v7a, /data/app/~~Gcugv7eeiy5Og6ory1jVrg==/com.tencent.mm-OKmJgE3WNLcG_AaLmxxzw==/lib/arm, /data/app/~~Gldgv7eeiy5Og6ory1jVrg==/com.tencent.mm-OKmJgE3WNLcG_AaLmxxzw==/base.apk!/lib/armeabi-v7a, /system/lib, /system/system_ext/lib]]]”
parent = {PathClassLoader@125091} “dalvik.system.PathClassLoader[DexPathList[[zip file “/data/app/~~IhVsaxjwrzj_c6zzMznrww==/com.tencent.mm-hX9AatQT4nETNnGgbug_GA==/base.apk”],nativeLibraryDirectories=[/data/app/~~IhVsaxjwrzj_c6zzMznrww==/com.tencent.mm-hX9AatQT4nETNnGgbug_GA==/lib/arm, /data/app/~~Gldgv7eeiy5Og6ory1jVrg==/com.tencent.mm-hX9AatQT4nETNnGgbug_GA==/base.apk!/lib/armeabi-v7a, /system/lib, /system/system_ext/lib]]]”
7. 微信內部tinker加載的流程
那么一個簡單的想法就是在微信設置DelegateLastClassLoader的時候還原成PathClassLoader,但是如果只修改LoadedApk.java,
你會發現會導致微信崩潰,也就是說修改不完善,還有別的初始化內容沒有還原
繼續在系統所有mClassLoader =的地方添加日志,發現根本就沒有跑系統代碼,
那只能是app本身調用的函數注入、反射等來實現設置的功能,這樣問題就比調用系統方法復雜。
1、分析三方應用有多種方法,如反編譯工具
jadx-gui-***.exe、jd-gui.exe => 這個可以直接得到java代碼,比較容易看,不過缺點是部分代碼轉換失敗
java -jar apktool_***.jar d + 路徑 => 這個是反編譯成class,并將class轉換成smali,不太好看(主要應該是不習慣,看得少),但是不會漏掉
2、反編譯之后看流程
源碼中從handleBindApplication開始,到Application.java的attach后進入微信重載流程
handleBindApplication(ActivityThread.java)->makeApplication(LoadedApk.java)->newApplication(Instrumentation.java)->attach(Application.java)
下面將這些流程貼一下
attach(Application.java) -> attachBaseContext/onBaseContextAttached/loadTinker(TinkerApplication.java) -> tryLoad/tryLoadPatchFilesInternal(判斷是否存在tinker的各類文件,patch.info(getPatchInfoFile)在這里判斷)(TinkerLoader.java) -> loadTinkerJars(TinkerDexLoader.java) -> installDexes(SystemClassLoaderAdder.java) -> inject(NewClassLoaderInjector.java) ->createNewClassLoader/doInject(NewClassLoaderInjector.java) -> Thread setContextClassLoader/ContextWrapper mBase mClassLoader/ContextImpl mPackageInfo mClassLoader
8. Android S優化tinker導致啟動慢的方案
1、參考Google play,不允許tinker其實功能還是可以正常運行的,那就還是限制tinker使用方面去。
2、重新做一次其針對該手機的dex優化,例如在新增known secondary dex files的時候,做一次dexOptSecondaryDexPathLI,
插莊的位置可以放在notifyDexLoadInternal。不過這個會涉及一系列問題,有多種場景可能會導致patch失效。(微信最新版本有odex文件的情況下提升不是很大)
3、限制tinker其實也是有很多種方法,通過上面的流程分析,可以在任意一段代碼卡住tinker流程即可。
如不讓tinker下載(com.tencent.mm:patch進程啟動com.tencent.mm/com.tencent.tinker.lib.service.TinkerPatchForeService}服務下載的)、或者tinker apk下載后刪除、或者阻斷tinker的識別patch.info等
這樣一看,貌似還是2更簡單一點。
最后提供一個思路,其實將patch.info刪除即可(其它騰訊系或者使用tinker的apk都可以用這類方法),刪除的位置可以放在handleBindApplication(ActivityThread.java)調用makeApplication(LoadedApk.java)之前即可
至于其它方法這里就不繼續討論了,各位有興趣自己嘗試一下
總結
以上是生活随笔為你收集整理的微信tinker导致冷启动变慢的问题优化的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 机器视觉python推荐书籍_智能硬件与
- 下一篇: centos8安装向日葵报错解决