微信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是否包含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<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、到这里基本上可以理清楚
- Open Dex是什么时候触发的? => 在创建初始化ClassLoader的时候会触发
- 其中传入的location又是那里来的? => 这个就是DexPathList,对应zip file,如***.apk
6. 断点看一下带tinker和不带tinker的ClassLoader
- 设置微信断点位置
//frameworks/base/core/java/android/app/LoadedApk.java
public 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 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) {
}
}
}
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流程即可。
- 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);//放着这里之前就行了