From 6347bd5bb4a9306e77de59651ba7f39361e4c494 Mon Sep 17 00:00:00 2001 From: shifujun Date: Thu, 27 Jun 2019 11:04:28 +0800 Subject: [PATCH] =?UTF-8?q?=E7=AE=80=E5=8C=96RePlugin=E5=AF=B9=E5=AE=BF?= =?UTF-8?q?=E4=B8=BBPathClassLoader=E7=9A=84=E5=A4=84=E7=90=86=EF=BC=8C?= =?UTF-8?q?=E5=8E=BB=E6=8E=89=E5=AF=B9=E7=A7=81=E6=9C=89API=E7=9A=84?= =?UTF-8?q?=E4=BE=9D=E8=B5=96=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../loader/utils/PatchClassLoaderUtils.java | 98 ------- .../main/java/com/qihoo360/loader2/PMF.java | 4 +- .../com/qihoo360/replugin/PMFClassLoader.java | 94 +++++++ .../qihoo360/replugin/RePluginCallbacks.java | 14 - .../replugin/RePluginClassLoader.java | 256 ------------------ 5 files changed, 96 insertions(+), 370 deletions(-) delete mode 100644 replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader/utils/PatchClassLoaderUtils.java create mode 100644 replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/PMFClassLoader.java delete mode 100644 replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/RePluginClassLoader.java diff --git a/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader/utils/PatchClassLoaderUtils.java b/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader/utils/PatchClassLoaderUtils.java deleted file mode 100644 index 7fbbc9af..00000000 --- a/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader/utils/PatchClassLoaderUtils.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (C) 2005-2017 Qihoo 360 Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed To in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.qihoo360.loader.utils; - -import android.app.Application; -import android.content.Context; -import android.util.Log; - -import com.qihoo360.replugin.RePlugin; -import com.qihoo360.replugin.helper.LogRelease; -import com.qihoo360.replugin.utils.ReflectUtils; - -import static com.qihoo360.replugin.helper.LogDebug.LOG; -import static com.qihoo360.replugin.helper.LogDebug.PLUGIN_TAG; -import static com.qihoo360.replugin.helper.LogRelease.LOGR; - -/** - * 对宿主的HostClassLoader做修改。这是RePlugin中唯一需要修改宿主私有属性的位置了 - * - * @author RePlugin Team - */ -public class PatchClassLoaderUtils { - - private static final String TAG = "PatchClassLoaderUtils"; - - public static boolean patch(Application application) { - try { - // 获取Application的BaseContext (来自ContextWrapper) - Context oBase = application.getBaseContext(); - if (oBase == null) { - if (LOGR) { - LogRelease.e(PLUGIN_TAG, "pclu.p: nf mb. ap cl=" + application.getClass()); - } - return false; - } - - // 获取mBase.mPackageInfo - // 1. ApplicationContext - Android 2.1 - // 2. ContextImpl - Android 2.2 and higher - // 3. AppContextImpl - Android 2.2 and higher - Object oPackageInfo = ReflectUtils.readField(oBase, "mPackageInfo"); - if (oPackageInfo == null) { - if (LOGR) { - LogRelease.e(PLUGIN_TAG, "pclu.p: nf mpi. mb cl=" + oBase.getClass()); - } - return false; - } - - // mPackageInfo的类型主要有两种: - // 1. android.app.ActivityThread$PackageInfo - Android 2.1 - 2.3 - // 2. android.app.LoadedApk - Android 2.3.3 and higher - if (LOG) { - Log.d(TAG, "patch: mBase cl=" + oBase.getClass() + "; mPackageInfo cl=" + oPackageInfo.getClass()); - } - - // 获取mPackageInfo.mClassLoader - ClassLoader oClassLoader = (ClassLoader) ReflectUtils.readField(oPackageInfo, "mClassLoader"); - if (oClassLoader == null) { - if (LOGR) { - LogRelease.e(PLUGIN_TAG, "pclu.p: nf mpi. mb cl=" + oBase.getClass() + "; mpi cl=" + oPackageInfo.getClass()); - } - return false; - } - - // 外界可自定义ClassLoader的实现,但一定要基于RePluginClassLoader类 - ClassLoader cl = RePlugin.getConfig().getCallbacks().createClassLoader(oClassLoader.getParent(), oClassLoader); - - // 将新的ClassLoader写入mPackageInfo.mClassLoader - ReflectUtils.writeField(oPackageInfo, "mClassLoader", cl); - - // 设置线程上下文中的ClassLoader为RePluginClassLoader - // 防止在个别Java库用到了Thread.currentThread().getContextClassLoader()时,“用了原来的PathClassLoader”,或为空指针 - Thread.currentThread().setContextClassLoader(cl); - - if (LOG) { - Log.d(TAG, "patch: patch mClassLoader ok"); - } - } catch (Throwable e) { - e.printStackTrace(); - return false; - } - return true; - } -} diff --git a/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/PMF.java b/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/PMF.java index 56a6bbc2..821cc120 100644 --- a/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/PMF.java +++ b/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/PMF.java @@ -26,7 +26,7 @@ import com.qihoo360.i.Factory; import com.qihoo360.i.Factory2; import com.qihoo360.i.IModule; -import com.qihoo360.loader.utils.PatchClassLoaderUtils; +import com.qihoo360.replugin.PMFClassLoader; import com.qihoo360.replugin.helper.LogRelease; import java.io.FileDescriptor; @@ -74,7 +74,7 @@ public static final void init(Application application) { Factory.sPluginManager = PMF.getLocal(); Factory2.sPLProxy = PMF.getInternal(); - PatchClassLoaderUtils.patch(application); + PMFClassLoader.hackHostClassLoader(); } /** diff --git a/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/PMFClassLoader.java b/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/PMFClassLoader.java new file mode 100644 index 00000000..e23eee8c --- /dev/null +++ b/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/PMFClassLoader.java @@ -0,0 +1,94 @@ +package com.qihoo360.replugin; + +import com.qihoo360.loader2.PMF; + +import java.lang.reflect.Field; + +/** + * 给宿主的ClassLoader新增一层parent ClassLoader.因为正常的ClassLoader都会遵循"双亲委派"机制, + * 所以宿主的PathClassLoader会尊重这个ClassLoader返回的结果.从而可以在这个ClassLoader中返回PMF想要返回的类. + *

+ * 方案来自: + * https://github.com/Tencent/Shadow/blob/dev/projects/sdk/dynamic/dynamic-host/src/main/java/com/tencent/shadow/dynamic/host/DynamicRuntime.java + * + * @author cubershi@tencent.com + */ +public class PMFClassLoader extends ClassLoader { + + private PMFClassLoader(ClassLoader parent) { + super(parent); + } + + @Override + protected Class loadClass(String className, boolean resolve) throws ClassNotFoundException { + // + Class c = null; + c = PMF.loadClass(className, resolve); + if (c != null) { + return c; + } + return super.loadClass(className, resolve); + } + + @Override + protected Package getPackage(String name) { + final Package aPackage = super.getPackage(name); + if (aPackage == null) { + return definePackage(name, "Unknown", "0.0", "Unknown", "Unknown", "0.0", "Unknown", null); + } else { + return aPackage; + } + } + + public static void hackHostClassLoader() { + ClassLoader hostClassLoader = PMF.class.getClassLoader(); + final PMFClassLoader newParentClassLoader = new PMFClassLoader(hostClassLoader.getParent()); + try { + PMFClassLoader.hackParentClassLoader(hostClassLoader, newParentClassLoader); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * 修改ClassLoader的parent + * + * @param classLoader 需要修改的ClassLoader + * @param newParentClassLoader classLoader的新的parent + */ + private static void hackParentClassLoader(ClassLoader classLoader, + ClassLoader newParentClassLoader) throws Exception { + Field field = getParentField(); + if (field == null) { + throw new Exception("在ClassLoader.class中没找到类型为ClassLoader的parent域"); + } + field.setAccessible(true); + field.set(classLoader, newParentClassLoader); + } + + /** + * 安全地获取到ClassLoader类的parent域 + * + * @return ClassLoader类的parent域.或不能通过反射访问该域时返回null. + */ + private static Field getParentField() { + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + ClassLoader parent = classLoader.getParent(); + Field field = null; + for (Field f : ClassLoader.class.getDeclaredFields()) { + try { + boolean accessible = f.isAccessible(); + f.setAccessible(true); + Object o = f.get(classLoader); + f.setAccessible(accessible); + if (o == parent) { + field = f; + break; + } + } catch (IllegalAccessException ignore) { + } + } + return field; + } + +} diff --git a/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/RePluginCallbacks.java b/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/RePluginCallbacks.java index c2696540..f821967c 100644 --- a/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/RePluginCallbacks.java +++ b/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/RePluginCallbacks.java @@ -41,20 +41,6 @@ public RePluginCallbacks(Context context) { mContext = context; } - /** - * 创建【宿主用的】 RePluginClassLoader 对象以支持大多数插件化特征。默认为:RePluginClassLoader的实例 - *

- * 子类可复写此方法,来创建自己的ClassLoader,做相应的事情(如Hook宿主的ClassLoader做一些特殊的事) - * - * @param parent 该ClassLoader的父亲,通常为BootClassLoader - * @param original 宿主的原ClassLoader,通常为PathClassLoader - * @return 支持插件化方案的ClassLoader对象,可直接返回RePluginClassLoader - * @see RePluginClassLoader - */ - public RePluginClassLoader createClassLoader(ClassLoader parent, ClassLoader original) { - return new RePluginClassLoader(parent, original); - } - /** * 插件【插件用的】 ClassLoader对象。默认为:PluginDexClassLoader的实例 *

diff --git a/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/RePluginClassLoader.java b/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/RePluginClassLoader.java deleted file mode 100644 index 391258a5..00000000 --- a/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/RePluginClassLoader.java +++ /dev/null @@ -1,256 +0,0 @@ -/* - * Copyright (C) 2005-2017 Qihoo 360 Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed To in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.qihoo360.replugin; - -import android.os.Build; - -import com.qihoo360.replugin.utils.ReflectUtils; -import com.qihoo360.loader.utils.StringUtils; -import com.qihoo360.loader2.PMF; -import com.qihoo360.replugin.base.IPC; -import com.qihoo360.replugin.helper.LogDebug; -import com.qihoo360.replugin.helper.LogRelease; - -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.net.URL; -import java.util.Enumeration; - -import dalvik.system.PathClassLoader; - -import static com.qihoo360.replugin.helper.LogDebug.LOG; -import static com.qihoo360.replugin.helper.LogDebug.PLUGIN_TAG; -import static com.qihoo360.replugin.helper.LogRelease.LOGR; - -/** - * 宿主的ClassLoader,插件框架的核心之一 - *

- * 注意:为了兼容Android 7.0以上的LoadedApk.updateApplicationInfo中,对addDexPath方法的依赖, - * 特将继承关系调整到PathClassLoader,以前是ClassLoader - * - * @author RePlugin Team - */ -public class RePluginClassLoader extends PathClassLoader { - - private static final String TAG = "RePluginClassLoader"; - - private final ClassLoader mOrig; - - /** - * 用load系列代替 - */ - //private Method findClassMethod; - - private Method findResourceMethod; - - private Method findResourcesMethod; - - private Method findLibraryMethod; - - private Method getPackageMethod; - - public RePluginClassLoader(ClassLoader parent, ClassLoader orig) { - - // 由于PathClassLoader在初始化时会做一些Dir的处理,所以这里必须要传一些内容进来 - // 但我们最终不用它,而是拷贝所有的Fields - super("", "", parent); - mOrig = orig; - - // 将原来宿主里的关键字段,拷贝到这个对象上,这样骗系统以为用的还是以前的东西(尤其是DexPathList) - // 注意,这里用的是“浅拷贝” - // Added by Jiongxuan Zhang - copyFromOriginal(orig); - - initMethods(orig); - } - - private void initMethods(ClassLoader cl) { - Class c = cl.getClass(); - findResourceMethod = ReflectUtils.getMethod(c, "findResource", String.class); - findResourceMethod.setAccessible(true); - findResourcesMethod = ReflectUtils.getMethod(c, "findResources", String.class); - findResourcesMethod.setAccessible(true); - findLibraryMethod = ReflectUtils.getMethod(c, "findLibrary", String.class); - findLibraryMethod.setAccessible(true); - getPackageMethod = ReflectUtils.getMethod(c, "getPackage", String.class); - getPackageMethod.setAccessible(true); - } - - private void copyFromOriginal(ClassLoader orig) { - if (LOG && IPC.isPersistentProcess()) { - LogDebug.d(TAG, "copyFromOriginal: Fields=" + StringUtils.toStringWithLines(ReflectUtils.getAllFieldsList(orig.getClass()))); - } - - if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.GINGERBREAD_MR1) { - // Android 2.2 - 2.3.7,有一堆字段,需要逐一复制 - // 以下方法在较慢的手机上用时:8ms左右 - copyFieldValue("libPath", orig); - copyFieldValue("libraryPathElements", orig); - copyFieldValue("mDexs", orig); - copyFieldValue("mFiles", orig); - copyFieldValue("mPaths", orig); - copyFieldValue("mZips", orig); - } else { - // Android 4.0以上只需要复制pathList即可 - // 以下方法在较慢的手机上用时:1ms - copyFieldValue("pathList", orig); - } - } - - private void copyFieldValue(String field, ClassLoader orig) { - try { - Field f = ReflectUtils.getField(orig.getClass(), field); - if (f == null) { - if (LOGR) { - LogRelease.e(PLUGIN_TAG, "rpcl.cfv: null! f=" + field); - } - return; - } - - // 删除final修饰符 - ReflectUtils.removeFieldFinalModifier(f); - - // 复制Field中的值到this里 - Object o = ReflectUtils.readField(f, orig); - ReflectUtils.writeField(f, this, o); - - if (LOG) { - Object test = ReflectUtils.readField(f, this); - LogDebug.d(TAG, "copyFieldValue: Copied. f=" + field + "; actually=" + test + "; orig=" + o); - } - } catch (IllegalAccessException e) { - if (LOGR) { - LogRelease.e(PLUGIN_TAG, "rpcl.cfv: fail! f=" + field); - } - } - } - - @Override - protected Class loadClass(String className, boolean resolve) throws ClassNotFoundException { - // - Class c = null; - c = PMF.loadClass(className, resolve); - if (c != null) { - return c; - } - // - try { - c = mOrig.loadClass(className); - // 只有开启“详细日志”才会输出,防止“刷屏”现象 - if (LogDebug.LOG && RePlugin.getConfig().isPrintDetailLog()) { - LogDebug.d(TAG, "loadClass: load other class, cn=" + className); - } - return c; - } catch (Throwable e) { - // - } - // - return super.loadClass(className, resolve); - } - - @Override - protected Class findClass(String className) throws ClassNotFoundException { - // INFO Never reach here since override loadClass , unless not found class - if (LOGR) { - LogRelease.w(PLUGIN_TAG, "NRH lcl.fc: c=" + className); - } - return super.findClass(className); - } - - @Override - protected URL findResource(String resName) { - try { - return (URL) findResourceMethod.invoke(mOrig, resName); - } catch (IllegalArgumentException e) { - e.printStackTrace(); - } catch (IllegalAccessException e) { - e.printStackTrace(); - } catch (InvocationTargetException e) { - e.printStackTrace(); - } - return super.findResource(resName); - } - - @SuppressWarnings("unchecked") - @Override - protected Enumeration findResources(String resName) { - try { - return (Enumeration) findResourcesMethod.invoke(mOrig, resName); - } catch (IllegalArgumentException e) { - e.printStackTrace(); - } catch (IllegalAccessException e) { - e.printStackTrace(); - } catch (InvocationTargetException e) { - e.printStackTrace(); - } - return super.findResources(resName); - } - - @Override - public String findLibrary(String libName) { - try { - return (String) findLibraryMethod.invoke(mOrig, libName); - } catch (IllegalArgumentException e) { - e.printStackTrace(); - } catch (IllegalAccessException e) { - e.printStackTrace(); - } catch (InvocationTargetException e) { - e.printStackTrace(); - } - return super.findLibrary(libName); - } - - @Override - protected Package getPackage(String name) { - // 金立手机的某些ROM(F103,F103L,F303,M3)代码ClassLoader.getPackage去掉了关键的保护和错误处理(2015.11~2015.12左右),会返回null - // 悬浮窗某些draw代码触发getPackage(...).getName(),getName出现空指针解引,导致悬浮窗进程出现了大量崩溃 - // 此处实现和AOSP一致确保不会返回null - // SONGZHAOCHUN, 2016/02/29 - if (name != null && !name.isEmpty()) { - Package pack = null; - try { - pack = (Package) getPackageMethod.invoke(mOrig, name); - } catch (IllegalArgumentException e) { - e.printStackTrace(); - } catch (IllegalAccessException e) { - e.printStackTrace(); - } catch (InvocationTargetException e) { - e.printStackTrace(); - } - if (pack == null) { - if (LOGR) { - LogRelease.w(PLUGIN_TAG, "NRH lcl.gp.1: n=" + name); - } - pack = super.getPackage(name); - } - if (pack == null) { - if (LOGR) { - LogRelease.w(PLUGIN_TAG, "NRH lcl.gp.2: n=" + name); - } - return definePackage(name, "Unknown", "0.0", "Unknown", "Unknown", "0.0", "Unknown", null); - } - return pack; - } - return null; - } - - @Override - public String toString() { - return getClass().getName() + "[mBase=" + mOrig.toString() + "]"; - } -}