赞
踩
首先第一个想到的就是dex的状态问题
=> 可以看到果然出现了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 .
=> 带有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]
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是否包含tinker if (hasTinker) {//包含则跳过 return false; } //... }
2、那么Android S现在art修改方案无效,我们怎么做呢?
还是那句话:先调查清楚,再来动笔
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<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 BaseDexClassLoader this.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) {//初始化时这个是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 /*addedPaths*/); } return mClassLoader; } } private void createOrUpdateClassLoaderLocked(List<String> addedPaths) { //.. //mApplicationInfo.sourceDir就是/data/app/***/com.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/app/***/com.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<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没有设置,故默认创建的都是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/app/***/com.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、到这里基本上可以理清楚
//frameworks/base/core/java/android/app/LoadedApk.java
public ClassLoader getClassLoader() {
synchronized (mLock) {
if (mClassLoader == null) {//可以在这里设置断点位置
createOrUpdateClassLoaderLocked(null /*addedPaths*/);
}
return mClassLoader;
}
}
/data/app/***/com.tencent.mm***/base.apk
,微信默认安装的apkmClassLoader = {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]]]”
/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]]]”
那么一个简单的想法就是在微信设置DelegateLastClassLoader的时候还原成PathClassLoader,但是如果只修改LoadedApk.java,
你会发现会导致微信崩溃,也就是说修改不完善,还有别的初始化内容没有还原
public ClassLoader getClassLoader() { synchronized (mLock) { if (mClassLoader == null) { createOrUpdateClassLoaderLocked(null /*addedPaths*/); } // 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 /* 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代表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) { /* // 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 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; /* public static File getPatchInfoFile(String str) { return new File(str + "/patch.info"); } */ 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等资源相关 /* 各类需要加载的内容默认i=7, 也就是isTinkerEnabledForDex、isTinkerEnabledForNativeLib、isTinkerEnabledForResource返回true public 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_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<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.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<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创建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<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.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) { } } }
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流程即可。
这样一看,貌似还是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);//放着这里之前就行了
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。