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

微信tinker导致冷启动变慢的问题优化,第1张

微信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的状态问题

  1. 是否有进行oat dex(例如bg dex或者其它类型dex),oat文件和art文件是否正常
  2. 是否未保护常用通讯类软件,用户感知度强的这类,一般都不建议随意回收,
  3. 老问题是否,存在tinker(微信热更新,在Google play是禁止这类行为的,一般出现在国内下载的app)
  4. 其它情况
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>* dex_files) const {
  ScopedTrace trace("Dex file open from Zip " + std::string(location));
//...
  //识别location是否包含tinker
  if (hasTinker) {//包含则跳过
    return false;
  }
//...
}

2、那么Android S现在art修改方案无效,我们怎么做呢?
还是那句话:先调查清楚,再来动笔

5. Open Dex是什么时候触发的?其中传入的location又是那里来的?

1、从OatFileManager::OpenDexFilesFromOat往上找
//

art/runtime/native/dalvik_system_DexFile.cc

static 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 error_msgs;
  const OatFile* oat_file = nullptr;
  std::vector> dex_files =
      Runtime::Current()->GetOatFileManager().OpenDexFilesFromOat(sourceName.c_str(),
                                                                  class_loader,
                                                                  dex_elements,
                                                                   &oat_file,
                                                                   &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 }, 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 baseDexClassLoader
        this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
                                           suppressedExceptions, definingContext, isTrusted);
        //...
    }

    private static Element[] makeDexElements(List files, File optimizedDirectory,
            List 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) {//初始化时这个是null
            return 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.java
    public ClassLoader getClassLoader() {
        synchronized (mLock) {
            if (mClassLoader == null) {
                createOrUpdateClassLoaderLocked(null );
            }
            return mClassLoader;
        }
    }

   private void createOrUpdateClassLoaderLocked(List addedPaths) {
        //..
        //mApplicationInfo.sourceDir就是/data/appcom.tencent.mm***/base.apk
        makePaths(mActivityThread, isBundledApp, mApplicationInfo, zipPaths, libPaths);
        //...
        final String zip = (zipPaths.size() == 1) ? zipPaths.get(0) :
                TextUtils.join(File.pathSeparator, zipPaths);//zip就是/data/appcom.tencent.mm***/base.apk
        //...
        if (mDefaultClassLoader == null) {
            //...
            //创建mDefaultClassLoader
            mDefaultClassLoader = ApplicationLoaders.getDefault().getClassLoaderWithSharedLibraries(
                    zip, mApplicationInfo.targetSdkVersion, isBundledApp, librarySearchPath,
                    libraryPermittedPath, mbaseClassLoader,
                    mApplicationInfo.classLoaderName, sharedLibraries, nativeSharedLibraries);
            //微信的mAppComponentFactory = androidx.core.app.CoreComponentFactory
            mAppComponentFactory = createAppFactory(mApplicationInfo, mDefaultClassLoader);
            //...
        }
        //...
        if (mClassLoader == null) {
            //通过mAppComponentFactory创建mClassLoader
            mClassLoader = mAppComponentFactory.instantiateClassLoader(mDefaultClassLoader,
                    new ApplicationInfo(mApplicationInfo));
        }
    }

继续看一下ApplicationLoaders.getDefault().getClassLoaderWithSharedLibraries

//frameworks/base/core/java/android/app/ApplicationLoaders.java
    ClassLoader getClassLoaderWithSharedLibraries(
            String zip, int targetSdkVersion, boolean isBundled,
            String librarySearchPath, String libraryPermittedPath,
            ClassLoader parent, String classLoaderName,
            List sharedLibraries, List 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 sharedLibraries,
                                       List 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 sharedLibraries, List nativeSharedLibraries) {

        final ClassLoader classLoader = createClassLoader(dexPath, librarySearchPath, parent,
                classLoaderName, sharedLibraries);
        //...
    }

    public static ClassLoader createClassLoader(String dexPath,
            String librarySearchPath, ClassLoader parent, String classloaderName,
            List sharedLibraries) {
        ClassLoader[] arrayOfSharedLibraries = (sharedLibraries == null)
                ? null
                : sharedLibraries.toArray(new ClassLoader[sharedLibraries.size()]);

        ClassLoader result = null;

		//一般由于mApplicationInfo.classLoaderName没有设置,故默认创建的都是PathClassLoader
        if (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.java
    public 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.java
    public 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/appcom.tencent.mm***/base.apk
        this.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 baseDexClassLoader
        this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
                                           suppressedExceptions, definingContext, isTrusted);
        //...
    }

6、到这里基本上可以理清楚

  1. Open Dex是什么时候触发的? => 在创建初始化ClassLoader的时候会触发
  2. 其中传入的location又是那里来的? => 这个就是DexPathList,对应zip file,如***.apk
6. 断点看一下带tinker和不带tinker的ClassLoader
  1. 设置微信断点位置
//frameworks/base/core/java/android/app/LoadedApk.java
    public ClassLoader getClassLoader() {
        synchronized (mLock) {
            if (mClassLoader == null) {//可以在这里设置断点位置
                createOrUpdateClassLoaderLocked(null );
            }
            return mClassLoader;
        }
    }
  1. 默认安装后不带tinker的ClassLoader
    打开的dex文件就是/data/appcom.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]]]”

  1. 带有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 );
            }
            // yunhen test start
			if (mResDir != null && mResDir.contains("tinker")) {
				mClassLoader = mDefaultClassLoader;
				if(mSourceDir != null) {
					mResDir = mSourceDir;
				}
			}
            // yunhen test end
            return 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
     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代表tinkerFlags
        super(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();//加载微信tinker
            this.mCurrentClassLoader = context.getClassLoader();//此处已经是加载过后,ClassLoader变成了tinker的ClassLoader
            this.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.java
    public static boolean isTinkerEnabled(int i) {
        return i != 0;
    }

//TinkerLoader.java
    public 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) {
        
        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 64
        invoke-virtual/range {p1 .. p1}, Lcom/tencent/tinker/loader/app/TinkerApplication;->getTinkerFlags()I    //获取tinkerFlags

        move-result v6

                .line 66
        invoke-static {v6}, Lcom/tencent/tinker/loader/shareutil/ShareTinkerInternals;->isTinkerEnabled(I)Z //调用isTinkerEnabled判断是否需要支持tinker

        move-result v2

        if-nez v2, :cond_0   //nez(not equal zero),如果tinkerFlags不为0,则v2=true进入cond_0,否则v2=false,进入下面逻辑,

                .line 67
    const-string/jumbo v2, "Tinker.TinkerLoader"

    const-string/jumbo v3, "tryLoadPatchFiles: tinker is disable, just return" //这里描述也很清楚,tinker不支持,返回不走tinker流程

    const/4 v4, 0x0

        new-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 68
    const/4 v2, -0x1

        move-object/from16 v0, p2

        invoke-static {v0, v2}, Lcom/tencent/tinker/loader/shareutil/ShareIntentUtil;->setIntentReturnCode(Landroid/content/Intent;I)V

                .line 392
    :goto_0
        return-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_3
        invoke-static {v10}, Lcom/tencent/tinker/loader/shareutil/SharePatchFileUtil;->getPatchInfoFile(Ljava/lang/String;)Ljava/io/File;

        move-result-object v11

                .line 97
        invoke-virtual {v11}, Ljava/io/File;->exists()Z

        move-result v2

        if-nez v2, :cond_4

                .line 98
    const-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等资源相关

//...
   :cond_1c
        if-nez v17, :cond_1f  //v17是isArkHotRuning针对huawei做的适配,一般都是false

        if-eqz v16, :cond_1f  //v16是isTinkerEnabledForDex的结果不为0

        move-object/from16 v2, p1

        move-object/from16 v5, p2

                .line 325
        //这个是调用加载tinker的下一步loadTinkerJars
        invoke-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)Z

        move-result v4

                .line 327
        if-eqz v6, :cond_29

