listPlugins();
void regActivity(int index, String plugin, String container, String activity);
void unregActivity(int index, String plugin, String container, String activity);
void regService(int index, String plugin, String service);
void unregService(int index, String plugin, String service);
void regPluginBinder(in PluginBinderInfo info, IBinder binder);
void unregPluginBinder(in PluginBinderInfo info, IBinder binder);
/**
* 注册某插件下所有静态声明的的 receiver 到常驻进程
*/
void regReceiver(String plugin, in Map receiverFilterMap);
void unregReceiver();
/**
* 插件收到广播
*
* @param plugin 插件名称
* @param receiver Receiver 名称
* @param Intent 广播的 Intent 数据
*/
void onReceive(String plugin, String receiver, in Intent intent);
int sumBinders(int index);
void updatePluginInfo(in PluginInfo info);
PluginInfo pluginDownloaded(String path);
boolean pluginUninstalled(in PluginInfo info);
boolean pluginExtracted(String path);
oneway void sendIntent2Process(String target, in Intent intent);
oneway void sendIntent2Plugin(String target, in Intent intent);
void sendIntent2ProcessSync(String target, in Intent intent);
void sendIntent2PluginSync(String target, in Intent intent);
boolean isProcessAlive(String name);
IBinder queryPluginBinder(String plugin, String binder);
/**
* 根据 Inent 查询所有插件中的与之匹配的 Receivers
*/
List queryPluginsReceiverList(in Intent intent);
/**
* 获取“全新Service管理方案”在Server端的服务
* Added by Jiongxuan Zhang
*/
IPluginServiceServer fetchServiceServer();
/**
* 获取 IPluginManagerServer(纯APK方案使用)的插件服务
* Added by Jiongxuan Zhang
*/
IPluginManagerServer fetchManagerServer();
/**
* 根据 taskAffinity,判断应该取第几组 TaskAffinity
* 由于 taskAffinity 是跨进程的属性,所以这里要将 taskAffinityGroup 的数据保存在常驻进程中
* Added by hujunjie
*/
int getTaskAffinityGroupIndex(String taskAffinity);
/**
* 通过进程名来获取PID
*/
int getPidByProcessName(String processName);
/**
* 通过PID来获取进程名
*/
String getProcessNameByPid(int pid);
/**
* dump详细的运行时信息
*/
String dump();
}
================================================
FILE: replugin-host-library/replugin-host-lib/src/main/aidl/com/qihoo360/loader2/PluginBinderInfo.aidl
================================================
package com.qihoo360.loader2;
parcelable PluginBinderInfo;
================================================
FILE: replugin-host-library/replugin-host-lib/src/main/aidl/com/qihoo360/loader2/mgr/IServiceConnection.aidl
================================================
// IServiceConnection.aidl
// Same as android.app.IServiceConnection
package com.qihoo360.loader2.mgr;
import android.content.ComponentName;
/** @hide */
oneway interface IServiceConnection {
void connected(in ComponentName name, IBinder service);
}
================================================
FILE: replugin-host-library/replugin-host-lib/src/main/aidl/com/qihoo360/loader2/sp/IPref.aidl
================================================
package com.qihoo360.loader2.sp;
import android.os.Bundle;
/**
* @author RePlugin Team
*/
interface IPref {
String get(String category, String key, String defValue);
void set(String category, String key, String value);
Bundle getAll(String category);
}
================================================
FILE: replugin-host-library/replugin-host-lib/src/main/aidl/com/qihoo360/mobilesafe/svcmanager/IServiceChannel.aidl
================================================
package com.qihoo360.mobilesafe.svcmanager;
import com.qihoo360.replugin.IBinderGetter;
interface IServiceChannel {
IBinder getService(String serviceName);
void addService(String serviceName, IBinder service);
void addServiceDelayed(String serviceName, IBinderGetter getter);
void removeService(String serviceName);
IBinder getPluginService(String pluginName, String serviceName, IBinder deathMonitor);
void onPluginServiceRefReleased(String pluginName, String serviceName);
}
================================================
FILE: replugin-host-library/replugin-host-lib/src/main/aidl/com/qihoo360/replugin/IBinderGetter.aidl
================================================
package com.qihoo360.replugin;
/**
* Binder的获取器,可用于延迟加载IBinder的情况。
*
* 目前用于:
*
* * RePlugin.registerGlobalBinderDelayed
*
* @author RePlugin Team
*/
interface IBinderGetter {
/**
* 获取IBinder对象
*/
IBinder get();
}
================================================
FILE: replugin-host-library/replugin-host-lib/src/main/aidl/com/qihoo360/replugin/component/service/server/IPluginServiceServer.aidl
================================================
package com.qihoo360.replugin.component.service.server;
import android.content.ComponentName;
import android.os.Messenger;
import com.qihoo360.loader2.mgr.IServiceConnection;
/**
* 负责Server端的服务调度、提供等工作,是服务的提供方,核心类之一
*
* @hide 框架内部使用
* @author RePlugin Team
*/
interface IPluginServiceServer {
ComponentName startService(in Intent intent, in Messenger client);
int stopService(in Intent intent, in Messenger client);
int bindService(in Intent intent, in IServiceConnection conn, int flags, in Messenger client);
boolean unbindService(in IServiceConnection conn);
String dump();
}
================================================
FILE: replugin-host-library/replugin-host-lib/src/main/aidl/com/qihoo360/replugin/model/PluginInfo.aidl
================================================
package com.qihoo360.replugin.model;
parcelable PluginInfo;
================================================
FILE: replugin-host-library/replugin-host-lib/src/main/aidl/com/qihoo360/replugin/packages/IPluginManagerServer.aidl
================================================
package com.qihoo360.replugin.packages;
import com.qihoo360.replugin.model.PluginInfo;
import com.qihoo360.replugin.packages.PluginRunningList;
/**
* 插件管理器。用来控制插件的安装、卸载、获取等。运行在常驻进程中
*
* 补充:涉及到插件交互、运行机制有关的管理器,在IPluginHost中
*
* @author RePlugin Team
*/
interface IPluginManagerServer {
/**
* 安装一个插件
*
* 注意:若为旧插件(p-n开头),则应使用IPluginHost的pluginDownloaded方法
*
* @return 安装的插件的PluginInfo对象
*/
PluginInfo install(String path);
/**
* 卸载一个插件
*
* 注意:只针对“纯APK”插件方案
*
* @param info 插件信息
* @return 是否成功卸载插件?
*/
boolean uninstall(in PluginInfo info);
/**
* 加载插件列表,方便之后使用
*
* TODO 这里只返回"新版插件",供PmBase使用。将来会合并
*
* @return PluginInfo的列表
*/
List load();
/**
* 更新所有插件列表
*
* @return PluginInfo的列表
*/
List updateAll();
/**
* 设置isUsed状态,并通知所有进程更新
*
* @param pluginName 插件名
* @param used 是否已经使用
*/
void updateUsed(String pluginName, boolean used);
/**
* 获取正在运行的插件列表
*
* @return 正在运行的插件名列表
*/
PluginRunningList getRunningPlugins();
/**
* 插件是否正在运行?
*
* @param pluginName 插件名
* @param process 指定进程名,如为Null则表示查所有
* @return 是否在运行?
*/
boolean isPluginRunning(String pluginName, String process);
/**
* 当进程启动时,同步正在运行的插件状态到Server端
*
* @param list 正在运行的插件名列表
*/
void syncRunningPlugins(in PluginRunningList list);
/**
* 当进程启动时,同步正在运行的插件状态到Server端
*
* @param processName 进程名
* @param pluginName 正在运行的插件名
*/
void addToRunningPlugins(String processName, int pid, String pluginName);
/**
* 获取正在运行此插件的进程名列表
*
* @param pluginName 要查询的插件名
* @return 正在运行此插件的进程名列表。一定不会为Null
*/
String[] getRunningProcessesByPlugin(String pluginName);
/**
* 预安装内置插件到app_p_a目录,只做p.l文件的写入,真正的安装执行,在做插件Load的时候执行
*
* @param 待安装的插件列表
* @return 安装后的所有插件列表
*/
List preInstallBuiltins(in List pluginInfos);
void updateTP(String plugin,int type,String path);
/**
* 设置isUsed状态,并通知所有进程更新
*
* @param pluginName 插件名
* @param path 是否已经使用
* @param type 是否已经使用
* @param used 是否已经使用
*/
void updateUsedNew(String pluginName, String path, int type, boolean used);
}
================================================
FILE: replugin-host-library/replugin-host-lib/src/main/aidl/com/qihoo360/replugin/packages/PluginRunningList.aidl
================================================
package com.qihoo360.replugin.packages;
parcelable PluginRunningList;
================================================
FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/i/Factory.java
================================================
/*
* 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.i;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageInfo;
import android.content.pm.ServiceInfo;
import android.content.res.Resources;
import android.net.Uri;
import android.os.Bundle;
import android.os.IBinder;
import android.text.TextUtils;
import com.qihoo360.loader2.PluginCommImpl;
import com.qihoo360.mobilesafe.core.BuildConfig;
import com.qihoo360.replugin.RePlugin;
import com.qihoo360.replugin.component.ComponentList;
import java.util.List;
/**
* Wrapper类,简化使用代码
*
* 插件文件名规范:barcode-1-10-2.jar
*
* -
* 最小支持版本 :例如 1
* 插件可选择host/adapter,低于该版本的host/adapter是无法使用
* 即插件可限制老的host/adapter不能使用,只能在特定版本以后的新的host/adapter使用
*
* -
* 当前接口版本:例如 10
* host/adapter可选择插件
*
* -
* 插件自身版本:例如 2
* 通常是用来表示是一个bug修复版本
*
*
*
* 适配器文件名规范:adapter-1-10-3.jar
*
* -
* 支持接口版本 :例如 1
* 表明该适配器支持的主程序
*
* -
* 当前接口版本:例如 10
* 当插件的当前接口版本小于或等于该值时才会加载,即适配器可选择特定版本以前的插件
* 也就是说,限制只能使用较老插件,不支持超出适配器能力之外的插件
*
* -
* 适配器自身版本:例如 3
* 通常是用来表示是一个bug修复版本
*
*
*
* @author RePlugin Team
* @deprecated 慢慢会被废弃掉,只留着旧卫士插件反射用。现阶段先不做优化
*/
public final class Factory {
/**
* 插件的入口包名前缀
* 在插件中,该包名不能混淆
* 例如,二维码的插件入口类为:com.qihoo360.plugin.barcode.Entry
* @hide 内部框架使用
*/
public static final String PLUGIN_ENTRY_PACKAGE_PREFIX = "com.qihoo360.plugin";
/**
* 新版SDK(RePlugin-library)插件入口报名前缀
* 在插件中,该包名不能混淆
*/
public static final String REPLUGIN_LIBRARY_ENTRY_PACKAGE_PREFIX = "com.qihoo360.replugin";
/**
* 插件的入口类
* 在插件中,该名字不能混淆
* @hide 内部框架使用
*/
public static final String PLUGIN_ENTRY_CLASS_NAME = "Entry";
/**
* 插件的入口类导出函数
* 在插件中,该方法名不能混淆
* 通过该函数创建IPlugin对象
* @hide 内部框架使用
*/
public static final String PLUGIN_ENTRY_EXPORT_METHOD_NAME = "create";
/**
* 参数1:插件上下文,可通过它获取应用上下文
* 参数2:
* @hide 内部框架使用
*/
public static final Class> PLUGIN_ENTRY_EXPORT_METHOD_PARAMS[] = {
Context.class, IPluginManager.class
};
/**
* 参数1:插件上下文,可通过它获取应用上下文
* 参数2:HOST的类加载器
* 参数3:已废弃
* 返回:插件 IPlugin.aidl
* @hide 内部框架使用
*/
public static final Class> PLUGIN_ENTRY_EXPORT_METHOD2_PARAMS[] = {
Context.class, ClassLoader.class, IBinder.class
};
/**
* @hide 内部框架使用
*/
public static PluginCommImpl sPluginManager;
/**
* @deprecated 新插件框架不再用i接口依赖,此接口已废弃
* @param name 插件名
* @param c 需要查询的interface的类
* @return
*/
@Deprecated
public static final IModule query(String name, Class extends IModule> c) {
return sPluginManager.query(name, c);
}
/**
* 调用此接口不会在当前进程加载插件
* @param name 插件名
* @return
*/
public static final boolean isPluginLoaded(String name) {
return sPluginManager.isPluginLoaded(name);
}
/**
* 调用此接口会在当前进程加载插件
* @param name 插件名
* @param binder 需要查询的binder的名称(不要用IXXX.class.getName,因为不再建议keep IXXX类,IXXX有可能被混淆)
* @return
*/
public static final IBinder query(String name, String binder) {
return sPluginManager.query(name, binder);
}
/**
* 调用此接口会在指定进程加载插件
* @param name 插件名
* @param binder 需要查询的binder的名称(不要用IXXX.class.getName,因为不再建议keep IXXX类,IXXX有可能被混淆)
* @param process 是否在指定进程中启动
* @return
*/
public static final IBinder query(String name, String binder, int process) {
return sPluginManager.query(name, binder, process);
}
/**
* 警告:低层接口
* 当插件升级之后,通过adapter.jar标准接口,甚至invoke接口都无法完成任务时,可通过此接口反射来完成任务
* 调用此接口会在当前进程加载插件
* @param name 插件名
* @return 插件的context,可通过此context得到插件的ClassLoader
*/
public static final Context queryPluginContext(String name) {
return sPluginManager.queryPluginContext(name);
}
/**
* 警告:低层接口
* 调用此接口会在当前进程加载插件(不加载代码,只加载资源和PackageInfo)
* @param name 插件名
* @return 插件的Resources
*/
public static final Resources queryPluginResouces(String name) {
return sPluginManager.queryPluginResouces(name);
}
/**
* 警告:低层接口
* 调用此接口会在当前进程加载插件(不加载代码和资源,只获取PackageInfo)
* @param name 插件名
* @return 插件的PackageInfo
*/
public static final PackageInfo queryPluginPackageInfo(String name) {
return sPluginManager.queryPluginPackageInfo(name);
}
/**
* 警告:低层接口
* 调用此接口会在当前进程加载插件(不加载代码和资源,只获取PackageInfo)
*
* @param pkgName 插件包名
* @param flags Flags
* @return 插件的PackageInfo
*/
public static final PackageInfo queryPluginPackageInfo(String pkgName, int flags) {
return sPluginManager.queryPluginPackageInfo(pkgName, flags);
}
/**
* 警告:低层接口
* 调用此接口会在当前进程加载插件(不加载代码和资源,只获取ComponentList)
* @param name 插件名
* @return 插件的ComponentList
*/
public static final ComponentList queryPluginComponentList(String name) {
return sPluginManager.queryPluginComponentList(name);
}
/**
* 警告:低层接口
* 调用此接口会在当前进程加载插件(不启动App)
* @param name 插件名
* @return 插件的ComponentList
*/
public static final ClassLoader queryPluginClassLoader(String name) {
return sPluginManager.queryPluginClassLoader(name);
}
/**
* 根据 插件名称 和 Activity 名称 查询 ActivityInfo
*
* @param name 插件名称
* @param className Activity 名称
* @return Activity 对应的 ActivityInfo
*/
public static final ActivityInfo queryActivityInfo(String name, String className) {
ComponentList componentList = sPluginManager.queryPluginComponentList(name);
if (componentList != null) {
return componentList.getActivity(className);
} else {
return null;
}
}
/**
* 根据 插件名称 和 Service 名称 查询 ServiceInfo
*
* @param name 插件名称
* @param className Service 名称
* @return Service 对应的 ServiceInfo
*/
public static final ServiceInfo queryServiceInfo(String name, String className) {
ComponentList componentList = sPluginManager.queryPluginComponentList(name);
if (componentList != null) {
return componentList.getService(className);
} else {
return null;
}
}
/**
* 根据 activity 和 intent 中的数据获取 ActivityInfo 信息
* @param plugin 插件名
* @param activity Activity 名称
* @param intent 其中可能包含 action
*/
public static ActivityInfo getActivityInfo(String plugin, String activity, Intent intent) {
return sPluginManager.getActivityInfo(plugin, activity, intent);
}
/**
* 根据 action 从插件获取 receiver 列表
*
* @return 符合 action 的所有 ReceiverInfo
*/
public static List queryPluginsReceiverList(Intent intent) {
return sPluginManager.queryPluginsReceiverList(intent);
}
/**
* 启动一个插件中的activity,如果插件不存在会触发下载界面
* @deprecated 只为旧插件而用。请使用RePlugin.startActivity方法
* @param context 应用上下文或者Activity上下文
* @param intent Intent对象
* @param plugin 插件名
* @param activity 待启动的activity类名
* @param process 是否在指定进程中启动
* @return 插件机制层,是否成功,例如没有插件存在、没有合适的Activity坑
*/
public static final boolean startActivity(Context context, Intent intent, String plugin, String activity, int process) {
// 此方法“唯一”调用路径是从插件或主程序中调用Factory.startActivity,表示调用方是“要求”打开插件Activity的,排除了要打开宿主Activity的情况
// 为了和旧插件Factory.startActivity方法兼容,判断当plugin和activity均有值,则自动帮其填入
// 注意:
// 1. 仅在此方法上生效,其余方法均不能这么做,防止出现“本想打开宿主,结果定向到了插件”的问题
// 2. 若以Action打开,则无需(也不能)填写ComponentName
// 3. plugin/activity会覆盖Intent.ComponentName(为兼容旧插件),毕竟在框架内部,这两个组合也是优先于CN的
// Added by Jiongxuan Zhang
if (!TextUtils.isEmpty(plugin) && !TextUtils.isEmpty(activity)) {
intent.setComponent(RePlugin.createComponentName(plugin, activity));
}
return startActivityWithNoInjectCN(context, intent, plugin, activity, process);
}
/**
* 内部接口,仅为Factory2.startActivity(context, intent) 和 RePlugin.startActivity方法而使用
*
* @param context 应用上下文或者Activity上下文
* @param intent Intent对象
* @param plugin 插件名
* @param activity 待启动的activity类名
* @param process 是否在指定进程中启动
* @return 插件机制层,是否成功,例如没有插件存在、没有合适的Activity坑
* Added by Jiongxuan Zhang
*/
public static final boolean startActivityWithNoInjectCN(Context context, Intent intent, String plugin, String activity, int process) {
boolean result = sPluginManager.startActivity(context, intent, plugin, activity, process);
RePlugin.getConfig().getEventCallbacks().onStartActivityCompleted(plugin, activity, result);
return result;
}
/**
* 加载插件Activity,在startActivity之前调用
* @param intent
* @param plugin 插件名
* @param target 目标Activity名,如果传null,则取获取到的第一个
* @param process 是否在指定进程中启动
* @return
*/
public static final ComponentName loadPluginActivity(Intent intent, String plugin, String target, int process) {
return sPluginManager.loadPluginActivity(intent, plugin, target, process);
}
/**
* 加载插件Service,在startService、bindService之前调用
* @param plugin 插件名
* @param target 目标Service名,如果传null,则取获取到的第一个
* @param process 是否在指定进程中启动
* @return
*/
public static final ComponentName loadPluginService(String plugin, String target, int process) {
return sPluginManager.loadPluginService(plugin, target, process);
}
/**
* 加载插件的Provider,在使用插件的Provider之前调用
* @param plugin 插件名
* @param target 目标Provider名,如果传null,则取获取到的第一个
* @param process 是否在指定进程中启动
* @return
*
* @deprecated 已废弃,请使用PluginProviderClient里面的方法
*/
@Deprecated
public static final Uri loadPluginProvider(String plugin, String target, int process) {
return sPluginManager.loadPluginProvider(plugin, target, process);
}
/**
* 不要直接使用该方法,否则会抛出异常(Debug)
* @deprecated 已废弃,请使用PluginProviderClient里面的方法
*/
public static final Uri makePluginProviderUri(String plugin, Uri uri, int process) {
// 因目前没有插件要用,所以直接抛出异常即可
if (BuildConfig.DEBUG) {
throw new IllegalStateException();
}
return uri;
}
/**
* 通过ClassLoader来获取插件名
*
* @param cl ClassLoader对象
* @return 插件名,若和主程序一致,则返回IModule.PLUGIN_NAME_MAIN(“main”)
* Added by Jiongxuan Zhang
*/
public static final String fetchPluginName(ClassLoader cl) {
return sPluginManager.fetchPluginName(cl);
}
/**
* 通过 forResult 方式启动一个插件的 Activity
*
* @param activity 源 Activity
* @param intent 要打开 Activity 的 Intent,其中 ComponentName 的 Key 必须为插件名
* @param requestCode 请求码
* @param options 附加的数据
* @since 2.1.3
*/
public static boolean startActivityForResult(Activity activity, Intent intent, int requestCode, Bundle options) {
return sPluginManager.startActivityForResult(activity, intent, requestCode, options);
}
}
================================================
FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/i/Factory2.java
================================================
/*
* 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.i;
import android.app.Activity;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import com.qihoo360.loader2.PluginLibraryInternalProxy;
import org.json.JSONArray;
/**
* plugin-library中,通过“反射”调用的内部逻辑(如PluginActivity类的调用等)均在此处
* 注意:务必要Keep住此类,否则插件调用将失败
*
* @author RePlugin Team
*/
public final class Factory2 {
/**
* @hide 内部框架使用
*/
public static PluginLibraryInternalProxy sPLProxy;
/**
* @hide 内部方法,插件框架使用
* 插件的Activity创建成功后通过此方法获取其base context
* @param activity
* @param newBase
* @return 为Activity构造一个base Context
*/
public static final Context createActivityContext(Activity activity, Context newBase) {
return sPLProxy.createActivityContext(activity, newBase);
}
/**
* @hide 内部方法,插件框架使用
* 插件的Activity的onCreate调用前调用此方法
* @param activity
* @param savedInstanceState
*/
public static final void handleActivityCreateBefore(Activity activity, Bundle savedInstanceState) {
sPLProxy.handleActivityCreateBefore(activity, savedInstanceState);
}
/**
* @hide 内部方法,插件框架使用
* 插件的Activity的onCreate调用后调用此方法
* @param activity
* @param savedInstanceState
*/
public static final void handleActivityCreate(Activity activity, Bundle savedInstanceState) {
sPLProxy.handleActivityCreate(activity, savedInstanceState);
}
/**
* @hide 内部方法,插件框架使用
* 插件的Activity的onDestroy调用后调用此方法
* @param activity
*/
public static final void handleActivityDestroy(Activity activity) {
sPLProxy.handleActivityDestroy(activity);
}
/**
* @hide 内部方法,插件框架使用
* 插件的Activity的onRestoreInstanceState调用后调用此方法
* @param activity
* @param savedInstanceState
*/
public static final void handleRestoreInstanceState(Activity activity, Bundle savedInstanceState) {
sPLProxy.handleRestoreInstanceState(activity, savedInstanceState);
}
/**
* @hide 内部方法,插件框架使用
* 插件的Service的onCreate调用后调用此方法
* @param service
*/
public static final void handleServiceCreate(Service service) {
sPLProxy.handleServiceCreate(service);
}
/**
* @hide 内部方法,插件框架使用
* 插件的Service的onDestroy调用后调用此方法
* @param service
*/
public static final void handleServiceDestroy(Service service) {
sPLProxy.handleServiceDestroy(service);
}
/**
* @hide 内部方法,插件框架使用
* 启动一个插件中的activity
* 通过Extra参数IPluginManager.KEY_COMPATIBLE,IPluginManager.KEY_PLUGIN,IPluginManager.KEY_ACTIVITY,IPluginManager.KEY_PROCESS控制
* @param context Context上下文
* @param intent
* @return 插件机制层是否成功,例如没有插件存在、没有合适的Activity坑
*/
public static final boolean startActivity(Context context, Intent intent) {
return sPLProxy.startActivity(context, intent);
}
/**
* @hide 内部方法,插件框架使用
* 启动一个插件中的activity
* 通过Extra参数IPluginManager.KEY_COMPATIBLE,IPluginManager.KEY_PLUGIN,IPluginManager.KEY_ACTIVITY,IPluginManager.KEY_PROCESS控制
* @param activity Activity上下文
* @param intent
* @return 插件机制层是否成功,例如没有插件存在、没有合适的Activity坑
*/
public static final boolean startActivity(Activity activity, Intent intent) {
return sPLProxy.startActivity(activity, intent);
}
/**
* @hide 内部方法,插件框架使用
* 启动一个插件中的activity,如果插件不存在会触发下载界面
* @param context 应用上下文或者Activity上下文
* @param intent
* @param plugin 插件名
* @param activity 待启动的activity类名
* @param process 是否在指定进程中启动
* @param download 下载
* @return 插件机制层是否成功,例如没有插件存在、没有合适的Activity坑
*/
public static final boolean startActivity(Context context, Intent intent, String plugin, String activity, int process, boolean download) {
return sPLProxy.startActivity(context, intent, plugin, activity, process, download);
}
/**
* 通过 forResult 方式启动一个插件的 Activity
*
* @param activity 源 Activity
* @param intent 要打开 Activity 的 Intent,其中 ComponentName 的 Key 必须为插件名
* @param requestCode 请求码
* @param options 附加的数据
* @see #startActivityForResult(Activity, Intent, int, Bundle)
*/
public static final boolean startActivityForResult(Activity activity, Intent intent, int requestCode, Bundle options) {
return sPLProxy.startActivityForResult(activity, intent, requestCode, options);
}
/**
* @hide 内部方法,插件框架使用
* 返回所有插件的json串,格式见plugins-builtin.json文件
* @param name 插件名,传null或者空串表示获取全部
* @return
*/
public static final JSONArray fetchPlugins(String name) {
return sPLProxy.fetchPlugins(name);
}
/**
* @hide 内部方法,插件框架使用
* 登记动态映射的类
* @param className 壳类名
* @param plugin 目标插件名
* @param type 目标类的类型: activity, service, provider
* @param target 目标类名
* @return
*/
public static final boolean registerDynamicClass(String className, String plugin, String type, String target) {
return sPLProxy.registerDynamicClass(className, plugin, type, target);
}
/**
* @hide 内部方法,插件框架使用
* 登记动态映射的类
* @param className 壳类名
* @param plugin 目标插件名
* @param target 目标类名
* @return
*/
public static final boolean registerDynamicClass(String className, String plugin, String target, Class defClass) {
return sPLProxy.registerDynamicClass(className, plugin, target, defClass);
}
/**
* @hide 内部方法,插件框架使用
* 查询动态映射的类
* @param className 壳类名
* @param plugin 目标插件名
* @return
*/
public static final boolean isDynamicClass(String plugin, String className) {
return sPLProxy.isDynamicClass(plugin, className);
}
public static void unregisterDynamicClass(String source) {
sPLProxy.unregisterDynamicClass(source);
}
/**
* @hide 内部方法,插件框架调用
* 根据动态注册的类,反查此类对应的插件名称
*
* @param className 动态类名称
* @return 插件名称
*/
public static final String getPluginByDynamicClass(String className) {
return sPLProxy.getPluginByDynamicClass(className);
}
}
================================================
FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/i/IModule.java
================================================
/*
* 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.i;
/**
* 所有可查询的接口都从此interface继承
* 在插件体系中,module是一种略高于interface的概念
* 一个插件可导出一个到多个module,这些module可输出自己业务的各种interface
*
* @author RePlugin Team
*
*/
public interface IModule {
/**
* 万能接口:当不能升级adapter.jar的时候再考虑使用
* @param args
* @return
*/
Object invoke(Object...args);
}
================================================
FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/i/IPlugin.java
================================================
/*
* 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.i;
/**
* 此接口由插件负责导出
* 表示一个具体的物理上的插件实体,例如barcode.jar
* 具体导出细节可看Factory
*
* @author RePlugin Team
*
*/
public interface IPlugin {
/**
* @param c 需要查询的interface的类
* @return
*/
IModule query(Class extends IModule> c);
}
================================================
FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/i/IPluginManager.java
================================================
/*
* 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.i;
/**
* 注意:这里目前仅放置一些常量,大部分方法已移动至 PluginCommImpl 中
*
* @author RePlugin Team
*/
public interface IPluginManager {
/**
* 插件Activity上下文通过startActivity启动,用系统默认的启动方法
* 如果设置该值,总是为boolean值true
*/
String KEY_COMPATIBLE = "compatible";
/**
* 通过Intent的extra参数指出目标插件名
* 插件Activity上下文通过startActivity启动其它插件Activity时用到
* 如果不指定,则默认用当前Activity的插件
* 如果设置了KEY_COMPATIBLE,则忽略此参数
*/
String KEY_PLUGIN = "plugin";
/**
* 通过Intent的extra参数指出目标Activity名
* 如果不指定,则默认用ComponentName参数的类名来启动
* 如果设置了KEY_COMPATIBLE,则忽略此参数
*/
String KEY_ACTIVITY = "activity";
/**
* 通过Intent的extra参数指出需要在指定进程中启动
* 只能启动standard的Activity,不能启动singleTask、singleInstance等
* 不指定时,自动分配插件进程,即PROCESS_AUTO
*/
String KEY_PROCESS = "process";
/**
* 自动分配插件进程
*/
int PROCESS_AUTO = Integer.MIN_VALUE;
/**
* UI进程
*/
int PROCESS_UI = -1;
/**
* 常驻进程
*/
int PROCESS_PERSIST = -2;
}
================================================
FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader/utils/LocalBroadcastManager.java
================================================
package com.qihoo360.loader.utils;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import com.qihoo360.replugin.utils.ReflectUtils;
public class LocalBroadcastManager {
private static String V4_MANAGER = "android.support.v4.content.LocalBroadcastManager";
private static String ANDROIDX_MANAGER = "androidx.localbroadcastmanager.content.LocalBroadcastManager";
private static LocalBroadcastManager instance;
private Context context;
private boolean init;
private static Object managerObj;
public static LocalBroadcastManager getInstance(Context context) {
if (instance == null) {
synchronized (LocalBroadcastManager.class) {
if (instance == null) {
instance = new LocalBroadcastManager(context);
}
}
}
return instance;
}
private LocalBroadcastManager(Context context) {
this.context = context.getApplicationContext();
loadClass();
}
private void loadClass() {
try {
Class cls = null;
try {
cls = ReflectUtils.getClass(ANDROIDX_MANAGER);
} catch (Exception e) {
cls = ReflectUtils.getClass(V4_MANAGER);
}
if (cls == null) {
return;
}
managerObj = ReflectUtils.getMethod(cls, "getInstance", new Class>[]{Context.class}).invoke(null, context);
init = true;
} catch (Exception e) {
}
}
public boolean registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
if (!init || managerObj == null) {
return false;
}
try {
ReflectUtils.getMethod(managerObj.getClass(), "registerReceiver",
BroadcastReceiver.class, IntentFilter.class).invoke(managerObj, receiver, filter);
return true;
} catch (Exception e) {
}
return false;
}
public boolean unregisterReceiver(BroadcastReceiver receiver) {
if (!init || managerObj == null) {
return false;
}
try {
ReflectUtils.getMethod(managerObj.getClass(), "unregisterReceiver",
BroadcastReceiver.class).invoke(managerObj, receiver);
return true;
} catch (Exception e) {
}
return false;
}
public boolean sendBroadcast(Intent intent) {
if (!init || managerObj == null) {
return false;
}
try {
ReflectUtils.getMethod(managerObj.getClass(), "sendBroadcast",
Intent.class).invoke(managerObj, intent);
return true;
} catch (Exception e) {
}
return false;
}
public boolean sendBroadcastSync(Intent intent) {
if (!init || managerObj == null) {
return false;
}
try {
ReflectUtils.getMethod(managerObj.getClass(), "sendBroadcastSync",
Intent.class).invoke(managerObj, intent);
return true;
} catch (Exception e) {
}
return false;
}
}
================================================
FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader/utils/PackageUtils.java
================================================
/*
* 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.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import com.qihoo360.replugin.helper.LogDebug;
/**
* @author RePlugin Team
*/
public class PackageUtils {
/**
* 获取PackageInfo对象
*
* 注:getPackageArchiveInfo Android 2.x上,可能拿不到signatures,本可以通过反射去获取,但是考虑到会触发Android 的灰/黑名单,这个方法就不再继续适配2.X了
*
* @return
*/
public static PackageInfo getPackageArchiveInfo(PackageManager pm, String pkgFilePath, int flags) {
PackageInfo info = null;
try {
info = pm.getPackageArchiveInfo(pkgFilePath, flags);
} catch (Throwable e) {
if (LogDebug.LOG) {
e.printStackTrace();
}
}
return info;
}
}
================================================
FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader/utils/PatchClassLoaderUtils.java
================================================
/*
* 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;
}
}
================================================
FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader/utils/ProcessLocker.java
================================================
/*
* 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.content.Context;
import com.qihoo360.replugin.helper.LogDebug;
import com.qihoo360.replugin.helper.LogRelease;
import com.qihoo360.replugin.utils.FileUtils;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import static com.qihoo360.replugin.helper.LogDebug.MAIN_TAG;
import static com.qihoo360.replugin.helper.LogRelease.LOGR;
/**
* 进程锁
*
* @author RePlugin Team
*/
public final class ProcessLocker {
private static final String TAG = LogDebug.PLUGIN_TAG;
private final Context mContext;
private FileOutputStream mFileOutputStream;
private FileChannel mFileChannel;
private FileLock mFileLock;
private File mFile;
/**
* @param context
* @param filename
*/
public ProcessLocker(Context context, String filename) {
mContext = context;
try {
mFile = new File(filename);
mFileOutputStream = mContext.openFileOutput(filename, 0);
if (mFileOutputStream != null) {
mFileChannel = mFileOutputStream.getChannel();
}
if (mFileChannel == null) {
if (LOGR) {
LogRelease.e(MAIN_TAG, "channel is null");
}
}
} catch (Throwable e) {
if (LOGR) {
LogRelease.e(MAIN_TAG, e.getMessage(), e);
}
}
}
/**
* 允许传递绝对路径
*
* @param context
* @param dir
* @param filename
*/
public ProcessLocker(Context context, String dir, String filename) {
mContext = context;
try {
mFile = new File(dir, filename);
if (!mFile.exists()) {
FileUtils.forceMkdirParent(mFile);
mFile.createNewFile();
}
mFileOutputStream = new FileOutputStream(mFile, false);
mFileChannel = mFileOutputStream.getChannel();
} catch (Throwable e) {
if (LOGR) {
LogRelease.e(MAIN_TAG, e.getMessage(), e);
}
}
}
/**
* 查看文件是否已经被上锁
*
* @return
*/
public final synchronized boolean isLocked() {
boolean ret = tryLock();
// 加锁成功说明文件还未被上锁
// 在退出之前一定要进行unlock
if (ret) {
unlock();
}
return !ret;
}
/**
* 加锁
*
* @return
*/
public final synchronized boolean tryLock() {
if (mFileChannel == null) {
return false;
}
try {
mFileLock = mFileChannel.tryLock();
if (mFileLock != null) {
return true;
}
} catch (Throwable e) {
if (LOGR) {
LogRelease.e(MAIN_TAG, e.getMessage(), e);
}
}
return false;
}
/**
* 加锁
*
* @param ms 毫秒
* @param interval 间隔
* @return
*/
public final synchronized boolean tryLockTimeWait(int ms, int interval) {
if (mFileChannel == null) {
return false;
}
// 自动修正到最小值,避免死锁
if (ms <= 0) {
ms = 1;
}
if (interval <= 0) {
interval = 1;
}
try {
for (int i = 0; i < ms; i += interval) {
try {
mFileLock = mFileChannel.tryLock();
} catch (IOException e) {
// 获取锁失败会抛异常,此处忽略
// java.io.IOException: fcntl failed: EAGAIN (Try again)
}
if (mFileLock != null) {
return true;
}
// 每秒钟输出一次日志,防止“刷屏”
if (LOGR) {
if (i % 1000 == 0) {
LogRelease.i(TAG, "wait process lock: " + i + "/" + ms);
}
}
Thread.sleep(interval, 0);
}
} catch (Throwable e) {
if (LOGR) {
LogRelease.e(MAIN_TAG, e.getMessage(), e);
}
}
return false;
}
/**
* 加锁
*
* @return
*/
public final synchronized boolean lock() {
if (mFileChannel == null) {
return false;
}
try {
mFileLock = mFileChannel.lock();
if (mFileLock != null) {
return true;
}
} catch (Throwable e) {
if (LOGR) {
LogRelease.e(MAIN_TAG, e.getMessage(), e);
}
}
return false;
}
/**
* 释放并且删除该锁文件
*/
public final synchronized void unlock() {
if (mFileLock != null) {
try {
mFileLock.release();
} catch (Throwable e) {
if (LOGR) {
LogRelease.e(TAG, e.getMessage(), e);
}
}
}
if (mFileChannel != null) {
try {
mFileChannel.close();
} catch (Throwable e) {
if (LOGR) {
LogRelease.e(TAG, e.getMessage(), e);
}
}
}
if (mFileOutputStream != null) {
try {
mFileOutputStream.close();
} catch (Throwable e) {
if (LOGR) {
LogRelease.e(TAG, e.getMessage(), e);
}
}
}
// 删除锁文件
if (mFile != null && mFile.exists()) {
mFile.delete();
}
}
}
================================================
FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader/utils/StringUtils.java
================================================
/*
* 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.util.Base64;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.List;
/**
* @author RePlugin Team
*/
public class StringUtils {
private static final char[] HEX_DIGITS = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'A', 'B', 'C', 'D', 'E', 'F'
};
public static final String toHexString(byte[] bytes) {
StringBuilder sb = new StringBuilder(bytes.length * 2);
for (byte b : bytes) {
sb.append(HEX_DIGITS[(b & 0xf0) >> 4]);
sb.append(HEX_DIGITS[b & 0x0f]);
}
return sb.toString();
}
public static final String md5base64(byte buffer[]) throws NoSuchAlgorithmException {
MessageDigest digest = MessageDigest.getInstance("MD5");
digest.update(buffer);
byte buf2[] = digest.digest();
return Base64.encodeToString(buf2, Base64.NO_WRAP | Base64.NO_PADDING | Base64.NO_CLOSE);
}
public static final String utf8md5base64(String str) throws NoSuchAlgorithmException {
byte buf1[] = str.getBytes();
return md5base64(buf1);
}
public static String toStringWithLines(List> list) {
String t = list.toString();
return t.replace(", ", ", \n");
}
public static char lastChar(String s) {
return s.charAt(s.length() - 1);
}
}
================================================
FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader/utils/SysUtils.java
================================================
/*
* 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 com.qihoo360.replugin.helper.LogRelease;
import com.qihoo360.replugin.utils.CloseableUtils;
import java.io.FileInputStream;
import static com.qihoo360.replugin.helper.LogRelease.LOGR;
/**
* @author RePlugin Team
*/
public final class SysUtils {
private static final String TAG = "Plugin.SysUtils";
/**
* 返回当前的进程名
*
* @return
*/
public static String getCurrentProcessName() {
FileInputStream in = null;
try {
String fn = "/proc/self/cmdline";
in = new FileInputStream(fn);
byte[] buffer = new byte[256];
int len = 0;
int b;
while ((b = in.read()) > 0 && len < buffer.length) {
buffer[len++] = (byte) b;
}
if (len > 0) {
String s = new String(buffer, 0, len, "UTF-8");
return s;
}
} catch (Throwable e) {
if (LOGR) {
LogRelease.e(TAG, e.getMessage(), e);
}
} finally {
CloseableUtils.closeQuietly(in);
}
return null;
}
}
================================================
FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader/utils2/FilePermissionUtils.java
================================================
/*
* 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.utils2;
import com.qihoo360.replugin.utils.ReflectUtils;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* @author RePlugin Team
*/
public class FilePermissionUtils {
public static final int S_IRWXU = 00700;
public static final int S_IRUSR = 00400;
public static final int S_IWUSR = 00200;
public static final int S_IXUSR = 00100;
public static final int S_IRWXG = 00070;
public static final int S_IRGRP = 00040;
public static final int S_IWGRP = 00020;
public static final int S_IXGRP = 00010;
public static final int S_IRWXO = 00007;
public static final int S_IROTH = 00004;
public static final int S_IWOTH = 00002;
public static final int S_IXOTH = 00001;
private static Class> sFileUtilsClass;
private static Method sSetPermissionMethod;
private static Method sGetPermissionMethod;
/**
* 设置文件的访问权限,使用反射调用系统隐藏同名函数。
* @param filePath 需要被设置访问权限的文件
* @param mode 文件访问权限,如0777,0755
* @param uid
* @param gid
* @return -1 表示设置失败
*/
public static int setPermissions(String filePath, int mode, int uid, int gid) {
try {
initClass();
if (sSetPermissionMethod == null) {
sSetPermissionMethod = ReflectUtils.getMethod(sFileUtilsClass, "setPermissions",
String.class, Integer.TYPE, Integer.TYPE, Integer.TYPE);
}
Object retObj = sSetPermissionMethod.invoke(null, filePath, mode, uid, gid);
if (retObj != null && retObj instanceof Integer) {
return (int) retObj;
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return -1;
}
/**
* 查询文件的访问权限,这个接口只能在4.1.2或之前有效
* @param filePath
* @param outPermissions
* @return -1 表示查询失败
*/
public static int getPermissions(String filePath, int[] outPermissions) {
try {
initClass();
if (sGetPermissionMethod == null) {
sGetPermissionMethod = ReflectUtils.getMethod(sFileUtilsClass, "getPermissions",
String.class, int[].class);
}
Object retObj = sGetPermissionMethod.invoke(null, filePath, outPermissions);
if (retObj != null && retObj instanceof Integer) {
return (int) retObj;
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return -1;
}
private static void initClass() throws ClassNotFoundException {
if (sFileUtilsClass == null) {
sFileUtilsClass = ReflectUtils.getClass("android.os.FileUtils");
}
}
}
================================================
FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/BinderCursor.java
================================================
/*
* 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.loader2;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
import com.qihoo360.replugin.helper.LogDebug;
import com.qihoo360.replugin.model.PluginInfo;
import static com.qihoo360.replugin.helper.LogDebug.LOG;
import static com.qihoo360.replugin.helper.LogDebug.PLUGIN_TAG;
/**
* @author RePlugin Team
*/
public class BinderCursor extends MatrixCursor {
static final String BINDER_KEY = "binder";
Bundle mBinderExtra = new Bundle();
public static class BinderParcelable implements Parcelable {
IBinder mBinder;
public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
@Override
public BinderParcelable createFromParcel(Parcel source) {
return new BinderParcelable(source);
}
@Override
public BinderParcelable[] newArray(int size) {
return new BinderParcelable[size];
}
};
BinderParcelable(IBinder binder) {
mBinder = binder;
}
BinderParcelable() {
//
}
BinderParcelable(Parcel source) {
mBinder = source.readStrongBinder();
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeStrongBinder(mBinder);
}
}
public BinderCursor(String[] columnNames, IBinder binder) {
super(columnNames);
if (binder != null) {
Parcelable value = new BinderParcelable(binder);
mBinderExtra.putParcelable(BINDER_KEY, value);
}
}
@Override
public Bundle getExtras() {
return mBinderExtra;
}
public static final Cursor queryBinder(IBinder binder) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "query binder = " + binder);
}
return new BinderCursor(PluginInfo.QUERY_COLUMNS, binder);
}
public static final IBinder getBinder(Cursor cursor) {
Bundle extras = cursor.getExtras();
extras.setClassLoader(BinderCursor.class.getClassLoader());
BinderParcelable w = (BinderParcelable) extras.getParcelable(BINDER_KEY);
if (LOG) {
LogDebug.d(PLUGIN_TAG, "get binder = " + w.mBinder);
}
return w.mBinder;
}
}
================================================
FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/BuildCompat.java
================================================
/*
* 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.loader2;
import android.os.Build;
/**
* @author RePlugin Team
*/
public class BuildCompat {
public static final String ARM = "arm";
public static final String ARM64 = "arm64";
public static final String[] SUPPORTED_ABIS;
public static final String[] SUPPORTED_32_BIT_ABIS;
public static final String[] SUPPORTED_64_BIT_ABIS;
static {
//init SUPPORTED_ABIS
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (Build.SUPPORTED_ABIS != null) {
SUPPORTED_ABIS = new String[Build.SUPPORTED_ABIS.length];
System.arraycopy(Build.SUPPORTED_ABIS, 0, SUPPORTED_ABIS, 0, SUPPORTED_ABIS.length);
} else {
SUPPORTED_ABIS = new String[]{Build.CPU_ABI, Build.CPU_ABI2};
}
} else {
SUPPORTED_ABIS = new String[]{Build.CPU_ABI, Build.CPU_ABI2};
}
//init SUPPORTED_32_BIT_ABIS
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (Build.SUPPORTED_32_BIT_ABIS != null) {
SUPPORTED_32_BIT_ABIS = new String[Build.SUPPORTED_32_BIT_ABIS.length];
System.arraycopy(Build.SUPPORTED_32_BIT_ABIS, 0, SUPPORTED_32_BIT_ABIS, 0, SUPPORTED_32_BIT_ABIS.length);
} else {
SUPPORTED_32_BIT_ABIS = new String[]{Build.CPU_ABI, Build.CPU_ABI2};
}
} else {
SUPPORTED_32_BIT_ABIS = new String[]{Build.CPU_ABI, Build.CPU_ABI2};
}
//init SUPPORTED_64_BIT_ABIS
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (Build.SUPPORTED_64_BIT_ABIS != null) {
SUPPORTED_64_BIT_ABIS = new String[Build.SUPPORTED_64_BIT_ABIS.length];
System.arraycopy(Build.SUPPORTED_64_BIT_ABIS, 0, SUPPORTED_64_BIT_ABIS, 0, SUPPORTED_64_BIT_ABIS.length);
} else {
SUPPORTED_64_BIT_ABIS = new String[]{Build.CPU_ABI, Build.CPU_ABI2};
}
} else {
SUPPORTED_64_BIT_ABIS = new String[]{Build.CPU_ABI, Build.CPU_ABI2};
}
}
}
================================================
FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/Builder.java
================================================
/*
* 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.loader2;
import android.content.Context;
import android.os.Build;
import com.qihoo360.replugin.helper.LogDebug;
import com.qihoo360.replugin.model.PluginInfo;
import com.qihoo360.replugin.utils.FileUtils;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import static com.qihoo360.replugin.helper.LogDebug.LOG;
import static com.qihoo360.replugin.helper.LogDebug.PLUGIN_TAG;
/**
* @author RePlugin Team
*/
public class Builder {
/**
* Plugins 信息搜集
* 插件搜索顺序:1、内置插件;2、V5复合插件;3、V5插件;4、释放出来的插件
*/
static final class PxAll {
/**
* 内置插件
*/
private final ArrayList builtins = new ArrayList();
/**
* V5单文件插件
*/
private final ArrayList v5 = new ArrayList();
/**
* 释放出来的普通插件
*/
private final ArrayList normals = new ArrayList();
/**
* 其它
*/
private final HashSet others = new HashSet();
/**
* 所有插件
*/
private final ArrayList all = new ArrayList();
/**
* 确保版本和插件唯一
* @param array
* @param info
* @param replace true表示更新相同的,false表示不更新相同的
* @return
*/
private final boolean insert(ArrayList array, PluginInfo info, boolean replace) {
for (int i = 0; i < array.size(); i++) {
PluginInfo pi = array.get(i);
// 存在
if (pi.getName().equals(info.getName())) {
// 忽略
if (replace) {
if (PluginInfo.VERSION_COMPARATOR.compare(pi, info) > 0) {
return false;
}
} else {
if (PluginInfo.VERSION_COMPARATOR.compare(pi, info) >= 0) {
return false;
}
}
// 更新
others.add(array.get(i));
array.set(i, info);
return true;
}
}
// 不存在,添加
array.add(info);
return true;
}
private final boolean hasOlder(ArrayList array, PluginInfo info) {
for (PluginInfo pi : array) {
if (pi.getName().equals(info.getName())) {
if (PluginInfo.VERSION_COMPARATOR.compare(pi, info) < 0) {
return true;
}
}
}
return false;
}
/**
* @param name
* @return
*/
private final PluginInfo getBuiltin(String name) {
for (PluginInfo pi : builtins) {
if (pi.getName().equals(name)) {
return pi;
}
}
return null;
}
/**
* @param name
* @return
*/
private final PluginInfo getV5(String name) {
for (PluginInfo pi : v5) {
if (pi.getName().equals(name)) {
return pi;
}
}
return null;
}
/**
* @return
*/
final HashSet getOthers() {
return others;
}
/**
* @return
*/
final ArrayList getPlugins() {
return all;
}
/**
* @param info
*/
final void addBuiltin(PluginInfo info) {
insert(builtins, info, false);
insert(all, info, false);
}
/**
* @param info
*/
final void addV5(PluginInfo info) {
if (!insert(all, info, false)) {
return;
}
insert(v5, info, false);
}
/**
* @param info
* @return
*/
final void addNormal(PluginInfo info) {
PluginInfo pi = null;
// FIXME 用all表
if ((pi = getBuiltin(info.getName())) != null && pi.getVersionValue() == info.getVersionValue()) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "builtin plugin: normal=" + info);
}
} else if ((pi = getV5(info.getName())) != null && pi.getVersionValue() == info.getVersionValue()) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "v5 plugin: normal=" + info);
}
} else {
others.add(info);
return;
}
insert(normals, info, false);
}
}
static final void builder(Context context, PxAll all) {
// 搜索所有本地插件和V5插件
Finder.search(context, all);
}
private static File getDexDir(Context context) {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) {
return new File(context.getDir(Constant.LOCAL_PLUGIN_SUB_DIR, 0) + File.separator + "oat" + File.separator + VMRuntimeCompat.getArtOatCpuType());
} else {
return context.getDir(Constant.LOCAL_PLUGIN_ODEX_SUB_DIR, 0);
}
}
private static void deleteUnknownDexs(Context context, PxAll all) {
HashSet names = new HashSet<>();
for (PluginInfo p : all.getPlugins()) {
names.add(p.getDexFile().getName());
if (LOG) {
LogDebug.d(PLUGIN_TAG, "dexFile:" + p.getDexFile().getName());
}
// add vdex for Android O
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) {
String fileNameWithoutExt = FileUtils.getFileNameWithoutExt(p.getDexFile().getAbsolutePath());
if (LOG) {
LogDebug.d(PLUGIN_TAG, "vdexFile:" + (fileNameWithoutExt + ".vdex"));
}
names.add(fileNameWithoutExt + ".vdex");
}
}
File dexDir = getDexDir(context);
if (LOG) {
LogDebug.d(PLUGIN_TAG, "to delete dex dir:" + dexDir);
}
File files[] = dexDir.listFiles();
if (files != null) {
for (File f : files) {
if (names.contains(f.getName())) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "no need delete " + f.getAbsolutePath());
}
continue;
}
if (LOG) {
LogDebug.d(PLUGIN_TAG, "delete unknown dex=" + f.getAbsolutePath());
}
try {
FileUtils.forceDelete(f);
} catch (IOException e) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "can't delete unknown dex=" + f.getAbsolutePath(), e);
}
} catch (IllegalArgumentException e2) {
if (LOG) {
e2.printStackTrace();
}
}
}
}
}
private static void deleteUnknownLibs(Context context, PxAll all) {
HashSet names = new HashSet<>();
for (PluginInfo p : all.getPlugins()) {
names.add(p.getOldNativeLibsDir().getName());
}
File dir = context.getDir(Constant.LOCAL_PLUGIN_DATA_LIB_DIR, 0);
File files[] = dir.listFiles();
if (files != null) {
for (File f : files) {
if (names.contains(f.getName())) {
continue;
}
if (LOG) {
LogDebug.d(PLUGIN_TAG, "delete unknown libs=" + f.getAbsolutePath());
}
try {
FileUtils.forceDelete(f);
} catch (IOException e) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "can't delete unknown libs=" + f.getAbsolutePath(), e);
}
} catch (IllegalArgumentException e2) {
if (LOG) {
e2.printStackTrace();
}
}
}
}
}
}
================================================
FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/CertUtils.java
================================================
/*
* 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.loader2;
import android.content.pm.PackageInfo;
import android.content.pm.Signature;
import android.text.TextUtils;
import com.qihoo360.loader.utils.StringUtils;
import com.qihoo360.replugin.helper.LogDebug;
import com.qihoo360.replugin.helper.LogRelease;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import static com.qihoo360.replugin.helper.LogDebug.LOG;
import static com.qihoo360.replugin.helper.LogDebug.PLUGIN_TAG;
/**
* @author RePlugin Team
*/
public class CertUtils {
/**
*
*/
public static final ArrayList SIGNATURES = new ArrayList();
public static final boolean isPluginSignatures(PackageInfo info) {
if (info == null) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "info is null");
}
return false;
}
if (info.signatures == null) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "signatures is null");
}
return false;
}
for (Signature signature : info.signatures) {
boolean match = false;
String md5 = StringUtils.toHexString(md5NonE(signature.toByteArray()));
for (String element : SIGNATURES) {
if (TextUtils.equals(md5, element)) {
match = true;
if (LOG) {
LogDebug.i(PLUGIN_TAG, "isPluginSignatures: match. " + md5 + " package=" + info.packageName);
}
break;
}
}
if (!match) {
if (LOG) {
LogDebug.e(PLUGIN_TAG, "isPluginSignatures: unknown signature: " + md5 + " package=" + info.packageName);
}
if (LogRelease.LOGR) {
LogRelease.e(PLUGIN_TAG, "ibs: us " + md5);
}
return false;
}
}
return true;
}
public static final byte[] md5(byte buffer[]) throws NoSuchAlgorithmException {
MessageDigest digest = MessageDigest.getInstance("MD5");
digest.update(buffer, 0, buffer.length);
return digest.digest();
}
public static final byte[] md5NonE(byte buffer[]) {
try {
return md5(buffer);
} catch (NoSuchAlgorithmException e) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, e.getMessage(), e);
}
}
return new byte[0];
}
}
================================================
FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/Constant.java
================================================
/*
* 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.loader2;
import com.qihoo360.replugin.helper.HostConfigHelper;
/**
* @author RePlugin Team
*/
public class Constant {
/**
* HOST向下兼容版本
*/
public static final int ADAPTER_COMPATIBLE_VERSION = HostConfigHelper.ADAPTER_COMPATIBLE_VERSION;
/**
* HOST版本
*/
public static final int ADAPTER_CURRENT_VERSION = HostConfigHelper.ADAPTER_CURRENT_VERSION;
/**
* 插件存放目录
*/
public static final String LOCAL_PLUGIN_SUB_DIR = "plugins_v3";
/**
* 插件ODEX存放目录
*/
public static final String LOCAL_PLUGIN_ODEX_SUB_DIR = "plugins_v3_odex";
/**
* 插件data存放目录
*/
public static final String LOCAL_PLUGIN_DATA_SUB_DIR = "plugins_v3_data";
/**
* 插件Native(SO库)存放目录
* Added by Jiongxuan Zhang
*/
public static final String LOCAL_PLUGIN_DATA_LIB_DIR = "plugins_v3_libs";
/**
* "纯APK"插件存放目录
* Added by Jiongxuan Zhang
*/
public static final String LOCAL_PLUGIN_APK_SUB_DIR = "p_a";
/**
* "纯APK"中释放Odex的目录
* Added by Jiongxuan Zhang
*/
public static final String LOCAL_PLUGIN_APK_ODEX_SUB_DIR = "p_od";
/**
* 纯"APK"插件的Native(SO库)存放目录
* Added by Jiongxuan Zhang
*/
public static final String LOCAL_PLUGIN_APK_LIB_DIR = "p_n";
/**
* "纯APK"插件同版本升级时插件、Odex、Native(SO库)的用于覆盖的存放目录
*/
public static final String LOCAL_PLUGIN_APK_COVER_DIR = "p_c";
/**
* 插件extra dex(优化前)释放的以插件名独立隔离的子目录
* 适用于 android 5.0 以下,5.0以上不会用到该目录
*/
public static final String LOCAL_PLUGIN_INDEPENDENT_EXTRA_DEX_SUB_DIR = "_ed";
/**
* 插件extra dex(优化后)释放的以插件名独立隔离的子目录
*/
public static final String LOCAL_PLUGIN_INDEPENDENT_EXTRA_ODEX_SUB_DIR = "_eod";
/**
* 插件文件名,name-low-high-current.jar
* 插件文件名规范:barcode-1-10-2.jar
*/
public static final String LOCAL_PLUGIN_FILE_PATTERN = "^([^-]+)-([0-9]+)-([0-9]+)-([0-9]+).jar$";
/**
* 插件加载时的进程锁文件,插件间不共用一把锁
*/
public static final String LOAD_PLUGIN_LOCK = "plugin_v3_%s.lock";
/**
* Stub进程数:不能超过10个
*/
public static final int STUB_PROCESS_COUNT = 2;
/**
* Stub进程后缀
*/
public static final String STUB_PROCESS_SUFFIX_PATTERN = "^(.*):loader([0-" + (STUB_PROCESS_COUNT - 1) + "])$";
/**
*
*/
public static final String PLUGIN_NAME_UI = "ui";
/**
*
*/
public static final boolean LOG_V5_FILE_SEARCH = false;
/**
*
*/
public static final boolean SIMPLE_QUIT_CONTROLLER = false;
/**
*
*/
public static final boolean ENABLE_PLUGIN_ACTIVITY_AND_BINDER_RUN_IN_MAIN_UI_PROCESS = true;
}
================================================
FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/DumpUtils.java
================================================
/*
* 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.loader2;
import android.os.IBinder;
import android.util.Log;
import com.qihoo360.replugin.RePluginInternal;
import java.io.FileDescriptor;
import java.io.PrintWriter;
/**
* 运行时 dump 工具类
*
* @author RePlugin Team
*/
public class DumpUtils {
private static final String TAG = RePluginInternal.FOR_DEV ? DumpUtils.class.getSimpleName() : "DumpUtils";
/**
* dump RePlugin框架运行时的详细信息,包括:Activity 坑位映射表,正在运行的 Service,以及详细的插件信息
*
* @param fd
* @param writer
* @param args
*/
public static void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
IBinder binder = PluginProviderStub.proxyFetchHostBinder(RePluginInternal.getAppContext());
if (binder == null) {
return;
}
IPluginHost pluginHost = IPluginHost.Stub.asInterface(binder);
try {
String dumpInfo = pluginHost.dump();
if (RePluginInternal.FOR_DEV) {
Log.d(TAG, "dumpInfo:" + dumpInfo);
}
if (writer != null) {
writer.println(dumpInfo);
}
} catch (Throwable e) {
e.printStackTrace();
}
}
}
================================================
FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/Finder.java
================================================
/*
* 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.loader2;
import android.content.Context;
import com.qihoo360.loader2.Builder.PxAll;
import com.qihoo360.replugin.helper.LogDebug;
import com.qihoo360.replugin.model.PluginInfo;
import java.io.File;
import java.util.HashSet;
import static com.qihoo360.replugin.helper.LogDebug.LOG;
import static com.qihoo360.replugin.helper.LogDebug.PLUGIN_TAG;
/**
* @author RePlugin Team
*/
public class Finder {
/**
* 扫描插件
*/
static final void search(Context context, PxAll all) {
// 扫描内置插件-builtin
FinderBuiltin.loadPlugins(context, all);
}
private static final void searchLocalPlugins(File dir, PxAll all, HashSet others) {
File files[] = dir.listFiles();
if (files == null) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "search local plugin: nothing");
}
return;
}
for (File f : files) {
if (f.isDirectory()) {
continue;
}
if (f.length() <= 0) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "search local plugin: zero length, file=" + f.getAbsolutePath());
}
if (others != null) {
others.add(f);
}
continue;
}
PluginInfo info = PluginInfo.build(f);
if (info == null) {
if (others != null) {
others.add(f);
}
continue;
}
if (!info.match()) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "search local plugin: mismatch, file=" + f.getAbsolutePath());
}
if (others != null) {
others.add(f);
}
continue;
}
all.addNormal(info);
}
}
}
================================================
FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/FinderBuiltin.java
================================================
/*
* 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.loader2;
import android.content.Context;
import android.text.TextUtils;
import android.util.Log;
import com.qihoo360.replugin.utils.Charsets;
import com.qihoo360.replugin.utils.CloseableUtils;
import com.qihoo360.loader2.Builder.PxAll;
import com.qihoo360.replugin.helper.LogDebug;
import com.qihoo360.replugin.model.PluginInfo;
import com.qihoo360.replugin.utils.IOUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import static com.qihoo360.replugin.helper.LogDebug.LOG;
import static com.qihoo360.replugin.helper.LogDebug.PLUGIN_TAG;
import static com.qihoo360.replugin.helper.LogDebug.TAG_NO_PN;
/**
* @author RePlugin Team
*/
public class FinderBuiltin {
static final void loadPlugins(Context context, PxAll all) {
InputStream in;
// 读取内部配置
in = null;
try {
in = context.getAssets().open("plugins-builtin.json");
// TODO 简化参数 all
readConfig(in, all);
} catch (FileNotFoundException e0) {
if (LOG) {
LogDebug.e(PLUGIN_TAG, "plugins-builtin.json" + " not found");
}
} catch (Throwable e) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, e.getMessage(), e);
}
}
CloseableUtils.closeQuietly(in);
}
private static final void readConfig(InputStream in, PxAll all) throws IOException, JSONException {
String str = IOUtils.toString(in, Charsets.UTF_8);
JSONArray ja = new JSONArray(str);
for (int i = 0; i < ja.length(); i++) {
JSONObject jo = ja.getJSONObject(i);
if (jo == null) {
continue;
}
String name = jo.getString("name");
if (TextUtils.isEmpty(name)) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "built-in plugins config: invalid item: name is empty, json=" + jo);
}
continue;
}
PluginInfo info = PluginInfo.buildFromBuiltInJson(jo);
if (!info.match()) {
if (LOG) {
LogDebug.e(PLUGIN_TAG, "built-in plugins config: mismatch item: " + info);
}
continue;
}
if (LOG) {
LogDebug.d(PLUGIN_TAG, "built-in plugins config: item: " + info);
}
if (LOG) {
Log.d(TAG_NO_PN, "add builtin plugin=" + info);
}
all.addBuiltin(info);
}
}
}
================================================
FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/LaunchModeStates.java
================================================
/*
* 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.loader2;
import android.content.pm.ActivityInfo;
import android.util.Log;
import com.qihoo360.loader2.PluginContainers.ActivityState;
import com.qihoo360.replugin.RePlugin;
import com.qihoo360.replugin.helper.HostConfigHelper;
import com.qihoo360.replugin.helper.LogDebug;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
/**
* 存储 LaunchMode + Theme -> 此种组合下的 ActivityState 状态集合
*
* @author RePlugin Team
*/
class LaunchModeStates {
public static final String TAG = "launchMode";
/**
* 目前的策略是,针对每一种 launchMode 分配两种坑位(透明主题(TS)和不透明主题(NTS))
*
* 例:透明主题
*
* NR + TS - >
*
*
* 例:不透明主题
*
* NR + NTS - >
*
*
* 其中:N1 表示当前为 UI 进程,NR 表示 launchMode 为 Standard,NTS 表示坑的 theme 为 Not Translucent。
*/
private Map> mStates = new HashMap<>();
/**
* 初始化 LaunchMode 和 Theme 对应的坑位
*
* @param containers 保存所有 activity 坑位的引用
* @param prefix 坑位前缀
* @param launchMode launchMode
* @param translucent 是否是透明的坑
* @param count 坑位数
*/
void addStates(Map allStates, HashSet containers, String prefix, int launchMode, boolean translucent, int count) {
String infix = getInfix(launchMode, translucent);
HashMap states = mStates.get(infix);
if (states == null) {
states = new HashMap<>();
mStates.put(infix, states);
}
for (int i = 0; i < count; i++) {
String key = prefix + infix + i;
// 只有开启“详细日志”时才输出每一个坑位的信息,防止刷屏
if (RePlugin.getConfig().isPrintDetailLog()) {
Log.d(TAG, "LaunchModeStates.add(" + key + ")");
}
ActivityState state = new ActivityState(key);
states.put(key, state);
allStates.put(key, state);
containers.add(key);
}
}
/**
* 初始化 LaunchMode 和 Theme 对应的坑位
*
* @param containers 保存所有 activity 坑位的引用
* @param prefix 坑位前缀
* @param launchMode launchMode
* @param translucent 是否是透明的坑
* @param count 坑位数
*/
void addLandStates(Map allStates, HashSet containers, String prefix, int launchMode, boolean translucent, int count) {
if (!HostConfigHelper.HOST_USE_OCCUPYLAND || count <= 0){
return;
}
String infix = getInfix(launchMode, translucent);
infix = "LAND" + infix;
HashMap states = mStates.get(infix);
if (states == null) {
states = new HashMap<>();
mStates.put(infix, states);
}
for (int i = 0; i < count; i++) {
String key = prefix + infix + i;
// 只有开启“详细日志”时才输出每一个坑位的信息,防止刷屏
if (RePlugin.getConfig().isPrintDetailLog()) {
LogDebug.d(TAG, "LaunchModeStates.add(" + key + ")");
}
ActivityState state = new ActivityState(key);
states.put(key, state);
allStates.put(key, state);
containers.add(key);
}
}
/**
* 根据 launchMode 和 theme 获取对应的坑位集合
*/
HashMap getStates(int screenOrientation, int launchMode, int theme) {
try {
String infix = getInfix(launchMode, isTranslucentTheme(theme));
if (HostConfigHelper.HOST_USE_OCCUPYLAND && screenOrientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) {
infix = "LAND" + infix;
}
return mStates.get(infix);
}catch (Exception e){
return null;
}
}
/**
* 根据 launchMode 和 '是否透明' 获取中缀符
*
* @return 如果是透明主题,返回 'launchMode'_TS_,否则返回 'launchMode'_NOT_TS_
*/
private static String getInfix(int launchMode, boolean translucent) {
String launchModeInfix = getLaunchModeInfix(launchMode);
return translucent ? launchModeInfix + "TS" : launchModeInfix + "NTS";
}
/**
* 手动判断主题是否是透明主题
*/
public static boolean isTranslucentTheme(int theme) {
return theme == android.R.style.Theme_Translucent
|| theme == android.R.style.Theme_Dialog
|| theme == android.R.style.Theme_Translucent_NoTitleBar
|| theme == android.R.style.Theme_Translucent_NoTitleBar_Fullscreen;
}
/**
* 获取 launchMode 对应的前缀
*/
private static String getLaunchModeInfix(int launchMode) {
switch (launchMode) {
case ActivityInfo.LAUNCH_SINGLE_TOP:
return "STP";
case ActivityInfo.LAUNCH_SINGLE_TASK:
return "ST";
case ActivityInfo.LAUNCH_SINGLE_INSTANCE:
return "SI";
default:
return "NR";
}
}
}
================================================
FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/Loader.java
================================================
/*
* 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.loader2;
import android.content.Context;
import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.ComponentInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
import android.os.Bundle;
import android.os.IBinder;
import android.text.TextUtils;
import android.util.Log;
import com.qihoo360.i.Factory;
import com.qihoo360.i.IModule;
import com.qihoo360.i.IPlugin;
import com.qihoo360.mobilesafe.core.BuildConfig;
import com.qihoo360.mobilesafe.parser.manifest.ManifestParser;
import com.qihoo360.replugin.RePlugin;
import com.qihoo360.replugin.base.IPC;
import com.qihoo360.replugin.component.ComponentList;
import com.qihoo360.replugin.component.process.PluginProcessHost;
import com.qihoo360.replugin.component.receiver.PluginReceiverProxy;
import com.qihoo360.replugin.helper.LogDebug;
import com.qihoo360.replugin.helper.LogRelease;
import com.qihoo360.replugin.model.PluginInfo;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.File;
import java.lang.ref.WeakReference;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static com.qihoo360.replugin.helper.LogDebug.LOADER_TAG;
import static com.qihoo360.replugin.helper.LogDebug.LOG;
import static com.qihoo360.replugin.helper.LogDebug.PLUGIN_TAG;
import static com.qihoo360.replugin.helper.LogDebug.TAG_NO_PN;
import static com.qihoo360.replugin.helper.LogRelease.LOGR;
/**
* @author RePlugin Team
*/
class Loader {
private final Context mContext;
private final String mPluginName;
final String mPath;
final Plugin mPluginObj;
PackageInfo mPackageInfo;
Resources mPkgResources;
Context mPkgContext;
ClassLoader mClassLoader;
/**
* 记录所有缓存的Component列表
*/
ComponentList mComponents;
Method mCreateMethod;
Method mCreateMethod2;
IPlugin mPlugin;
IPluginHost mPluginHost;
ProxyPlugin mBinderPlugin;
/**
* layout缓存:忽略表
*/
HashSet mIgnores = new HashSet();
/**
* layout缓存:构造器表
*/
HashMap> mConstructors = new HashMap>();
static class ProxyPlugin implements IPlugin {
com.qihoo360.loader2.IPlugin mPlugin;
ProxyPlugin(IBinder plugin) {
mPlugin = com.qihoo360.loader2.IPlugin.Stub.asInterface(plugin);
}
@Override
public IModule query(Class extends IModule> c) {
IBinder b = null;
try {
b = mPlugin.query(c.getName());
} catch (Throwable e) {
if (LOGR) {
LogRelease.e(PLUGIN_TAG, "query(" + c + ") exception: " + e.getMessage(), e);
}
}
// TODO: return IModule
return null;
}
}
/**
* 初始化Loader对象
*
* @param p Plugin类的对象
* 为何会反向依赖plugin对象?因为plugin.mInfo对象会发生变化,
* 缓存plugin可以实时拿到最新的mInfo对象,防止出现问题
* FIXME 有优化空间,但改动量会很大,暂缓
*/
Loader(Context context, String name, String path, Plugin p) {
mContext = context;
mPluginName = name;
mPath = path;
mPluginObj = p;
}
final boolean isPackageInfoLoaded() {
return mPackageInfo != null;
}
final boolean isResourcesLoaded() {
return isPackageInfoLoaded() && mPkgResources != null;
}
final boolean isDexLoaded() {
return isResourcesLoaded() && mClassLoader != null;
}
final boolean isAppLoaded() {
return mPlugin != null;
}
final Context createBaseContext(Context newBase) {
return new PluginContext(newBase, android.R.style.Theme, mClassLoader, mPkgResources, mPluginName, this);
}
final boolean loadDex(ClassLoader parent, int load) {
try {
PackageManager pm = mContext.getPackageManager();
mPackageInfo = Plugin.queryCachedPackageInfo(mPath);
if (mPackageInfo == null) {
// PackageInfo
mPackageInfo = pm.getPackageArchiveInfo(mPath,
PackageManager.GET_ACTIVITIES | PackageManager.GET_SERVICES | PackageManager.GET_PROVIDERS | PackageManager.GET_RECEIVERS | PackageManager.GET_META_DATA);
if (mPackageInfo == null || mPackageInfo.applicationInfo == null) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "get package archive info null");
}
mPackageInfo = null;
return false;
}
if (LOG) {
LogDebug.d(PLUGIN_TAG, "get package archive info, pi=" + mPackageInfo);
}
mPackageInfo.applicationInfo.sourceDir = mPath;
mPackageInfo.applicationInfo.publicSourceDir = mPath;
if (TextUtils.isEmpty(mPackageInfo.applicationInfo.processName)) {
mPackageInfo.applicationInfo.processName = mPackageInfo.applicationInfo.packageName;
}
// 添加针对SO库的加载
// 此属性最终用于ApplicationLoaders.getClassLoader,在创建PathClassLoader时成为其参数
// 这样findLibrary可不用覆写,即可直接实现SO的加载
// Added by Jiongxuan Zhang
PluginInfo pi = mPluginObj.mInfo;
File ld = pi.getNativeLibsDir();
mPackageInfo.applicationInfo.nativeLibraryDir = ld.getAbsolutePath();
// // 若PluginInfo.getFrameworkVersion为FRAMEWORK_VERSION_UNKNOWN(p-n才会有),则这里需要读取并修改
// if (pi.getFrameworkVersion() == PluginInfo.FRAMEWORK_VERSION_UNKNOWN) {
// pi.setFrameworkVersionByMeta(mPackageInfo.applicationInfo.metaData);
// }
// 缓存表: pkgName -> pluginName
synchronized (Plugin.PKG_NAME_2_PLUGIN_NAME) {
Plugin.PKG_NAME_2_PLUGIN_NAME.put(mPackageInfo.packageName, mPluginName);
}
// 缓存表: pluginName -> fileName
synchronized (Plugin.PLUGIN_NAME_2_FILENAME) {
Plugin.PLUGIN_NAME_2_FILENAME.put(mPluginName, mPath);
}
// 缓存表: fileName -> PackageInfo
synchronized (Plugin.FILENAME_2_PACKAGE_INFO) {
Plugin.FILENAME_2_PACKAGE_INFO.put(mPath, new WeakReference(mPackageInfo));
}
}
// TODO preload预加载虽然通知到常驻了(但pluginInfo是通过MP.getPlugin(name, true)完全clone出来的),本进程的PluginInfo并没有得到更新
// TODO 因此preload会造成某些插件真正生效时由于cache,造成插件版本号2.0或者以上无法生效。
// TODO 这里是临时做法,避免发版前出现重大问题,后面可以修过修改preload的流程来优化
// 若PluginInfo.getFrameworkVersion为FRAMEWORK_VERSION_UNKNOWN(p-n才会有),则这里需要读取并修改
if (mPluginObj.mInfo.getFrameworkVersion() == PluginInfo.FRAMEWORK_VERSION_UNKNOWN) {
mPluginObj.mInfo.setFrameworkVersionByMeta(mPackageInfo.applicationInfo.metaData);
// 只有“P-n”插件才会到这里,故无需调用“纯APK”的保存功能
// PluginInfoList.save();
}
// 创建或获取ComponentList表
// Added by Jiongxuan Zhang
mComponents = Plugin.queryCachedComponentList(mPath);
if (mComponents == null) {
// ComponentList
mComponents = new ComponentList(mPackageInfo, mPath, mPluginObj.mInfo);
// 动态注册插件中声明的 receiver
regReceivers();
// 缓存表:ComponentList
synchronized (Plugin.FILENAME_2_COMPONENT_LIST) {
Plugin.FILENAME_2_COMPONENT_LIST.put(mPath, new WeakReference<>(mComponents));
}
/* 只调整一次 */
// 调整插件中组件的进程名称
adjustPluginProcess(mPackageInfo.applicationInfo);
// 调整插件中 Activity 的 TaskAffinity
adjustPluginTaskAffinity(mPluginName, mPackageInfo.applicationInfo);
}
if (load == Plugin.LOAD_INFO) {
return isPackageInfoLoaded();
}
mPkgResources = Plugin.queryCachedResources(mPath);
// LOAD_RESOURCES和LOAD_ALL都会获取资源,但LOAD_INFO不可以(只允许获取PackageInfo)
if (mPkgResources == null) {
// Resources
try {
if (BuildConfig.DEBUG) {
// 如果是Debug模式的话,防止与Instant Run冲突,资源重新New一个
Resources r = pm.getResourcesForApplication(mPackageInfo.applicationInfo);
mPkgResources = new Resources(r.getAssets(), r.getDisplayMetrics(), r.getConfiguration());
} else {
mPkgResources = pm.getResourcesForApplication(mPackageInfo.applicationInfo);
}
} catch (NameNotFoundException e) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, e.getMessage(), e);
}
return false;
}
if (mPkgResources == null) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "get resources null");
}
return false;
}
if (LOG) {
LogDebug.d(PLUGIN_TAG, "get resources for app, r=" + mPkgResources);
}
// 缓存表: Resources
synchronized (Plugin.FILENAME_2_RESOURCES) {
Plugin.FILENAME_2_RESOURCES.put(mPath, new WeakReference<>(mPkgResources));
}
}
if (load == Plugin.LOAD_RESOURCES) {
return isResourcesLoaded();
}
mClassLoader = Plugin.queryCachedClassLoader(mPath);
if (mClassLoader == null) {
// ClassLoader
String out = mPluginObj.mInfo.getDexParentDir().getPath();
//changeDexMode(out);
//
Log.i("dex", "load " + mPath + " ...");
if (BuildConfig.DEBUG) {
// 因为Instant Run会替换parent为IncrementalClassLoader,所以在DEBUG环境里
// 需要替换为BootClassLoader才行
// Added by yangchao-xy & Jiongxuan Zhang
parent = ClassLoader.getSystemClassLoader();
} else {
// 线上环境保持不变
parent = getClass().getClassLoader().getParent(); // TODO: 这里直接用父类加载器
}
String soDir = mPackageInfo.applicationInfo.nativeLibraryDir;
long begin = 0;
boolean isDexExist = false;
if (LOG) {
begin = System.currentTimeMillis();
File dexFile = mPluginObj.mInfo.getDexFile();
if (dexFile.exists() && dexFile.length() > 0) {
isDexExist = true;
}
}
File dexFile = new File(mPath);
if (dexFile.exists()){ // Android 14 安全的动态代码加载
dexFile.setWritable(false);
}
mClassLoader = RePlugin.getConfig().getCallbacks().createPluginClassLoader(mPluginObj.mInfo, mPath, out, soDir, parent);
Log.i("dex", "load " + mPath + " = " + mClassLoader);
if (dexFile.exists()){ // Android 14 安全的动态代码加载
dexFile.setWritable(true);
}
if (mClassLoader == null) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "get dex null");
}
return false;
}
if (LOG) {
if (!isDexExist) {
Log.d(LOADER_TAG, " --释放DEX, " + "(plugin=" + mPluginName + ", version=" + mPluginObj.mInfo.getVersion() + ")"
+ ", use:" + (System.currentTimeMillis() - begin)
+ ", process:" + IPC.getCurrentProcessName());
} else {
Log.d(LOADER_TAG, " --无需释放DEX, " + "(plugin=" + mPluginName + ", version=" + mPluginObj.mInfo.getVersion() + ")"
+ ", use:" + (System.currentTimeMillis() - begin)
+ ", process:" + IPC.getCurrentProcessName());
}
}
// 缓存表:ClassLoader
synchronized (Plugin.FILENAME_2_DEX) {
Plugin.FILENAME_2_DEX.put(mPath, new WeakReference<>(mClassLoader));
}
}
if (load == Plugin.LOAD_DEX) {
return isDexLoaded();
}
// Context
mPkgContext = new PluginContext(mContext, android.R.style.Theme, mClassLoader, mPkgResources, mPluginName, this);
if (LOG) {
LogDebug.d(PLUGIN_TAG, "pkg context=" + mPkgContext);
}
} catch (Throwable e) {
if (LOGR) {
LogRelease.e(PLUGIN_TAG, "p=" + mPath + " m=" + e.getMessage(), e);
}
return false;
}
return true;
}
/**
* 动态注册插件中静态声明的 receiver 到常驻进程
*
* @throws android.os.RemoteException
*/
private void regReceivers() throws android.os.RemoteException {
String plugin = mPluginObj.mInfo.getName();
Map> map = ManifestParser.INS.getReceiverFilterMap(plugin);
if (map == null || map.size() == 0) {
return;
}
if (mPluginHost == null) {
mPluginHost = getPluginHost();
}
if (mPluginHost != null) {
mPluginHost.regReceiver(plugin, map);
}
}
/**
* 获取 IPluginHost Binder 接口
*/
private IPluginHost getPluginHost() {
IBinder binder = PluginProviderStub.proxyFetchHostBinder(mContext);
if (binder == null) {
if (LOG) {
LogDebug.e(PluginReceiverProxy.TAG, "p.p fhb fail");
}
return null;
} else {
return IPluginHost.Stub.asInterface(binder);
}
}
final boolean loadEntryMethod(boolean log) {
//
try {
String className = Factory.PLUGIN_ENTRY_PACKAGE_PREFIX + "." + mPluginName + "." + Factory.PLUGIN_ENTRY_CLASS_NAME;
Class> c = mClassLoader.loadClass(className);
if (LOG) {
LogDebug.d(PLUGIN_TAG, "found entry: className=" + className + ", loader=" + c.getClassLoader());
}
mCreateMethod = c.getDeclaredMethod(Factory.PLUGIN_ENTRY_EXPORT_METHOD_NAME, Factory.PLUGIN_ENTRY_EXPORT_METHOD_PARAMS);
} catch (Throwable e) {
if (log) {
if (LOGR) {
LogRelease.e(PLUGIN_TAG, e.getMessage(), e);
}
} else {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "loadEntryMethod exception");
}
}
}
return mCreateMethod != null;
}
final boolean invoke(PluginCommImpl manager) {
try {
mPlugin = (IPlugin) mCreateMethod.invoke(null, mPkgContext, manager);
if (LOG) {
LogDebug.d(PLUGIN_TAG, "Loader.invoke(): plugin=" + mPath + ", cl=" + (mPlugin != null ? mPlugin.getClass().getClassLoader() : "null"));
}
} catch (Throwable e) {
if (LOGR) {
LogRelease.e(PLUGIN_TAG, e.getMessage(), e);
}
return false;
}
return true;
}
final boolean loadEntryMethod2() {
long start = System.currentTimeMillis();
//
try {
String className = Factory.PLUGIN_ENTRY_PACKAGE_PREFIX + "." + mPluginName + "." + Factory.PLUGIN_ENTRY_CLASS_NAME;
Class> c = mClassLoader.loadClass(className);
if (LOG) {
LogDebug.d(PLUGIN_TAG, "found entry: className=" + className + ", loader=" + c.getClassLoader());
}
mCreateMethod2 = c.getDeclaredMethod(Factory.PLUGIN_ENTRY_EXPORT_METHOD_NAME, Factory.PLUGIN_ENTRY_EXPORT_METHOD2_PARAMS);
} catch (Throwable e) {
// 老版本的插件才会用到这个方法,因后面还有新版本的load方式,这里不打log
// if (LOGR) {
// LogRelease.e(PLUGIN_TAG, e.getMessage(), e);
// }
}
if (LOG) {
Log.d(TAG_NO_PN, "load loadEntryMethod2 for " + mPluginName + " time=" + (System.currentTimeMillis() - start));
}
return mCreateMethod2 != null;
}
final boolean loadEntryMethod3() {
//
try {
String className = Factory.REPLUGIN_LIBRARY_ENTRY_PACKAGE_PREFIX + "." + Factory.PLUGIN_ENTRY_CLASS_NAME;
Class> c = mClassLoader.loadClass(className);
if (LOG) {
LogDebug.d(PLUGIN_TAG, "found entry: className=" + className + ", loader=" + c.getClassLoader());
}
mCreateMethod2 = c.getDeclaredMethod(Factory.PLUGIN_ENTRY_EXPORT_METHOD_NAME, Factory.PLUGIN_ENTRY_EXPORT_METHOD2_PARAMS);
} catch (Throwable e) {
if (LOGR) {
LogRelease.e(PLUGIN_TAG, e.getMessage(), e);
}
}
return mCreateMethod2 != null;
}
final boolean invoke2(PluginCommImpl x) {
try {
long start = System.currentTimeMillis();
IBinder manager = null; // TODO
IBinder b = (IBinder) mCreateMethod2.invoke(null, mPkgContext, getClass().getClassLoader(), manager);
if (b == null) {
if (LOGR) {
LogRelease.e(PLUGIN_TAG, "p.e.r.b n");
}
return false;
}
mBinderPlugin = new ProxyPlugin(b);
mPlugin = mBinderPlugin;
if (LOG) {
LogDebug.d(PLUGIN_TAG, "Loader.invoke2(): plugin=" + mPath + ", plugin.binder.cl=" + b.getClass().getClassLoader());
}
} catch (Throwable e) {
if (LOGR) {
LogRelease.e(PLUGIN_TAG, e.getMessage(), e);
}
return false;
}
return true;
}
/**
* 获取宿主中可分配的自定义进程列表
*
* @return
*/
private List getHostProcessList() {
List pluginProcessList = new ArrayList<>();
for (int i = 0; i < PluginProcessHost.PROCESS_COUNT; i++) {
pluginProcessList.add(IPC.getPackageName() + PluginProcessHost.PROCESS_PLUGIN_SUFFIX2 + i);
}
return pluginProcessList;
}
/**
* 读取插件中自定义进程列表
*
* @return
*/
private List getPluginProcessList() {
Set processSet = new HashSet<>();
String pluginUIProcess = mComponents.getApplication().packageName;
getPluginProcess(processSet, mComponents.getProviders());
getPluginProcess(processSet, mComponents.getActivities());
getPluginProcess(processSet, mComponents.getServices());
getPluginProcess(processSet, mComponents.getReceivers());
processSet.remove(pluginUIProcess);
return Arrays.asList(processSet.toArray(new String[0]));
}
/**
* 把来自插件的进程去重
*
* @param processSet
* @param componentInfos
*/
private void getPluginProcess(Set processSet, ComponentInfo[] componentInfos) {
if (componentInfos != null) {
for (ComponentInfo componentInfo : componentInfos) {
processSet.add(componentInfo.processName);
}
}
}
/**
* 生成进程映射表,把插件中的自定义进程映射到宿主
*
* @return
*/
private HashMap genDynamicProcessMap() {
HashMap processMap = new HashMap<>();
List hostProcessList = getHostProcessList();
List pluginProcessList = getPluginProcessList();
int hostProcessCount = hostProcessList != null ? hostProcessList.size() : 0;
if (hostProcessCount <= 0) {
return processMap;
}
int pluginProcessCount = pluginProcessList != null ? pluginProcessList.size() : 0;
for (int i = 0; i < pluginProcessCount; i++) {
int hostProcessIndex = i % hostProcessCount;
processMap.put(pluginProcessList.get(i), hostProcessList.get(hostProcessIndex));
}
return processMap;
}
/**
* 获取插件AndroidMainfest中配置的静态进程映射表,meta-data:"process_map"
*
* @param appInfo
* @return
*/
private HashMap getConfigProcessMap(ApplicationInfo appInfo) {
HashMap processMap = new HashMap<>();
Bundle bdl = appInfo.metaData;
if (bdl == null || TextUtils.isEmpty(bdl.getString("process_map"))) {
return processMap;
}
try {
String processMapStr = bdl.getString("process_map");
JSONArray ja = new JSONArray(processMapStr);
for (int i = 0; i < ja.length(); i++) {
JSONObject jo = (JSONObject) ja.get(i);
if (jo != null) {
String to = jo.getString("to").toLowerCase();
if (to.equals("$ui")) {
to = IPC.getPackageName();
} else {
// 非 UI 进程,且是用户自定义的进程
if (to.contains("$" + PluginProcessHost.PROCESS_PLUGIN_SUFFIX)) {
to = PluginProcessHost.PROCESS_ADJUST_MAP.get(to);
}
}
processMap.put(jo.getString("from"), to);
}
}
} catch (JSONException e) {
if (BuildConfig.DEBUG) {
e.printStackTrace();
}
}
return processMap;
}
/**
* 调整插件中组件的进程名称,用宿主中的进程坑位来接收插件中的自定义进程
*
* 注:
* 如果插件中没有配置静态的 “meta-data:process_map” 进行静态的进程映射,则自动为插件中组件分配进程
*
* @param appInfo
*/
private void adjustPluginProcess(ApplicationInfo appInfo) {
HashMap processMap = getConfigProcessMap(appInfo);
if (processMap == null || processMap.isEmpty()) {
PluginInfo pi = MP.getPlugin(mPluginName, false);
if (pi != null && pi.getFrameworkVersion() >= 4) {
processMap = genDynamicProcessMap();
}
}
if (LOG) {
Log.d(PLUGIN_TAG, "--- 调整插件中组件的进程 BEGIN ---");
for (Map.Entry entry : processMap.entrySet()) {
Log.d(PLUGIN_TAG, entry.getKey() + " -> " + entry.getValue());
}
}
doAdjust(processMap, mComponents.getActivityMap());
doAdjust(processMap, mComponents.getServiceMap());
doAdjust(processMap, mComponents.getReceiverMap());
doAdjust(processMap, mComponents.getProviderMap());
if (LOG) {
Log.d(PLUGIN_TAG, "--- 调整插件中组件的进程 END --- " + IPC.getCurrentProcessName());
}
}
private void doAdjust(HashMap processMap, HashMap infos) {
if (processMap == null || processMap.isEmpty()) {
return;
}
for (HashMap.Entry entry : infos.entrySet()) {
ComponentInfo info = entry.getValue();
if (info != null) {
String targetProcess = processMap.get(info.processName);
if (!TextUtils.isEmpty(targetProcess)) {
if (LOG) {
Log.d(TaskAffinityStates.TAG, String.format("--- 调整组件 %s, %s -> %s", info.name, info.processName, targetProcess));
}
info.processName = targetProcess;
}
}
}
}
/**
* 调整插件中 Activity 的默认 TaskAffinity
*
* @param plugin 插件名称
*/
private void adjustPluginTaskAffinity(String plugin, ApplicationInfo appInfo) {
if (appInfo == null) {
return;
}
Bundle bdl = appInfo.metaData;
if (bdl != null) {
boolean useDefault = bdl.getBoolean("use_default_task_affinity", true);
if (LOG) {
LogDebug.d(TaskAffinityStates.TAG, "useDefault = " + useDefault);
}
if (!useDefault) {
if (LOG) {
LogDebug.d(TaskAffinityStates.TAG, String.format("替换插件 %s 中默认的 TaskAffinity", plugin));
}
String defaultPluginTaskAffinity = appInfo.packageName;
for (HashMap.Entry entry : mComponents.getActivityMap().entrySet()) {
ActivityInfo info = entry.getValue();
if (LOG) {
if (info != null) {
LogDebug.d(TaskAffinityStates.TAG, String.format("%s.taskAffinity = %s ", info.name, info.taskAffinity));
}
}
// 如果是默认 TaskAffinity
if (info != null && info.taskAffinity.equals(defaultPluginTaskAffinity)) {
info.taskAffinity = info.taskAffinity + "." + plugin;
if (LOG) {
LogDebug.d(TaskAffinityStates.TAG, String.format("修改 %s 的 TaskAffinity 为 %s", info.name, info.taskAffinity));
}
}
}
}
}
}
}
================================================
FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/MP.java
================================================
/*
* 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.loader2;
import android.os.IBinder;
import android.os.RemoteException;
import com.qihoo360.loader.utils.ProcessLocker;
import com.qihoo360.replugin.IHostBinderFetcher;
import com.qihoo360.replugin.RePlugin;
import com.qihoo360.replugin.RePluginInternal;
import com.qihoo360.replugin.helper.LogDebug;
import com.qihoo360.replugin.helper.LogRelease;
import com.qihoo360.replugin.model.PluginInfo;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import static com.qihoo360.replugin.helper.LogDebug.LOG;
import static com.qihoo360.replugin.helper.LogDebug.MAIN_TAG;
import static com.qihoo360.replugin.helper.LogDebug.PLUGIN_TAG;
import static com.qihoo360.replugin.helper.LogRelease.LOGR;
/**
* 对外接口代码
*
* @author RePlugin Team
*/
public class MP {
/**
* 需要重启常驻进程
*/
public static final String ACTION_REQUEST_RESTART = "com.qihoo360.loader2.ACTION_REQUEST_RESTART";
/**
* 马上重启常驻服务
*/
public static final String ACTION_QUICK_RESTART = "com.qihoo360.loader2.ACTION_QUICK_RESTART";
/**
* 调试用
*/
static volatile HashMap sBinderReasons;
/**
* 仿插件对象,用来实现主程序提供binder给其他模块
*
* @param name
* @param p
*/
public static final void installBuiltinPlugin(String name, IHostBinderFetcher p) {
PMF.sPluginMgr.installBuiltinPlugin(name, p);
}
/**
* @param name
* @param binder
*/
public static final void installBinder(String name, IBinder binder) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "installBinder n=" + name + " b=" + binder);
}
try {
PluginProcessMain.getPluginHost().installBinder(name, binder);
} catch (Throwable e) {
if (LOGR) {
LogRelease.e(PLUGIN_TAG, "mp i.b: " + e.getMessage(), e);
}
}
}
/**
* @param name
* @return
*/
public static final IBinder fetchBinder(String name) {
try {
IBinder binder = PluginProcessMain.getPluginHost().fetchBinder(name);
if (LOG) {
LogDebug.d(PLUGIN_TAG, "fetchBinder n=" + name + " b=" + binder);
}
return binder;
} catch (Throwable e) {
if (LOGR) {
LogRelease.e(PLUGIN_TAG, "mp f.b: " + e.getMessage(), e);
}
}
return null;
}
/**
* 拉起插件(或UI)进程,并查询对应的binder接口对象
*
* @param plugin
* @param process
* @param binder
* @return
*/
public static final PluginBinder fetchPluginBinder(String plugin, int process, String binder) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "MP.fetchPluginBinder ... plugin=" + plugin + " binder.name=" + binder);
}
// 若开启了“打印详情”则打印调用栈,便于观察
if (RePlugin.getConfig().isPrintDetailLog()) {
String reason = "";
StackTraceElement elements[] = Thread.currentThread().getStackTrace();
for (StackTraceElement item : elements) {
if (item.isNativeMethod()) {
continue;
}
String cn = item.getClassName();
String mn = item.getMethodName();
String filename = item.getFileName();
int line = item.getLineNumber();
reason += cn + "." + mn + "(" + filename + ":" + line + ")" + "\n";
}
if (sBinderReasons == null) {
sBinderReasons = new HashMap();
}
sBinderReasons.put(plugin + ":" + binder, reason);
}
PluginBinderInfo info = new PluginBinderInfo(PluginBinderInfo.BINDER_REQUEST);
IBinder b = null;
try {
// 容器选择
IPluginClient client = MP.startPluginProcess(plugin, process, info);
if (client == null) {
if (LOGR) {
LogRelease.e(PLUGIN_TAG, "mp.f.p.b: s c fail");
}
return null;
}
// 远程获取
b = client.queryBinder(plugin, binder);
if (LOG) {
LogDebug.d(PLUGIN_TAG, "MP.fetchPluginBinder binder.object=" + b + " pid=" + info.pid);
}
// 增加计数器
if (b != null) {
PluginProcessMain.getPluginHost().regPluginBinder(info, b);
}
} catch (Throwable e) {
if (LOGR) {
LogRelease.e(PLUGIN_TAG, "mp.f.p.b: p=" + info.pid, e);
}
}
if (b == null) {
return null;
}
return new PluginBinder(plugin, binder, info.pid, b);
}
/**
* @param binder
*/
public static final void releasePluginBinder(PluginBinder binder) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "MP.releasePluginBinder ... pid=" + binder.pid + " binder=" + binder.binder);
}
// 记录调用栈,便于观察:删除
if (LOG) {
if (sBinderReasons != null) {
sBinderReasons.remove(binder.plugin + ":" + binder.name);
}
}
PluginBinderInfo info = new PluginBinderInfo(PluginBinderInfo.BINDER_REQUEST);
info.pid = binder.pid;
try {
PluginProcessMain.getPluginHost().unregPluginBinder(info, binder.binder);
} catch (Throwable e) {
if (LOGR) {
LogRelease.e(PLUGIN_TAG, "mp.r.p.b: " + e.getMessage(), e);
}
}
}
/**
* @param path
* @return
*/
public static final PluginInfo pluginDownloaded(String path) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "MP.pluginDownloaded ... path=" + path);
}
/**
* 问题描述:
*
* 对于正在生效的插件,如果当前时机pluginHost没有存活,那么这里会先启动pluginHost,然后再调用它的PluginHost进程的pluginDownloaded接口
*
* 这里的问题是:pluginHost进程在启动过程会通过扫描文件的方式将当前即将生效的插件识别到,
* 而在进程ready后,再去调用pluginDownloaded接口的时候会认为它不是新插件,从而不会通过NEW_PLUGIN广播来周知所有进程新插件生效了
* 因此,当前进程也不会同步新插件生效的逻辑。
* so,问题就来了,当前进程新下载的插件由于pluginHost的逻辑无法正常生效。
* 当然该问题只针对p-n格式的插件,而纯APK格式的插件不再使用进程启动的时候通过扫描文件目录的方式来来识别所有准备好的插件
*
* 解决办法:
* 对于处于该流程的插件文件(p-n插件)加上lock文件,以表示当前插件正在生效,不需要plugHost进程再次扫描生效了,也就不存在新插件在新进程中成为了老插件
*/
ProcessLocker lock = null;
try {
if (path != null) {
File f = new File(path);
String fileName = f.getName();
String fileDir = f.getParent();
if (fileName.startsWith("p-n-")) {
lock = new ProcessLocker(RePluginInternal.getAppContext(), fileDir, fileName + ".lock");
}
}
if (lock != null && !lock.tryLock()) {
// 加锁
if (LOG) {
LogDebug.d(PLUGIN_TAG, "MP.pluginDownloaded ... lock file + " + path + " failed! ");
}
}
PluginInfo info = PluginProcessMain.getPluginHost().pluginDownloaded(path);
if (info != null) {
RePlugin.getConfig().getEventCallbacks().onInstallPluginSucceed(info);
}
return info;
} catch (Throwable e) {
if (LOGR) {
LogRelease.e(PLUGIN_TAG, "mp.pded: " + e.getMessage(), e);
}
} finally {
// 去锁
if (lock != null) {
lock.unlock();
}
}
return null;
}
/**
* 插件卸载
* 判断插件是否已安装:插件未安装,不做处理
* 插件已安装,正在运行,则记录“卸载状态”,推迟到到主程序进程重启的时执行卸载
* 插件已安装,未在运行,则直接删除Dex、Native库等资源
*
* @param pluginName
* @return 插件卸载成功与否
*/
public static final boolean pluginUninstall(String pluginName) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "MP.pluginUninstall ... pluginName=" + pluginName);
}
PluginInfo pi = getPlugin(pluginName, true);
// 插件未安装
if (pi == null) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "Not installed. pluginName=" + pluginName);
}
return true;
}
try {
return PluginProcessMain.getPluginHost().pluginUninstalled(pi);
} catch (Throwable e) {
if (LOGR) {
LogRelease.e(PLUGIN_TAG, "uninstall. error: " + e.getMessage(), e);
}
}
return false;
}
/**
* @param path
* @return
*/
public static final boolean pluginExtracted(String path) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "MP.pluginExtracted ... path=" + path);
}
try {
return PluginProcessMain.getPluginHost().pluginExtracted(path);
} catch (Throwable e) {
if (LOGR) {
LogRelease.e(PLUGIN_TAG, "mp.peed: " + e.getMessage(), e);
}
}
return false;
}
/**
* 获取当前所有插件信息快照。内部框架使用
*
* @param clone true:深拷贝 false:浅拷贝
* @return
*/
public static final List getPlugins(boolean clone) {
ArrayList array = new ArrayList<>();
Set pathSet = new HashSet<>();
synchronized (PluginTable.PLUGINS) {
for (PluginInfo info : PluginTable.PLUGINS.values()) {
String path = info.getPath();
// 避免加了两次,毕竟包名和别名都会加进来
if (!pathSet.contains(path)) {
pathSet.add(path);
PluginInfo addTo;
if (clone) {
addTo = (PluginInfo) info.clone();
} else {
addTo = info;
}
array.add(addTo);
}
}
}
return array;
}
/**
* 获取某个插件信息快照。内部框架使用
* @return
*/
public static final PluginInfo getPlugin(String name, boolean clone) {
synchronized (PluginTable.PLUGINS) {
PluginInfo info = PluginTable.PLUGINS.get(name);
if (clone && info != null) {
// 防止外界可以修改PluginTable表中的元素,故对外必须Clone一份
return (PluginInfo) info.clone();
} else {
return info;
}
}
}
/**
* 注:内部接口
*
* @return
*/
public static final int sumActivities() {
int rc = 0;
rc = PluginProcessMain.sumActivities();
if (LOG) {
LogDebug.d(MAIN_TAG, "MP.sumActivities = " + rc);
}
return rc;
}
/**
* 注:内部接口
*
* @return
*/
public static final int sumBinders() {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "MP.sumBinders ... index=" + PluginManager.sPluginProcessIndex);
}
try {
return PluginProcessMain.getPluginHost().sumBinders(PluginManager.sPluginProcessIndex);
} catch (Throwable e) {
if (LOGR) {
LogRelease.e(PLUGIN_TAG, "mp.s.b: " + e.getMessage(), e);
}
}
return -2;
}
/**
* 根据Activity坑的名称,返回对应的Activity对象
*
* @param container Activity坑名称
* @return 插件名 + 插件Activity名 + 启动时间
*/
public static final String[] resolvePluginActivity(String container) {
return PluginContainers.resolvePluginActivity(container);
}
/**
* 检测卫士进程是否正在运行
*
* @param name 进程名(全名)
* @return
*/
public static final boolean isMsProcessAlive(String name) {
try {
return PluginProcessMain.getPluginHost().isProcessAlive(name);
} catch (Throwable e) {
if (LOGR) {
LogRelease.e(PLUGIN_TAG, "mp.i.p.a: " + e.getMessage(), e);
}
}
return false;
}
/**
* 注:内部接口
*
* @param plugin
* @param process
* @param info
* @return
* @throws RemoteException
* @hide 内部框架使用
*/
public static final IPluginClient startPluginProcess(String plugin, int process, PluginBinderInfo info) throws RemoteException {
return PluginProcessMain.getPluginHost().startPluginProcess(plugin, process, info);
}
/**
* 根据 taskAffinity,判断应该取第几组 TaskAffinity
* 由于 taskAffinity 是跨进程的属性,所以这里要将 taskAffinityGroup 的数据保存在常驻进程中
* @param taskAffinity
* @return 索引值
*/
public static int getTaskAffinityGroupIndex(String taskAffinity) throws RemoteException {
return PluginProcessMain.getPluginHost().getTaskAffinityGroupIndex(taskAffinity);
}
public static final class PluginBinder {
public final String plugin;
public final String name;
public final int pid;
public final IBinder binder;
PluginBinder(String plugin, String name, int pid, IBinder binder) {
this.plugin = plugin;
this.name = name;
this.binder = binder;
this.pid = pid;
}
}
}
================================================
FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/PMF.java
================================================
/*
* 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.loader2;
import android.app.Activity;
import android.app.Application;
import android.content.Context;
import android.content.Intent;
import android.os.RemoteException;
import android.text.TextUtils;
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.helper.LogRelease;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import static com.qihoo360.replugin.helper.LogDebug.PLUGIN_TAG;
import static com.qihoo360.replugin.helper.LogRelease.LOGR;
/**
* 框架和主程序接口代码
*
* @author RePlugin Team
*/
public class PMF {
private static Context sContext;
static PmBase sPluginMgr;
/**
* @param context
*/
private static final void setApplicationContext(Context context) {
sContext = context;
}
/**
* @return
*/
public static final Context getApplicationContext() {
return sContext;
}
/**
* @param application
*/
public static final void init(Application application) {
setApplicationContext(application);
PluginManager.init(application);
sPluginMgr = new PmBase(application);
sPluginMgr.init();
Factory.sPluginManager = PMF.getLocal();
Factory2.sPLProxy = PMF.getInternal();
PatchClassLoaderUtils.patch(application);
}
/**
*
*/
public static final void callAppCreate() {
sPluginMgr.callAppCreate();
}
/**
*
*/
public static final void callAttach() {
sPluginMgr.callAttach();
}
/**
* @param name
* @param modc
* @param module
*/
public static final void addBuiltinModule(String name, Class extends IModule> modc, IModule module) {
sPluginMgr.addBuiltinModule(name, modc, module);
}
/**
* @return
*/
public static final PluginCommImpl getLocal() {
return sPluginMgr.mLocal;
}
/**
* @return
*/
public static final PluginLibraryInternalProxy getInternal() {
return sPluginMgr.mInternal;
}
/**
* @param className
* @param resolve
* @return
*/
public static final Class> loadClass(String className, boolean resolve) {
return sPluginMgr.loadClass(className, resolve);
}
/**
* @param activity
* @param intent
*/
public static final void forward(Activity activity, Intent intent) {
//
activity.finish();
//
try {
PluginIntent ii = new PluginIntent(intent);
// 原容器
String original = ii.getOriginal();
if (TextUtils.isEmpty(original)) {
if (LOGR) {
LogRelease.e(PLUGIN_TAG, "f.a f: orig=nul i=" + intent);
}
return;
}
// 容器,检查
String container = ii.getContainer();
if (TextUtils.isEmpty(container)) {
if (LOGR) {
LogRelease.e(PLUGIN_TAG, "f.a f: c=nul i=" + intent);
}
return;
}
// 目标插件,检查
String plugin = ii.getPlugin();
if (TextUtils.isEmpty(plugin)) {
if (LOGR) {
LogRelease.e(PLUGIN_TAG, "f.a f: n=nul i=" + intent);
}
return;
}
// 目标activity,检查
String target = ii.getActivity();
if (TextUtils.isEmpty(target)) {
if (LOGR) {
LogRelease.e(PLUGIN_TAG, "f.a f: t=nul i=" + intent);
}
return;
}
// 进程,检查
int process = ii.getProcess();
if (!PluginManager.isValidActivityProcess(process)) {
if (LOGR) {
LogRelease.e(PLUGIN_TAG, "f.a f: p=" + process + " i=" + intent);
}
return;
}
// 计数器,检查
int counter = ii.getCounter();
if (counter < 0 || counter >= PluginManager.COUNTER_MAX) {
if (LOGR) {
LogRelease.e(PLUGIN_TAG, "f.a f: ooc c=" + counter);
}
return;
}
// 计数器,递增
counter++;
ii.setCounter(counter);
//
sPluginMgr.mClient.mACM.forwardIntent(activity, intent, original, container, plugin, target, process);
} catch (Throwable e) {
if (LOGR) {
LogRelease.e(PLUGIN_TAG, "f.a f: " + e.getMessage(), e);
}
}
}
public static final void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
sPluginMgr.dump(fd, writer, args);
}
// 只为PluginServiceServer调用而准备,不对外公开
// Added by Jiongxuan Zhang
public static void stopService(Intent intent) throws RemoteException {
sPluginMgr.mClient.fetchServiceServer().stopService(intent, null);
}
}
================================================
FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/Plugin.java
================================================
/*
* 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.loader2;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.res.Resources;
import android.os.Build;
import android.os.ConditionVariable;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
import android.text.TextUtils;
import android.util.Log;
import com.qihoo360.i.IModule;
import com.qihoo360.i.IPlugin;
import com.qihoo360.loader.utils.ProcessLocker;
import com.qihoo360.mobilesafe.api.Tasks;
import com.qihoo360.replugin.RePlugin;
import com.qihoo360.replugin.component.ComponentList;
import com.qihoo360.replugin.component.app.PluginApplicationClient;
import com.qihoo360.replugin.helper.LogDebug;
import com.qihoo360.replugin.helper.LogRelease;
import com.qihoo360.replugin.model.PluginInfo;
import com.qihoo360.replugin.packages.PluginManagerProxy;
import com.qihoo360.replugin.utils.AssetsUtils;
import com.qihoo360.replugin.utils.FileUtils;
import java.io.File;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashMap;
import static com.qihoo360.replugin.helper.LogDebug.LOG;
import static com.qihoo360.replugin.helper.LogDebug.MAIN_TAG;
import static com.qihoo360.replugin.helper.LogDebug.PLUGIN_TAG;
import static com.qihoo360.replugin.helper.LogDebug.TAG_NO_PN;
import static com.qihoo360.replugin.helper.LogRelease.LOGR;
/**
* @author RePlugin Team
*/
class Plugin {
private static final String TAG = "Plugin";
// 只加载Service/Activity/ProviderInfo信息(包含ComponentList)
static final int LOAD_INFO = 0;
// 加载插件信息和资源
static final int LOAD_RESOURCES = 1;
// 加载插件信息、资源和Dex
static final int LOAD_DEX = 2;
// 加载插件信息、资源、Dex,并运行Entry类
static final int LOAD_APP = 3;
/**
* 专门针对LoadEntry(见方法)的锁
*/
private static final byte[] LOCK_LOAD_ENTRY = new byte[0];
/**
* 保存插件 pkgName 至 pluginName 的映射
*/
static final HashMap PKG_NAME_2_PLUGIN_NAME = new HashMap<>();
/**
* 保存插件 pluginName 至 fileName 的映射
*/
static final HashMap PLUGIN_NAME_2_FILENAME = new HashMap<>();
/**
*
*/
static final HashMap> FILENAME_2_DEX = new HashMap<>();
/**
*
*/
static final HashMap> FILENAME_2_RESOURCES = new HashMap<>();
/**
*
*/
static final HashMap> FILENAME_2_PACKAGE_INFO = new HashMap<>();
/**
*
*/
static final HashMap> FILENAME_2_COMPONENT_LIST = new HashMap<>();
/**
* 调试用
*/
static volatile ArrayList sLoadedReasons;
/**
*
*/
PluginInfo mInfo;
/**
* sync
*/
private final Object LOCK = new Object();
private final ConditionVariable APPLICATION_LOCK = new ConditionVariable();
/**
* 没有IPlugin对象
*/
boolean mDummyPlugin;
/**
*
*/
Context mContext;
/**
*
*/
ClassLoader mParent;
/**
*
*/
PluginCommImpl mPluginManager;
/**
*
*/
volatile boolean mInitialized;
/**
*
*/
Loader mLoader;
/**
* 跑在UI线程里的Handler对象
*/
final Handler mMainH = new Handler(Looper.getMainLooper());
/**
* 用来控制插件里的Application对象
*/
volatile PluginApplicationClient mApplicationClient;
private static class UpdateInfoTask implements Runnable {
PluginInfo mInfo;
UpdateInfoTask(PluginInfo info) {
mInfo = info;
}
@Override
public void run() {
try {
PluginProcessMain.getPluginHost().updatePluginInfo(mInfo);
} catch (Throwable e) {
if (LOGR) {
LogRelease.e(PLUGIN_TAG, "ph u p i: " + e.getMessage(), e);
}
}
}
}
static final Plugin build(PluginInfo info) {
return new Plugin(info);
}
static final Plugin cloneAndReattach(Context c, Plugin p, ClassLoader parent, PluginCommImpl pm) {
if (p == null) {
return null;
}
p = build(p.mInfo);
p.attach(c, parent, pm);
return p;
}
/**
* 根据插件 pkgName 取 pluginName
*/
static final String queryPluginNameByPkgName(String pkgName) {
String pluginName;
synchronized (PKG_NAME_2_PLUGIN_NAME) {
pluginName = PKG_NAME_2_PLUGIN_NAME.get(pkgName);
if (LOG) {
LogDebug.d(PLUGIN_TAG, "cached pluginName: " + pkgName + " -> " + pluginName);
}
}
return pluginName;
}
static final String queryCachedFilename(String name) {
String filename = null;
synchronized (PLUGIN_NAME_2_FILENAME) {
filename = PLUGIN_NAME_2_FILENAME.get(name);
if (LOG) {
LogDebug.d(PLUGIN_TAG, "cached filename: " + name + " -> " + filename);
}
}
return filename;
}
static final ClassLoader queryCachedClassLoader(String filename) {
ClassLoader dex = null;
if (!TextUtils.isEmpty(filename)) {
synchronized (FILENAME_2_DEX) {
WeakReference ref = FILENAME_2_DEX.get(filename);
if (ref != null) {
dex = ref.get();
if (dex == null) {
FILENAME_2_DEX.remove(filename);
}
if (LOG) {
LogDebug.d(PLUGIN_TAG, "cached Dex " + filename + " -> " + dex);
}
}
}
}
return dex;
}
static final Resources queryCachedResources(String filename) {
Resources resources = null;
if (!TextUtils.isEmpty(filename)) {
synchronized (FILENAME_2_RESOURCES) {
WeakReference ref = FILENAME_2_RESOURCES.get(filename);
if (ref != null) {
resources = ref.get();
if (resources == null) {
FILENAME_2_RESOURCES.remove(filename);
}
if (LOG) {
LogDebug.d(PLUGIN_TAG, "cached Resources " + filename + " -> " + resources);
}
}
}
}
return resources;
}
static final PackageInfo queryCachedPackageInfo(String filename) {
PackageInfo packageInfo = null;
if (!TextUtils.isEmpty(filename)) {
synchronized (FILENAME_2_PACKAGE_INFO) {
WeakReference ref = FILENAME_2_PACKAGE_INFO.get(filename);
if (ref != null) {
packageInfo = ref.get();
if (packageInfo == null) {
FILENAME_2_PACKAGE_INFO.remove(filename);
}
if (LOG) {
LogDebug.d(PLUGIN_TAG, "cached packageInfo " + filename + " -> " + packageInfo);
}
}
}
}
return packageInfo;
}
static final ComponentList queryCachedComponentList(String filename) {
ComponentList cl = null;
if (!TextUtils.isEmpty(filename)) {
synchronized (FILENAME_2_COMPONENT_LIST) {
WeakReference ref = FILENAME_2_COMPONENT_LIST.get(filename);
if (ref != null) {
cl = ref.get();
if (cl == null) {
FILENAME_2_COMPONENT_LIST.remove(filename);
}
if (LOG) {
LogDebug.d(PLUGIN_TAG, "cached componentList " + filename + " -> " + cl);
}
}
}
}
return cl;
}
static final void clearCachedPlugin(String filename) {
if (TextUtils.isEmpty(filename)) {
return;
}
ClassLoader dex = null;
synchronized (FILENAME_2_DEX) {
WeakReference ref = FILENAME_2_DEX.get(filename);
if (ref != null) {
dex = ref.get();
FILENAME_2_DEX.remove(filename);
if (LOG) {
LogDebug.d(PLUGIN_TAG, "clear Cached Dex " + filename + " -> " + dex);
}
}
}
Resources resources = null;
synchronized (FILENAME_2_RESOURCES) {
WeakReference ref = FILENAME_2_RESOURCES.get(filename);
if (ref != null) {
resources = ref.get();
FILENAME_2_RESOURCES.remove(filename);
if (LOG) {
LogDebug.d(PLUGIN_TAG, "clear Cached Resources " + filename + " -> " + resources);
}
}
}
PackageInfo packageInfo = null;
synchronized (FILENAME_2_PACKAGE_INFO) {
WeakReference ref = FILENAME_2_PACKAGE_INFO.get(filename);
if (ref != null) {
packageInfo = ref.get();
FILENAME_2_PACKAGE_INFO.remove(filename);
if (LOG) {
LogDebug.d(PLUGIN_TAG, "clear Cached packageInfo " + filename + " -> " + packageInfo);
}
}
}
ComponentList cl = null;
synchronized (FILENAME_2_COMPONENT_LIST) {
WeakReference ref = FILENAME_2_COMPONENT_LIST.get(filename);
if (ref != null) {
cl = ref.get();
FILENAME_2_COMPONENT_LIST.remove(filename);
if (LOG) {
LogDebug.d(PLUGIN_TAG, "clear Cached componentList " + filename + " -> " + cl);
}
}
}
}
static final void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
if (LogDebug.DUMP_ENABLED) {
writer.println("--- cached plugin filename ---");
// 懒得锁了
for (String name : PLUGIN_NAME_2_FILENAME.keySet()) {
writer.println(name + ": " + PLUGIN_NAME_2_FILENAME.get(name));
}
writer.println("--- cached plugin Resources ---");
// 懒得锁了
for (String name : FILENAME_2_RESOURCES.keySet()) {
writer.println(name + ": " + FILENAME_2_RESOURCES.get(name));
}
writer.println("--- cached plugin PackageInfo ---");
// 懒得锁了
for (String name : FILENAME_2_PACKAGE_INFO.keySet()) {
writer.println(name + ": " + FILENAME_2_PACKAGE_INFO.get(name));
}
writer.println("--- cached plugin ComponentList ---");
// 懒得锁了
for (String name : FILENAME_2_COMPONENT_LIST.keySet()) {
writer.println(name + ": " + FILENAME_2_COMPONENT_LIST.get(name));
}
}
}
private Plugin(PluginInfo info) {
mInfo = info;
}
@Override
public String toString() {
if (LOG) {
return super.toString() + " {info=" + mInfo + "}";
}
return super.toString();
}
final void attach(Context context, ClassLoader parent, PluginCommImpl manager) {
mContext = context;
mParent = parent;
mPluginManager = manager;
}
/**
* @return
*/
final ClassLoader getClassLoader() {
if (mLoader == null) {
return null;
}
return mLoader.mClassLoader;
}
/**
* @return
*/
final boolean isInitialized() {
return mInitialized;
}
/**
* @return
*/
final boolean isLoaded() {
if (mLoader == null) {
return false;
}
return mLoader.isAppLoaded();
}
/**
* @return
*/
final boolean isPackageInfoLoaded() {
if (mLoader == null) {
return false;
}
return mLoader.isPackageInfoLoaded();
}
/**
*
*/
final boolean load(int load, boolean useCache) {
PluginInfo info = mInfo;
boolean rc = loadLocked(load, useCache);
// 尝试在此处调用Application.onCreate方法
// Added by Jiongxuan Zhang
if (load == LOAD_APP && rc) {
callApp();
}
// 如果info改了,通知一下常驻
// 只针对P-n的Type转化来处理,一定要通知,这样Framework_Version也会得到更新
if (rc && mInfo != info) {
UpdateInfoTask task = new UpdateInfoTask((PluginInfo) mInfo.clone());
Tasks.post2Thread(task);
}
return rc;
}
final void replaceInfo(PluginInfo info) {
boolean rc = false;
if (mInfo.canReplaceForPn(info)) {
mInfo = info;
rc = true;
}
if (LOG) {
LogDebug.d(PLUGIN_TAG, "replace plugin info: info=" + info + " rc=" + rc);
}
}
/**
* 从缓存中读取Loader信息
* @param load 加载类型
* @return true: 缓存命中 false: 没有缓存
*/
private boolean loadByCache(int load) {
if (load == LOAD_INFO) {
// 提取PackageInfo对象
String filename = queryCachedFilename(mInfo.getName());
PackageInfo pi = queryCachedPackageInfo(filename);
ComponentList cl = queryCachedComponentList(filename);
if (pi != null && cl != null) {
mLoader = new Loader(mContext, mInfo.getName(), null, this);
mLoader.mPackageInfo = pi;
mLoader.mComponents = cl;
if (LOG) {
LogDebug.i(MAIN_TAG, "loadLocked(): Cached, pkgInfo loaded");
}
return true;
}
}
if (load == LOAD_RESOURCES) {
// 提取PackageInfo和Resources对象
String filename = queryCachedFilename(mInfo.getName());
Resources r = queryCachedResources(filename);
PackageInfo pi = queryCachedPackageInfo(filename);
ComponentList cl = queryCachedComponentList(filename);
if (r != null && pi != null && cl != null) {
mLoader = new Loader(mContext, mInfo.getName(), null, this);
mLoader.mPkgResources = r;
mLoader.mPackageInfo = pi;
mLoader.mComponents = cl;
if (LOG) {
LogDebug.i(MAIN_TAG, "loadLocked(): Cached, resource loaded");
}
return true;
}
}
if (load == LOAD_DEX) {
// 提取PackageInfo、Resources和DexClassLoader对象
String filename = queryCachedFilename(mInfo.getName());
Resources r = queryCachedResources(filename);
PackageInfo pi = queryCachedPackageInfo(filename);
ComponentList cl = queryCachedComponentList(filename);
ClassLoader clzl = queryCachedClassLoader(filename);
if (r != null && pi != null && cl != null && clzl != null) {
mLoader = new Loader(mContext, mInfo.getName(), null, this);
mLoader.mPkgResources = r;
mLoader.mPackageInfo = pi;
mLoader.mComponents = cl;
mLoader.mClassLoader = clzl;
if (LOG) {
LogDebug.i(MAIN_TAG, "loadLocked(): Cached, dex loaded");
}
return true;
}
}
return false;
}
/**
* @param load
* @return
*/
private boolean loadLocked(int load, boolean useCache) {
// 若插件被“禁用”,则即便上次加载过(且进程一直活着),这次也不能再次使用了
// Added by Jiongxuan Zhang
int status = PluginStatusController.getStatus(mInfo.getName(), mInfo.getVersion());
if (status < PluginStatusController.STATUS_OK) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "loadLocked(): Disable in=" + mInfo.getName() + ":" + mInfo.getVersion() + "; st=" + status);
}
return false;
}
synchronized (LOCK) {
if (mInitialized) {
if (mLoader == null) {
if (LOG) {
LogDebug.i(MAIN_TAG, "loadLocked(): Initialized but mLoader is Null");
}
return false;
}
if (load == LOAD_INFO) {
boolean rl = mLoader.isPackageInfoLoaded();
if (LOG) {
LogDebug.i(MAIN_TAG, "loadLocked(): Initialized, pkginfo loaded = " + rl);
}
return rl;
}
if (load == LOAD_RESOURCES) {
boolean rl = mLoader.isResourcesLoaded();
if (LOG) {
LogDebug.i(MAIN_TAG, "loadLocked(): Initialized, resource loaded = " + rl);
}
return rl;
}
if (load == LOAD_DEX) {
boolean rl = mLoader.isDexLoaded();
if (LOG) {
LogDebug.i(MAIN_TAG, "loadLocked(): Initialized, dex loaded = " + rl);
}
return rl;
}
boolean il = mLoader.isAppLoaded();
if (LOG) {
LogDebug.i(MAIN_TAG, "loadLocked(): Initialized, is loaded = " + il);
}
return il;
}
mInitialized = true;
}
// 若开启了“打印详情”则打印调用栈,便于观察
if (RePlugin.getConfig().isPrintDetailLog()) {
String reason = "";
reason += "--- plugin: " + mInfo.getName() + " ---\n";
reason += "load=" + load + "\n";
StackTraceElement elements[] = Thread.currentThread().getStackTrace();
for (StackTraceElement item : elements) {
if (item.isNativeMethod()) {
continue;
}
String cn = item.getClassName();
String mn = item.getMethodName();
String filename = item.getFileName();
int line = item.getLineNumber();
if (LOG) {
LogDebug.i(PLUGIN_TAG, cn + "." + mn + "(" + filename + ":" + line + ")");
}
reason += cn + "." + mn + "(" + filename + ":" + line + ")" + "\n";
}
if (sLoadedReasons == null) {
sLoadedReasons = new ArrayList();
}
sLoadedReasons.add(reason);
}
// 这里先处理一下,如果cache命中,省了后面插件提取(如释放Jar包等)操作
if (useCache) {
boolean result = loadByCache(load);
// 如果缓存命中,则直接返回
if (result) {
return true;
}
}
Context context = mContext;
ClassLoader parent = mParent;
PluginCommImpl manager = mPluginManager;
//
String logTag = "try1";
String lockFileName = String.format(Constant.LOAD_PLUGIN_LOCK, mInfo.getApkFile().getName());
ProcessLocker lock = new ProcessLocker(context, lockFileName);
if (LOG) {
LogDebug.i(PLUGIN_TAG, "loadLocked(): Ready to lock! logtag = " + logTag + "; pn = " + mInfo.getName());
}
if (!lock.tryLockTimeWait(5000, 10)) {
// 此处仅仅打印错误
if (LOGR) {
LogRelease.w(PLUGIN_TAG, logTag + ": failed to lock: can't wait plugin ready");
}
}
//
long t1 = System.currentTimeMillis();
boolean rc = doLoad(logTag, context, parent, manager, load);
if (LOG) {
LogDebug.i(PLUGIN_TAG, "load " + mInfo.getPath() + " " + hashCode() + " c=" + load + " rc=" + rc + " delta=" + (System.currentTimeMillis() - t1));
}
//
lock.unlock();
if (LOG) {
LogDebug.i(PLUGIN_TAG, "loadLocked(): Unlock! logtag = " + logTag + "; pn = " + mInfo.getName());
}
if (!rc) {
if (LOGR) {
LogRelease.e(PLUGIN_TAG, logTag + ": loading fail1");
}
}
if (rc) {
// 打印当前内存占用情况,只针对Dex和App加载做输出
// 只有开启“详细日志”才会输出,防止“消耗性能”
if (LOG && RePlugin.getConfig().isPrintDetailLog()) {
if (load == LOAD_DEX || load == LOAD_APP) {
LogDebug.printPluginInfo(mInfo, load);
LogDebug.printMemoryStatus(LogDebug.TAG, "act=, loadLocked, flag=, End-1, pn=, " + mInfo.getName() + ", type=, " + load);
}
}
try {
// 至此,该插件已开始运行
PluginManagerProxy.addToRunningPluginsNoThrows(mInfo.getName());
} catch (Throwable e) {
if (LOGR) {
LogRelease.e(PLUGIN_TAG, "p.u.1: " + e.getMessage(), e);
}
}
return true;
}
//
logTag = "try2";
lock = new ProcessLocker(context, lockFileName);
if (!lock.tryLockTimeWait(5000, 10)) {
// 此处仅仅打印错误
if (LOGR) {
LogRelease.w(PLUGIN_TAG, logTag + ": failed to lock: can't wait plugin ready");
}
}
// 删除优化dex文件
File odex = mInfo.getDexFile();
if (odex.exists()) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, logTag + ": delete exist odex=" + odex.getAbsolutePath());
}
odex.delete();
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
// support for multidex below LOLLIPOP:delete Extra odex,if need
try {
FileUtils.forceDelete(mInfo.getExtraOdexDir());
} catch (IOException e) {
e.printStackTrace();
} catch (IllegalArgumentException e2) {
e2.printStackTrace();
}
}
t1 = System.currentTimeMillis();
// 尝试再次加载该插件
rc = tryLoadAgain(logTag, context, parent, manager, load);
if (LOG) {
LogDebug.i(PLUGIN_TAG, "load2 " + mInfo.getPath() + " " + hashCode() + " c=" + load + " rc=" + rc + " delta=" + (System.currentTimeMillis() - t1));
}
//
lock.unlock();
if (!rc) {
if (LOGR) {
LogRelease.e(PLUGIN_TAG, logTag + ": loading fail2");
}
return false;
}
// 打印当前内存占用情况,只针对Dex和App加载做输出
// 只有开启“详细日志”才会输出,防止“消耗性能”
if (LOG && RePlugin.getConfig().isPrintDetailLog()) {
if (load == LOAD_DEX || load == LOAD_APP) {
LogDebug.printPluginInfo(mInfo, load);
LogDebug.printMemoryStatus(LogDebug.TAG, "act=, loadLocked, flag=, End-2, pn=, " + mInfo.getName() + ", type=, " + load);
}
}
try {
// 至此,该插件已开始运行
PluginManagerProxy.addToRunningPluginsNoThrows(mInfo.getName());
} catch (Throwable e) {
if (LOGR) {
LogRelease.e(PLUGIN_TAG, "p.u.2: " + e.getMessage(), e);
}
}
return true;
}
final IModule query(Class extends IModule> c) {
return mLoader.mPlugin.query(c);
}
final IBinder query(String binder) {
try {
return mLoader.mBinderPlugin.mPlugin.query(binder);
} catch (Throwable e) {
if (LOGR) {
LogRelease.e(PLUGIN_TAG, "q.b.e.m" + e.getMessage(), e);
}
}
return null;
}
/**
* 抽出方法
*/
private boolean tryLoadAgain(String tag, Context context, ClassLoader parent, PluginCommImpl manager, int load) {
mLoader = null;
return doLoad(tag, context, parent, manager, load);
}
private final boolean doLoad(String tag, Context context, ClassLoader parent, PluginCommImpl manager, int load) {
if (mLoader == null) {
// 试图释放文件
PluginInfo info = null;
if (mInfo.getType() == PluginInfo.TYPE_BUILTIN) {
//内置插件,首次加载的时候,把数据写到p.l中,然后把文件拷贝到对应目录
File dir = new File(mInfo.getApkDir());
File dexdir = mInfo.getDexParentDir();
String dstName = mInfo.getApkFile().getName();
boolean rc = AssetsUtils.quickExtractTo(context, mInfo, dir.getAbsolutePath(), dstName, dexdir.getAbsolutePath());
if (!rc) {
// extract built-in plugin failed: plugin=
if (LOGR) {
LogRelease.e(PLUGIN_TAG, "p e b i p f " + mInfo);
}
return false;
}
File file = new File(dir, dstName);
info = (PluginInfo) mInfo.clone();
info.setPath(file.getPath());
info.setType(PluginInfo.TYPE_EXTRACTED);
} else if(mInfo.getType() == PluginInfo.TYPE_PN_INSTALLED || mInfo.getType() == PluginInfo.TYPE_EXTRACTED){
try {
//针对升级上来的用户,重新释放已安装插件的so,更换路径
File oldSoLibDir = mInfo.getOldNativeLibsDir();
File soLibDir = mInfo.getNativeLibsDir();
if (oldSoLibDir.exists() && oldSoLibDir.listFiles() != null && oldSoLibDir.listFiles().length > 0
&& (!soLibDir.exists() || soLibDir.listFiles() == null || soLibDir.listFiles().length == 0)){
PluginNativeLibsHelper.install(mInfo.getPath(), soLibDir);
}
}catch (Exception e){
return false;
}
}
//
if (info != null) {
// 替换
mInfo = info;
}
//
mLoader = new Loader(context, mInfo.getName(), mInfo.getPath(), this);
if (!mLoader.loadDex(parent, load)) {
//内置插件加载失败后,需要把释放的文件路径和类型写入到p.l中去。
try {
PluginManagerProxy.updateTP(mInfo.getName(), mInfo.getType(), mInfo.getPath());
} catch (RemoteException e) {
}
return false;
}
// 设置插件为“使用过的”
// 注意,需要重新获取当前的PluginInfo对象,而非使用“可能是新插件”的mInfo
try {
long start = System.currentTimeMillis();
PluginManagerProxy.updateUsedIfNeeded(mInfo.getName(), mInfo.getPath(), mInfo.getType(), true);
mInfo.setIsUsed(true);
if (LOG) {
Log.d(TAG_NO_PN, "update " + mInfo.getName() + " time=" + (System.currentTimeMillis() - start));
}
} catch (RemoteException e) {
// 同步出现问题,但仍继续进行
if (LOGR) {
e.printStackTrace();
}
}
long startApp = System.currentTimeMillis();
// 若需要加载Dex,则还同时需要初始化插件里的Entry对象
if (load == LOAD_APP) {
// NOTE Entry对象是可以在任何线程中被调用到
if (!loadEntryLocked(manager)) {
return false;
}
// NOTE 在此处调用则必须Post到UI,但此时有可能Activity已被加载
// 会出现Activity.onCreate比Application更早的情况,故应放在load外面立即调用
// callApp();
}
if (LOG) {
Log.d(TAG_NO_PN, "load entry for " + mInfo.getName() + " time=" + (System.currentTimeMillis() - startApp));
}
}
if (load == LOAD_INFO) {
return mLoader.isPackageInfoLoaded();
} else if (load == LOAD_RESOURCES) {
return mLoader.isResourcesLoaded();
} else if (load == LOAD_DEX) {
return mLoader.isDexLoaded();
} else {
return mLoader.isAppLoaded();
}
}
private boolean loadEntryLocked(PluginCommImpl manager) {
if (mDummyPlugin) {
if (LOGR) {
LogRelease.w(PLUGIN_TAG, "p.lel dm " + mInfo.getName());
}
mLoader.mPlugin = new IPlugin() {
@Override
public IModule query(Class extends IModule> c) {
return null;
}
};
} else {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "Plugin.loadEntryLocked(): Load entry, info=" + mInfo);
}
if (mLoader.loadEntryMethod2()) {
if (!mLoader.invoke2(manager)) {
return false;
}
} else if (mLoader.loadEntryMethod(false)) {
if (!mLoader.invoke(manager)) {
return false;
}
} else if (mLoader.loadEntryMethod3()) {
if (!mLoader.invoke2(manager)) {
return false;
}
} else {
if (LOGR) {
LogRelease.e(PLUGIN_TAG, "p.lel f " + mInfo.getName());
}
return false;
}
}
return true;
}
// 确保在UI线程中调用
// ATTENTION 必须在LOCK锁之外调用此方法
// 否则一旦LOCK锁内任一位置再次调用Plugin.doLoad(如打开另一插件)时会造成循环锁
// Added by Jiongxuan Zhang
private void callApp() {
if (Looper.myLooper() == Looper.getMainLooper()) {
callAppLocked();
} else {
// 确保一定在UI的最早消息处调用
mMainH.postAtFrontOfQueue(new Runnable() {
@Override
public void run() {
callAppLocked();
}
});
if(!APPLICATION_LOCK.block(3 * 1000)){
if (LOGR) {
LogRelease.e(PLUGIN_TAG, "p.cal timeout " + mInfo.getName());
}
}
}
}
private synchronized void callAppLocked() {
// 获取并调用Application的几个核心方法
if (!mDummyPlugin) {
// NOTE 不排除A的Application中调到了B,B又调回到A,或在同一插件内的onCreate开启Service/Activity,而内部逻辑又调用fetchContext并再次走到这里
// NOTE 因此需要对mApplicationClient做判断,确保永远只执行一次,无论是否成功
if (mApplicationClient != null) {
// 已经初始化过,无需再次处理
APPLICATION_LOCK.open();
return;
}
mApplicationClient = PluginApplicationClient.getOrCreate(
mInfo.getName(), mLoader.mClassLoader, mLoader.mComponents, mLoader.mPluginObj.mInfo);
if (mApplicationClient != null) {
mApplicationClient.callAttachBaseContext(mLoader.mPkgContext);
mApplicationClient.callOnCreate();
}
} else {
if (LOGR) {
LogRelease.e(PLUGIN_TAG, "p.cal dm " + mInfo.getName());
}
}
APPLICATION_LOCK.open();
}
}
================================================
FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/PluginBinderInfo.java
================================================
/*
* 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.loader2;
import android.os.Parcel;
import android.os.Parcelable;
/**
* 插件框架内部接口
*
* @hide 内部框架使用
* @author RePlugin Team
*/
public final class PluginBinderInfo implements Parcelable {
public static final int NONE_REQUEST = 0;
public static final int ACTIVITY_REQUEST = 1;
public static final int SERVICE_REQUEST = 2;
public static final int PROVIDER_REQUEST = 3;
public static final int BINDER_REQUEST = 4;
public int request;
public int pid;
public int index;
public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
@Override
public PluginBinderInfo createFromParcel(Parcel source) {
return new PluginBinderInfo(source);
}
@Override
public PluginBinderInfo[] newArray(int size) {
return new PluginBinderInfo[size];
}
};
PluginBinderInfo() {
request = NONE_REQUEST;
pid = -1;
index = -1;
}
// @hide
public PluginBinderInfo(int req) {
request = req;
pid = -1;
index = -1;
}
PluginBinderInfo(Parcel source) {
readFromParcel(source);
}
final void readFromParcel(Parcel source) {
request = source.readInt();
pid = source.readInt();
index = source.readInt();
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(request);
dest.writeInt(pid);
dest.writeInt(index);
}
}
================================================
FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/PluginCommImpl.java
================================================
/*
* 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.loader2;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageInfo;
import android.content.res.Resources;
import android.net.Uri;
import android.os.Bundle;
import android.os.IBinder;
import android.text.TextUtils;
import com.qihoo360.i.IModule;
import com.qihoo360.i.IPluginManager;
import com.qihoo360.mobilesafe.svcmanager.QihooServiceManager;
import com.qihoo360.replugin.IHostBinderFetcher;
import com.qihoo360.replugin.RePlugin;
import com.qihoo360.replugin.base.IPC;
import com.qihoo360.replugin.component.ComponentList;
import com.qihoo360.replugin.component.process.PluginProcessHost;
import com.qihoo360.replugin.component.utils.IntentMatcherHelper;
import com.qihoo360.replugin.component.utils.PluginClientHelper;
import com.qihoo360.replugin.helper.LogDebug;
import com.qihoo360.replugin.helper.LogRelease;
import com.qihoo360.replugin.model.PluginInfo;
import java.util.HashMap;
import java.util.List;
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;
/**
* 负责宿主与插件、插件间的互通,可通过插件的Factory直接调用,也可通过RePlugin来跳转
* TODO 原名为PmLocalImpl。新名字也不太好,待重构后会去掉
*
* @author RePlugin Team
*/
public class PluginCommImpl {
private static final String CONTAINER_PROVIDER_AUTHORITY_PART = ".loader.p.pr";
static final String INTENT_KEY_THEME_ID = "__themeId";
/**
*
*/
Context mContext;
/**
*
*/
PmBase mPluginMgr;
PluginCommImpl(Context context, PmBase pm) {
mContext = context;
mPluginMgr = pm;
}
/**
* @param name 插件名
* @return
*/
public boolean isPluginLoaded(String name) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "isPluginLoaded: name=" + name);
}
Plugin plugin = mPluginMgr.getPlugin(name);
if (plugin == null) {
return false;
}
return plugin.isLoaded();
}
/**
* 此方法调用主程序或特定插件的IPlugin.query,当插件未加载时会尝试加载
* @param name 插件名
* @param c 需要查询的interface的类
* @return
*/
public IModule query(String name, Class extends IModule> c) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "query: name=" + name + " class=" + c.getName());
}
HashMap modules = mPluginMgr.getBuiltinModules(name);
if (modules != null) {
return modules.get(c.getName());
}
Plugin p = mPluginMgr.loadAppPlugin(name);
if (p == null) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "query: not found plugin, name=" + name + " class=" + c.getName());
}
return null;
}
return p.query(c);
}
/**
* @param name 插件名
* @param binder 需要查询的binder的类
* @return
*/
public IBinder query(String name, String binder) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "query: name=" + name + " binder=" + binder);
}
// 先用仿插件对象判断
{
IHostBinderFetcher p = mPluginMgr.getBuiltinPlugin(name);
if (p != null) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "use buildin plugin");
}
return p.query(binder);
}
}
Plugin p = mPluginMgr.loadAppPlugin(name);
if (p == null) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "query: not found plugin, name=" + name + " binder=" + binder);
}
return null;
}
return p.query(binder);
}
/**
* @param name 插件名
* @param binder 需要查询的binder的类
* @param process 是否在指定进程中启动
* @return
*/
public IBinder query(String name, String binder, int process) {
// 自己进程
if (IPC.isPersistentProcess() && process == IPluginManager.PROCESS_PERSIST) {
return query(name, binder);
}
// 自己进程
if (IPC.isUIProcess() && process == IPluginManager.PROCESS_UI) {
return query(name, binder);
}
// 自己进程(自定义进程)
String processTail = PluginProcessHost.processTail(IPC.getCurrentProcessName());
if (PluginProcessHost.PROCESS_INT_MAP.containsKey(processTail)
&& process == PluginProcessHost.PROCESS_INT_MAP.get(processTail)) {
return query(name, binder);
}
// 需要在常驻里启动
if (process == IPluginManager.PROCESS_PERSIST) {
try {
return PluginProcessMain.getPluginHost().queryPluginBinder(name, binder);
} catch (Throwable e) {
if (LOGR) {
LogRelease.e(PLUGIN_TAG, "q.p.b: " + e.getMessage(), e);
}
}
return null;
}
// 剩下的交给SvcManager去干
return QihooServiceManager.getPluginService(mContext, name, binder);
}
/**
* 警告:低层接口
* 当插件升级之后,通过adapter.jar标准接口,甚至invoke接口都无法完成任务时,可通过此接口反射来完成任务
* @param name 插件名
* @return 插件的context,可通过此context得到插件的ClassLoader
*/
public Context queryPluginContext(String name) {
Plugin p = mPluginMgr.loadAppPlugin(name);
if (p != null) {
return p.mLoader.mPkgContext;
}
if (LOG) {
LogDebug.d(PLUGIN_TAG, "not found plugin=" + name);
}
return null;
}
/**
* 警告:低层接口
* 调用此接口会在当前进程加载插件(不加载代码,只加载资源)
* @param name 插件名
* @return 插件的Resources
*/
public Resources queryPluginResouces(String name) {
// 先从缓存获取
Resources resources = Plugin.queryCachedResources(Plugin.queryCachedFilename(name));
if (resources != null) {
return resources;
}
Plugin p = mPluginMgr.loadResourcePlugin(name, this);
if (p != null) {
return p.mLoader.mPkgResources;
}
if (LOG) {
LogDebug.d(PLUGIN_TAG, "not found plugin=" + name);
}
return null;
}
/**
* 警告:低层接口
* 调用此接口会在当前进程加载插件(不加载代码,只加载资源)
* @param name 插件名
* @return 插件的PackageInfo
*/
public PackageInfo queryPluginPackageInfo(String name) {
// 先从缓存获取
PackageInfo packageInfo = Plugin.queryCachedPackageInfo(Plugin.queryCachedFilename(name));
if (packageInfo != null) {
return packageInfo;
}
Plugin p = mPluginMgr.loadPackageInfoPlugin(name, this);
if (p != null) {
return p.mLoader.mPackageInfo;
}
if (LOG) {
LogDebug.d(PLUGIN_TAG, "not found plugin=" + name);
}
return null;
}
/**
* 警告:低层接口
* 调用此接口会在当前进程加载插件(不加载代码和资源,只获取PackageInfo)
*
* @param pkgName 插件包名
* @param flags Flags
* @return 插件的PackageInfo
*/
public PackageInfo queryPluginPackageInfo(String pkgName, int flags) {
// 根据 pkgName 取得 pluginName
String pluginName = Plugin.queryPluginNameByPkgName(pkgName);
if (!TextUtils.isEmpty(pluginName)) {
return queryPluginPackageInfo(pluginName);
}
return null;
}
/**
* 警告:低层接口
* 调用此接口会在当前进程加载插件(不加载代码和资源,只获取ComponentList)
* @param name 插件名
* @return 插件的ComponentList
*/
public ComponentList queryPluginComponentList(String name) {
// 先从缓存获取
ComponentList cl = Plugin.queryCachedComponentList(Plugin.queryCachedFilename(name));
if (cl != null) {
return cl;
}
Plugin p = mPluginMgr.loadPackageInfoPlugin(name, this);
if (p != null) {
return p.mLoader.mComponents;
}
if (LOG) {
LogDebug.d(PLUGIN_TAG, "not found plugin=" + name);
}
return null;
}
/**
* 警告:低层接口
* 调用此接口会在当前进程加载插件(不启动App)
* @param name 插件名
* @return 插件的Resources
*/
public ClassLoader queryPluginClassLoader(String name) {
// 先从缓存获取
ClassLoader cl = Plugin.queryCachedClassLoader(Plugin.queryCachedFilename(name));
if (cl != null) {
return cl;
}
Plugin p = mPluginMgr.loadDexPlugin(name, this);
if (p != null) {
return p.mLoader.mClassLoader;
}
if (LOG) {
LogDebug.d(PLUGIN_TAG, "not found plugin=" + name);
}
return null;
}
/**
* 警告:低层接口
* 调用此接口会“依据PluginInfo中指定的插件信息”,在当前进程加载插件(不启动App)。通常用于“指定路径来直接安装”的情况
* 注意:调用此接口将不会“通知插件更新”
* Added by Jiongxuan Zhang
* @param pi 插件信息
* @return 插件的Resources
*/
public ClassLoader loadPluginClassLoader(PluginInfo pi) {
// 不从缓存中获取,而是直接初始化ClassLoader
Plugin p = mPluginMgr.loadPlugin(pi, this, Plugin.LOAD_DEX, false);
if (p != null) {
return p.mLoader.mClassLoader;
}
if (LOG) {
LogDebug.d(PLUGIN_TAG, "not found plugin=" + pi.getName());
}
return null;
}
/**
* 警告:低层接口
* 调用此接口会在当前进程加载插件(不加载代码和资源,只获取Collection)
* @return 符合 action 的所有 ReceiverInfo
*/
public List queryPluginsReceiverList(Intent intent) {
IPluginHost pluginHost = PluginProcessMain.getPluginHost();
if (pluginHost != null) {
try {
return pluginHost.queryPluginsReceiverList(intent);
} catch (Throwable e) {
if (LOG) {
LogDebug.e(PLUGIN_TAG, "Query PluginsReceiverList fail:" + e.toString());
}
}
}
return null;
}
/**
* 启动一个插件中的activity,如果插件不存在会触发下载界面
* @param context 应用上下文或者Activity上下文
* @param intent
* @param plugin 插件名
* @param activity 待启动的activity类名
* @param process 是否在指定进程中启动
* @return 插件机制层是否成功,例如没有插件存在、没有合适的Activity坑
*/
public boolean startActivity(Context context, Intent intent, String plugin, String activity, int process) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "start activity: intent=" + intent + " plugin=" + plugin + " activity=" + activity + " process=" + process);
}
return mPluginMgr.mInternal.startActivity(context, intent, plugin, activity, process, true);
}
/**
* 启动一个插件中的 activity 'forResult'
* @return 插件机制层是否成功,例如没有插件存在、没有合适的Activity坑
*/
public boolean startActivityForResult(Activity activity, Intent intent, int requestCode, Bundle options) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "startActivityForResult: intent=" + intent + " requestCode=" + requestCode+ " options=" + options);
}
return mPluginMgr.mInternal.startActivityForResult(activity, intent, requestCode, options);
}
/**
* 加载插件Activity,在startActivity之前调用
* @param intent
* @param plugin 插件名
* @param target 目标Service名,如果传null,则取获取到的第一个
* @param process 是否在指定进程中启动
* @return
*/
public ComponentName loadPluginActivity(Intent intent, String plugin, String activity, int process) {
ActivityInfo ai = null;
String container = null;
PluginBinderInfo info = new PluginBinderInfo(PluginBinderInfo.ACTIVITY_REQUEST);
try {
// 获取 ActivityInfo(可能是其它插件的 Activity,所以这里使用 pair 将 pluginName 也返回)
ai = getActivityInfo(plugin, activity, intent);
if (ai == null) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "PACM: bindActivity: activity not found");
}
return null;
}
// 存储此 Activity 在插件 Manifest 中声明主题到 Intent
intent.putExtra(INTENT_KEY_THEME_ID, ai.theme);
if (LOG) {
LogDebug.d("theme", String.format("intent.putExtra(%s, %s);", ai.name, ai.theme));
}
// 根据 activity 的 processName,选择进程 ID 标识
if (ai.processName != null) {
process = PluginClientHelper.getProcessInt(ai.processName);
}
// 容器选择(启动目标进程)
IPluginClient client = MP.startPluginProcess(plugin, process, info);
if (client == null) {
return null;
}
// 远程分配坑位
container = client.allocActivityContainer(plugin, process, ai.name, intent);
if (LOG) {
LogDebug.i(PLUGIN_TAG, "alloc success: container=" + container + " plugin=" + plugin + " activity=" + activity);
}
} catch (Throwable e) {
if (LOGR) {
LogRelease.e(PLUGIN_TAG, "l.p.a spp|aac: " + e.getMessage(), e);
}
}
// 分配失败
if (TextUtils.isEmpty(container)) {
return null;
}
PmBase.cleanIntentPluginParams(intent);
// TODO 是否重复
// 附上额外数据,进行校验
// intent.putExtra(PluginManager.EXTRA_PLUGIN, plugin);
// intent.putExtra(PluginManager.EXTRA_ACTIVITY, activity);
// intent.putExtra(PluginManager.EXTRA_PROCESS, process);
// intent.putExtra(PluginManager.EXTRA_CONTAINER, container);
PluginIntent ii = new PluginIntent(intent);
ii.setPlugin(plugin);
ii.setActivity(ai.name);
ii.setProcess(IPluginManager.PROCESS_AUTO);
ii.setContainer(container);
ii.setCounter(0);
return new ComponentName(IPC.getPackageName(), container);
}
/**
* 启动插件Service,在startService、bindService之前调用
* @param plugin 插件名
* @param target 目标Service名,如果传null,则取获取到的第一个
* @param process 是否在指定进程中启动
* @return
*/
public ComponentName loadPluginService(String plugin, String target, int process) {
String container = null;
PluginBinderInfo info = new PluginBinderInfo(PluginBinderInfo.SERVICE_REQUEST);
try {
// 容器选择
IPluginClient client = MP.startPluginProcess(plugin, process, info);
if (client == null) {
return null;
}
// 直接分配
container = IPC.getPackageName() + PmBase.CONTAINER_SERVICE_PART + info.index;
return new ComponentName(IPC.getPackageName(), container);
} catch (Throwable e) {
if (LOGR) {
LogRelease.e(PLUGIN_TAG, "l.p.p spp: " + e.getMessage(), e);
}
}
return null;
}
/**
* 启动插件的Provider
* @param plugin 插件名
* @param target 目标Provider名,如果传null,则取获取到的第一个
* @param process 是否在指定进程中启动
* @return
* @deprecated 已废弃该方法,请使用PluginProviderClient里面的方法
*/
public Uri loadPluginProvider(String plugin, String target, int process) {
PluginBinderInfo info = new PluginBinderInfo(PluginBinderInfo.PROVIDER_REQUEST);
try {
// 容器选择
IPluginClient client = MP.startPluginProcess(plugin, process, info);
if (client == null) {
return null;
}
String auth = IPC.getPackageName() + CONTAINER_PROVIDER_AUTHORITY_PART + info.index;
// 直接分配
return new Uri.Builder().scheme("content").encodedAuthority(auth).encodedPath("main").build();
} catch (Throwable e) {
if (LOGR) {
LogRelease.e(PLUGIN_TAG, "l.p.p spp: " + e.getMessage(), e);
}
}
return null;
}
/**
* 通过ClassLoader来获取插件名
*
* @param cl ClassLoader对象
* @return 插件名,若和主程序一致,则返回IModule.PLUGIN_NAME_MAIN(“main”)
* Added by Jiongxuan Zhang
*/
public String fetchPluginName(ClassLoader cl) {
if (cl == mContext.getClassLoader()) {
// Main工程的ClassLoader
return RePlugin.PLUGIN_NAME_MAIN;
}
Plugin p = mPluginMgr.lookupPlugin(cl);
if (p == null) {
// 没有拿到插件的
return null;
}
return p.mInfo.getName();
}
/**
* 根据条件,查找 ActivityInfo 对象
*
* @param plugin 插件名称
* @param activity Activity 名称
* @param intent 调用者传递过来的 Intent
* @return 插件中 Activity 的 ActivityInfo
*/
public ActivityInfo getActivityInfo(String plugin, String activity, Intent intent) {
// 获取插件对象
Plugin p = mPluginMgr.loadAppPlugin(plugin);
if (p == null) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "PACM: bindActivity: may be invalid plugin name or load plugin failed: plugin=" + p);
}
return null;
}
ActivityInfo ai = null;
// activity 不为空时,从插件声明的 Activity 集合中查找
if (!TextUtils.isEmpty(activity)) {
ai = p.mLoader.mComponents.getActivity(activity);
} else {
// activity 为空时,根据 Intent 匹配
ai = IntentMatcherHelper.getActivityInfo(mContext, plugin, intent);
}
return ai;
}
}
================================================
FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/PluginContainers.java
================================================
/*
* 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.loader2;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.text.TextUtils;
import com.qihoo360.i.IPluginManager;
import com.qihoo360.mobilesafe.api.Pref;
import com.qihoo360.replugin.base.IPC;
import com.qihoo360.replugin.component.process.PluginProcessHost;
import com.qihoo360.replugin.helper.HostConfigHelper;
import com.qihoo360.replugin.helper.JSONHelper;
import com.qihoo360.replugin.helper.LogDebug;
import com.qihoo360.replugin.helper.LogRelease;
import org.json.JSONArray;
import org.json.JSONObject;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import static android.content.pm.ActivityInfo.LAUNCH_MULTIPLE;
import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE;
import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TASK;
import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TOP;
import static com.qihoo360.loader2.PluginContainers.ActivityState.toName;
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;
/**
* 插件容器管理
* Plugin Activity Container Manager (PACM)
*
* @author RePlugin Team
*/
public class PluginContainers {
/*
1 请求者:请求分配坑
2 代理模块:依次串联执行如下的 3,4,5 步骤,并返回坑给请求者
3 核心进程:启动宿主进程,返回宿主进程Binder
4 宿主进程:启动时从核心进程加载登记表(坑和目标activity表),减少forward activity和恢复activity时的二次触发
5 宿主进程:查找合适的坑并返回(登记)
6 请求者:发起start Activity请求给系统
7 系统:调度宿主进程
8 宿主进程:查找登记表,加载类
9 宿主进程:触发on Create,校验【Intent的目标和当前activity对象】是否一致,如果一致则确认登记,否则忽略
a 流程完成
登记:
登记用于明确的请求意图
并记录到当前宿主进程的登记表,同时让核心进程也记录(用于宿主进程挂掉时恢复用)
例外情况:
1 坑耗尽,强制分配
2 核心进程死亡,信息丢失
3 宿主进程意外死亡后恢复,系统重新发起Activity创建(恢复)
例外环节处理:
5 分配出不稳定的坑
原因:坑耗尽,复用;由于核心进程和宿主进程挂掉由系统恢复导致的无记录,新分配或复用;
处理:区分开坑不够还是宿主进程或核心进程意外重启导致的信息丢失;可以先不考虑复用的情况;
8 查找坑失败,登记表没有记录
原因:宿主进程和核心进程意外重启,由系统恢复Activity,因此此时无记录
处理:启动forward activity,该activity在on create时获取真正的目标后,立即登记,再次启动Intent的目标Activity
9 检查不合格,Intent的目标和当前activity对象不一致
原因:宿主进程或核心进程意外重启;或者坑跑飞?
处理:
1 如果登记表中找到分配记录,则说明这是一个明确的请求:重新启动分配的Activity
2 否则,表明是系统恢复Activity动作:立即登记,重新启动Intent的目标Activity
重启目标时:
增加计数器,当超过一定次数时停止
*/
private static final String CONTAINER_ACTIVITY_PART = ".loader.a.Activity";
/**
*
*/
private final Object mLock = new Object();
/**
* 所有坑的状态集合
*/
private HashMap mStates = new HashMap<>();
/**
* 非默认 TaskAffinity 下,坑位的状态信息。
*/
private TaskAffinityStates mTaskAffinityStates = new TaskAffinityStates();
/**
* 默认 TaskAffinity 下,坑位的状态信息。
*/
private LaunchModeStates mLaunchModeStates = new LaunchModeStates();
/**
* 保存进程和进程中坑位状态的 Map
*/
private final Map mProcessStatesMap = new HashMap<>();
private static final int STATE_NONE = 0;
private static final int STATE_OCCUPIED = 1;
private static final int STATE_RESTORED = 2;
static final class ActivityState {
final String container;
int state;
String plugin;
String activity;
long timestamp;
final ArrayList> refs;
ActivityState(String container) {
this.container = container;
this.refs = new ArrayList>();
}
public ActivityState(ActivityState state) {
this.container = state.container;
this.state = state.state;
this.plugin = state.plugin;
this.activity = state.activity;
this.timestamp = state.timestamp;
this.refs = new ArrayList>(state.refs);
}
@Override
public String toString() {
if (LogDebug.LOG) {
String s = " state=" + toName(this.state);
String p = " plugin=" + this.plugin;
String a = " activity=" + this.activity;
String sz = " size=" + refs.size();
return "ActivityState {container=" + this.container + s + p + a + sz + "}";
}
return super.toString();
}
static final String toName(int state) {
switch (state) {
case STATE_NONE:
return "none";
case STATE_OCCUPIED:
return "occupied";
case STATE_RESTORED:
return "restored";
}
return "unknown";
}
private final boolean isTarget(String plugin, String activity) {
if (TextUtils.equals(this.plugin, plugin) && TextUtils.equals(this.activity, activity)) {
return true;
}
return false;
}
private final void occupy(String plugin, String activity) {
if (TextUtils.isEmpty(plugin) || TextUtils.isEmpty(activity)) {
if (LOG) {
LogDebug.w(PLUGIN_TAG, "PACM: occupy: invalid s=" + toName(this.state) + " plugin=" + plugin + " activity=" + activity);
}
return;
}
this.state = STATE_OCCUPIED;
this.plugin = plugin;
this.activity = activity;
cleanRefs();
this.timestamp = System.currentTimeMillis();
//
save2Pref(this.plugin, this.activity, this.container);
}
private final void restore(String plugin, String activity, long timestamp) {
if (TextUtils.isEmpty(plugin) || TextUtils.isEmpty(activity)) {
if (LOG) {
LogDebug.w(PLUGIN_TAG, "PACM: restore: invalid s=" + toName(this.state) + " plugin=" + plugin + " activity=" + activity);
}
return;
}
this.state = STATE_RESTORED;
this.plugin = plugin;
this.activity = activity;
cleanRefs();
this.timestamp = timestamp;
}
private final void recycle() {
this.state = STATE_NONE;
this.plugin = null;
this.activity = null;
cleanRefs();
this.timestamp = System.currentTimeMillis();
}
private final void create(String plugin, Activity activity) {
if (this.state == STATE_OCCUPIED || this.state == STATE_RESTORED) { // 当处于restored状态时,表明是系统恢复activity(没有经过register,无显式start activity,核心进程有记录)
if (!TextUtils.equals(this.plugin, plugin)) {
if (LOG) {
LogDebug.w(PLUGIN_TAG, "PACM: create: invalid plugin=" + plugin + " this.plugin=" + this.plugin);
}
return;
}
if (!TextUtils.equals(this.activity, activity.getClass().getName())) {
if (LOG) {
LogDebug.w(PLUGIN_TAG, "PACM: create: invalid a=" + activity.getClass().getName() + " this.a=" + this.activity);
}
return;
}
if (this.state == STATE_RESTORED) {
if (LOG) {
LogDebug.i(PLUGIN_TAG, "PACM: create: relaunch activity: history: container=" + container + " plugin=" + plugin + " activity=" + activity);
}
}
} else if (this.state == STATE_NONE) { // 当处于none状态时,表明是系统恢复activity(没有经过register,无显式start activity,核心进程数据丢失)
if (LOG) {
LogDebug.i(PLUGIN_TAG, "PACM: create: relaunch activity: blank");
}
return;
} else { // Never: 当前已经在created状态?一坑多实例?
if (LOG) {
LogDebug.w(PLUGIN_TAG, "PACM: create: invalid s=" + toName(this.state) + " e=registered c=" + this.container);
}
return;
}
addRef(activity);
this.timestamp = System.currentTimeMillis();
}
private final boolean hasRef() {
for (int i = refs.size() - 1; i >= 0; i--) {
WeakReference ref = refs.get(i);
if (ref.get() == null) {
refs.remove(i);
}
}
return refs.size() > 0;
}
private final void cleanRefs() {
if (LOG) {
for (WeakReference ref : refs) {
if (ref.get() != null) {
LogDebug.w(PLUGIN_TAG, "PACM: clean refs: exist a=" + ref.get());
}
}
}
refs.clear();
}
private final void addRef(Activity activity) {
for (WeakReference ref : refs) {
if (ref.get() == activity) {
return;
}
}
refs.add(new WeakReference(activity));
}
private final void removeRef(Activity activity) {
for (int i = refs.size() - 1; i >= 0; i--) {
WeakReference ref = refs.get(i);
if (ref.get() == activity) {
refs.remove(i);
break;
}
}
}
private final void finishRefs() {
for (WeakReference ref : refs) {
Activity a = ref.get();
if (a != null) {
a.finish();
}
}
}
final void forwardSelf(Activity activity1, Intent intent) {
try {
// 补齐参数:附上额外数据,进行校验
PluginIntent ii = new PluginIntent(intent);
ii.setPlugin(plugin);
ii.setActivity(activity);
ii.setProcess(IPluginManager.PROCESS_AUTO);
ii.setContainer(container);
// 直接启动,避免再次进入插件框架
intent.putExtra(IPluginManager.KEY_COMPATIBLE, true);
// 设定组件
intent.setComponent(new ComponentName(IPC.getPackageName(), container));
//
activity1.startActivity(intent);
} catch (Throwable e) {
if (LOGR) {
LogRelease.e(PLUGIN_TAG, "f.a fs: " + e.getMessage(), e);
}
}
}
}
final void init(int process, HashSet containers) {
if (process != IPluginManager.PROCESS_UI
&& !PluginProcessHost.isCustomPluginProcess(process)
&& !PluginManager.isPluginProcess()) {
return;
}
String prefix = IPC.getPackageName() + CONTAINER_ACTIVITY_PART;
// 因为自定义进程可能也会唤起使用 UI 进程的坑,所以这里使用'或'条件
if (process == IPluginManager.PROCESS_UI || PluginProcessHost.isCustomPluginProcess(process)) {
/* UI 进程标识为 N1 */
String suffix = "N1";
// Standard
mLaunchModeStates.addStates(mStates, containers, prefix + suffix, LAUNCH_MULTIPLE, true, HostConfigHelper.ACTIVITY_PIT_COUNT_TS_STANDARD);
mLaunchModeStates.addStates(mStates, containers, prefix + suffix, LAUNCH_MULTIPLE, false, HostConfigHelper.ACTIVITY_PIT_COUNT_NTS_STANDARD);
// SingleTop
mLaunchModeStates.addStates(mStates, containers, prefix + suffix, LAUNCH_SINGLE_TOP, true, HostConfigHelper.ACTIVITY_PIT_COUNT_TS_SINGLE_TOP);
mLaunchModeStates.addStates(mStates, containers, prefix + suffix, LAUNCH_SINGLE_TOP, false, HostConfigHelper.ACTIVITY_PIT_COUNT_NTS_SINGLE_TOP);
// SingleTask
mLaunchModeStates.addStates(mStates, containers, prefix + suffix, LAUNCH_SINGLE_TASK, true, HostConfigHelper.ACTIVITY_PIT_COUNT_TS_SINGLE_TASK);
mLaunchModeStates.addStates(mStates, containers, prefix + suffix, LAUNCH_SINGLE_TASK, false, HostConfigHelper.ACTIVITY_PIT_COUNT_NTS_SINGLE_TASK);
// SingleInstance
mLaunchModeStates.addStates(mStates, containers, prefix + suffix, LAUNCH_SINGLE_INSTANCE, true, HostConfigHelper.ACTIVITY_PIT_COUNT_TS_SINGLE_INSTANCE);
mLaunchModeStates.addStates(mStates, containers, prefix + suffix, LAUNCH_SINGLE_INSTANCE, false, HostConfigHelper.ACTIVITY_PIT_COUNT_NTS_SINGLE_INSTANCE);
// Standard 横屏
mLaunchModeStates.addLandStates(mStates, containers, prefix + suffix, LAUNCH_MULTIPLE, true, HostConfigHelper.ACTIVITY_PIT_COUNT_TS_STANDARD_LAND);
mLaunchModeStates.addLandStates(mStates, containers, prefix + suffix, LAUNCH_MULTIPLE, false, HostConfigHelper.ACTIVITY_PIT_COUNT_NTS_STANDARD_LAND);
// SingleTop 横屏
mLaunchModeStates.addLandStates(mStates, containers, prefix + suffix, LAUNCH_SINGLE_TOP, true, HostConfigHelper.ACTIVITY_PIT_COUNT_TS_SINGLE_TOP_LAND);
mLaunchModeStates.addLandStates(mStates, containers, prefix + suffix, LAUNCH_SINGLE_TOP, false, HostConfigHelper.ACTIVITY_PIT_COUNT_NTS_SINGLE_TOP_LAND);
// SingleTask 横屏
mLaunchModeStates.addLandStates(mStates, containers, prefix + suffix, LAUNCH_SINGLE_TASK, true, HostConfigHelper.ACTIVITY_PIT_COUNT_TS_SINGLE_TASK_LAND);
mLaunchModeStates.addLandStates(mStates, containers, prefix + suffix, LAUNCH_SINGLE_TASK, false, HostConfigHelper.ACTIVITY_PIT_COUNT_NTS_SINGLE_TASK_LAND);
// SingleInstance 横屏
mLaunchModeStates.addLandStates(mStates, containers, prefix + suffix, LAUNCH_SINGLE_INSTANCE, true, HostConfigHelper.ACTIVITY_PIT_COUNT_TS_SINGLE_INSTANCE_LAND);
mLaunchModeStates.addLandStates(mStates, containers, prefix + suffix, LAUNCH_SINGLE_INSTANCE, false, HostConfigHelper.ACTIVITY_PIT_COUNT_NTS_SINGLE_INSTANCE_LAND);
// taskAffinity
mTaskAffinityStates.init(prefix, suffix, mStates, containers);
// 因为有可能会在 UI 进程启动自定义进程的 Activity,所以此处也要初始化自定义进程的坑位数据
for (int i = 0; i < PluginProcessHost.PROCESS_COUNT; i++) {
ProcessStates processStates = new ProcessStates();
// [":p1": state("P1"), ":p2": state("P2")]
mProcessStatesMap.put(PluginProcessHost.PROCESS_PLUGIN_SUFFIX2 + i, processStates);
init2(prefix, containers, processStates, PluginProcessHost.PROCESS_PLUGIN_SUFFIX + i);
}
// 从内存中加载
loadFromPref();
}
}
/**
* 初始化自定义进程坑坑位
*
* @param prefix xxx.xx.loader.a.Activity
* @param containers 保存所有 Activity 坑名称
* @param states 当前进程所有坑位的状态
* @param suffix p0, p1, p2
*/
private void init2(String prefix, HashSet containers, ProcessStates states, String suffix) {
suffix = suffix.toUpperCase();
// Standard
states.mLaunchModeStates.addStates(mStates, containers, prefix + suffix, LAUNCH_MULTIPLE, true, HostConfigHelper.ACTIVITY_PIT_COUNT_TS_STANDARD);
states.mLaunchModeStates.addStates(mStates, containers, prefix + suffix, LAUNCH_MULTIPLE, false, HostConfigHelper.ACTIVITY_PIT_COUNT_NTS_STANDARD);
// SingleTop
states.mLaunchModeStates.addStates(mStates, containers, prefix + suffix, LAUNCH_SINGLE_TOP, true, HostConfigHelper.ACTIVITY_PIT_COUNT_TS_SINGLE_TOP);
states.mLaunchModeStates.addStates(mStates, containers, prefix + suffix, LAUNCH_SINGLE_TOP, false, HostConfigHelper.ACTIVITY_PIT_COUNT_NTS_SINGLE_TOP);
// SingleTask
states.mLaunchModeStates.addStates(mStates, containers, prefix + suffix, LAUNCH_SINGLE_TASK, true, HostConfigHelper.ACTIVITY_PIT_COUNT_TS_SINGLE_TASK);
states.mLaunchModeStates.addStates(mStates, containers, prefix + suffix, LAUNCH_SINGLE_TASK, false, HostConfigHelper.ACTIVITY_PIT_COUNT_NTS_SINGLE_TASK);
// SingleInstance
states.mLaunchModeStates.addStates(mStates, containers, prefix + suffix, LAUNCH_SINGLE_INSTANCE, true, HostConfigHelper.ACTIVITY_PIT_COUNT_TS_SINGLE_INSTANCE);
states.mLaunchModeStates.addStates(mStates, containers, prefix + suffix, LAUNCH_SINGLE_INSTANCE, false, HostConfigHelper.ACTIVITY_PIT_COUNT_NTS_SINGLE_INSTANCE);
// Standard 横屏
states.mLaunchModeStates.addLandStates(mStates, containers, prefix + suffix, LAUNCH_MULTIPLE, true, HostConfigHelper.ACTIVITY_PIT_COUNT_TS_STANDARD_LAND);
states.mLaunchModeStates.addLandStates(mStates, containers, prefix + suffix, LAUNCH_MULTIPLE, false, HostConfigHelper.ACTIVITY_PIT_COUNT_NTS_STANDARD_LAND);
// SingleTop 横屏
states.mLaunchModeStates.addLandStates(mStates, containers, prefix + suffix, LAUNCH_SINGLE_TOP, true, HostConfigHelper.ACTIVITY_PIT_COUNT_TS_SINGLE_TOP_LAND);
states.mLaunchModeStates.addLandStates(mStates, containers, prefix + suffix, LAUNCH_SINGLE_TOP, false, HostConfigHelper.ACTIVITY_PIT_COUNT_NTS_SINGLE_TOP_LAND);
// SingleTask 横屏
states.mLaunchModeStates.addLandStates(mStates, containers, prefix + suffix, LAUNCH_SINGLE_TASK, true, HostConfigHelper.ACTIVITY_PIT_COUNT_TS_SINGLE_TASK_LAND);
states.mLaunchModeStates.addLandStates(mStates, containers, prefix + suffix, LAUNCH_SINGLE_TASK, false, HostConfigHelper.ACTIVITY_PIT_COUNT_NTS_SINGLE_TASK_LAND);
// SingleInstance 横屏
states.mLaunchModeStates.addLandStates(mStates, containers, prefix + suffix, LAUNCH_SINGLE_INSTANCE, true, HostConfigHelper.ACTIVITY_PIT_COUNT_TS_SINGLE_INSTANCE_LAND);
states.mLaunchModeStates.addLandStates(mStates, containers, prefix + suffix, LAUNCH_SINGLE_INSTANCE, false, HostConfigHelper.ACTIVITY_PIT_COUNT_NTS_SINGLE_INSTANCE_LAND);
// taskAffinity
states.mTaskAffinityStates.init(prefix, suffix, mStates, containers);
}
private final void loadFromPref() {
try {
Map a = Pref.ipcGetAll();
if (LOG) {
LogDebug.d(PLUGIN_TAG, "PACM: restore table: size=" + a.size());
}
for (Entry i : a.entrySet()) {
String k = i.getKey();
Object v = i.getValue();
ActivityState state = mStates.get(k);
String item[] = v.toString().split(":");
if (state != null && item != null && item.length == 3) {
String plugin = item[0];
String activity = item[1];
long timestamp = Long.parseLong(item[2]);
if (LOG) {
LogDebug.d(PLUGIN_TAG, "PACM: restore table: " + " container=" + k + " plugin=" + plugin + " activity=" + activity);
}
if (!TextUtils.isEmpty(plugin) && !TextUtils.isEmpty(activity)) {
state.restore(plugin, activity, timestamp);
}
} else {
if (LOG) {
LogDebug.w(PLUGIN_TAG, "PACM: invalid table: k=" + k + " v=" + v);
}
}
}
} catch (Throwable e) {
if (LOGR) {
LogRelease.e(PLUGIN_TAG, "PACM: init e=" + e.getMessage(), e);
}
}
}
private static final void save2Pref(String plugin, String activity, String container) {
String v = plugin + ":" + activity + ":" + System.currentTimeMillis();
if (LOG) {
LogDebug.d(PLUGIN_TAG, "PACM: save 2 pref: k=" + container + " v=" + v);
}
Pref.ipcSet(container, v);
}
static final String[] resolvePluginActivity(String container) {
String v = Pref.ipcGet(container, "");
//String v = plugin + ":" + activity + ":" + System.currentTimeMillis();
if (LOG) {
LogDebug.d(PLUGIN_TAG, "PACM: load special pref: k=" + container + " v=" + v);
}
//
if (TextUtils.isEmpty(v)) {
return null;
}
return v.split(":");
}
final void forwardIntent(Activity activity, Intent intent, String original, String container, String plugin, String target, int process) {
// 找到容器
ActivityState so = null;
ActivityState state = null;
synchronized (mLock) {
HashMap map = mStates;
so = map.get(original);
state = map.get(container);
}
if (so == null) {
if (LOGR) {
LogRelease.e(PLUGIN_TAG, "f.a fi: cc: inv c.c=" + original);
}
return;
}
if (state == null) {
if (LOGR) {
LogRelease.e(PLUGIN_TAG, "f.a fi: cc: inv t.c=" + container);
}
return;
}
// 检查
if (state.state == STATE_NONE) {
if (LOGR) {
LogRelease.e(PLUGIN_TAG, "f.a fi: cc: ok, t.c empty, t.c=" + container);
}
// 重新登记
state.occupy(plugin, target);
} else if (!state.isTarget(plugin, target)) {
if (LOGR) {
LogRelease.e(PLUGIN_TAG, "f.a fi: cc: fly, force, t.c=" + container);
}
// 如果已经有实例存在了,只能打打日志
if (state.hasRef()) {
if (LOGR) {
LogRelease.e(PLUGIN_TAG, "f.a fi: cc: exists instances");
}
}
// 重新登记
state.occupy(plugin, target);
} else {
if (LOGR) {
LogRelease.e(PLUGIN_TAG, "f.a fi: cc: same, t.c=" + container);
}
}
if (so != state) {
if (LOGR) {
LogRelease.e(PLUGIN_TAG, "f.a fi: t t.c=" + container);
}
if (LOG) {
LogDebug.i(PLUGIN_TAG, "forward fly: container=" + container + " plugin=" + plugin + " activity=" + target);
}
so.recycle();
} else {
if (LOGR) {
LogRelease.i(PLUGIN_TAG, "f.a fi: same t.c=" + container);
}
if (LOG) {
LogDebug.i(PLUGIN_TAG, "forward registered: container=" + container + " plugin=" + plugin + " activity=" + target);
}
}
// 启动目标activity
state.forwardSelf(activity, intent);
}
/**
* 登记坑和activity的映射
* 当前进程:宿主进程
*
* @param ai
* @param plugin
* @param activity
* @param process
* @param intent
* @return
*/
final String alloc(ActivityInfo ai, String plugin, String activity, int process, Intent intent) {
ActivityState state;
String defaultPluginTaskAffinity = ai.applicationInfo.packageName;
if (LOG) {
LogDebug.d(TaskAffinityStates.TAG, "originTaskAffinity is " + ai.taskAffinity);
}
/* SingleInstance 优先级最高 */
if (ai.launchMode == LAUNCH_SINGLE_INSTANCE) {
synchronized (mLock) {
state = allocLocked(ai, mLaunchModeStates.getStates(ai.screenOrientation, ai.launchMode, ai.theme), plugin, activity, intent);
}
/* TaskAffinity */
} else if (!defaultPluginTaskAffinity.equals(ai.taskAffinity)) { // 非默认 taskAffinity
synchronized (mLock) {
state = allocLocked(ai, mTaskAffinityStates.getStates(ai), plugin, activity, intent);
}
/* SingleTask, SingleTop, Standard */
} else {
synchronized (mLock) {
state = allocLocked(ai, mLaunchModeStates.getStates(ai.screenOrientation, ai.launchMode, ai.theme), plugin, activity, intent);
}
}
if (state != null) {
return state.container;
}
return null;
}
/**
* @param ai
* @param map
* @param plugin
* @param activity
* @param intent
* @return
*/
private final ActivityState allocLocked(ActivityInfo ai, HashMap map,
String plugin, String activity, Intent intent) {
// 坑和状态的 map 为空
if (map == null) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "PACM: alloc fail, map is null");
}
return null;
}
// 首先找上一个活的,或者已经注册的,避免多个坑到同一个activity的映射
for (ActivityState state : map.values()) {
if (state.isTarget(plugin, activity)) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "PACM: alloc registered container=" + state.container);
}
return state;
}
}
// 新分配:找空白的,第一个
for (ActivityState state : map.values()) {
if (state.state == STATE_NONE) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "PACM: alloc empty container=" + state.container);
}
state.occupy(plugin, activity);
return state;
}
}
ActivityState found;
// 重用:则找最老的那个
found = null;
for (ActivityState state : map.values()) {
if (!state.hasRef()) {
if (found == null) {
found = state;
} else if (state.timestamp < found.timestamp) {
found = state;
}
}
}
if (found != null) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "PACM: alloc recycled container=" + found.container);
}
found.occupy(plugin, activity);
return found;
}
// 强挤:最后一招,挤掉:最老的那个
found = null;
for (ActivityState state : map.values()) {
if (found == null) {
found = state;
} else if (state.timestamp < found.timestamp) {
found = state;
}
}
if (found != null) {
if (LOG) {
LogDebug.w(PLUGIN_TAG, "PACM: force alloc container=" + found.container);
}
found.finishRefs();
found.occupy(plugin, activity);
return found;
}
if (LOG) {
LogDebug.w(PLUGIN_TAG, "PACM: alloc failed: plugin=" + plugin + " activity=" + activity);
}
// never reach here
return null;
}
String alloc2(ActivityInfo ai, String plugin, String activity, int process, Intent intent, String processTail) {
// 根据进程名称,取得该进程对应的 PluginContainerStates
ProcessStates states = mProcessStatesMap.get(processTail);
ActivityState state;
String defaultPluginTaskAffinity = ai.applicationInfo.packageName;
if (LOG) {
LogDebug.d(TaskAffinityStates.TAG, String.format("插件 %s 默认 TaskAffinity 为 %s", plugin, defaultPluginTaskAffinity));
LogDebug.d(TaskAffinityStates.TAG, String.format("%s 的 TaskAffinity 为 %s", activity, ai.taskAffinity));
}
/* SingleInstance */
if (ai.launchMode == LAUNCH_SINGLE_INSTANCE) {
synchronized (mLock) {
state = allocLocked(ai, states.mLaunchModeStates.getStates(ai.screenOrientation, ai.launchMode, ai.theme), plugin, activity, intent);
}
/* TaskAffinity */
} else if (!defaultPluginTaskAffinity.equals(ai.taskAffinity)) { // 非默认 taskAffinity
synchronized (mLock) {
state = allocLocked(ai, states.mTaskAffinityStates.getStates(ai), plugin, activity, intent);
}
/* other mode */
} else {
synchronized (mLock) {
state = allocLocked(ai, states.mLaunchModeStates.getStates(ai.screenOrientation, ai.launchMode, ai.theme), plugin, activity, intent);
}
}
if (state != null) {
return state.container;
}
return null;
}
final void handleCreate(String plugin, Activity activity, String container) {
ComponentName cn = activity.getComponentName();
if (cn != null) {
container = cn.getClassName();
}
if (LOG) {
LogDebug.d(PLUGIN_TAG, "PACM: activity created h=" + activity.hashCode() + " class=" + activity.getClass().getName() + " container=" + container);
}
synchronized (mLock) {
HashMap map = mStates;
ActivityState state = map.get(container);
if (state != null) {
state.create(plugin, activity);
}
}
}
final void handleDestroy(Activity activity) {
String container = null;
//
ComponentName cn = activity.getComponentName();
if (cn != null) {
container = cn.getClassName();
}
if (LOG) {
LogDebug.d(PLUGIN_TAG, "PACM: activity destroy h=" + activity.hashCode() + " class=" + activity.getClass().getName() + " container=" + container);
}
if (container == null) {
return;
}
synchronized (mLock) {
HashMap map = mStates;
ActivityState state = map.get(container);
if (state != null) {
state.removeRef(activity);
}
}
}
/**
* 容器对应的类
*
* @param container
* @return 注意,对于返回值:可能是登记的,可能是activity退出的残留,也可能是进程恢复后上次的记录
*/
final ActivityState lookupByContainer(String container) {
if (container == null) {
return null;
}
synchronized (mLock) {
HashMap map = mStates;
ActivityState state = map.get(container);
if (state != null && state.state != STATE_NONE) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "found: " + state);
}
return new ActivityState(state);
}
}
// PC: lookupByContainer
if (LOGR) {
long s = mStates.size();
LogRelease.e(PLUGIN_TAG, "not found:" + " c=" + container + " pool=" + s);
}
return null;
}
final String dump() {
JSONArray activityArr = new JSONArray();
JSONObject activityObj;
for (Map.Entry entry : mStates.entrySet()) {
String container = entry.getKey();
ActivityState state = entry.getValue();
if (!TextUtils.isEmpty(state.plugin) && !TextUtils.isEmpty(state.activity)) {
activityObj = new JSONObject();
JSONHelper.putNoThrows(activityObj, "process", IPC.getCurrentProcessName());
JSONHelper.putNoThrows(activityObj, "className", container);
JSONHelper.putNoThrows(activityObj, "plugin", state.plugin);
JSONHelper.putNoThrows(activityObj, "realClassName", state.activity);
JSONHelper.putNoThrows(activityObj, "state", toName(state.state));
JSONHelper.putNoThrows(activityObj, "refs", state.refs != null ? state.refs.size() : 0);
activityArr.put(activityObj);
}
}
return activityArr.toString();
}
}
================================================
FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/PluginContext.java
================================================
/*
* 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.loader2;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.os.Bundle;
import android.util.AttributeSet;
import android.view.ContextThemeWrapper;
import android.view.InflateException;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewStub;
import com.qihoo360.i.Factory2;
import com.qihoo360.loader.utils2.FilePermissionUtils;
import com.qihoo360.replugin.ContextInjector;
import com.qihoo360.replugin.RePlugin;
import com.qihoo360.replugin.component.service.PluginServiceClient;
import com.qihoo360.replugin.component.utils.PluginClientHelper;
import com.qihoo360.replugin.helper.LogDebug;
import com.qihoo360.replugin.helper.LogRelease;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.lang.reflect.Constructor;
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;
/**
* @author RePlugin Team
*/
public class PluginContext extends ContextThemeWrapper {
private final ClassLoader mNewClassLoader;
private final Resources mNewResources;
private final String mPlugin;
private final Loader mLoader;
private final Object mSync = new Object();
private File mFilesDir;
private File mCacheDir;
private File mDatabasesDir;
private LayoutInflater mInflater;
private ContextInjector mContextInjector;
LayoutInflater.Factory mFactory = new LayoutInflater.Factory() {
@Override
public View onCreateView(String name, Context context, AttributeSet attrs) {
return handleCreateView(name, context, attrs);
}
};
public PluginContext(Context base, int themeres, ClassLoader cl, Resources r, String plugin, Loader loader) {
super(base, themeres);
mNewClassLoader = cl;
mNewResources = r;
mPlugin = plugin;
mLoader = loader;
mContextInjector = RePlugin.getConfig().getCallbacks().createContextInjector();
}
@Override
public ClassLoader getClassLoader() {
if (mNewClassLoader != null) {
return mNewClassLoader;
}
return super.getClassLoader();
}
@Override
public Resources getResources() {
if (mNewResources != null) {
return mNewResources;
}
return super.getResources();
}
@Override
public AssetManager getAssets() {
if (mNewResources != null) {
return mNewResources.getAssets();
}
return super.getAssets();
}
@Override
public Object getSystemService(String name) {
if (LAYOUT_INFLATER_SERVICE.equals(name)) {
if (mInflater == null) {
LayoutInflater inflater = (LayoutInflater) super.getSystemService(name);
// 新建一个,设置其工厂
mInflater = inflater.cloneInContext(this);
mInflater.setFactory(mFactory);
// 再新建一个,后续可再次设置工厂
mInflater = mInflater.cloneInContext(this);
}
return mInflater;
}
return super.getSystemService(name);
}
@Override
public SharedPreferences getSharedPreferences(String name, int mode) {
name = "plugin_" + name;
return super.getSharedPreferences(name, mode);
}
@Override
public FileInputStream openFileInput(String name) throws FileNotFoundException {
File f = makeFilename(getFilesDir(), name);
return new FileInputStream(f);
}
@Override
public FileOutputStream openFileOutput(String name, int mode) throws FileNotFoundException {
final boolean append = (mode & MODE_APPEND) != 0;
File f = makeFilename(getFilesDir(), name);
try {
FileOutputStream fos = new FileOutputStream(f, append);
setFilePermissionsFromMode(f.getPath(), mode, 0);
return fos;
} catch (FileNotFoundException e) {
//
}
File parent = f.getParentFile();
parent.mkdir();
FilePermissionUtils.setPermissions(parent.getPath(), FilePermissionUtils.S_IRWXU | FilePermissionUtils.S_IRWXG, -1, -1);
FileOutputStream fos = new FileOutputStream(f, append);
setFilePermissionsFromMode(f.getPath(), mode, 0);
return fos;
}
@Override
public boolean deleteFile(String name) {
File f = makeFilename(getFilesDir(), name);
return f.delete();
}
@Override
public File getFilesDir() {
synchronized (mSync) {
if (mFilesDir == null) {
mFilesDir = new File(getDataDirFile(), "files");
}
if (!mFilesDir.exists()) {
if (!mFilesDir.mkdirs()) {
if (mFilesDir.exists()) {
// spurious failure; probably racing with another process for this app
return mFilesDir;
}
if (LOGR) {
LogRelease.e(PLUGIN_TAG, "Unable to create files directory " + mFilesDir.getPath());
}
return null;
}
FilePermissionUtils.setPermissions(mFilesDir.getPath(), FilePermissionUtils.S_IRWXU | FilePermissionUtils.S_IRWXG | FilePermissionUtils.S_IXOTH, -1, -1);
}
return mFilesDir;
}
}
@Override
public File getCacheDir() {
synchronized (mSync) {
if (mCacheDir == null) {
mCacheDir = new File(getDataDirFile(), "cache");
}
if (!mCacheDir.exists()) {
if (!mCacheDir.mkdirs()) {
if (mCacheDir.exists()) {
// spurious failure; probably racing with another process for this app
return mCacheDir;
}
if (LOGR) {
LogRelease.e(PLUGIN_TAG, "Unable to create cache directory " + mCacheDir.getAbsolutePath());
}
return null;
}
FilePermissionUtils.setPermissions(mCacheDir.getPath(), FilePermissionUtils.S_IRWXU | FilePermissionUtils.S_IRWXG | FilePermissionUtils.S_IXOTH, -1, -1);
}
}
return mCacheDir;
}
/*
为了适配 Android 8.1 及后续版本,该方法不再重写,因此,需要各插件之间约定,防止出现重名数据库。
by cundong
@Override
public File getDatabasePath(String name) {
return validateFilePath(name, false);
}
*/
@Override
public File getFileStreamPath(String name) {
return makeFilename(getFilesDir(), name);
}
@Override
public File getDir(String name, int mode) {
name = "app_" + name;
File file = makeFilename(getDataDirFile(), name);
if (!file.exists()) {
file.mkdir();
setFilePermissionsFromMode(file.getPath(), mode, FilePermissionUtils.S_IRWXU | FilePermissionUtils.S_IRWXG | FilePermissionUtils.S_IXOTH);
}
return file;
}
private File getDatabasesDir() {
synchronized (mSync) {
if (mDatabasesDir == null) {
mDatabasesDir = new File(getDataDirFile(), "databases");
}
if (mDatabasesDir.getPath().equals("databases")) {
mDatabasesDir = new File("/data/system");
}
return mDatabasesDir;
}
}
private File validateFilePath(String name, boolean createDirectory) {
File dir;
File f;
if (name.charAt(0) == File.separatorChar) {
String dirPath = name.substring(0, name.lastIndexOf(File.separatorChar));
dir = new File(dirPath);
name = name.substring(name.lastIndexOf(File.separatorChar));
f = new File(dir, name);
} else {
dir = getDatabasesDir();
f = makeFilename(dir, name);
}
if (createDirectory && !dir.isDirectory() && dir.mkdir()) {
FilePermissionUtils.setPermissions(dir.getPath(), FilePermissionUtils.S_IRWXU | FilePermissionUtils.S_IRWXG | FilePermissionUtils.S_IXOTH, -1, -1);
}
return f;
}
private final File makeFilename(File base, String name) {
if (name.indexOf(File.separatorChar) < 0) {
return new File(base, name);
}
throw new IllegalArgumentException("File " + name + " contains a path separator");
}
/**
* 设置文件的访问权限
*
* @param name 需要被设置访问权限的文件
* @param mode 文件操作模式
* @param extraPermissions 文件访问权限
*
* 注意:
* 此部分经由360安全部门审核后,在所有者|同组用户|其他用户三部分的权限设置中,认为在其他用户的权限设置存在一定的安全风险
* 目前暂且忽略传入的文件操作模式参数,并移除了允许其他用户的读写权限的操作
* 对于文件操作模式以及其他用户访问权限的设置,开发者可自行评估
* @return
*/
private final void setFilePermissionsFromMode(String name, int mode, int extraPermissions) {
int perms = FilePermissionUtils.S_IRUSR | FilePermissionUtils.S_IWUSR | FilePermissionUtils.S_IRGRP | FilePermissionUtils.S_IWGRP | extraPermissions;
// if ((mode & MODE_WORLD_READABLE) != 0) {
// perms |= FilePermissionUtils.S_IROTH;
// }
// if ((mode & MODE_WORLD_WRITEABLE) != 0) {
// perms |= FilePermissionUtils.S_IWOTH;
// }
if (LOG) {
LogDebug.d(PLUGIN_TAG, "File " + name + ": mode=0x" + Integer.toHexString(mode) + ", perms=0x" + Integer.toHexString(perms));
}
FilePermissionUtils.setPermissions(name, perms, -1, -1);
}
/**
* @return
*/
private final File getDataDirFile() {
// 原本用 getDir(Constant.LOCAL_PLUGIN_DATA_SUB_DIR)
// 由于有些模块的数据写死在files目录下,这里不得已改为getFilesDir + Constant.LOCAL_PLUGIN_DATA_SUB_DIR
// File dir = getApplicationContext().getDir(Constant.LOCAL_PLUGIN_DATA_SUB_DIR, 0);
// files
// huchangqing getApplicationContext()会返回null
File dir0 = getBaseContext().getFilesDir();
// v3 data
File dir = new File(dir0, Constant.LOCAL_PLUGIN_DATA_SUB_DIR);
if (!dir.exists()) {
if (!dir.mkdir()) {
if (LOGR) {
LogRelease.e(PLUGIN_TAG, "can't create dir: " + dir.getAbsolutePath());
}
return null;
}
setFilePermissionsFromMode(dir.getPath(), 0, FilePermissionUtils.S_IRWXU | FilePermissionUtils.S_IRWXG | FilePermissionUtils.S_IXOTH);
}
// 插件名
File file = makeFilename(dir, mPlugin);
if (!file.exists()) {
if (!file.mkdir()) {
if (LOGR) {
LogRelease.e(PLUGIN_TAG, "can't create dir: " + file.getAbsolutePath());
}
return null;
}
setFilePermissionsFromMode(file.getPath(), 0, FilePermissionUtils.S_IRWXU | FilePermissionUtils.S_IRWXG | FilePermissionUtils.S_IXOTH);
}
return file;
}
private final View handleCreateView(String name, Context context, AttributeSet attrs) {
// 忽略表命中,返回
if (mLoader.mIgnores.contains(name)) {
// 只有开启“详细日志”才会输出,防止“刷屏”现象
if (LogDebug.LOG && RePlugin.getConfig().isPrintDetailLog()) {
LogDebug.d(PLUGIN_TAG, "layout.cache: ignore plugin=" + mPlugin + " name=" + name);
}
return null;
}
// 构造器缓存
Constructor> construct = mLoader.mConstructors.get(name);
// 缓存失败
if (construct == null) {
// 找类
Class> c = null;
boolean found = false;
do {
try {
c = mNewClassLoader.loadClass(name);
if (c == null) {
// 没找到,不管
break;
}
if (c == ViewStub.class) {
// 系统特殊类,不管
break;
}
if (c.getClassLoader() != mNewClassLoader) {
// 不是插件类,不管
break;
}
// 找到
found = true;
} catch (ClassNotFoundException e) {
// 失败,不管
break;
}
} while (false);
if (!found) {
// 只有开启“详细日志”才会输出,防止“刷屏”现象
if (LogDebug.LOG && RePlugin.getConfig().isPrintDetailLog()) {
LogDebug.d(PLUGIN_TAG, "layout.cache: new ignore plugin=" + mPlugin + " name=" + name);
}
mLoader.mIgnores.add(name);
return null;
}
// 找构造器
try {
construct = c.getConstructor(Context.class, AttributeSet.class);
if (LOG) {
LogDebug.d(PLUGIN_TAG, "layout.cache: new constructor. plugin=" + mPlugin + " name=" + name);
}
mLoader.mConstructors.put(name, construct);
} catch (Exception e) {
InflateException ie = new InflateException(attrs.getPositionDescription() + ": Error inflating mobilesafe class " + name, e);
throw ie;
}
}
// 构造
try {
View v = (View) construct.newInstance(context, attrs);
// 只有开启“详细日志”才会输出,防止“刷屏”现象
if (LogDebug.LOG && RePlugin.getConfig().isPrintDetailLog()) {
LogDebug.d(PLUGIN_TAG, "layout.cache: create view ok. plugin=" + mPlugin + " name=" + name);
}
return v;
} catch (Exception e) {
InflateException ie = new InflateException(attrs.getPositionDescription() + ": Error inflating mobilesafe class " + name, e);
throw ie;
}
}
@Override
public String getPackageName() {
// NOTE 请不要修改此方法,因为有太多的地方用到了PackageName
// 为兼容性考虑,请直接返回卫士自身的包名
return super.getPackageName();
}
// --------------
// WARNING 注意!
// --------------
// 以下所有方法均需框架版本(Framework Ver,见说明书)>=3时才有效(有的需要更高版本)
// Added by Jiongxuan Zhang
@Override
public Context getApplicationContext() {
if (mLoader.mPluginObj.mInfo.getFrameworkVersion() <= 2) {
// 仅框架版本为3及以上的,才支持直接获取PluginContext,而非卫士ApplicationContext
return super.getApplicationContext();
}
// 直接获取插件的Application对象
// NOTE 切勿获取mLoader.mPkgContext,因为里面的一些方法会调用getApplicationContext(如registerComponentCallback)
// NOTE 这样会造成StackOverflow异常。所以只能获取Application对象(框架版本为3以上的会创建此对象)
//entry中调用context.getApplicationContext时mApplicationClient还没被赋值,会导致空指针造成插件安装失败
if (mLoader.mPluginObj.mApplicationClient == null) {
return this;
} else {
return mLoader.mPluginObj.mApplicationClient.getObj();
}
}
@Override
public void startActivity(Intent intent) {
// HINT 只有插件Application才会走这里
// 而Activity.startActivity系统最终会走startActivityForResult,不会走这儿
// 这里会被调用两次:
// 第一次:获取各种信息,最终确认坑位,并走startActivity,再次回到这里
// 第二次:判断要打开的是“坑位Activity”,则返回False,直接走super,后面的事情你们都懂的
// 当然,如果在获取坑位信息时遇到任何情况(例如要打开的是宿主的Activity),则直接返回false,走super
if (!Factory2.startActivity(this, intent)) {
if (mContextInjector != null) {
mContextInjector.startActivityBefore(intent);
}
super.startActivity(intent);
if (mContextInjector != null) {
mContextInjector.startActivityAfter(intent);
}
}
}
@Override
public void startActivity(Intent intent, Bundle options) {
// HINT 保险起见,startActivity写两套相似逻辑
// 具体见startActivity(intent)的描述(上面)
if (!Factory2.startActivity(this, intent)) {
if (mContextInjector != null) {
mContextInjector.startActivityBefore(intent, options);
}
super.startActivity(intent, options);
if (mContextInjector != null) {
mContextInjector.startActivityAfter(intent, options);
}
}
}
@Override
public ComponentName startService(Intent service) {
if (mContextInjector != null) {
mContextInjector.startServiceBefore(service);
}
if (mLoader.mPluginObj.mInfo.getFrameworkVersion() <= 2) {
// 仅框架版本为3及以上的才支持
return super.startService(service);
}
try {
return PluginServiceClient.startService(this, service, true);
} catch (PluginClientHelper.ShouldCallSystem e) {
// 若打开插件出错,则直接走系统逻辑
return super.startService(service);
} finally {
if (mContextInjector != null) {
mContextInjector.startServiceAfter(service);
}
}
}
@Override
public boolean stopService(Intent name) {
if (mLoader.mPluginObj.mInfo.getFrameworkVersion() <= 2) {
// 仅框架版本为3及以上的才支持
return super.stopService(name);
}
try {
return PluginServiceClient.stopService(this, name, true);
} catch (PluginClientHelper.ShouldCallSystem e) {
// 若打开插件出错,则直接走系统逻辑
return super.stopService(name);
}
}
@Override
public boolean bindService(Intent service, ServiceConnection conn, int flags) {
if (mLoader.mPluginObj.mInfo.getFrameworkVersion() <= 2) {
// 仅框架版本为3及以上的才支持
return super.bindService(service, conn, flags);
}
try {
return PluginServiceClient.bindService(this, service, conn, flags, true);
} catch (PluginClientHelper.ShouldCallSystem e) {
// 若打开插件出错,则直接走系统逻辑
return super.bindService(service, conn, flags);
}
}
@Override
public void unbindService(ServiceConnection conn) {
if (mLoader.mPluginObj.mInfo.getFrameworkVersion() <= 2) {
// 仅框架版本为3及以上的才支持
super.unbindService(conn);
return;
}
// 先走一遍系统的逻辑
try {
super.unbindService(conn);
} catch (Throwable e) {
// Ignore
}
// 再走插件的unbindService
// NOTE 由于不应重新调用context.unbind命令,故传进去的是false
PluginServiceClient.unbindService(this, conn, false);
}
@Override
public String getPackageCodePath() {
if (mLoader.mPluginObj.mInfo.getFrameworkVersion() <= 2) {
// 仅框架版本为3及以上的才支持
return super.getPackageCodePath();
}
// 获取插件Apk的路径
return mLoader.mPath;
}
@Override
public ApplicationInfo getApplicationInfo() {
if (mLoader.mPluginObj.mInfo.getFrameworkVersion() <= 2) {
// 仅框架版本为3及以上的才支持
return super.getApplicationInfo();
}
return mLoader.mComponents.getApplication();
}
}
================================================
FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/PluginDesc.java
================================================
/*
* 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.loader2;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.text.TextUtils;
import android.util.Log;
import com.qihoo360.loader.utils.LocalBroadcastManager;
import com.qihoo360.replugin.utils.Charsets;
import com.qihoo360.replugin.utils.CloseableUtils;
import com.qihoo360.mobilesafe.core.BuildConfig;
import com.qihoo360.replugin.RePlugin;
import com.qihoo360.replugin.utils.IOUtils;
import org.json.JSONArray;
import org.json.JSONObject;
import java.io.InputStream;
import java.util.HashMap;
/**
* 获取插件介绍有关的信息,包括:
* 插件显示名、简介、是否为大插件等。此文件可被云控
*
* 注意:此类有别于PluginInfo,前者主要记录版本、路径等
*
* @author RePlugin Team
*/
public class PluginDesc {
private static final String TAG = PluginDesc.class.getSimpleName();
private static final boolean DEBUG = BuildConfig.DEBUG;
private static final byte[] INSTANCE_LOCKER = new byte[0];
private static volatile BroadcastReceiver sUpdateReceiver;
private static final byte[] REG_RECEIVER_LOCKER = new byte[0];
private static volatile boolean sChanged;
public static final String ACTION_UPDATE = "com.qihoo360.mobilesafe.plugin_desc_update";
private String mDisplay;
private String mPlugin;
private String mDesc;
private boolean mLarge;
private static volatile HashMap sMap;
/**
* 通过插件名来获取PluginDesc对象
*/
public static PluginDesc get(String pn) {
return getCurrentMap().get(pn);
}
private static HashMap getCurrentMap() {
registerReceiverIfNeeded();
if (sMap != null && !sChanged) {
return sMap;
}
synchronized (INSTANCE_LOCKER) {
if (sMap != null && !sChanged) {
return sMap;
}
if (DEBUG) {
Log.d(TAG, "load(): Change, Ready to load");
}
sMap = new HashMap<>();
load(PMF.getApplicationContext());
sChanged = false;
}
return sMap;
}
public PluginDesc(String plugin) {
mPlugin = plugin;
}
/**
* 获取插件名
*/
public String getPluginName() {
return mPlugin;
}
/**
* 下载时显示插件的名字
* @return 如果display为空,则返回插件名
*/
public String getDisplayName() {
if (!TextUtils.isEmpty(mDisplay)) {
return mDisplay;
}
return mPlugin;
}
/**
* 下载时的具体描述信息
*/
public String getDescription() {
return mDesc;
}
/**
* 是否为“大插件”,也即首次加载其Activity前,需要弹窗让用户等待
* 绝大多数插件都比较小,但如“手心”、“通讯录”等可能需要特殊对待
*/
public boolean isLarge() {
return mLarge;
}
private static boolean load(Context context) {
JSONArray jsonArray = loadArray(context);
if (jsonArray == null) {
return false;
}
for (int i = 0; i < jsonArray.length(); i++) {
JSONObject jo = jsonArray.optJSONObject(i);
if (jo == null) {
continue;
}
String pn = jo.optString("name");
if (TextUtils.isEmpty(pn)) {
continue;
}
PluginDesc pi = new PluginDesc(pn);
pi.mDisplay = jo.optString("display");
pi.mDesc = jo.optString("desc");
pi.mLarge = jo.optBoolean("large");
sMap.put(pn, pi);
}
return true;
}
private static JSONArray loadArray(Context context) {
InputStream in;
// 读取内部配置
in = null;
try {
in = RePlugin.getConfig().getCallbacks().openLatestFile(context, "plugins-list.json");
if (in != null) {
String str = IOUtils.toString(in, Charsets.UTF_8);
return new JSONArray(str);
}
} catch (Exception e) {
if (DEBUG) {
Log.e(TAG, e.getMessage(), e);
}
} finally {
CloseableUtils.closeQuietly(in);
}
return null;
}
private static void registerReceiverIfNeeded() {
if (sUpdateReceiver != null) {
return;
}
synchronized (REG_RECEIVER_LOCKER) {
if (sUpdateReceiver != null) {
return;
}
sUpdateReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
// 标记已改,可以重新Load了
if (DEBUG) {
Log.d(TAG, "Receiver.onReceive(): Mark change!");
}
// 重新加载
sChanged = true;
getCurrentMap();
}
};
IntentFilter filter = new IntentFilter(ACTION_UPDATE);
LocalBroadcastManager.getInstance(PMF.getApplicationContext()).registerReceiver(sUpdateReceiver,filter);
}
}
}
================================================
FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/PluginIntent.java
================================================
/*
* 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.loader2;
import android.content.ComponentName;
import android.content.Intent;
import android.text.TextUtils;
import com.qihoo360.i.IPluginManager;
import com.qihoo360.replugin.helper.LogRelease;
import java.util.Set;
import static com.qihoo360.replugin.helper.LogDebug.PLUGIN_TAG;
import static com.qihoo360.replugin.helper.LogRelease.LOGR;
/**
* @author RePlugin Team
*/
class PluginIntent {
public static final String EXTRA_PLUGIN = "plugin:";
public static final String EXTRA_ACTIVITY = "activity:";
public static final String EXTRA_PROCESS = "process:";
public static final String EXTRA_CONTAINER = "container:";
public static final String EXTRA_COUNTER = "counter:";
private final Intent mIntent;
PluginIntent(Intent intent) {
mIntent = intent;
}
private final void remove(String prefix) {
Set categories = mIntent.getCategories();
if (categories != null) {
for (String category : categories) {
if (category.startsWith(prefix)) {
mIntent.removeCategory(category);
break;
}
}
}
}
private final String getS(String prefix) {
Set categories = mIntent.getCategories();
if (categories != null) {
for (String category : categories) {
if (category.startsWith(prefix)) {
return category.substring(prefix.length());
}
}
}
return null;
}
private final void setS(String prefix, String value) {
remove(prefix);
mIntent.addCategory(prefix + value);
}
private final int getI(String prefix, int defValue) {
Set categories = mIntent.getCategories();
if (categories != null) {
String v = "";
for (String category : categories) {
if (category.startsWith(prefix)) {
v = category.substring(prefix.length());
break;
}
}
if (!TextUtils.isEmpty(v)) {
try {
int i = Integer.parseInt(v);
return i;
} catch (Throwable e) {
if (LOGR) {
LogRelease.e(PLUGIN_TAG, e.getMessage(), e);
}
}
}
}
return defValue;
}
private final void setI(String prefix, int value) {
remove(prefix);
mIntent.addCategory(prefix + value);
}
/**
* @return
*/
final String getOriginal() {
ComponentName cn = mIntent.getComponent();
if (cn != null) {
return cn.getClassName();
}
return null;
}
final String getPlugin() {
return getS(EXTRA_PLUGIN);
}
final void setPlugin(String plugin) {
setS(EXTRA_PLUGIN, plugin);
}
final String getActivity() {
return getS(EXTRA_ACTIVITY);
}
final void setActivity(String activity) {
setS(EXTRA_ACTIVITY, activity);
}
final int getProcess() {
return getI(EXTRA_PROCESS, IPluginManager.PROCESS_AUTO);
}
final void setProcess(int process) {
setI(EXTRA_PROCESS, process);
}
final String getContainer() {
return getS(EXTRA_CONTAINER);
}
final void setContainer(String container) {
setS(EXTRA_CONTAINER, container);
}
final int getCounter() {
return getI(EXTRA_COUNTER, 0);
}
final void setCounter(int counter) {
setI(EXTRA_COUNTER, counter);
}
}
================================================
FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/PluginLibraryInternalProxy.java
================================================
/*
* 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.loader2;
import android.app.Activity;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.text.TextUtils;
import com.qihoo360.i.Factory;
import com.qihoo360.i.Factory2;
import com.qihoo360.i.IPluginManager;
import com.qihoo360.replugin.utils.FixOTranslucentOrientation;
import com.qihoo360.replugin.utils.ReflectUtils;
import com.qihoo360.replugin.RePlugin;
import com.qihoo360.replugin.base.IPC;
import com.qihoo360.replugin.component.activity.ActivityInjector;
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 org.json.JSONArray;
import org.json.JSONObject;
import java.io.File;
import java.util.List;
import java.util.Set;
import static com.qihoo360.i.Factory.loadPluginActivity;
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;
/**
* plugin-library中,通过“反射”调用的内部逻辑(如PluginActivity类的调用、Factory2等)均在此处
*
* @author RePlugin Team
*/
public class PluginLibraryInternalProxy {
/**
*
*/
PmBase mPluginMgr;
PluginLibraryInternalProxy(PmBase pm) {
mPluginMgr = pm;
}
/**
* @hide 内部方法,插件框架使用
* 启动一个插件中的activity
* 通过Extra参数IPluginManager.KEY_COMPATIBLE,IPluginManager.KEY_PLUGIN,IPluginManager.KEY_ACTIVITY,IPluginManager.KEY_PROCESS控制
* @param context Context上下文
* @param intent
* @return 插件机制层是否成功,例如没有插件存在、没有合适的Activity坑
*/
public boolean startActivity(Context context, Intent intent) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "start context: intent=" + intent);
}
// 兼容模式,直接使用标准方式启动
if (intent.getBooleanExtra(IPluginManager.KEY_COMPATIBLE, false)) {
PmBase.cleanIntentPluginParams(intent);
if (LOG) {
LogDebug.d(PLUGIN_TAG, "start context: COMPATIBLE is true, direct start");
}
return false;
}
// 获取Activity的名字,有两种途径:
// 1. 从Intent里取。通常是明确知道要打开的插件的Activity时会用
// 2. 从Intent的ComponentName中获取
String name = intent.getStringExtra(IPluginManager.KEY_ACTIVITY);
if (TextUtils.isEmpty(name)) {
ComponentName cn = intent.getComponent();
if (cn != null) {
name = cn.getClassName();
if (LOG) {
LogDebug.d(PLUGIN_TAG, "start context: custom context=" + context);
}
}
}
// 已经是标准坑了(例如N1ST1这样的),则无需再过“坑位分配”逻辑,直接使用标准方式启动
if (mPluginMgr.isActivity(name)) {
PmBase.cleanIntentPluginParams(intent);
if (LOG) {
LogDebug.d(PLUGIN_TAG, "start context: context is container, direct start");
}
return false;
}
// 获取插件名,有三种途径:
// 1. 从Intent里取。通常是明确知道要打开的插件时会用
// 2. 根据当前Activity的坑位名来“反查”其插件名。通常是插件内开启自己的Activity时用到
// 3. 通过获得Context的类加载器来判断其插件名
String plugin = intent.getStringExtra(IPluginManager.KEY_PLUGIN);
/* 检查是否是动态注册的类 */
// 如果要启动的 Activity 是动态注册的类,则不使用坑位机制,而是直接动态类。
// 原因:宿主的某些动态注册的类不能运行在坑位中(如'桌面'插件的入口Activity)
ComponentName componentName = intent.getComponent();
if (componentName != null) {
if (LogDebug.LOG) {
LogDebug.d("loadClass", "isHookingClass(" + plugin + "," + componentName.getClassName() + ") = "
+ isDynamicClass(plugin, componentName.getClassName()));
}
if (isDynamicClass(plugin, componentName.getClassName())) {
intent.putExtra(IPluginManager.KEY_COMPATIBLE, true);
intent.setComponent(new ComponentName(IPC.getPackageName(), componentName.getClassName()));
context.startActivity(intent);
return false;
}
}
if (TextUtils.isEmpty(plugin)) {
// 看下Context是否为Activity,如是则直接从坑位中获取插件名(最准确)
if (context instanceof Activity) {
plugin = fetchPluginByPitActivity((Activity) context);
}
if (LOG) {
LogDebug.d(PLUGIN_TAG, "start context: custom plugin is empty, query plugin=" + plugin);
}
}
// 没拿到插件名?再从 ClassLoader 获取插件名称(兜底)
if (TextUtils.isEmpty(plugin)) {
plugin = RePlugin.fetchPluginNameByClassLoader(context.getClassLoader());
}
// 仍然拿不到插件名?(例如从宿主中调用),则打开的Activity可能是宿主的。直接使用标准方式启动
if (TextUtils.isEmpty(plugin)) {
PmBase.cleanIntentPluginParams(intent);
if (LOG) {
LogDebug.d(PLUGIN_TAG, "start context: plugin and context is empty, direct start");
}
return false;
}
// 获取进程值,看目标Activity要打开哪个进程
int process = intent.getIntExtra(IPluginManager.KEY_PROCESS, Integer.MIN_VALUE);
PmBase.cleanIntentPluginParams(intent);
// 调用“特殊版”的startActivity,不让自动填写ComponentName,防止外界再用时出错
return Factory.startActivityWithNoInjectCN(context, intent, plugin, name, process);
}
// 通过Activity坑位来获取插件名
private String fetchPluginByPitActivity(Activity a) {
PluginContainers.ActivityState state = null;
if (a.getComponentName() != null) {
state = mPluginMgr.mClient.mACM.lookupByContainer(a.getComponentName().getClassName());
}
if (state != null) {
return state.plugin;
} else {
return null;
}
}
// FIXME 建议去掉plugin和activity参数,直接用intent代替
/**
* @hide 内部方法,插件框架使用
* 启动一个插件中的activity,如果插件不存在会触发下载界面
* @param context 应用上下文或者Activity上下文
* @param intent
* @param plugin 插件名
* @param activity 待启动的activity类名
* @param process 是否在指定进程中启动
* @param download 下载
* @return 插件机制层是否成功,例如没有插件存在、没有合适的Activity坑
*/
public boolean startActivity(Context context, Intent intent, String plugin, String activity, int process, boolean download) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "start activity: intent=" + intent + " plugin=" + plugin + " activity=" + activity + " process=" + process + " download=" + download);
}
// 是否启动下载
// 若插件不可用(不存在或版本不匹配),则直接弹出“下载插件”对话框
// 因为已经打开UpdateActivity,故在这里返回True,告诉外界已经打开,无需处理
if (download) {
if (PluginTable.getPluginInfo(plugin) == null) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "plugin=" + plugin + " not found, start download ...");
}
// 如果用户在下载即将完成时突然点按“取消”,则有可能出现插件已下载成功,但没有及时加载进来的情况
// 因此我们会判断这种情况,如果是,则重新加载一次即可,反之则提示用户下载
// 原因:“取消”会触发Task.release方法,最终调用mDownloadTask.destroy,导致“下载服务”的Receiver被注销,即使文件下载了也没有回调回来
// NOTE isNeedToDownload方法会调用pluginDownloaded再次尝试加载
if (isNeedToDownload(context, plugin)) {
return RePlugin.getConfig().getCallbacks().onPluginNotExistsForActivity(context, plugin, intent, process);
}
}
}
/* 检查是否是动态注册的类 */
// 如果要启动的 Activity 是动态注册的类,则不使用坑位机制,而是直接动态类。
// 原因:宿主的某些动态注册的类不能运行在坑位中(如'桌面'插件的入口Activity)
if (LOG) {
LogDebug.d("loadClass", "isHookingClass(" + plugin + " , " + activity + ") = "
+ Factory2.isDynamicClass(plugin, activity));
}
if (Factory2.isDynamicClass(plugin, activity)) {
intent.putExtra(IPluginManager.KEY_COMPATIBLE, true);
intent.setComponent(new ComponentName(IPC.getPackageName(), activity));
context.startActivity(intent);
return true;
}
// 如果插件状态出现问题,则每次弹此插件的Activity都应提示无法使用,或提示升级(如有新版)
// Added by Jiongxuan Zhang
if (PluginStatusController.getStatus(plugin) < PluginStatusController.STATUS_OK) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "PluginLibraryInternalProxy.startActivity(): Plugin Disabled. pn=" + plugin);
}
return RePlugin.getConfig().getCallbacks().onPluginNotExistsForActivity(context, plugin, intent, process);
}
// 若为首次加载插件,且是“大插件”,则应异步加载,同时弹窗提示“加载中”
// Added by Jiongxuan Zhang
if (!RePlugin.isPluginDexExtracted(plugin)) {
PluginDesc pd = PluginDesc.get(plugin);
if (pd != null && pd.isLarge()) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "PM.startActivity(): Large Plugin! p=" + plugin);
}
return RePlugin.getConfig().getCallbacks().onLoadLargePluginForActivity(context, plugin, intent, process);
}
}
// WARNING:千万不要修改intent内容,尤其不要修改其ComponentName
// 因为一旦分配坑位有误(或压根不是插件Activity),则外界还需要原封不动的startActivity到系统中
// 可防止出现“本来要打开宿主,结果被改成插件”,进而无法打开宿主Activity的问题
// 缓存打开前的Intent对象,里面将包括Action等内容
Intent from = new Intent(intent);
// 帮助填写打开前的Intent的ComponentName信息(如有。没有的情况如直接通过Action打开等)
if (!TextUtils.isEmpty(plugin) && !TextUtils.isEmpty(activity)) {
from.setComponent(new ComponentName(plugin, activity));
}
ComponentName cn = mPluginMgr.mLocal.loadPluginActivity(intent, plugin, activity, process);
if (cn == null) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "plugin cn not found: intent=" + intent + " plugin=" + plugin + " activity=" + activity + " process=" + process);
}
return false;
}
// 将Intent指向到“坑位”。这样:
// from:插件原Intent
// to:坑位Intent
intent.setComponent(cn);
if (LOG) {
LogDebug.d(PLUGIN_TAG, "start activity: real intent=" + intent);
}
// if (RePluginInternal.FOR_DEV) {
// try {
// String str = cn.getPackageName() + "/" + cn.getClassName();
// if (LOG) {
// LogDebug.d(PLUGIN_TAG, "str=" + str);
// }
// new ProcessBuilder().command("am", "start", "-D", "--user", "0", "-n", str).start();
// } catch (IOException e) {
// e.printStackTrace();
// }
// } else {
context.startActivity(intent);
// 通知外界,已准备好要打开Activity了
// 其中:from为要打开的插件的Intent,to为坑位Intent
RePlugin.getConfig().getEventCallbacks().onPrepareStartPitActivity(context, from, intent);
return true;
}
/**
* 通过 forResult 方式启动一个插件的 Activity
*
* @param activity 源 Activity
* @param intent 要打开 Activity 的 Intent,其中 ComponentName 的 Key 必须为插件名
* @param requestCode 请求码
* @param options 附加的数据
*/
public boolean startActivityForResult(Activity activity, Intent intent, int requestCode, Bundle options) {
String plugin = getPluginName(activity, intent);
if (LOG) {
LogDebug.d(PLUGIN_TAG, "start activity with startActivityForResult: intent=" + intent);
}
if (TextUtils.isEmpty(plugin)) {
return false;
}
ComponentName cn = intent.getComponent();
if (cn == null) {
return false;
}
String name = cn.getClassName();
ComponentName cnNew = loadPluginActivity(intent, plugin, name, IPluginManager.PROCESS_AUTO);
if (cnNew == null) {
return false;
}
intent.setComponent(cnNew);
if (Build.VERSION.SDK_INT >= 16) {
activity.startActivityForResult(intent, requestCode, options);
} else {
activity.startActivityForResult(intent, requestCode);
}
return true;
}
/**
* 获取插件名称
*/
private static String getPluginName(Activity activity, Intent intent) {
String plugin = "";
if (intent.getComponent() != null) {
plugin = intent.getComponent().getPackageName();
}
// 如果 plugin 是包名,则说明启动的是本插件。
if (TextUtils.isEmpty(plugin) || plugin.contains(".")) {
plugin = RePlugin.fetchPluginNameByClassLoader(activity.getClassLoader());
}
// 否则是其它插件
return plugin;
}
private boolean isNeedToDownload(Context context, String plugin) {
// 以下两种情况需要下载插件:
// 1、V5文件不存在(常见);
// 2、V5文件非法(加载失败)
String n = V5FileInfo.getFileName(plugin);
File f = new File(RePlugin.getConfig().getPnInstallDir(), n);
if (!f.exists()) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "isNeedToDownload(): V5 file not exists. Plugin = " + plugin);
}
return true;
}
if (LOG) {
LogDebug.d(PLUGIN_TAG, "isNeedToDownload(): V5 file exists. Extracting... Plugin = " + plugin);
}
PluginInfo i = MP.pluginDownloaded(f.getAbsolutePath());
if (i == null) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "isNeedToDownload(): V5 file is invalid. Plugin = " + plugin);
}
return true;
}
if (LOG) {
LogDebug.d(PLUGIN_TAG, "isNeedToDownload(): V5 file is Okay. Loading... Plugin = " + plugin);
}
return false;
}
/**
* @hide 内部方法,插件框架使用
* 插件的Activity创建成功后通过此方法获取其base context
* @param activity
* @param newBase
* @return 为Activity构造一个base Context
*/
public Context createActivityContext(Activity activity, Context newBase) {
// PluginContainers.ActivityState state = mPluginMgr.mClient.mACM.lookupLastLoading(activity.getClass().getName());
// if (state == null) {
// if (LOG) {
// LogDebug.w(PLUGIN_TAG, "PACM: createActivityContext: can't found plugin activity: activity=" + activity.getClass().getName());
// }
// return null;
// }
// Plugin plugin = mPluginMgr.loadAppPlugin(state.mCN.getPackageName());
// 此时插件必须被加载,因此通过class loader一定能找到对应的PLUGIN对象
Plugin plugin = mPluginMgr.lookupPlugin(activity.getClass().getClassLoader());
if (plugin == null) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "PACM: createActivityContext: can't found plugin object for activity=" + activity.getClass().getName());
}
return null;
}
return plugin.mLoader.createBaseContext(newBase);
}
/**
* @hide 内部方法,插件框架使用
* 插件的Activity的onCreate调用前调用此方法
* @param activity
* @param savedInstanceState
*/
public void handleActivityCreateBefore(Activity activity, Bundle savedInstanceState) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "activity create before: " + activity.getClass().getName() + " this=" + activity.hashCode() + " taskid=" + activity.getTaskId());
}
// 对FragmentActivity做特殊处理
if (savedInstanceState != null) {
//
savedInstanceState.setClassLoader(activity.getClassLoader());
//
try {
savedInstanceState.remove("android:support:fragments");
} catch (Throwable e) {
if (LOGR) {
LogRelease.e(PLUGIN_TAG, "a.c.b1: " + e.getMessage(), e);
}
}
}
// 对FragmentActivity做特殊处理
Intent intent = activity.getIntent();
if (intent != null) {
intent.setExtrasClassLoader(activity.getClassLoader());
activity.setTheme(getThemeId(activity, intent));
}
FixOTranslucentOrientation.fix(activity);
}
/**
* @hide 内部方法,插件框架使用
* 插件的Activity的onCreate调用后调用此方法
* @param activity
* @param savedInstanceState
*/
public void handleActivityCreate(Activity activity, Bundle savedInstanceState) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "activity create: " + activity.getClass().getName() + " this=" + activity.hashCode() + " taskid=" + activity.getTaskId());
}
if (activity.getIntent() != null) {
try {
Intent intent = new Intent(activity.getIntent());
// String pluginName = intent.getStringExtra(PluginManager.EXTRA_PLUGIN);
// String activityName = intent.getStringExtra(PluginManager.EXTRA_ACTIVITY);
// int process = intent.getIntExtra(PluginManager.EXTRA_PROCESS, PluginManager.PROCESS_AUTO);
// String container = intent.getStringExtra(PluginManager.EXTRA_CONTAINER);
// int counter = intent.getIntExtra(PluginManager.EXTRA_COUNTER, 0);
PluginIntent ii = new PluginIntent(intent);
String pluginName = ii.getPlugin();
String activityName = ii.getActivity();
int process = ii.getProcess();
String container = ii.getContainer();
int counter = ii.getCounter();
if (LOG) {
LogDebug.d(PLUGIN_TAG, "activity create: name=" + pluginName + " activity=" + activityName + " process=" + process + " container=" + container + " counter=" + counter);
}
// activity跑飞
if (!TextUtils.equals(activityName, activity.getClass().getName())) {
// activity=, l=
if (LOGR) {
LogRelease.w(PLUGIN_TAG, "a.c.1: a=" + activityName + " l=" + activity.getClass().getName());
}
PMF.forward(activity, intent);
return;
}
if (LOG) {
LogDebug.i(PLUGIN_TAG, "perfect: container=" + container + " plugin=" + pluginName + " activity=" + activityName);
}
} catch (Throwable e) {
if (LOGR) {
LogRelease.e(PLUGIN_TAG, "a.c.2: exception: " + e.getMessage(), e);
}
}
}
//
PluginContainers.ActivityState state = null;
if (activity.getComponentName() != null) {
state = mPluginMgr.mClient.mACM.lookupByContainer(activity.getComponentName().getClassName());
}
if (state == null) {
// PACM: handleActivityCreate: can't found PLUGIN activity: loaded=
if (LOGR) {
LogRelease.e(PLUGIN_TAG, "a.c1: l=" + activity.getClass().getName());
}
return;
}
// 记录坑
mPluginMgr.mClient.mACM.handleCreate(state.plugin, activity, state.container);
// 插件进程信息登记,用于插件进程管理(例如可能用于插件进程分配/回收)
try {
PluginProcessMain.getPluginHost().regActivity(PluginManager.sPluginProcessIndex, state.plugin, state.container, activity.getClass().getName());
} catch (Throwable e) {
if (LOGR) {
LogRelease.e(PLUGIN_TAG, "a.c2: " + e.getMessage(), e);
}
}
//
if (savedInstanceState != null) {
savedInstanceState.setClassLoader(activity.getClassLoader());
}
//
Intent intent = activity.getIntent();
if (intent != null) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "set activity intent cl=" + activity.getClassLoader());
}
intent.setExtrasClassLoader(activity.getClassLoader());
}
// 开始填充一些必要的属性给Activity对象
// Added by Jiongxuan Zhang
ActivityInjector.inject(activity, state.plugin, state.activity);
}
/**
* @hide 内部方法,插件框架使用
* 插件的Activity的onRestoreInstanceState调用后调用此方法
* @param activity
* @param savedInstanceState
*/
public void handleRestoreInstanceState(Activity activity, Bundle savedInstanceState) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "activity restore instance state: " + activity.getClass().getName());
}
//
if (savedInstanceState != null) {
savedInstanceState.setClassLoader(activity.getClassLoader());
// 二级修正
Set set = savedInstanceState.keySet();
if (set != null) {
for (String key : set) {
Object obj = savedInstanceState.get(key);
if (obj instanceof Bundle) {
((Bundle) obj).setClassLoader(activity.getClassLoader());
}
}
}
}
}
/**
* @hide 内部方法,插件框架使用
* 插件的Activity的onDestroy调用后调用此方法
* @param activity
*/
public void handleActivityDestroy(Activity activity) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "activity destroy: " + activity.getClass().getName() + " this=" + activity.hashCode() + " taskid=" + activity.getTaskId());
}
// 回收坑
mPluginMgr.mClient.mACM.handleDestroy(activity);
//
PluginContainers.ActivityState state = null;
if (activity.getComponentName() != null) {
state = mPluginMgr.mClient.mACM.lookupByContainer(activity.getComponentName().getClassName());
}
if (state == null) {
// PACM: handleActivityDestroy: can't found plugin activity: activity=
if (LOGR) {
LogRelease.e(PLUGIN_TAG, "p a h a d c f p a " + activity.getClass().getName());
}
return;
}
// 插件进程信息登记,用于插件进程管理(例如可能用于插件进程分配/回收)
// int pid = Process.myPid();
String plugin = state.plugin;
String container = state.container;
try {
PluginProcessMain.getPluginHost().unregActivity(PluginManager.sPluginProcessIndex, plugin, container, activity.getClass().getName());
} catch (Throwable e) {
if (LOGR) {
LogRelease.e(PLUGIN_TAG, "ur.a: " + e.getMessage(), e);
}
}
// 触发退出检测
RePlugin.getConfig().getEventCallbacks().onActivityDestroyed(activity);
}
/**
* @hide 内部方法,插件框架使用
* 插件的Service的onCreate调用后调用此方法
* @param service
*/
public void handleServiceCreate(Service service) {
mPluginMgr.handleServiceCreated(service);
}
/**
* @hide 内部方法,插件框架使用
* 插件的Service的onDestroy调用后调用此方法
* @param service
*/
public void handleServiceDestroy(Service service) {
mPluginMgr.handleServiceDestroyed(service);
}
/**
* @hide 内部方法,插件框架使用
* 返回所有插件的json串,格式见plugins-builtin.json文件
* @param name 插件名,传null或者空串表示获取全部
* @return
*/
public JSONArray fetchPlugins(String name) {
// 先获取List,然后再逐步搞JSON
List l = MP.getPlugins(false);
JSONArray ja = new JSONArray();
synchronized (PluginTable.PLUGINS) {
for (PluginInfo info : l) {
if (TextUtils.isEmpty(name) || TextUtils.equals(info.getName(), name)) {
JSONObject jo = info.getJSON();
ja.put(jo);
}
}
}
return ja;
}
/**
* @hide 内部方法,插件框架使用
* 登记动态映射的类(6.5.0 later)
* @param className 壳类名
* @param plugin 目标插件名
* @param type 目标类的类型: activity, service, provider
* @param target 目标类名
* @return
*/
public boolean registerDynamicClass(String className, String plugin, String type, String target) {
return mPluginMgr.addDynamicClass(className, plugin, type, target, null);
}
/**
* @hide 内部方法,插件框架使用
* 登记动态映射的类(7.7.0 later)
*/
public boolean registerDynamicClass(String className, String plugin, String target, Class defClass) {
return mPluginMgr.addDynamicClass(className, plugin, "", target, defClass);
}
/**
* @hide 内部方法,插件框架使用
* 查询某个类是否是动态映射的类(7.7.0 later)
*/
public boolean isDynamicClass(String plugin, String className) {
return mPluginMgr.isDynamicClass(plugin, className);
}
/**
* @hide 内部方法,插件框架使用
* 取消动态映射类的注册
*/
public void unregisterDynamicClass(String className) {
mPluginMgr.removeDynamicClass(className);
}
/**
* @hide 内部方法,插件框架使用
* 查询某个动态映射的类对应的插件(7.7.0 later)
*/
public String getPluginByDynamicClass(String className) {
return mPluginMgr.getPluginByDynamicClass(className);
}
/**
* 获取插件中使用代码设置的主题 id
*/
private int getDynamicThemeId(Activity activity) {
int dynamicThemeId = -1;
try {
dynamicThemeId = (int) ReflectUtils.invokeMethod(activity.getClassLoader(),
"android.view.ContextThemeWrapper", "getThemeResId", activity, null);
} catch (Exception e) {
e.printStackTrace();
}
return dynamicThemeId;
}
/**
* 获取坑位应该使用的主题
*/
private int getThemeId(Activity activity, Intent intent) {
// 通过反射获取主题(可能获取到坑的主题,或者程序员通过代码设置的主题)
int dynamicThemeId = getDynamicThemeId(activity);
// 插件 manifest 中设置的 ThemeId
int manifestThemeId = intent.getIntExtra(PluginCommImpl.INTENT_KEY_THEME_ID, 0);
//如果插件上没有主题则使用Application节点的Theme
if (manifestThemeId == 0) {
manifestThemeId = activity.getApplicationInfo().theme;
}
// 根据 manifest 中声明主题是否透明,获取默认主题
int defaultThemeId = getDefaultThemeId();
if (LaunchModeStates.isTranslucentTheme(manifestThemeId)) {
defaultThemeId = android.R.style.Theme_Translucent_NoTitleBar;
}
int themeId;
if (LOG) {
LogDebug.d("theme", "defaultThemeId = " + defaultThemeId);
LogDebug.d("theme", "dynamicThemeId = " + dynamicThemeId);
LogDebug.d("theme", "manifestThemeId = " + manifestThemeId);
}
// 通过反射获取主题成功
if (dynamicThemeId != -1) {
// 如果动态主题是默认主题,说明插件未通过代码设置主题,此时应该使用 AndroidManifest 中设置的主题。
if (dynamicThemeId == defaultThemeId) {
// AndroidManifest 中有声明主题
if (manifestThemeId != 0) {
themeId = manifestThemeId;
} else {
themeId = defaultThemeId;
}
} else {
// 动态主题不是默认主题,说明主题是插件通过代码设置的,使用此代码设置的主题。
themeId = dynamicThemeId;
}
// 反射失败,检查 AndroidManifest 是否有声明主题
} else {
if (manifestThemeId != 0) {
themeId = manifestThemeId;
} else {
themeId = defaultThemeId;
}
}
if (LOG) {
LogDebug.d("theme", "themeId = " + themeId);
}
return themeId;
}
/**
* 获取默认 ThemeID
* 如果 Host 配置了使用 AppCompat,则此处通过反射调用 AppCompat 主题。
*
* 注:Host 必须配置 AppCompat 依赖,否则反射调用会失败,导致宿主编译不过。
*/
private static int getDefaultThemeId() {
if (HostConfigHelper.ACTIVITY_PIT_USE_APPCOMPAT) {
Class clazz;
try {
if (HostConfigHelper.HOST_USE_ANDROIDX){
clazz = ReflectUtils.getClass("androidx.appcompat.R$style");
} else {
clazz = ReflectUtils.getClass("android.support.v7.appcompat.R$style");
}
return (int) ReflectUtils.readStaticField(clazz, "Theme_AppCompat");
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
return android.R.style.Theme_NoTitleBar;
}
}
================================================
FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/PluginManager.java
================================================
/*
* 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.loader2;
import android.content.Context;
import android.text.TextUtils;
import com.qihoo360.i.IPluginManager;
import com.qihoo360.mobilesafe.api.Tasks;
import com.qihoo360.replugin.base.IPC;
import com.qihoo360.replugin.component.process.PluginProcessHost;
import com.qihoo360.replugin.helper.LogDebug;
import java.util.regex.MatchResult;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static com.qihoo360.replugin.helper.LogDebug.LOG;
import static com.qihoo360.replugin.helper.LogDebug.PLUGIN_TAG;
/**
* @author RePlugin Team
*/
public class PluginManager {
private static final Pattern PROCESS_NAME_PATTERN = Pattern.compile(Constant.STUB_PROCESS_SUFFIX_PATTERN);
public static final int PROCESS_AUTO = IPluginManager.PROCESS_AUTO;
public static final int COUNTER_MAX = 10;
/**
* @deprecated 临时实现
*/
@Deprecated
static int sUid;
/**
* @deprecated 临时实现
*/
@Deprecated
static int sPluginProcessIndex = -1;
/**
* @return
*/
public static final boolean isPluginProcess() {
return sPluginProcessIndex >= 0 && sPluginProcessIndex < Constant.STUB_PROCESS_COUNT;
}
public static final boolean isValidActivityProcess(int process) {
if (process == IPluginManager.PROCESS_UI || process == IPluginManager.PROCESS_AUTO || isPluginProcess(process)) {
return true;
}
return false;
}
/**
* @return
*/
static final boolean isPluginProcess(int index) {
return index >= 0 && index < Constant.STUB_PROCESS_COUNT;
}
static final int getPluginProcessIndex() {
return sPluginProcessIndex;
}
/**
* @deprecated 临时实现
*/
@Deprecated
static final void init(Context context) {
// 初始化操作,方便后面执行任务,不必担心Handler为空的情况
Tasks.init();
//
sUid = android.os.Process.myUid();
//
sPluginProcessIndex = evalPluginProcess(IPC.getCurrentProcessName());
}
static final int evalPluginProcess(String name) {
int index = IPluginManager.PROCESS_AUTO;
try {
if (TextUtils.equals(IPC.getPackageName(), name)) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "plugin process checker: default, index=" + 0);
}
return IPluginManager.PROCESS_UI;
}
if (!TextUtils.isEmpty(name)) {
if (name.contains(PluginProcessHost.PROCESS_PLUGIN_SUFFIX2)) {
String tail = PluginProcessHost.processTail(name);
return PluginProcessHost.PROCESS_INT_MAP.get(tail);
}
}
Matcher m = PROCESS_NAME_PATTERN.matcher(name);
if (m == null || !m.matches()) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "plugin process checker: non plugin process in=" + name);
}
return IPluginManager.PROCESS_AUTO;
}
MatchResult r = m.toMatchResult();
if (r == null || r.groupCount() != 2) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "plugin process checker: no group in=" + name);
}
return IPluginManager.PROCESS_AUTO;
}
String pr = r.group(1);
if (!TextUtils.equals(IPC.getPackageName(), pr)) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "plugin process checker: package name not match in=" + name);
}
return IPluginManager.PROCESS_AUTO;
}
String str = r.group(2);
index = Integer.parseInt(str);
if (LOG) {
LogDebug.d(PLUGIN_TAG, "plugin process checker: index=" + index);
}
} catch (Throwable e) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, e.getMessage(), e);
}
}
return index;
}
}
================================================
FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/PluginNativeLibsHelper.java
================================================
/*
* 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.loader2;
import android.text.TextUtils;
import android.util.Log;
import com.qihoo360.replugin.helper.LogRelease;
import com.qihoo360.replugin.utils.CloseableUtils;
import com.qihoo360.mobilesafe.core.BuildConfig;
import com.qihoo360.replugin.helper.LogDebug;
import com.qihoo360.replugin.utils.FileUtils;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
/**
* 与插件Native(SO)库有关的方法,都放在此处
* 该类主要用于:安装插件的SO库,获取SO库路径等
* 【仅框架内部使用】
*
* @author RePlugin Team
*/
public class PluginNativeLibsHelper {
private static final String TAG = "PluginNativeLibsHelper";
/**
* 安装Native SO库
* 模拟系统安装流程,最终只释放一个最合身的SO库进入Libs目录中
*
* @param apkPath APK文件路径
* @param nativeDir 要释放的Libs目录,通常从getLibDir中获取
* @return 安装是否成功
*/
public static boolean install(String apkPath, File nativeDir) {
if (BuildConfig.DEBUG) {
Log.d(TAG, "install(): Start. apkp=" + apkPath + "; nd=" + nativeDir.getAbsolutePath());
}
// TODO 线程同步
// 为防止加载旧SO,先清空目录
clear(nativeDir);
ZipFile zipFile = null;
try {
zipFile = new ZipFile(apkPath);
Map libZipEntries = new HashMap<>();
Map> soList = new HashMap<>();
// 找到所有的SO库,包括各种版本的,方便findSoPathForAbis中过滤
injectEntriesAndLibsMap(zipFile, libZipEntries, soList);
for (String soName : soList.keySet()) {
Set soPaths = soList.get(soName);
String soPath = findSoPathForAbis(soPaths, soName);
if (BuildConfig.DEBUG) {
Log.d(TAG, "install(): Ready to extract. so=" + soName + "; sop=" + soPath);
}
if (soPath == null) {
continue;
}
File file = new File(nativeDir, soName);
extractFile(zipFile, libZipEntries.get(soPath), file);
}
return true;
} catch (Throwable e) {
if (BuildConfig.DEBUG) {
e.printStackTrace();
}
// 清除所有释放的文件,防止释放了一半
clear(nativeDir);
return false;
} finally {
CloseableUtils.closeQuietly(zipFile);
}
}
/**
* 删除插件的SO库,通常在插件SO释放失败后,或者已有新插件,需要清除老插件时才会生效
*/
public static void clear(File nativeDir) {
if (!nativeDir.exists()) {
return;
}
try {
FileUtils.forceDelete(nativeDir);
} catch (IOException e) {
// IOException:有可能是IO,如权限出现问题等,打出日志
e.printStackTrace();
} catch (IllegalArgumentException e2) {
if (LogRelease.LOGR) {
e2.printStackTrace();
}
}
}
private static void injectEntriesAndLibsMap(ZipFile zipFile, Map libZipEntries, Map> soList) {
Enumeration extends ZipEntry> entries = zipFile.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
String name = entry.getName();
if (name.contains("../")) {
// 过滤,防止被攻击
continue;
}
if (name.startsWith("lib/") && !entry.isDirectory()) {
libZipEntries.put(name, entry);
String soName = new File(name).getName();
Set fs = soList.get(soName);
if (fs == null) {
fs = new TreeSet<>();
soList.put(soName, fs);
}
fs.add(name);
}
}
}
private static void extractFile(ZipFile zipFile, ZipEntry ze, File outFile) throws IOException {
InputStream in = null;
try {
in = zipFile.getInputStream(ze);
FileUtils.copyInputStreamToFile(in, outFile);
if (BuildConfig.DEBUG) {
Log.i(TAG, "extractFile(): Success! fn=" + outFile.getName());
}
} finally {
CloseableUtils.closeQuietly(in);
}
}
// 根据Abi来获取需要释放的SO在压缩包中的位置
private static String findSoPathForAbis(Set soPaths, String soName) {
if (soPaths == null || soPaths.size() <= 0) {
return null;
}
// 若主程序用的是64位进程,则所属的SO必须只拷贝64位的,否则会出异常。32位也是如此
// 问:如果用户用的是64位处理器,宿主没有放任何SO,那么插件会如何?
// 答:宿主在被安装时,系统会标记此为64位App,则之后的SO加载则只认64位的
// 问:如何让插件支持32位?
// 答:宿主需被标记为32位才可以。可在宿主App中放入任意32位的SO(如放到libs/armeabi目录下)即可。
// 获取指令集列表
boolean is64 = VMRuntimeCompat.is64Bit();
String[] abis;
if (is64) {
abis = BuildCompat.SUPPORTED_64_BIT_ABIS;
} else {
abis = BuildCompat.SUPPORTED_32_BIT_ABIS;
}
// 开始寻找合适指定指令集的SO路径
String soPath = findSoPathWithAbiList(soPaths, soName, abis);
if (LogDebug.LOG) {
LogDebug.d(TAG, "findSoPathForAbis: Find so path. name=" + soName + "; list=" + soPath +
"; Host-is-64bit?=" + is64 + "; abis=" + Arrays.toString(abis));
}
return soPath;
}
private static String findSoPathWithAbiList(Set soPaths, String soName, String[] supportAbis) {
Arrays.sort(supportAbis);
for (String soPath : soPaths) {
String abi = soPath.replaceFirst("lib/", "");
abi = abi.replace("/" + soName, "");
if (!TextUtils.isEmpty(abi) && Arrays.binarySearch(supportAbis, abi) >= 0) {
return soPath;
}
}
return null;
}
}
================================================
FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/PluginProcessMain.java
================================================
/*
* 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.loader2;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.text.TextUtils;
import android.util.Log;
import com.qihoo360.i.IPluginManager;
import com.qihoo360.replugin.base.IPC;
import com.qihoo360.replugin.component.process.PluginProcessHost;
import com.qihoo360.replugin.helper.LogDebug;
import com.qihoo360.replugin.helper.LogRelease;
import com.qihoo360.replugin.model.PluginInfo;
import com.qihoo360.replugin.packages.PluginManagerProxy;
import com.qihoo360.replugin.packages.PluginManagerServer;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import static com.qihoo360.replugin.helper.LogDebug.LOG;
import static com.qihoo360.replugin.helper.LogDebug.MAIN_TAG;
import static com.qihoo360.replugin.helper.LogDebug.PLUGIN_TAG;
import static com.qihoo360.replugin.helper.LogRelease.LOGR;
/**
* 进程管理类
* @author RePlugin Team
*/
public class PluginProcessMain {
public static final String TAG = PluginProcessMain.class.getSimpleName();
/**
* 常驻进程使用,非常驻进程为null buyuntao
*/
private static IPluginHost sPluginHostLocal;
/**
* 非常驻进程使用,常驻进程为null,用于非常驻进程连接常驻进程 buyuntao
*/
private static IPluginHost sPluginHostRemote;
/**
* 提供binder保存功能,无其他逻辑
*/
static HashMap sBinders = new HashMap();
/**
* 当前运行的所有进程的列表(常驻进程除外)
*/
private static final Map ALL = new HashMap();
/**
* ALL的读写锁,用于并发时的性能提升
*/
private static final ReentrantReadWriteLock PROCESS_CLIENT_LOCK = new ReentrantReadWriteLock();
private static final Object COOKIE_LOCK = new Object();
private static boolean sPersisistCookieInitialized;
/**
* 常驻进程cookie,用来控制卫士进程组是否需要退出等
*/
private static long sPersisistCookie;
/**
* 进程记录,用于进程及进程列表管理 buyuntao
*/
private static final class ProcessClientRecord implements IBinder.DeathRecipient {
String name; //进程名称
String plugin;
int pid;
int index;
IBinder binder;
IPluginClient client;
PluginManagerServer pluginManager; //单个进程的插件管理类
public ProcessClientRecord(String process, String plugin, int pid, int index, IBinder binder, IPluginClient client, PluginManagerServer pms) {
this.name = process;
this.plugin = plugin;
this.pid = pid;
this.index = index;
this.binder = binder;
this.client = client;
this.pluginManager = pms;
}
@Override
public void binderDied() {
handleBinderDied(this);
}
@Override
public String toString() {
if (LOG) {
return super.toString() + " {name=" + name + " plugin=" + plugin + " pid=" + pid + " index=" + index + " binder=" + binder + " client=" + client + "}";
}
return super.toString();
}
public IPluginClient getClient() {
return client;
}
}
static final String dump() {
// 1.dump Activity映射表, service列表
JSONArray activityArr = new JSONArray();
JSONArray serviceArr = new JSONArray();
for (ProcessClientRecord clientRecord : ALL.values()) {
try {
IPluginClient pluginClient = clientRecord.getClient();
if (pluginClient == null) {
continue;
}
String activityDumpInfo = pluginClient.dumpActivities();
if (!TextUtils.isEmpty(activityDumpInfo)) {
JSONArray activityList = new JSONArray(activityDumpInfo);
int activityCount = activityList.length();
if (activityCount > 0) {
for (int i = 0; i < activityCount; i++) {
activityArr.put(activityList.getJSONObject(i));
}
}
}
String serviceDumpInfo = pluginClient.dumpServices();
if (!TextUtils.isEmpty(serviceDumpInfo)) {
JSONArray serviceList = new JSONArray(serviceDumpInfo);
int serviceCount = serviceList.length();
if (serviceCount > 0) {
for (int i = 0; i < serviceCount; i++) {
serviceArr.put(serviceList.getJSONObject(i));
}
}
}
} catch (Throwable e) {
e.printStackTrace();
}
}
// 2.dump 插件信息表
JSONArray pluginArr = new JSONArray();
List pluginList = MP.getPlugins(false);
if (pluginList != null) {
JSONObject pluginObj;
for (PluginInfo pluginInfo : pluginList) {
try {
pluginObj = new JSONObject();
pluginObj.put(pluginInfo.getName(), pluginInfo.toString());
pluginArr.put(pluginObj);
} catch (JSONException e) {
e.printStackTrace();
}
}
}
JSONObject detailObj = new JSONObject();
try {
detailObj.put("activity", activityArr);
detailObj.put("service", serviceArr);
detailObj.put("plugin", pluginArr);
} catch (JSONException e) {
e.printStackTrace();
}
return detailObj.toString();
}
static final void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
if (LogDebug.DUMP_ENABLED) {
writer.println("--- ALL.length = " + ALL.size() + " ---");
for (ProcessClientRecord r : ALL.values()) {
writer.println(r);
}
writer.println();
StubProcessManager.dump(writer);
writer.println();
// writer.println("--- USED_PLUGINS.size = " + USED_PLUGINS.size() + " ---");
// for (ProcessPluginInfo r : USED_PLUGINS.values()) {
// writer.println(r);
// }
writer.println();
PluginTable.dump(fd, writer, args);
}
}
/**
* 常驻进程调用,缓存自己的 IPluginHost
*/
static final void installHost(IPluginHost host) {
sPluginHostLocal = host;
// 连接到插件化管理器的服务端
// Added by Jiongxuan Zhang
try {
PluginManagerProxy.connectToServer(sPluginHostLocal);
} catch (RemoteException e) {
// 基本不太可能到这里,直接打出日志
if (LOGR) {
e.printStackTrace();
}
}
}
/**
* 连接常驻进程后的Action
*/
public interface DiedAction {
void onDied();
}
/**
* 非常驻进程调用,获取常驻进程的 IPluginHost
*/
static final void connectToHostSvc() {
connectToHostSvc(null);
}
/**
* 非常驻进程调用,获取常驻进程的 IPluginHost
*/
static final void connectToHostSvc(final DiedAction diedAction) {
Context context = PMF.getApplicationContext();
IBinder binder = PluginProviderStub.proxyFetchHostBinder(context);
if (LOG) {
LogDebug.d(PLUGIN_TAG, "host binder = " + binder);
}
if (binder == null) {
// 无法连接到常驻进程,当前进程自杀
if (LOGR) {
LogRelease.e(PLUGIN_TAG, "p.p fhb fail");
}
System.exit(1);
}
try {
binder.linkToDeath(new IBinder.DeathRecipient() {
@Override
public void binderDied() {
if (LOGR) {
LogRelease.i(PLUGIN_TAG, "p.p d, p.h s n");
}
// 检测到常驻进程退出,插件进程自杀
if (PluginManager.isPluginProcess()) {
if (LOGR) {
// persistent process exception, PLUGIN process quit now
LogRelease.i(MAIN_TAG, "p p e, pp q n");
}
System.exit(0);
}
sPluginHostRemote = null;
// 断开和插件化管理器服务端的连接,因为已经失效
PluginManagerProxy.disconnect();
//断开连接后,需要尝试重新连接
if (diedAction != null) {
diedAction.onDied();
}
}
}, 0);
} catch (RemoteException e) {
// 无法连接到常驻进程,当前进程自杀
if (LOGR) {
LogRelease.e(PLUGIN_TAG, "p.p p.h l2a: " + e.getMessage(), e);
}
System.exit(1);
}
//
sPluginHostRemote = IPluginHost.Stub.asInterface(binder);
if (LOG) {
LogDebug.d(PLUGIN_TAG, "host binder.i = " + PluginProcessMain.sPluginHostRemote);
}
// 连接到插件化管理器的服务端
// Added by Jiongxuan Zhang
try {
PluginManagerProxy.connectToServer(sPluginHostRemote);
// 将当前进程的"正在运行"列表和常驻做同步
// TODO 若常驻进程重启,则应在启动时发送广播,各存活着的进程调用该方法来同步
PluginManagerProxy.syncRunningPlugins();
} catch (RemoteException e) {
// 获取PluginManagerServer时出现问题,可能常驻进程突然挂掉等,当前进程自杀
if (LOGR) {
LogRelease.e(PLUGIN_TAG, "p.p p.h l3a: " + e.getMessage(), e);
}
System.exit(1);
}
// 注册该进程信息到“插件管理进程”中
PMF.sPluginMgr.attach();
}
/**
* sPluginHostLocal 常驻进程使用,非常驻进程为null buyuntao
* sPluginHostRemote 非常驻进程使用,常驻进程为null,用于非常驻进程连接常驻进程 buyuntao
* @hide 内部框架使用
*/
public static final IPluginHost getPluginHost() {
if (sPluginHostLocal != null) {
return sPluginHostLocal;
}
// 可能是第一次,或者常驻进程退出了
if (sPluginHostRemote == null) {
if (LogDebug.LOG) {
if (IPC.isPersistentProcess()) {
LogDebug.e(PLUGIN_TAG, "插件框架未正常初始化");
throw new RuntimeException("插件框架未正常初始化");
}
}
// 再次唤起常驻进程
connectToHostSvc();
}
return sPluginHostRemote;
}
static final long getPersistentCookie() {
synchronized (COOKIE_LOCK) {
if (!sPersisistCookieInitialized) {
sPersisistCookieInitialized = true;
if (IPC.isPersistentProcess()) {
sPersisistCookie = System.currentTimeMillis();
if (LOG) {
LogDebug.d(PLUGIN_TAG, "generate cookie: " + sPersisistCookie);
}
}
}
return sPersisistCookie;
}
}
/**
* @param plugin
* @param process
* @param info
* @return
*/
static final IPluginClient probePluginClient(final String plugin, final int process, final PluginBinderInfo info) {
return readProcessClientLock(new Action() {
@Override
public IPluginClient call() {
for (ProcessClientRecord r : ALL.values()) {
if (process == IPluginManager.PROCESS_UI) {
if (!TextUtils.equals(r.plugin, Constant.PLUGIN_NAME_UI)) {
continue;
}
/* 是否是用户自定义进程 */
} else if (PluginProcessHost.isCustomPluginProcess(process)) {
if (!TextUtils.equals(r.plugin, getProcessStringByIndex(process))) {
continue;
}
} else {
if (!TextUtils.equals(r.plugin, plugin)) {
continue;
}
}
if (!isBinderAlive(r)) {
return null;
}
if (!r.binder.pingBinder()) {
return null;
}
info.pid = r.pid;
info.index = r.index;
return r.client;
}
return null;
}
});
}
/**
* 根据进程索引,取进程名称标识
*
* @param index -99
* @return :p1
*/
private static String getProcessStringByIndex(int index) {
return PluginProcessHost.PROCESS_PLUGIN_SUFFIX2 + (index - PluginProcessHost.PROCESS_INIT);
}
/**
* @param pid
* @param info
* @return
*/
static final IPluginClient probePluginClientByPid(final int pid, final PluginBinderInfo info) {
return readProcessClientLock(new Action() {
@Override
public IPluginClient call() {
for (ProcessClientRecord r : ALL.values()) {
if (r.pid != pid) {
continue;
}
if (!isBinderAlive(r)) {
return null;
}
if (!r.binder.pingBinder()) {
return null;
}
info.pid = r.pid;
info.index = r.index;
return r.client;
}
return null;
}
});
}
/**
* 发送intent给进程 buyuntao
* @param target
* @param intent
*/
static final void sendIntent2Process(final String target, Intent intent, boolean sync) {
final Map map = readProcessClientLock(new Action