/* * 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.app.Activity; import android.app.Application; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageInfo; import android.content.res.Configuration; import android.content.res.Resources; import android.os.Build; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; import android.support.v4.content.LocalBroadcastManager; import android.text.TextUtils; import com.qihoo360.i.PluginFactory; import com.qihoo360.i.Factory2; import com.qihoo360.i.IPluginManager; import com.qihoo360.utils.CertUtils; import com.qihoo360.loader.RePluginOS; import com.qihoo360.loader.PluginMgrFacade; import com.qihoo360.loader.PluginStatusController; import com.qihoo360.mobilesafe.api.AppVar; import com.qihoo360.mobilesafe.api.Tasks; import com.qihoo360.mobilesafe.core.BuildConfig; import com.qihoo360.mobilesafe.svcmanager.QihooServiceManager; import com.qihoo360.replugin.base.IPC; import com.qihoo360.replugin.component.ComponentList; import com.qihoo360.replugin.component.app.PluginApplicationClient; import com.qihoo360.replugin.debugger.DebuggerReceivers; import com.qihoo360.replugin.helper.HostConfigHelper; import com.qihoo360.replugin.helper.LogDebug; import com.qihoo360.replugin.helper.LogRelease; import com.qihoo360.replugin.model.PluginInfo; import com.qihoo360.replugin.packages.PluginFastInstallProviderProxy; import com.qihoo360.replugin.packages.PluginInfoUpdater; import com.qihoo360.replugin.packages.PluginManagerProxy; import com.qihoo360.replugin.packages.PluginRunningList; import java.io.File; import java.util.List; import static com.qihoo360.replugin.helper.LogDebug.LOG; /** * RePlugin的对外入口类 <p> * 宿主App可直接调用此类中的方法,来使用插件化的几乎全部的逻辑。 * * @author RePlugin Team */ public class RePlugin { private static final String TAG = "RePlugin"; /** * 插件名为“宿主”。这样插件可以直接通过一些方法来使用“宿主”的接口 */ public static final String PLUGIN_NAME_MAIN = "main"; /** * 表示目标进程根据实际情况自动调配 */ public static final String PROCESS_AUTO = "" + IPluginManager.PROCESS_AUTO; /** * 表示目标为UI进程 */ public static final String PROCESS_UI = "" + IPluginManager.PROCESS_UI; /** * 表示目标为常驻进程(名字可变,见BuildConfig内字段) */ public static final String PROCESS_PERSIST = "" + IPluginManager.PROCESS_PERSIST; private static RePluginConfig sConfig; /** * 安装此插件 <p> * 注意: <p> * 1、这里只将APK移动(或复制)到“插件路径”下,不释放优化后的Dex和Native库,不会加载插件 <p> * 2、此方法是【同步】的,耗时较少 * * @param path 插件安装的地址。必须是“绝对路径”。通常可以用context.getFilesDir()来做 * @return 安装成功的插件信息,外界可直接读取 * @since 2.0.0 (1.x版本为installDelayed) */ public static PluginInfo install(String path) { if (TextUtils.isEmpty(path)) { throw new IllegalArgumentException(); } // 判断文件合法性 File file = new File(path); if (!file.exists()) { if (LogDebug.LOG) { LogDebug.e(TAG, "plugin install: File not exists. path=" + path); } return null; } else if (!file.isFile()) { if (LogDebug.LOG) { LogDebug.e(TAG, "plugin install: Not a valid file. path=" + path); } return null; } return RePluginOS.pluginDownloaded(path); } /** * 卸载此插件 <p> * 注意: <p> * 此卸载功能只针对"纯APK"插件方案 <p> * * @param pluginName 待卸载插件名字 * @return 插件卸载是否成功 * @since 2.1.0 */ public static boolean uninstall(String pluginName) { if (TextUtils.isEmpty(pluginName)) { throw new IllegalArgumentException(); } return RePluginOS.pluginUninstall(pluginName); } /** * 预加载此插件。此方法会立即释放优化后的Dex和Native库,但不会运行插件代码。 <p> * 具体用法可参见preload(PluginInfo)的说明 * * @param pluginName 要加载的插件名 * @return 预加载是否成功 * @see #preload(PluginInfo) * @since 2.0.0 */ public static boolean preload(String pluginName) { PluginInfo pi = getPluginInfo(pluginName); if (pi == null) { // 插件不存在,无法加载Dex if (LogDebug.LOG) { LogDebug.e(TAG, "preload: Plugin not found! pn=" + pluginName); } return false; } return preload(pi); } /** * 预加载此插件。此方法会立即释放优化后的Dex和Native库,但不会运行插件代码。 <p> * 使用场景:在“安装”完成后“提前释放Dex”(时间算在“安装过程”中)。这样下次启动插件时则速度飞快 <p> * 注意: <p> * 1、该方法非必须调用(见“使用场景”)。换言之,只要涉及到插件加载,就会自动完成preload操作,无需开发者关心 <p> * 2、Dex和Native库会占用大量的“内部存储空间”。故除非插件是“确定要用的”,否则不必在安装完成后立即调用此方法 <p> * 3、该方法为【同步】调用,且耗时较久(尤其是dex2oat的过程),建议在线程中使用 * * @param pi 要加载的插件信息 * @return 预加载是否成功 * @see #install(String) * @since 2.0.0 */ public static boolean preload(PluginInfo pi) { if (pi == null) { return false; } // 借助“UI进程”来快速释放Dex(见PluginFastInstallProviderProxy的说明) return PluginFastInstallProviderProxy.install(RePluginInternal.getAppContext(), pi); } /** * 是否启用调试器,Debug阶段建议开启,Release阶段建议关闭,默认为关闭状态 * * @param context Context对象 * @param enable true=开启 * @return 是否执行成功 * @since 2.0.0 */ public static boolean enableDebugger(Context context, boolean enable) { if ((null != context) && enable) { DebuggerReceivers debuggerReceivers = new DebuggerReceivers(); debuggerReceivers.registerReceivers(context); } return true; } /** * 开启一个插件的Activity <p> * 其中Intent的ComponentName的Key应为插件名(而不是包名),可使用createIntent方法来创建Intent对象 * * @param context Context对象 * @param intent 要打开Activity的Intent,其中ComponentName的Key必须为插件名 * @return 插件Activity是否被成功打开? * FIXME 是否需要Exception来做? * @see #createIntent(String, String) * @since 1.0.0 */ public static boolean startActivity(Context context, Intent intent) { // TODO 先用旧的开启Activity方案,以后再优化 ComponentName cn = intent.getComponent(); if (cn == null) { // TODO 需要支持Action方案 return false; } String plugin = cn.getPackageName(); String cls = cn.getClassName(); return PluginFactory.startActivityWithNoInjectCN(context, intent, plugin, cls, IPluginManager.PROCESS_AUTO); } /** * 开启一个插件的Activity,无需调用createIntent或设置ComponentName来修改Intent * * @param context Context对象 * @param intent 要打开Activity的Intent,其中ComponentName的Key必须为插件名 * @param pluginName 插件名。稍后会填充到Intent中 * @param activity 插件的Activity。稍后会填充到Intent中 * @see #startActivity(Context, Intent) * @since 1.0.0 */ public static boolean startActivity(Context context, Intent intent, String pluginName, String activity) { // TODO 先用旧的开启Activity方案,以后再优化 return PluginFactory.startActivity(context, intent, pluginName, activity, IPluginManager.PROCESS_AUTO); } /** * 通过 forResult 方式启动一个插件的 Activity * * @param activity 源 Activity * @param intent 要打开 Activity 的 Intent,其中 ComponentName 的 Key 必须为插件名 * @param requestCode 请求码 * @see #startActivityForResult(Activity, Intent, int, Bundle) * @since 2.1.3 */ public static boolean startActivityForResult(Activity activity, Intent intent, int requestCode) { return PluginFactory.startActivityForResult(activity, intent, requestCode, null); } /** * 通过 forResult 方式启动一个插件的 Activity * * @param activity 源 Activity * @param intent 要打开 Activity 的 Intent,其中 ComponentName 的 Key 必须为插件名 * @param requestCode 请求码 * @param options 附加的数据 * @see #startActivityForResult(Activity, Intent, int, Bundle) * @since 2.1.3 */ public static boolean startActivityForResult(Activity activity, Intent intent, int requestCode, Bundle options) { return PluginFactory.startActivityForResult(activity, intent, requestCode, options); } /** * 创建一个用来定向到插件组件的Intent <p> * <p> * 推荐用法: <p> * <code> * Intent in = RePlugin.createIntent("clean", "com.qihoo360.mobilesafe.clean.CleanActivity"); * </code> <p> * 当然,也可以用标准的Android创建方法: <p> * <code> * Intent in = new Intent(); <p> * in.setComponent(new ComponentName("clean", "com.qihoo360.mobilesafe.clean.CleanActivity")); * </code> * * @param pluginName 插件名 * @param cls 目标全名 * @return 可以被RePlugin识别的Intent * @since 1.0.0 */ public static Intent createIntent(String pluginName, String cls) { Intent in = new Intent(); in.setComponent(createComponentName(pluginName, cls)); return in; } /** * 创建一个用来定向到插件组件的ComponentName,其Key为插件名,Value为目标组件的类全名 * * @param pluginName 插件名 * @param cls 目标组件全名 * @return 一个修改过的ComponentName对象 * @since 1.0.0 */ public static ComponentName createComponentName(String pluginName, String cls) { return new ComponentName(pluginName, cls); } /** * 添加允许插件使用的签名指纹。一旦添加进来,则通过该签名制作的插件将允许被执行,否则将不能被执行 <p> * 注意:请不要从Prefs中“缓存”签名信息,防止别他人篡改后,导致恶意插件被校验通过 * * @param sign 签名指纹 * @since 1.0.0 */ public static void addCertSignature(String sign) { if (TextUtils.isEmpty(sign)) { throw new IllegalArgumentException("arg is null"); } CertUtils.SIGNATURES.add(sign.toUpperCase()); } /** * 是否使用Dev版AAR?可支持一些"调试特性",但该AAR【千万不要用于发布环境】 <p> * Dev版的AAR可支持如下特性: <p> * 1、插件签名不正确时仍允许被安装进来,这样利于调试(发布环境上则容易导致严重安全隐患) <p> * 2、可以打出一些完整的日志(发布环境上则容易被逆向,进而对框架稳定性、私密性造成严重影响) * * @return 是否使用Dev版的AAR? * @since 1.0.0 */ public static boolean isForDev() { return RePluginInternal.FOR_DEV; } /** * 获取SDK的版本信息 * * @return SDK的版本,如2.0.0等 * @since 2.0.0 */ public static String getSDKVersion() { return BuildConfig.VERSION_NAME; } /** * 获取插件的组件列表(ComponentList) <p> * 注意:这里会尝试安装插件,但不会加载资源和代码,因此会有一些耗时的情况 <p> * 性能消耗(小 → 大):ComponentList/PackageInfo(This) < Resources < ClassLoader < Context < Binder * * @param pluginName 要获取的插件名 * @return ComponentList对象 * @see ComponentList * @since 1.0.0 */ public static ComponentList fetchComponentList(String pluginName) { return PluginFactory.queryPluginComponentList(pluginName); } /** * 加载插件,并获取插件的包信息 <p> * 注意:这里会尝试加载插件,并释放其Jar包。但不会读取资源,也不会释放oat/odex <p> * 性能消耗(小 → 大):ComponentList/PackageInfo(This) < Resources < ClassLoader < Context < Binder * * @param pluginName 插件名 * @return PackageInfo对象 * @see PackageInfo * @since 1.0.0 */ public static PackageInfo fetchPackageInfo(String pluginName) { return PluginFactory.queryPluginPackageInfo(pluginName); } /** * 加载插件,并获取插件的资源信息 <p> * 注意:这里会尝试安装插件,并释放其Jar包,读取资源,但不会释放oat/odex。 <p> * 性能消耗(小 → 大):ComponentList/PackageInfo < Resources(This) < ClassLoader < Context < Binder * * @param pluginName 插件名 * @return Resources对象 * @see Resources * @since 1.0.0 */ public static Resources fetchResources(String pluginName) { return PluginFactory.queryPluginResouces(pluginName); } /** * 加载插件,并获取插件自身的ClassLoader对象,以调用插件内部的类 <p> * 注意:这里会尝试安装插件,并同时加载资源和代码,耗时可能较久 <p> * 性能消耗(小 → 大):ComponentList/PackageInfo < Resources < ClassLoader(This) < Context < Binder * * @param pluginName 插件名 * @return 插件的ClassLoader对象 * @since 1.0.0 */ public static ClassLoader fetchClassLoader(String pluginName) { return PluginFactory.queryPluginClassLoader(pluginName); } /** * 加载插件,并获取插件自身的Context对象,以获取资源等信息 <p> * 注意:这里会尝试安装插件,并同时加载资源和代码,耗时可能较久 <p> * 性能消耗(小 → 大):ComponentList/PackageInfo < Resources < ClassLoader < Context(This) < Binder * * @param pluginName 插件名 * @return 插件的Context对象 * @since 1.0.0 */ public static Context fetchContext(String pluginName) { return PluginFactory.queryPluginContext(pluginName); } /** * 加载插件,并通过插件里的Plugin类,获取插件定义的IBinder <p> * 注意:这里会尝试安装插件,并同时加载资源和代码,耗时可能较久 <p> * 性能消耗(小 → 大):ComponentList/PackageInfo < Resources < Context/ClassLoader < Binder(This) <p> * <p> * PluginBinder(如使用使用本方法)和GlobalBinder类方法(如getGlobalBinder)的不同: <p> * 1、PluginBinder需要指定插件;GlobalBinder无需指定 <p> * 2、PluginBinder获取的是插件内部已定义好的Binder;GlobalBinder在获取时必须先在代码中注册 * * @param pluginName 插件名 * @param module 要加载的插件模块 * @param process 进程名 TODO 现阶段只能使用IPluginManager中的值,请务必使用它们,否则会出现问题 * @return 返回插件定义的IBinder对象,供外界使用 * @see #getGlobalBinder(String) * @since 1.0.0 */ public static IBinder fetchBinder(String pluginName, String module, String process) { return PluginFactory.query(pluginName, module, Integer.parseInt(process)); } /** * 在当前进程加载插件,并通过插件里的Plugin类,获取插件定义的IBinder <p> * 注意:这里会尝试安装插件,并同时加载资源和代码,耗时可能较久 <p> * 性能消耗(小 → 大):ComponentList/PackageInfo < Resources < Context/ClassLoader < Binder(This) <p> * <p> * PluginBinder(如使用使用本方法)和GlobalBinder类方法(如getGlobalBinder)的不同: <p> * 1、PluginBinder需要指定插件;GlobalBinder无需指定 <p> * 2、PluginBinder获取的是插件内部已定义好的Binder;GlobalBinder在获取时必须先在代码中注册 * * @param pluginName 插件名 * @param module 要加载的插件模块 * @return 返回插件定义的IBinder对象,供外界使用 * @see #getGlobalBinder(String) * @since 2.1.0 */ public static IBinder fetchBinder(String pluginName, String module) { return PluginFactory.query(pluginName, module); } /** * 通过ClassLoader对象来获取该ClassLoader应属于哪个插件 * <p> * 该方法消耗非常小,可直接使用 * * @param cl ClassLoader对象 * @return 插件名 * @since 1.0.0 */ public static String fetchPluginNameByClassLoader(ClassLoader cl) { return PluginFactory.fetchPluginName(cl); } /** * 获取所有插件的列表(指已安装的) * * @return PluginInfo的表 * @since 2.0.0(1.x版本为getExistPlugins) */ public static List<PluginInfo> getPluginInfoList() { return RePluginOS.getPlugins(true); } /** * 获取指定插件的信息 * * @param name 插件名 * @return PluginInfo对象 * @since 1.2.0 */ public static PluginInfo getPluginInfo(String name) { return RePluginOS.getPlugin(name, true); } /** * 获取当前插件的版本号,可以是VersionCode,也可以是meta-data中的ver。 * * @param name 插件名 * @return 插件版本号。若为-1则表示插件不存在 * @since 2.0.0 */ public static int getPluginVersion(String name) { PluginInfo pi = RePluginOS.getPlugin(name, false); if (pi == null) { return -1; } return pi.getVersion(); } /** * 判断插件是否已被安装(但不一定被使用过,如可能不会释放Dex、Native库等) <p> * 注意:RePlugin 1.x版本中,isPluginInstalled方法等于现在的isPluginUsed,故含义有变 * * @param pluginName 插件名 * @return 是否被安装 * @since 2.0.0 (1.x版本为isPluginExists) */ public static boolean isPluginInstalled(String pluginName) { PluginInfo pi = RePluginOS.getPlugin(pluginName, false); return pi != null; } /** * 判断插件是否曾被使用过。只要释放过Dex、Native的,就认为是“使用过”的 <p> * 和isPluginDexExtracted的区别:插件会在升级完成后,会删除旧Dex。其isPluginDexExtracted为false,而isPluginUsed仍为true * * @param pluginName 插件名 * @return 插件是否已被使用过 * @since 2.0.0 */ public static boolean isPluginUsed(String pluginName) { PluginInfo pi = RePluginOS.getPlugin(pluginName, false); return pi != null && pi.isUsed(); } /** * 判断当前插件是否已释放了Dex、Native库等 * * @param pluginName 插件名 * @return 是否已被使用过 * @since 2.0.0 (原为isPluginInstalled) */ public static boolean isPluginDexExtracted(String pluginName) { PluginInfo pi = RePluginOS.getPlugin(pluginName, false); return pi != null && pi.isDexExtracted(); } /** * 当前插件是否在运行。只要任意进程在,就都属于此情况 * * @param pluginName 插件名 * @return 插件是否正在被运行 * @since 2.0.0 */ public static boolean isPluginRunning(String pluginName) { try { return PluginManagerProxy.isPluginRunning(pluginName); } catch (RemoteException e) { // 常驻进程中断,且当前进程也没有运行。先返回False if (LogRelease.LOGR) { e.printStackTrace(); } return false; } } /** * 当前插件是否在指定进程中运行 * * @param pluginName 插件名 * @param process 指定的进程名,必须为全名 * @return 插件是否在指定进程中运行 * @since 2.0.0 */ public static boolean isPluginRunningInProcess(String pluginName, String process) { try { return PluginManagerProxy.isPluginRunningInProcess(pluginName, process); } catch (RemoteException e) { // 常驻进程中断,且当前进程也没有运行。先返回False if (LogRelease.LOGR) { e.printStackTrace(); } return false; } } /** * 获取所有正在运行的插件列表 * * @return 所有正在运行的插件的List * @see PluginRunningList * @since 2.0.0 */ public static PluginRunningList getRunningPlugins() { return PluginManagerProxy.getRunningPluginsNoThrows(); } /** * 获取正在运行此插件的进程名列表 <p> * 若要获取PID,可在拿到列表后,通过IPC.getPidByProcessName来反查 * * @param pluginName 要查询的插件名 * @return 正在运行此插件的进程名列表。一定不会为Null * @see IPC#getPidByProcessName(String) * @since 2.0.0 */ public static String[] getRunningProcessesByPlugin(String pluginName) { return PluginManagerProxy.getRunningProcessesByPluginNoThrows(pluginName); } /** * 当前是否处于"常驻进程"? * * @return 是否处于常驻进程 * @since 1.1.0 */ public static boolean isCurrentPersistentProcess() { return IPC.isPersistentProcess(); } /** * 获取RePlugin的Config对象。请参见RePluginConfig类的说明 * * @return RePluginConfig对象。注意,即便没有在attachBaseContext中自定义,也仍会有个默认的对象 * @see RePluginConfig * @see App#attachBaseContext(Application, RePluginConfig) * @since 1.2.0 */ public static RePluginConfig getConfig() { return sConfig; } /** * 注册“安装完成后的通知”广播 <p> * 此为“本地”广播,插件内也可以接收到。开发者也可以自行注册,做法: <p> * <code> * IntentFilter itf = new IntentFilter(RePluginOS.ACTION_NEW_PLUGIN); <p> * LocalBroadcastManager.getInstance(context).registerReceiver(r, itf); * </code> * * @param context Context对象 * @param r 要绑定的BroadcastReceiver对象 * @since 1.0.0 */ public static void registerInstalledReceiver(Context context, BroadcastReceiver r) { IntentFilter itf = new IntentFilter(RePluginConstants.ACTION_NEW_PLUGIN); LocalBroadcastManager.getInstance(context).registerReceiver(r, itf); } /** * 在宿主内注册一个可被其它插件所获取的Binder的对象 * * @param hbf 一个IHostBinderFetcher对象 * @see #fetchBinder(String, String, String) * @since 1.0.0 */ public static void registerHostBinder(IHostBinderFetcher hbf) { RePluginOS.installBuiltinPlugin("main", hbf); } /** * 注册一个无需插件名,可被全局使用的Binder对象。Binder对象必须事先创建好 <p> * 有关GlobalBinder的详细介绍,请参见getGlobalBinder的说明 <p> * 有关此方法和registerGlobalBinderDelayed的区别,请参见其方法说明。 * * @param name Binder的描述名 * @param binder Binder对象 * @return 是否注册成功 * @see #getGlobalBinder(String) * @see #registerGlobalBinderDelayed(String, IBinderGetter) * @since 1.2.0 */ public static boolean registerGlobalBinder(String name, IBinder binder) { return QihooServiceManager.addService(RePluginInternal.getAppContext(), name, binder); } /** * 注册一个无需插件名,可被全局使用的Binder对象,但Binder对象只有在“用到时”才会被创建 <p> * 有关GlobalBinder的详细介绍,请参见getGlobalBinder的说明 <p> * <p> * 和registerGlobalBinder不同的是: <p> * 1、前者的binder对象必须事先创建好并传递到参数中 <p> * 适用于Binder在注册时就立即创建(性能消耗小),或未来使用频率非常多的情况。如“用户账号服务”、“基础服务”等 <p> * 2、后者会在getGlobalBinder指定的name被首次调用后,才会尝试获取Binder对象 <p> * 适用于Binder只在使用时才被创建(确保启动性能快),或未来调用频率较少的情况。如“Root服务”、“特色功能服务”等 <p> * * @param name Binder的描述名 * @param getter 当getGlobalBinder调用时匹配到name后,会调用getter.get()方法来获取IBinder对象 * @return 是否延迟注册成功 * @since 1.2.0 */ public static boolean registerGlobalBinderDelayed(String name, IBinderGetter getter) { return QihooServiceManager.addService(RePluginInternal.getAppContext(), name, getter); } /** * 取消全局Binder对象的注册。这样当调用getGlobalBinder时将不再返回结果 <p> * 有关globalBinder的详细介绍,请参见registerGlobalBinder的说明 * * @param name Binder的描述名 * @return 是否取消成功 * @see #getGlobalBinder(String) * @since 1.2.0 */ public static boolean unregisterGlobalBinder(String name) { return QihooServiceManager.removeService(RePluginInternal.getAppContext(), name, null); } /** * 获取一个无需插件名,可被全局使用的Binder对象。必须先调用registerGlobalBinder注册后才能获得 <p> * PluginBinder(如使用fetchBinder)和GlobalBinder类方法的不同: <p> * 1、PluginBinder需要指定插件;GlobalBinder无需指定 <p> * 2、PluginBinder获取的是插件内部已定义好的Binder;GlobalBinder在获取时必须先在代码中注册 * * @param name Binder的描述名 * @return Binder对象。若之前从未注册过,或已被取消注册,则返回Null。 * @see #registerGlobalBinder(String, IBinder) * @see #unregisterGlobalBinder(String) * @see #fetchBinder(String, String, String) * @since 1.2.0 */ public static IBinder getGlobalBinder(String name) { return QihooServiceManager.getService(RePluginInternal.getAppContext(), name); } /** * 注册一个“跳转”类。一旦系统或自身想调用指定类时,将自动跳转到插件里的另一个类。 <p> * 例如,系统想访问CallShowService类,但此类在宿主中不存在,只在CallShow中有,则: <p> * 未注册“跳转类”时:直接到宿主中寻找CallShowService类,找到后就加载,找不到就崩溃(若不Catch) <p> * 注册“挑转类”后,直接将CallShowService的调用“跳转到”插件的CallShowService类中(名字可以不同)。这种情况下,需要调用: <p> * <code> * RePlugin.registerHookingClass("com.qihoo360.mobilesafe.CallShowService", <p> * RePlugin.createComponentName("callshow", "com.qihoo360.callshow.CallShowService2"), <p> * DummyService.class); * </code> * <p> <p> * 该方法可以玩出很多【新花样】。如,可用于以下场景: <p> * <b>* 已在宿主Manifest中声明了插件的四大组件,只是想借助插件化框架来找到该类并加载进来(如前述例子)。</b> <p> * <b>* 某些不方便(不好用,或需要云控)的类,想借机替换成插件里的。</b> <p> * 如我们有一个LaunchUtils类,现在想使用Utils插件中的同样的类来替代。 * * @param source 要替换的类的全名 * @param target 要替换的类的目标,需要使用 createComponentName 方法来创建 * @param defClass 若要替换的类不存在,或插件不可用,则应该使用一个默认的Class。 * <p> * 可替换成如下的形式,也可以传Null。但若访问的是四大组件,传Null可能会导致出现App崩溃(且无法被Catch) * <p> * DummyService.class * <p> * DummyActivity.class * <p> * DummyProvider.class * <p> * DummyReceiver.class * @see com.qihoo360.replugin.component.dummy.DummyActivity * @see com.qihoo360.replugin.component.dummy.DummyService * @see com.qihoo360.replugin.component.dummy.DummyReceiver * @see com.qihoo360.replugin.component.dummy.DummyProvider * @since 1.0.0 */ public static void registerHookingClass(String source, ComponentName target, Class defClass) { Factory2.registerDynamicClass(source, target.getPackageName(), target.getClassName(), defClass); } /** * 查询某个 Component 是否是“跳转”类 * * @param component 要查询的组件信息,其中 packageName 为插件名称,className 为要查询的类名称 * @since 2.0.0 */ public static boolean isHookingClass(ComponentName component) { return Factory2.isDynamicClass(component.getPackageName(), component.getClassName()); } /** * 取消对某个“跳转”类的注册,恢复原状。<p> * 请参见 registerHookingClass 的详细说明 * * @param source 要替换的类的全名 * @see #registerHookingClass(String, ComponentName, Class) * @since 2.1.6 */ public static void unregisterHookingClass(String source) { Factory2.unregisterDynamicClass(source); } /** * RePlugin中,针对Application的入口类 <p> * 所有针对Application的调用应从此类开始 * * @author RePlugin Team */ public static class App { static boolean sAttached; /** * 当Application的attachBaseContext调用时需调用此方法 <p> * 使用插件框架默认的方案 * * @param app Application对象 * @see Application#attachBaseContext(Context) */ public static void attachBaseContext(Application app) { attachBaseContext(app, new RePluginConfig()); } /** * 当Application的attachBaseContext调用时触发 <p> * 可自定义插件框架的回调行为。参见RePluginCallbacks类的说明 * 此方法的定制性不如RePluginConfig的版本 * * @param app Application对象 * @param pc 可供外界使用的回调 * @see Application#attachBaseContext(Context) * @see RePluginCallbacks */ public static void attachBaseContext(Application app, RePluginCallbacks pc) { attachBaseContext(app, new RePluginConfig().setCallbacks(pc)); } /** * (推荐)当Application的attachBaseContext调用时需调用此方法 <p> * 可自定义插件框架的行为。参见RePluginConfig类的说明 * * @param app Application对象 * @see Application#attachBaseContext(Context) * @see RePluginConfig * @since 1.2.0 */ public static void attachBaseContext(Application app, RePluginConfig config) { if (sAttached) { if (LogDebug.LOG) { LogDebug.d(TAG, "attachBaseContext: Already called"); } return; } RePluginInternal.init(app); sConfig = config; sConfig.initDefaults(app); IPC.init(app); // 打印当前内存占用情况 // 只有开启“详细日志”才会输出,防止“消耗性能” if (LOG && RePlugin.getConfig().isPrintDetailLog()) { LogDebug.printMemoryStatus(LogDebug.TAG, "act=, init, flag=, Start, pn=, framework, func=, attachBaseContext, lib=, RePlugin"); } // 初始化HostConfigHelper(通过反射HostConfig来实现) // NOTE 一定要在IPC类初始化之后才使用 HostConfigHelper.init(); // FIXME 此处需要优化掉 AppVar.sAppContext = app; // Plugin Status Controller PluginStatusController.setAppContext(app); PluginMgrFacade.init(app); PluginMgrFacade.callAttach(); sAttached = true; } /** * 当Application的onCreate调用时触发。 <p> * 务必先调用attachBaseContext后,才能调用此方法 * * @throws IllegalStateException 若没有调用attachBaseContext,则抛出此异常 * @see Application#onCreate() */ public static void onCreate() { if (!sAttached) { throw new IllegalStateException(); } Tasks.init(); PluginMgrFacade.callAppCreate(); // 注册监听PluginInfo变化的广播以接受来自常驻进程的更新 if (!IPC.isPersistentProcess()) { PluginInfoUpdater.register(RePluginInternal.getAppContext()); } // 打印当前内存占用情况 // 只有开启“详细日志”才会输出,防止“消耗性能” if (LOG && RePlugin.getConfig().isPrintDetailLog()) { LogDebug.printMemoryStatus(LogDebug.TAG, "act=, init, flag=, End, pn=, framework, func=, onCreate, lib=, RePlugin"); } } /** * 当Application的onLowMemory调用时触发 <p> * 除了插件化框架本身会做一些事情外,该方法也来通知插件onLowMemory的行为 * <p> * 如果App的minSdkVersion >= 14,该方法不用调用 * * @see Application#onLowMemory() */ public static void onLowMemory() { // API>14采用注册回调的方式执行插件中该方法 if (Build.VERSION.SDK_INT >= 14) { return; } // 遍历插件的Application对象,并调用其onLowMemory PluginApplicationClient.notifyOnLowMemory(); } /** * 当Application的onTrimMemory调用时触发 <p> * 除了插件化框架本身会做一些事情外,该方法也来通知插件onTrimMemory的行为 * <p> * 如果App的minSdkVersion >= 14,该方法不用调用 * * @see Application#onTrimMemory(int) */ public static void onTrimMemory(int level) { // API>14采用注册回调的方式执行插件中该方法 if (Build.VERSION.SDK_INT >= 14) { return; } // 遍历插件的Application对象,并调用其onTrimMemory PluginApplicationClient.notifyOnTrimMemory(level); } /** * 当Application的onConfigurationChanged调用时触发 <p> * 除了插件化框架本身会做一些事情外,该方法也来通知插件onConfigurationChanged的行为 * <p> * 如果App的minSdkVersion >= 14,该方法不用调用 * * @see Application#onConfigurationChanged(Configuration) */ public static void onConfigurationChanged(Configuration newConfig) { // API>14采用注册回调的方式执行插件中该方法 if (Build.VERSION.SDK_INT >= 14) { return; } // 遍历插件的Application对象,并调用其onConfigurationChanged PluginApplicationClient.notifyOnConfigurationChanged(newConfig); } } }