//TinkerDexLoader.java
    public 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.apk
                    File 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.apk
                    SystemClassLoaderAdder.installDexes(tinkerApplication, classLoader, file3, arrayList, z2, tinkerApplication.isUseDelegateLastClassLoader());
                    return true;
                //...
    }

//SystemClassLoaderAdder.java
    public static void installDexes(Application application, ClassLoader classLoader, File file, List list, boolean z, boolean z2) {
        ShareTinkerLog.i(TAG, "installDexes dexOptDir: " + file.getAbsolutePath() + ", dex size:" + list.size(), new Object[0]);
        if (!list.isEmpty()) {
            List createSortedAdditionalPathEntries = createSortedAdditionalPathEntries(list);
            if (Build.VERSION.SDK_INT < 24 || z) {
                injectDexesInternal(classLoader, createSortedAdditionalPathEntries, file);
            } else {
                //Android S走的是这里,注入,其实就是反射调用系统代码, createSortedAdditionalPathEntries包含tinker_classN.apk
                classLoader = 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.java

    public static ClassLoader inject(Application application, ClassLoader classLoader, File file, boolean z, List 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创建ClassLoader
        ClassLoader 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 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.apk
        Field 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.apk
            tinkerClassLoader = 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 mbase
        try {
            findField(context.getClass(), "mClassLoader").set(context, classLoader);//将tinker的ClassLoader赋值给ContextImpl mClassLoader
        } catch (Throwable th) {
        }
        Object obj = findField(context.getClass(), "mPackageInfo").get(context);//ContextImpl mPackageInfo
        findField(obj.getClass(), "mClassLoader").set(obj, classLoader);//将tinker的ClassLoader赋值给ContextImpl mPackageInfo
        if (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 mClassLoader
                Object obj2 = findField(resources.getClass(), "mDrawableInflater").get(resources);//DrawableInflater mDrawableInflater
                if (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.apk
      arm: [status=verify] [reason=install]
      known secondary dex files:
        /data/user/0/com.tencent.mm/app_xwalk_3164/apk/base.apk
          class loader context: PCL[];PCL[]
        /data/user/0/com.tencent.mm/tinker/patch-66e50d2a/dex/tinker_classN.apk
          class loader context: DLC[];PCL[]
        /data/user/0/com.tencent.mm/app_xwalkplugin/XFilesPPTReader_338/extracted/pptreader.apk

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

  1. Android S之前,如Anroid O、P、Q、R等,可以直接在art中跳过
  2. 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等
  3. 可以将微信设置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);//放着这里之前就行了

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5685107.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存