日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

微信tinker导致冷启动变慢的问题优化

發布時間:2023/12/31 编程问答 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 微信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的狀態問題

  • 是否有進行oat dex(例如bg dex或者其它類型dex),oat文件和art文件是否正常
  • 是否未保護常用通訊類軟件,用戶感知度強的這類,一般都不建議隨意回收,
  • 老問題是否,存在tinker(微信熱更新,在Google play是禁止這類行為的,一般出現在國內下載的app)
  • 其它情況
  • 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往上找
    //

    art/runtime/native/dalvik_system_DexFile.ccstatic jobject DexFile_openDexFileNative(JNIEnv* env,jclass,jstring javaSourceName,jstring javaOutputName ATTRIBUTE_UNUSED,jint flags ATTRIBUTE_UNUSED,jobject class_loader,jobjectArray dex_elements) {ScopedUtfChars sourceName(env, javaSourceName);if (sourceName.c_str() == nullptr) {return nullptr;}std::vector<std::string> error_msgs;const OatFile* oat_file = nullptr;std::vector<std::unique_ptr<const DexFile>> dex_files =Runtime::Current()->GetOatFileManager().OpenDexFilesFromOat(sourceName.c_str(),class_loader,dex_elements,/*out*/ &oat_file,/*out*/ &error_msgs);return CreateCookieFromOatFileManagerResult(env, dex_files, oat_file, error_msgs); }//這是一個jni過來的方法 static JNINativeMethod gMethods[] = {NATIVE_METHOD(DexFile, openDexFileNative,"(Ljava/lang/String;""Ljava/lang/String;""I""Ljava/lang/ClassLoader;""[Ldalvik/system/DexPathList$Element;"")Ljava/lang/Object;"),

    2、這里上一級目錄在libcore中libcore/dalvik/src/main/java/dalvik/system/DexFile.java,
    在創建DexFile對象的時候就會打開dex file

    private static Object openDexFile(String sourceName, String outputName, int flags,ClassLoader loader, DexPathList.Element[] elements) throws IOException {// Use absolute paths to enable the use of relative paths when testing on host.return openDexFileNative(new File(sourceName).getAbsolutePath(),(outputName == null)? null: new File(outputName).getAbsolutePath(),flags,loader,elements);}private DexFile(String sourceName, String outputName, int flags, ClassLoader loader,DexPathList.Element[] elements) throws IOException {//...mCookie = openDexFile(sourceName, outputName, flags, loader, elements);mInternalCookie = mCookie;mFileName = sourceName;//System.out.println("DEX FILE cookie is " + mCookie + " sourceName=" + sourceName + " outputName=" + outputName);}

    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);

    DexPathList(ClassLoader definingContext, String dexPath,String librarySearchPath, File optimizedDirectory, boolean isTrusted) {//...// save dexPath for BaseDexClassLoaderthis.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,suppressedExceptions, definingContext, isTrusted);//...}private static Element[] makeDexElements(List<File> files, File optimizedDirectory,List<IOException> suppressedExceptions, ClassLoader loader, boolean isTrusted) {//...dex = loadDexFile(file, optimizedDirectory, loader, elements);//...}private static DexFile loadDexFile(File file, File optimizedDirectory, ClassLoader loader,Element[] elements)throws IOException {if (optimizedDirectory == null) {//初始化時這個是nullreturn new DexFile(file, loader, elements);} else {String optimizedPath = optimizedPathFor(file, optimizedDirectory);return DexFile.loadDex(file.getPath(), optimizedPath, 0, loader, elements);//打開微信***.apk走的是這里}}

    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、到這里基本上可以理清楚

  • Open Dex是什么時候觸發的? => 在創建初始化ClassLoader的時候會觸發
  • 其中傳入的location又是那里來的? => 這個就是DexPathList,對應zip file,如***.apk
  • 6. 斷點看一下帶tinker和不帶tinker的ClassLoader

  • 設置微信斷點位置
  • //frameworks/base/core/java/android/app/LoadedApk.javapublic ClassLoader getClassLoader() {synchronized (mLock) {if (mClassLoader == null) {//可以在這里設置斷點位置createOrUpdateClassLoaderLocked(null /*addedPaths*/);}return mClassLoader;}}
  • 默認安裝后不帶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]]]”

  • 帶有tinker的ClassLoader,但是默認的PathClassLoader其實也是創建的,相當于創建了2個ClassLoader(注意此處需要載入2個apk,本身需要的時間就會變長)
    可以看到打開的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,
    你會發現會導致微信崩潰,也就是說修改不完善,還有別的初始化內容沒有還原

    public ClassLoader getClassLoader() {synchronized (mLock) {if (mClassLoader == null) {createOrUpdateClassLoaderLocked(null /*addedPaths*/);}// yunhen test startif (mResDir != null && mResDir.contains("tinker")) {mClassLoader = mDefaultClassLoader;if(mSourceDir != null) {mResDir = mSourceDir;}}// yunhen test endreturn mClassLoader;}}

    繼續在系統所有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

    //frameworks/base/core/java/android/app/Application.java/* package */ final void attach(Context context) {attachBaseContext(context);mLoadedApk = ContextImpl.getImpl(context).mPackageInfo;}// Application.java package com.tencent.mm.app;import com.tencent.tinker.loader.app.TinkerApplication;public class Application extends TinkerApplication {private static final String TINKER_LOADER_ENTRY_CLASSNAME = "com.tencent.tinker.loader.TinkerLoader";private static final String WECHAT_APPLICATION_LIKE_CLASSNAME = "com.tencent.mm.app.MMApplicationLike";public Application() {//微信的TinkerLoader(TINKER_LOADER_ENTRY_CLASSNAME = com.tencent.tinker.loader.TinkerLoader),同時7代表tinkerFlagssuper(7, WECHAT_APPLICATION_LIKE_CLASSNAME, TINKER_LOADER_ENTRY_CLASSNAME, true, true);} }//TinkerApplication.java package com.tencent.tinker.loader.app;protected TinkerApplication(int i, String str, String str2, boolean z, boolean z2) {this.mCurrentClassLoader = null;this.mInlineFence = null;synchronized (SELF_HOLDER) {SELF_HOLDER[0] = this;}this.tinkerFlags = i;this.delegateClassName = str;this.loaderClassName = str2;this.tinkerLoadVerifyFlag = z;this.useDelegateLastClassLoader = z2;}public void attachBaseContext(Context context) {super.attachBaseContext(context);long elapsedRealtime = SystemClock.elapsedRealtime();long currentTimeMillis = System.currentTimeMillis();Thread.setDefaultUncaughtExceptionHandler(new TinkerUncaughtHandler(this));onBaseContextAttached(context, elapsedRealtime, currentTimeMillis);//這里是tinker的下一步流程}public void onBaseContextAttached(Context context, long j, long j2) {try {loadTinker();//加載微信tinkerthis.mCurrentClassLoader = context.getClassLoader();//此處已經是加載過后,ClassLoader變成了tinker的ClassLoaderthis.mInlineFence = createInlineFence(this, this.tinkerFlags, this.delegateClassName, this.tinkerLoadVerifyFlag, j, j2, this.tinkerResultIntent);TinkerInlineFenceAction.callOnBaseContextAttached(this.mInlineFence, context);if (this.useSafeMode) {ShareTinkerInternals.setSafeModeCount(this, 0);}} catch (TinkerRuntimeException e2) {throw e2;} catch (Throwable th) {throw new TinkerRuntimeException(th.getMessage(), th);}}private static final String TINKER_LOADER_METHOD = "tryLoad";private void loadTinker() {try {Class<?> cls = Class.forName(this.loaderClassName, false, TinkerApplication.class.getClassLoader());//調用的是TinkerLoader的tryLoad的方法(用的反射調用,應該是tinker是一個公共組件才這么做)this.tinkerResultIntent = (Intent) cls.getMethod(TINKER_LOADER_METHOD, TinkerApplication.class).invoke(cls.getConstructor(new Class[0]).newInstance(new Object[0]), this);} catch (Throwable th) {this.tinkerResultIntent = new Intent();ShareIntentUtil.setIntentReturnCode(this.tinkerResultIntent, -20);this.tinkerResultIntent.putExtra("intent_patch_exception", th);}}//ShareTinkerInternals.javapublic static boolean isTinkerEnabled(int i) {return i != 0;}//TinkerLoader.javapublic Intent tryLoad(TinkerApplication tinkerApplication) {ShareTinkerLog.d(TAG, "tryLoad test test", new Object[0]);Intent intent = new Intent();long elapsedRealtime = SystemClock.elapsedRealtime();tryLoadPatchFilesInternal(tinkerApplication, intent);ShareIntentUtil.setIntentPatchCostTime(intent, SystemClock.elapsedRealtime() - elapsedRealtime);return intent;}private void tryLoadPatchFilesInternal(com.tencent.tinker.loader.app.TinkerApplication r22, android.content.Intent r23) {/*// Method dump skipped, instructions count: 1301 這里反編譯失敗,通過apktool去拿到TinkerLoader.smali可以看到里面的虛擬機代碼*/throw new UnsupportedOperationException("Method not decompiled: com.tencent.tinker.loader.TinkerLoader.tryLoadPatchFilesInternal(com.tencent.tinker.loader.app.TinkerApplication, android.content.Intent):void");.locals 21.prologue.line 64invoke-virtual/range {p1 .. p1}, Lcom/tencent/tinker/loader/app/TinkerApplication;->getTinkerFlags()I //獲取tinkerFlagsmove-result v6.line 66invoke-static {v6}, Lcom/tencent/tinker/loader/shareutil/ShareTinkerInternals;->isTinkerEnabled(I)Z //調用isTinkerEnabled判斷是否需要支持tinkermove-result v2if-nez v2, :cond_0 //nez(not equal zero),如果tinkerFlags不為0,則v2=true進入cond_0,否則v2=false,進入下面邏輯,.line 67const-string/jumbo v2, "Tinker.TinkerLoader"const-string/jumbo v3, "tryLoadPatchFiles: tinker is disable, just return" //這里描述也很清楚,tinker不支持,返回不走tinker流程const/4 v4, 0x0new-array v4, v4, [Ljava/lang/Object;invoke-static {v2, v3, v4}, Lcom/tencent/tinker/loader/shareutil/ShareTinkerLog;->w(Ljava/lang/String;Ljava/lang/String;[Ljava/lang/Object;)V.line 68const/4 v2, -0x1move-object/from16 v0, p2invoke-static {v0, v2}, Lcom/tencent/tinker/loader/shareutil/ShareIntentUtil;->setIntentReturnCode(Landroid/content/Intent;I)V.line 392:goto_0return-void //...不關注的流程我們跳過吧,這里只是目的告知大家方法和自己記錄一下分析過程,不是去剖析微信代碼 //后面判斷是否在做tinker的過程中、是否存在tinker目錄 //其中tinker的目錄的邏輯,一般是在applicationInfo.dataDir的tinker目錄,如Android S的/data/user/0/com.tencent.mm/tinker,微信針對oppo特殊做了手腳,放在wc_tinker_dir //如果不存在tinker目錄,不走tinker流程,此處直接返回public static final String PATCH_DIRECTORY_NAME = "tinker";public static final String PATCH_DIRECTORY_NAME_SPEC = "wc_tinker_dir";public static File getPatchDirectory(Context context) {ApplicationInfo applicationInfo = context.getApplicationInfo();if (applicationInfo == null) {return null;}return new File(applicationInfo.dataDir, (!"oppo".equalsIgnoreCase(Build.MANUFACTURER) || Build.VERSION.SDK_INT != 22) ? ShareConstants.PATCH_DIRECTORY_NAME : ShareConstants.PATCH_DIRECTORY_NAME_SPEC);} //... //這里有判斷/data/user/0/com.tencent.mm/tinker/patch.info是否存在,如果不存在則不走tinker流程:cond_3invoke-static {v10}, Lcom/tencent/tinker/loader/shareutil/SharePatchFileUtil;->getPatchInfoFile(Ljava/lang/String;)Ljava/io/File; /*public static File getPatchInfoFile(String str) {return new File(str + "/patch.info");} */move-result-object v11.line 97invoke-virtual {v11}, Ljava/io/File;->exists()Zmove-result v2if-nez v2, :cond_4.line 98const-string/jumbo v2, "Tinker.TinkerLoader"new-instance v3, Ljava/lang/StringBuilder;const-string/jumbo v4, "tryLoadPatchFiles:patch info not exist:" //patch.info文件不存在 //... goto/16 :goto_0.line 104 //... info.lock是PatchInfo同步鎖,防止讀取時多線程邏輯異常//... 讀取patchInfo的信息,如識別的目的主要是為了選取patch-66e50d2a目錄的內容//... 其它如intent_is_protected_app設置,版本識別(識別異常則返回,成功會將舊的tinker(如果有多個tinker的話)刪除)、檢查tinker ota文件是否正確是否需要重新生成,一堆異常處理 //... isTinkerEnabledForResource是否需要加載/data/user/0/com.tencent.mm/tinker/patch***/res/resources.apk // (loadTinkerResources除了會設置resources.apk, 還會對設置mResDir/publicSourceDir = "/data/user/0/com.tencent.mm/tinker/patch-***/res/resources.apk"),addAssetPath/mAssets等資源相關 /* 各類需要加載的內容默認i=7, 也就是isTinkerEnabledForDex、isTinkerEnabledForNativeLib、isTinkerEnabledForResource返回truepublic static boolean isTinkerEnabledForDex(int i) {return (i & 1) != 0;}public static boolean isTinkerEnabledForNativeLib(int i) {return (i & 2) != 0;}public static boolean isTinkerEnabledForResource(int i) {return (i & 4) != 0;}public static boolean isTinkerEnabledForArkHot(int i) {return (i & 8) != 0;} */ //...:cond_1cif-nez v17, :cond_1f //v17是isArkHotRuning針對huawei做的適配,一般都是falseif-eqz v16, :cond_1f //v16是isTinkerEnabledForDex的結果不為0move-object/from16 v2, p1move-object/from16 v5, p2.line 325//這個是調用加載tinker的下一步loadTinkerJarsinvoke-static/range {v2 .. v7}, Lcom/tencent/tinker/loader/TinkerDexLoader;->loadTinkerJars(Lcom/tencent/tinker/loader/app/TinkerApplication;Ljava/lang/String;Ljava/lang/String;Landroid/content/Intent;ZZ)Zmove-result v4.line 327if-eqz v6, :cond_29//TinkerDexLoader.javapublic static boolean loadTinkerJars(TinkerApplication tinkerApplication, String str, String str2, Intent intent, boolean z, boolean z2) {if (!LOAD_DEX_LIST.isEmpty() || !classNDexInfo.isEmpty()) {ClassLoader classLoader = TinkerDexLoader.class.getClassLoader();if (classLoader != null) {//...if (isVmArt && !classNDexInfo.isEmpty()) {android S上, isVmArt = true//file2就是data/user/0/com.tencent.mm/tinker/patch-***/dex/tinker_classN.apkFile file2 = new File(str3 + ShareConstants.CLASS_N_APK_NAME);//CLASS_N_APK_NAME = "tinker_classN.apk";//...arrayList.add(file2);}//...if (z) {//...//微信也可以手動調用dex2oat,目前這個沒跑,tinker_classN.odex應該是下載的TinkerDexOptimizer.optimizeAll(tinkerApplication, arrayList, file4, true, tinkerApplication.isUseDelegateLastClassLoader(), currentInstructionSet, new TinkerDexOptimizer.ResultCallback() {//...}try {//installDexes是tinker的下一步流程, arrayList包含tinker_classN.apkSystemClassLoaderAdder.installDexes(tinkerApplication, classLoader, file3, arrayList, z2, tinkerApplication.isUseDelegateLastClassLoader());return true;//...}//SystemClassLoaderAdder.javapublic static void installDexes(Application application, ClassLoader classLoader, File file, List<File> list, boolean z, boolean z2) {ShareTinkerLog.i(TAG, "installDexes dexOptDir: " + file.getAbsolutePath() + ", dex size:" + list.size(), new Object[0]);if (!list.isEmpty()) {List<File> createSortedAdditionalPathEntries = createSortedAdditionalPathEntries(list);if (Build.VERSION.SDK_INT < 24 || z) {injectDexesInternal(classLoader, createSortedAdditionalPathEntries, file);} else {//Android S走的是這里,注入,其實就是反射調用系統代碼, createSortedAdditionalPathEntries包含tinker_classN.apkclassLoader = NewClassLoaderInjector.inject(application, classLoader, file, z2, createSortedAdditionalPathEntries);}sPatchDexCount = createSortedAdditionalPathEntries.size();ShareTinkerLog.i(TAG, "after loaded classloader: " + classLoader + ", dex size:" + sPatchDexCount, new Object[0]);if (!checkDexInstall(classLoader)) {uninstallPatchDex(classLoader);throw new TinkerRuntimeException(ShareConstants.CHECK_DEX_INSTALL_FAIL);}}}//NewClassLoaderInjector.javapublic static ClassLoader inject(Application application, ClassLoader classLoader, File file, boolean z, List<File> list) {String[] strArr = new String[list.size()];for (int i = 0; i < strArr.length; i++) {strArr[i] = list.get(i).getAbsolutePath();//此流程中strArr包含tinker_classN.apk}//眾里尋他千百度,終于來了,createNewClassLoader創建ClassLoaderClassLoader createNewClassLoader = createNewClassLoader(classLoader, file, z, true, strArr);//將新的class loader反射設置到系統中去doInject(application, createNewClassLoader);return createNewClassLoader;}private static ClassLoader createNewClassLoader(ClassLoader classLoader, File file, boolean z, boolean z2, String... strArr) {List<File> list;ClassLoader tinkerClassLoader;Object obj = findField(Class.forName("dalvik.system.BaseDexClassLoader", false, classLoader), "pathList").get(classLoader);StringBuilder sb = new StringBuilder();if (strArr != null && strArr.length > 0) {for (int i = 0; i < strArr.length; i++) {if (i > 0) {sb.append(File.pathSeparator);}sb.append(strArr[i]);}}String sb2 = sb.toString();//此流程中sb2包含tinker_classN.apkField findField = findField(obj.getClass(), "nativeLibraryDirectories");if (findField.getType().isArray()) {list = Arrays.asList((File[]) findField.get(obj));} else {list = (List) findField.get(obj);}StringBuilder sb3 = new StringBuilder();boolean z3 = true;for (File file2 : list) {if (file2 != null) {if (z3) {z3 = false;} else {sb3.append(File.pathSeparator);}sb3.append(file2.getAbsolutePath());}}String sb4 = sb3.toString();if (!z || Build.VERSION.SDK_INT < 27) {tinkerClassLoader = new TinkerClassLoader(sb2, file, sb4, classLoader);} else {//目前Android S中微信使用的是DelegateLastClassLoader//DelegateLastClassLoader(String dexPath, String librarySearchPath, ClassLoader parent)//sb2是dexPath包含tinker_classN.apktinkerClassLoader = new DelegateLastClassLoader(sb2, sb4, ClassLoader.getSystemClassLoader());Field declaredField = ClassLoader.class.getDeclaredField("parent");declaredField.setAccessible(true);//設置DelegateLastClassLoader(DexPathList是/data/user/**)的parent是原來的PathClassLoader(DexPathList是/data/app**)declaredField.set(tinkerClassLoader, classLoader);}if (z2 && Build.VERSION.SDK_INT < 26) {findField(obj.getClass(), "definingContext").set(obj, tinkerClassLoader);}return tinkerClassLoader;}private static void doInject(Application application, ClassLoader classLoader) {Thread.currentThread().setContextClassLoader(classLoader);Context context = (Context) findField(application.getClass(), "mBase").get(application);//ContextWrapper mBasetry {findField(context.getClass(), "mClassLoader").set(context, classLoader);//將tinker的ClassLoader賦值給ContextImpl mClassLoader} catch (Throwable th) {}Object obj = findField(context.getClass(), "mPackageInfo").get(context);//ContextImpl mPackageInfofindField(obj.getClass(), "mClassLoader").set(obj, classLoader);//將tinker的ClassLoader賦值給ContextImpl mPackageInfoif (Build.VERSION.SDK_INT < 27) { //Android S SDK_INT > 27不跑這里Resources resources = application.getResources();//ContextImpl public Resources getResources() {return mResources;}try {findField(resources.getClass(), "mClassLoader").set(resources, classLoader);//Resources mClassLoaderObject obj2 = findField(resources.getClass(), "mDrawableInflater").get(resources);//DrawableInflater mDrawableInflaterif (obj2 != null) {findField(obj2.getClass(), "mClassLoader").set(obj2, classLoader);//DrawableInflater private final ClassLoader mClassLoader;}} catch (Throwable th2) {}}}

    8. Android S優化tinker導致啟動慢的方案

    1、參考Google play,不允許tinker其實功能還是可以正常運行的,那就還是限制tinker使用方面去。

    2、重新做一次其針對該手機的dex優化,例如在新增known secondary dex files的時候,做一次dexOptSecondaryDexPathLI,
    插莊的位置可以放在notifyDexLoadInternal。不過這個會涉及一系列問題,有多種場景可能會導致patch失效。(微信最新版本有odex文件的情況下提升不是很大)

    Dexopt state:[com.tencent.mm]path: /data/app/~~Gldgv7eeiy5Og6ory1jVrg==/com.tencent.mm-OKmJgE3WNLcG_AbdLmxxzw==/base.apkarm: [status=verify] [reason=install]known secondary dex files:/data/user/0/com.tencent.mm/app_xwalk_3164/apk/base.apkclass loader context: PCL[];PCL[]/data/user/0/com.tencent.mm/tinker/patch-66e50d2a/dex/tinker_classN.apkclass loader context: DLC[];PCL[]/data/user/0/com.tencent.mm/app_xwalkplugin/XFilesPPTReader_338/extracted/pptreader.apk

    3、限制tinker其實也是有很多種方法,通過上面的流程分析,可以在任意一段代碼卡住tinker流程即可。

  • Android S之前,如Anroid O、P、Q、R等,可以直接在art中跳過
  • Android S main line art之后,可以考慮上面tinker的一些判斷,主要是tryLoadPatchFilesInternal里面
    如不讓tinker下載(com.tencent.mm:patch進程啟動com.tencent.mm/com.tencent.tinker.lib.service.TinkerPatchForeService}服務下載的)、或者tinker apk下載后刪除、或者阻斷tinker的識別patch.info等
  • 可以將微信設置tinker后馬上還原即可, 如章節7里面的嘗試繼續根據反編譯看到的流程里面做完善(不僅僅LoadedApk.java,還有Thread、ContextImpl也需要修改,包括resource相關也需要改回去,甚至包含微信內部的邏輯也需要考慮)
  • 這樣一看,貌似還是2更簡單一點。

    最后提供一個思路,其實將patch.info刪除即可(其它騰訊系或者使用tinker的apk都可以用這類方法),刪除的位置可以放在handleBindApplication(ActivityThread.java)調用makeApplication(LoadedApk.java)之前即可
    至于其它方法這里就不繼續討論了,各位有興趣自己嘗試一下

    private void handleBindApplication(AppBindData data) {// ...try {File tinker = new File("/data/user/0/com.tencent.mm/tinker/patch.info");if (tinker.exists()) {tinker.delete();}} catch (Exception e) {Slog.w(TAG, "tinker.delete Exception e = " + e);}// ...try {// If the app is being launched for full backup or restore, bring it up in// a restricted environment with the base application class.app = data.info.makeApplication(data.restrictedBackupMode, null);//放著這里之前就行了

    總結

    以上是生活随笔為你收集整理的微信tinker导致冷启动变慢的问题优化的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。