package com.limpoxe.fairy.manager;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.Signature;
import android.os.Build;
import android.text.TextUtils;
import android.util.Base64;
import com.limpoxe.fairy.content.PluginDescriptor;
import com.limpoxe.fairy.core.FairyGlobal;
import com.limpoxe.fairy.core.PluginCreator;
import com.limpoxe.fairy.core.PluginLauncher;
import com.limpoxe.fairy.core.PluginManifestParser;
import com.limpoxe.fairy.core.localservice.LocalServiceManager;
import com.limpoxe.fairy.util.FileUtil;
import com.limpoxe.fairy.util.LogUtil;
import com.limpoxe.fairy.util.PackageVerifyer;
import com.limpoxe.fairy.util.ProcessUtil;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Collection;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
class PluginManagerImpl {
private static final boolean NEED_VERIFY_CERT = true;
private static final String INSTALLED_KEY = "plugins.list";
private static final String PENDING_KEY = "plugins.pending";
private final Hashtable<String, PluginDescriptor> sInstalledPlugins = new Hashtable<String, PluginDescriptor>();
private final Hashtable<String, PluginDescriptor> sPendingPlugins = new Hashtable<String, PluginDescriptor>();
PluginManagerImpl() {
if (!ProcessUtil.isPluginProcess()) {
throw new IllegalAccessError("本类仅在插件进程使用");
}
}
/**
* 插件的安装目录, 插件apk将来会被放在这个目录下面
*/
private String genInstallPath(String pluginId, String pluginVersoin) {
if (pluginId.indexOf(File.separatorChar) >= 0 || pluginVersoin.indexOf(File.separatorChar) >= 0) {
throw new IllegalArgumentException("path contains a path separator");
}
return getPluginRootDir() + "/" + pluginId + "/" + pluginVersoin + "/base-1.apk";
}
private String getPluginRootDir() {
return FairyGlobal.getApplication().getDir("plugin_dir", Context.MODE_PRIVATE).getAbsolutePath();
}
@SuppressWarnings("unchecked")
synchronized void loadInstalledPlugins() {
if (sInstalledPlugins.size() == 0) {
long t1 = System.currentTimeMillis();
Hashtable<String, PluginDescriptor> installedPlugin = readPlugins(INSTALLED_KEY);
if (installedPlugin != null) {
sInstalledPlugins.putAll(installedPlugin);
}
//把pending合并到install
Hashtable<String, PluginDescriptor> pendingPlugin = readPlugins(PENDING_KEY);
if (pendingPlugin != null) {
Iterator<Map.Entry<String, PluginDescriptor>> itr = pendingPlugin.entrySet().iterator();
while (itr.hasNext()) {
Map.Entry<String, PluginDescriptor> entry = itr.next();
//删除旧版
remove(entry.getKey());
}
//保存新版
sInstalledPlugins.putAll(pendingPlugin);
savePlugins(INSTALLED_KEY, sInstalledPlugins);
//清除pending
getSharedPreference().edit().remove(PENDING_KEY).commit();
}
long t2 = System.currentTimeMillis();
LogUtil.i("加载所有插件列表, 耗时 : " + (t2 - t1));
}
}
private boolean addOrReplace(PluginDescriptor pluginDescriptor) {
sInstalledPlugins.put(pluginDescriptor.getPackageName(), pluginDescriptor);
boolean isSaveSuccess = savePlugins(INSTALLED_KEY, sInstalledPlugins);
if (!isSaveSuccess) {
sInstalledPlugins.remove(pluginDescriptor.getPackageName());
}
return isSaveSuccess;
}
private boolean pending(PluginDescriptor pluginDescriptor) {
sPendingPlugins.put(pluginDescriptor.getPackageName(), pluginDescriptor);
return savePlugins(PENDING_KEY, sPendingPlugins);
}
synchronized boolean removeAll() {
PluginManagerHelper.clearLocalCache();
sInstalledPlugins.clear();
boolean isSuccess = savePlugins(INSTALLED_KEY, sInstalledPlugins);
FileUtil.deleteAll(new File(getPluginRootDir()));
return isSuccess;
}
synchronized int remove(String pluginId) {
PluginManagerHelper.clearLocalCache();
PluginDescriptor old = sInstalledPlugins.remove(pluginId);
boolean result = false;
if (old != null) {
PluginLauncher.instance().stopPlugin(pluginId, old);
result = savePlugins(INSTALLED_KEY, sInstalledPlugins);
boolean deleteSuccess = FileUtil.deleteAll(new File(old.getInstalledPath()).getParentFile());
LogUtil.w("delete old", result, deleteSuccess, old.getInstalledPath(), old.getPackageName());
if (deleteSuccess) {
return PluginManagerHelper.SUCCESS;
} else {
return PluginManagerHelper.REMOVE_FAIL;
}
} else {
LogUtil.e("插件未安装", pluginId);
return PluginManagerHelper.PLUGIN_NOT_EXIST;
}
}
Collection<PluginDescriptor> getPlugins() {
return sInstalledPlugins.values();
}
/**
* for Fragment
*
* @param clazzId
* @return
*/
PluginDescriptor getPluginDescriptorByFragmenetId(String clazzId) {
Iterator<PluginDescriptor> itr = sInstalledPlugins.values().iterator();
while (itr.hasNext()) {
PluginDescriptor descriptor = itr.next();
if (descriptor.containsFragment(clazzId)) {
return descriptor;
}
}
return null;
}
PluginDescriptor getPluginDescriptorByPluginId(String pluginId) {
PluginDescriptor pluginDescriptor = sInstalledPlugins.get(pluginId);
if (pluginDescriptor != null && pluginDescriptor.isEnabled()) {
return pluginDescriptor;
}
return null;
}
PluginDescriptor getPluginDescriptorByClassName(String clazzName) {
Iterator<PluginDescriptor> itr = sInstalledPlugins.values().iterator();
while (itr.hasNext()) {
PluginDescriptor descriptor = itr.next();
if (descriptor.containsName(clazzName)) {
return descriptor;
}
}
return null;
}
/**
* 安装一个插件
*
* @param srcPluginFile
* @return
*/
synchronized InstallResult installPlugin(String srcPluginFile) {
LogUtil.w("开始安装插件", srcPluginFile);
long startAt = System.currentTimeMillis();
if (TextUtils.isEmpty(srcPluginFile) || !new File(srcPluginFile).exists()) {
return new InstallResult(PluginManagerHelper.SRC_FILE_NOT_FOUND);
}
// 先将apk复制到宿主程序私有目录,防止在安装过程中文件被篡改
if (!srcPluginFile.startsWith(FairyGlobal.getApplication().getCacheDir().getAbsolutePath())) {
String tempFilePath = FairyGlobal.getApplication().getCacheDir().getAbsolutePath()
+ File.separator + System.currentTimeMillis() + ".apk";
if (FileUtil.copyFile(srcPluginFile, tempFilePath)) {
srcPluginFile = tempFilePath;
} else {
LogUtil.e("复制插件文件失败", srcPluginFile, tempFilePath);
return new InstallResult(PluginManagerHelper.COPY_FILE_FAIL);
}
}
// 解析Manifest,获得插件详情
final PluginDescriptor pluginDescriptor = PluginManifestParser.parseManifest(srcPluginFile);
if (pluginDescriptor == null || TextUtils.isEmpty(pluginDescriptor.getPackageName())) {
LogUtil.e("解析插件Manifest文件失败", srcPluginFile);
new File(srcPluginFile).delete();
return new InstallResult(PluginManagerHelper.PARSE_MANIFEST_FAIL);
}
//判断插件适用系统版本
if (pluginDescriptor.getMinSdkVersion() != null && Build.VERSION.SDK_INT < Integer.valueOf(pluginDescriptor.getMinSdkVersion())) {
LogUtil.e("当前系统版本过低, 不支持此插件", "系统:" + Build.VERSION.SDK_INT, "插件:" + pluginDescriptor.getMinSdkVersion(), pluginDescriptor.getPackageName());
new File(srcPluginFile).delete();
return new InstallResult(PluginManagerHelper.MIN_API_NOT_SUPPORTED, pluginDescriptor.getPackageName(), pluginDescriptor.getVersion());
}
// 验证插件APK签名,如果被篡改过,将获取不到证书
// 之所以把验证签名步骤在放在验证适用系统版本之后,
// 是因为不同的minSdkVersion在签名时使用的sha算法长度不同,
// 也即高版本的minSdkVersion的插件,即使签名没有被篡改过,在低版本的系统中仍然会校验失败
// 所以先校验minSdkVersion,再校验签名
//sApplication.getPackageManager().getPackageArchiveInfo(srcPluginFile, PackageManager.GET_SIGNATURES);
Signature[] pluginSignatures = PackageVerifyer.collectCertificates(srcPluginFile, false);
boolean isDebugable = (0 != (FairyGlobal.getApplication().getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE));
if (pluginSignatures == null) {
LogUtil.e("插件签名验证失败", srcPluginFile);
new File(srcPluginFile).delete();
return new InstallResult(PluginManagerHelper.SIGNATURES_INVALIDATE);
}
//可选步骤,验证插件APK证书是否和宿主程序证书相同。
//证书中存放的是公钥和算法信息,而公钥和私钥是1对1的
//公钥相同意味着是同一个作者发布的程序
if (NEED_VERIFY_CERT && !isDebugable) {
Signature[] mainSignatures = null;
try {
PackageInfo pkgInfo = FairyGlobal.getApplication().getPackageManager().getPackageInfo(FairyGlobal.getApplication().getPackageName(), PackageManager.GET_SIGNATURES);
mainSignatures = pkgInfo.signatures;
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
if (!PackageVerifyer.isSignaturesSame(mainSignatures, pluginSignatures)) {
LogUtil.e("插件证书和宿主证书不一致", srcPluginFile);
new File(srcPluginFile).delete();
return new InstallResult(PluginManagerHelper.VERIFY_SIGNATURES_FAIL);
}
}
// 检查当前宿主版本是否匹配此非独立插件需要的版本
PackageManager packageManager = FairyGlobal.getApplication().getPackageManager();
String requireHostVerName = pluginDescriptor.getRequiredHostVersionName();
if (!pluginDescriptor.isStandalone() && requireHostVerName != null) {
//是非独立插件,而且指定了插件运行需要的的宿主版本
try {
PackageInfo hostPackageInfo = packageManager.getPackageInfo(FairyGlobal.getApplication().getPackageName(), PackageManager.GET_META_DATA);
//判断宿主版本是否满足要求
LogUtil.v(pluginDescriptor.getPackageName(), requireHostVerName, hostPackageInfo.versionName);
if (!requireHostVerName.equals(hostPackageInfo.versionName)) {
//不满足要求,不可安装此插件
LogUtil.e("当前宿主版本不支持此插件版本", "宿主versionName:" + hostPackageInfo.versionName, "插件RequiredHostVersionName:" + pluginDescriptor.getRequiredHostVersionName());
new File(srcPluginFile).delete();
return new InstallResult(PluginManagerHelper.HOST_VERSION_NOT_SUPPORT_CURRENT_PLUGIN, pluginDescriptor.getPackageName(), pluginDescriptor.getVersion());
}
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
}
// 检查插件是否已经存在,若存在删除旧的
PluginDescriptor oldPluginDescriptor = getPluginDescriptorByPluginId(pluginDescriptor.getPackageName());
if (oldPluginDescriptor != null) {
LogUtil.d("已安装过,安装路径为", oldPluginDescriptor.getInstalledPath(), oldPluginDescriptor.getVersion(), pluginDescriptor.getVersion());
//检查插件是否已经加载
if (PluginLauncher.instance().isRunning(oldPluginDescriptor.getPackageName())) {
if (!oldPluginDescriptor.getVersion().equals(pluginDescriptor.getVersion())) {
LogUtil.w("旧版插件已经加载, 且新版插件和旧版插件版本不同,直接删除旧版,进行热更新");
remove(oldPluginDescriptor.getPackageName());
} else {
LogUtil.e("旧版插件已经加载, 且新版插件和旧版插件版本相同,拒绝安装");
new File(srcPluginFile).delete();
return new InstallResult(PluginManagerHelper.FAIL_BECAUSE_SAME_VER_HAS_LOADED, pluginDescriptor.getPackageName(), pluginDescriptor.getVersion());
}
} else {
LogUtil.v("旧版插件还未加载,忽略版本,直接删除旧版,尝试安装新版");
remove(oldPluginDescriptor.getPackageName());
}
}
// 复制插件到插件目录
String destApkPath = genInstallPath(pluginDescriptor.getPackageName(), pluginDescriptor.getVersion());
boolean isCopySuccess = FileUtil.copyFile(srcPluginFile, destApkPath);
if (!isCopySuccess) {
LogUtil.e("复制插件到安装目录失败", srcPluginFile);
//删掉临时文件
new File(srcPluginFile).delete();
return new InstallResult(PluginManagerHelper.COPY_FILE_FAIL, pluginDescriptor.getPackageName(), pluginDescriptor.getVersion());
} else {
//第5步,先解压so到临时目录,再从临时目录复制到插件so目录。 在构造插件Dexclassloader的时候,会使用这个so目录作为参数
File apkParent = new File(destApkPath).getParentFile();
File tempSoDir = new File(apkParent, "temp");
Set<String> soList = FileUtil.unZipSo(srcPluginFile, tempSoDir);
if (soList != null) {
for (String soName : soList) {
FileUtil.copySo(tempSoDir, soName, apkParent.getAbsolutePath());
}
//删掉临时文件
FileUtil.deleteAll(tempSoDir);
}
//try {
//ArrayList<String> multiDexFiles = PluginMultiDexExtractor.performExtractions(new File(destApkPath), new File(apkParent, "secondDexes"));
//pluginDescriptor.setMuliDexList(multiDexFiles);
//} catch (IOException e) {
// e.printStackTrace();
//}
//万事具备 添加到已安装插件列表
PackageInfo packageInfo = packageManager.getPackageArchiveInfo(destApkPath, PackageManager.GET_GIDS);
if (packageInfo != null) {
pluginDescriptor.setApplicationTheme(packageInfo.applicationInfo.theme);
pluginDescriptor.setApplicationIcon(packageInfo.applicationInfo.icon);
pluginDescriptor.setApplicationLogo(packageInfo.applicationInfo.logo);
}
pluginDescriptor.setInstalledPath(destApkPath);
boolean isInstallSuccess = addOrReplace(pluginDescriptor);
//删掉临时文件
new File(srcPluginFile).delete();
if (!isInstallSuccess) {
LogUtil.e("安装插件失败", srcPluginFile);
new File(destApkPath).delete();
return new InstallResult(PluginManagerHelper.INSTALL_FAIL, pluginDescriptor.getPackageName(), pluginDescriptor.getVersion());
} else {
//通过创建classloader来触发dexopt,但不加载
LogUtil.d("正在进行DEXOPT...", pluginDescriptor.getInstalledPath());
//ActivityThread.getPackageManager().performDexOptIfNeeded()
FileUtil.deleteAll(new File(apkParent, "dalvik-cache"));
ClassLoader cl = PluginCreator.createPluginClassLoader(pluginDescriptor.getInstalledPath(), pluginDescriptor.isStandalone(), null, null);
try {
cl.loadClass(Object.class.getName());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
LogUtil.d("DEXOPT完毕");
LogUtil.d("注册localService");
LocalServiceManager.registerService(pluginDescriptor);
long endAt = System.currentTimeMillis();
LogUtil.w("插件安装成功", pluginDescriptor.getPackageName(), "耗时 : " + (endAt - startAt));
LogUtil.v("安装路径", pluginDescriptor.getInstalledPath());
//打印一下目录结构
if (isDebugable) {
FileUtil.printAll(new File(FairyGlobal.getApplication().getApplicationInfo().dataDir));
}
return new InstallResult(PluginManagerHelper.SUCCESS, pluginDescriptor.getPackageName(), pluginDescriptor.getVersion());
}
}
}
private static SharedPreferences getSharedPreference() {
SharedPreferences sp = FairyGlobal.getApplication().getSharedPreferences("plugins.installed",
Build.VERSION.SDK_INT < 11 ? Context.MODE_PRIVATE : Context.MODE_PRIVATE | 0x0004);
return sp;
}
private synchronized boolean savePlugins(String key, Hashtable<String, PluginDescriptor> plugins) {
ObjectOutputStream objectOutputStream = null;
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
try {
objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(plugins);
objectOutputStream.flush();
byte[] data = byteArrayOutputStream.toByteArray();
String list = Base64.encodeToString(data, Base64.DEFAULT);
getSharedPreference().edit().putString(key, list).commit();
return true;
} catch (Exception e) {
e.printStackTrace();
} finally {
if (objectOutputStream != null) {
try {
objectOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (byteArrayOutputStream != null) {
try {
byteArrayOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return false;
}
@SuppressWarnings("unchecked")
private synchronized Hashtable<String, PluginDescriptor> readPlugins(String key) {
String list = getSharedPreference().getString(key, "");
Serializable object = null;
if (!TextUtils.isEmpty(list)) {
ByteArrayInputStream byteArrayInputStream = null;
ObjectInputStream objectInputStream = null;
try {
byteArrayInputStream = new ByteArrayInputStream(Base64.decode(list, Base64.DEFAULT));
objectInputStream = new ObjectInputStream(byteArrayInputStream);
object = (Serializable) objectInputStream.readObject();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (objectInputStream != null) {
try {
objectInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (byteArrayInputStream != null) {
try {
byteArrayInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
return (Hashtable<String, PluginDescriptor>) object;
}
}