/*
* Copyright (C) 2014 The AppCan Open Source Project.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.zywx.wbpalmstar.engine.universalex;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.util.Xml;
import com.ryg.dynamicload.internal.DLPluginManager;
import com.ryg.dynamicload.internal.DLPluginPackage;
import org.xmlpull.v1.XmlPullParser;
import org.zywx.wbpalmstar.base.BDebug;
import org.zywx.wbpalmstar.engine.EBrowserView;
import org.zywx.wbpalmstar.engine.ELinkedList;
import org.zywx.wbpalmstar.engine.EngineEventListener;
import org.zywx.wbpalmstar.widgetone.Smith;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Constructor;
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.Map;
import dalvik.system.DexClassLoader;
public class ThirdPluginMgr {
private final static String pluginNode = "plugin";
private final static String methodNode = "method";
private final static String propertyNode = "property";
private final static String uexNameAttr = "uexName";
private final static String classNameAttr = "className";
private final static String nameAttr = "name";
private final static String startupAttr = "startup";
private final static String globalAttr = "global";
private static final String F_SP_NAME_PLUGIN_LOADING = "plugins_loading";
private static final String F_SP_KEY_NAME_PLUGIN_COPY_FINISHED = "isPluginCopyFinished";
private static final String F_SP_KEY_NAME_PLUGIN_COPY_LAST_PKG_VERSION = "lastCopyPkgVersion";
private static final String dexApk = "apkfile";
private static final String dexJar = "dexfile/jar";
private static final String dexLib = "dexfile/armeabi";
private static final String optFile = "dexfile/out";
private Context mContext;
private DexClassLoader mParentClassLoader;
private AssetManager mAssetManager;
private Resources mResources;
private StringBuffer script;
private Map<String, ThirdPluginObject> mThirdClass;
private LinkedList<String> javaNames;
private int PluginCount = 0;
private String libsParentDir = null;
private String[] pluginJars = null;
public ThirdPluginMgr(Context context) {
mThirdClass = new Hashtable<String, ThirdPluginObject>();
javaNames = new LinkedList<String>();
libsParentDir = context.getFilesDir().getAbsolutePath();
mContext = context;
}
public Map<String, ThirdPluginObject> getPlugins() {
return mThirdClass;
}
public void clean() {
mThirdClass.clear();
mThirdClass = null;
script = null;
}
public void loadInitAllDexPluginClass() {
copyLib();
copyJar();
initClassLoader();
}
private void copyLib() {
InputStream in = null;
BufferedInputStream bis = null;
FileOutputStream fos = null;
String libPath = libsParentDir + File.separator + dexLib;
File dirFile = new File(libPath);
if (dirFile != null)
dirFile.delete();
if (!dirFile.exists()) {
dirFile.mkdirs();
}
try {
String[] libs = mContext.getAssets().list(dexLib);
if (null != libs && libs.length > 0) {
for (int i = 0; i < libs.length; i++) {
in = mContext.getAssets().open(
dexLib + File.separator + libs[i]);
File file = new File(libPath + File.separator + libs[i]);
if (!file.exists()) {
try {
file.createNewFile();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
bis = new BufferedInputStream(in);
fos = new FileOutputStream(file);
byte[] b = new byte[1024];
int len = 0;
while ((len = bis.read(b)) != -1) {
fos.write(b, 0, len);
}
fos.flush();
in.close();
bis.close();
}
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
try {
if (in != null && bis != null && fos != null) {
in.close();
bis.close();
fos.close();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
private void copyJar() {
InputStream in = null;
BufferedInputStream bis = null;
FileOutputStream fos = null;
String jarPath = libsParentDir + File.separator + dexJar;
File dirFile = new File(jarPath);
pluginJars = null;
if (dirFile != null)
dirFile.delete();
if (!dirFile.exists()) {
dirFile.mkdirs();
}
try {
pluginJars = mContext.getAssets().list(dexJar);
if (pluginJars != null && pluginJars.length > 0) {
for (int i = 0; i < pluginJars.length; i++) {
in = mContext.getAssets().open(
dexJar + File.separator + pluginJars[i]);
File file = new File(jarPath + File.separator
+ pluginJars[i]);
if (!file.exists()) {
try {
file.createNewFile();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
bis = new BufferedInputStream(in);
fos = new FileOutputStream(file);
byte[] b = new byte[1024];
int len = 0;
while ((len = bis.read(b)) != -1) {
fos.write(b, 0, len);
}
fos.flush();
in.close();
bis.close();
}
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
if (in != null && bis != null && fos != null) {
try {
in.close();
bis.close();
fos.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
public ClassLoader getClassLoader() {
return mParentClassLoader;
}
public AssetManager getAssets() {
return mAssetManager;
}
public Resources getResources() {
return mResources;
}
/**
* 判断是否已经加载了重复的插件
*
* @param jsName 插件名称
* @param javaName 插件入口类名称
* @return
*/
private boolean isDuplicatedPlugin(String jsName, String javaName) {
return (jsName == null || javaName == null) ? false : (mThirdClass
.containsKey(jsName) || javaNames.contains(javaName));
}
/**
* 加载apk形式的动态库插件,获取classLoader
*
* @param apkPath 动态加载的apk路径
* @return DLPluginPackage对象,包含动态加载的apk信息
*/
private DLPluginPackage loadDynamicPluginClass(String apkPath) {
DLPluginPackage dlPkg = DLPluginManager.getInstance(mContext).loadApk(
apkPath, false, mParentClassLoader, null);
mParentClassLoader = dlPkg.classLoader;
mAssetManager = dlPkg.assetManager;
mResources = dlPkg.resources;
return dlPkg;
}
/**
* 拷贝并初始化所有动态库插件
*
* @param listenerQueue
*/
public void loadInitAllDynamicPluginClass(
ELinkedList<EngineEventListener> listenerQueue) {
long time = System.currentTimeMillis();
long cost = 0;
this.copyDynamicApk();
XmlPullParser plugins = null;
// 动态加载apk插件
File apkPluginParentDir = new File(libsParentDir + File.separator + dexApk);
File[] pluginApks = apkPluginParentDir.listFiles();
if (pluginApks != null) {
for (int i = 0; i < pluginApks.length; i++) {
try {
File apkPluginDir = pluginApks[i];
if (apkPluginDir.isDirectory()) {
// 一个动态插件所在目录
String uexName = apkPluginDir.getName();
File apkPluginFile = new File(apkPluginDir
+ File.separator + uexName + ".apk");
DLPluginPackage dlPkg = this
.loadDynamicPluginClass(apkPluginFile
.getAbsolutePath());
ClassLoader classLoader = dlPkg.classLoader;
File apkPluginXmlFile = new File(apkPluginDir
+ File.separator + "plugin.xml");
FileInputStream inputStream = new FileInputStream(
apkPluginXmlFile);
plugins = Xml.newPullParser();
plugins.setInput(inputStream, "UTF-8");
this.initClass(plugins, listenerQueue,
classLoader);
}
} catch (Exception e) {
e.printStackTrace();
BDebug.e(e.toString());
}
}
}
if(mParentClassLoader != null){
try {
replaceCurrentClassLoader(mParentClassLoader);
} catch (Exception e) {
e.printStackTrace();
}
}
cost = System.currentTimeMillis() - time;
BDebug.i("DL", "dynamic plugins loading costs " + cost);
}
/**
* 根据plugin.xml中的配置,加载插件
*
* @param plugins
* @param listenerQueue
* @param classLoader
* 如果不需要指定classLoader,传null
*/
public void initClass(XmlPullParser plugins,
ELinkedList<EngineEventListener> listenerQueue,
ClassLoader classLoader) {
int eventType = -1;
String jsName = "", javaName = "", startup = "";
String globalStr = "";
ThirdPluginObject scriptObj = null;
if (classLoader == null) {
// 传空则使用默认的classLoader
classLoader = mContext.getClassLoader();
}
try {
script = new StringBuffer();
while ((eventType = plugins.next()) != XmlResourceParser.END_DOCUMENT) {
if (eventType == XmlResourceParser.START_TAG) {
String strNode = plugins.getName();
if (strNode.equals(pluginNode)) {
jsName = plugins.getAttributeValue(null, uexNameAttr);
javaName = plugins.getAttributeValue(null,
classNameAttr);
startup = plugins.getAttributeValue(null, startupAttr);
if (null != javaName && javaName.trim().length() != 0
&& !isDuplicatedPlugin(jsName, javaName)) {
if (null != startup && "true".equals(startup)) {
Constructor<?> object = loadEngineEventClass(javaName);
handlerStartupAttr(object, listenerQueue);
} else {
Constructor<?> object = null;
object = loadUexDexClass(classLoader, javaName);
if (null == object) {
object = loadUexClass(javaName);
}
if (null != object) {
scriptObj = new ThirdPluginObject(object);
scriptObj.oneObjectBegin(jsName);
scriptObj.jclass = javaName;
globalStr = plugins.getAttributeValue(null, globalAttr);
if (null != globalStr && "true".equals(globalStr)) {
scriptObj.isGlobal = true;
}
}
}
}
} else if (strNode.equals(methodNode)) {
String methodValue = plugins.getAttributeValue(null,
nameAttr);
if (null != scriptObj
&& !isDuplicatedPlugin(jsName, javaName)) {
scriptObj.addMethod(methodValue);
}
} else if (strNode.equals(propertyNode)) {
String propertyValue = plugins.getAttributeValue(null,
nameAttr);
if (null != scriptObj
&& !isDuplicatedPlugin(jsName, javaName)) {
scriptObj.addProperty(propertyValue);
}
}
} else if (eventType == XmlResourceParser.END_TAG) {
String strNode = plugins.getName();
if (strNode.equals(pluginNode)) {
if (null != scriptObj
&& !isDuplicatedPlugin(jsName, javaName)) {
scriptObj.oneObjectOver(script);
mThirdClass.put(jsName, scriptObj);
javaNames.add(javaName);
BDebug.i("DL", jsName + " plugin loaded.");
scriptObj = null;
PluginCount++;
}
}
}
}
BDebug.i("DL", PluginCount + " plugins total loaded.");
EUExScript.F_UEX_SCRIPT += script.toString();
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(EUExUtil.getString("load_uex_object_error"));
}
}
private void handlerStartupAttr(Constructor<?> object,
ELinkedList<EngineEventListener> listenerQueue) {
EngineEventListener objectIntance = null;
try {
objectIntance = (EngineEventListener) object.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
if (null != objectIntance) {
listenerQueue.add(objectIntance);
}
}
private Constructor<?> loadUexClass(String name) {
Class<?> target = null;
Constructor<?> targetStruct = null;
if (name != null) {
try {
target = Class.forName(name);
Class<?>[] paramTypes = {Context.class, EBrowserView.class};
targetStruct = target.getConstructor(paramTypes);
} catch (Exception e) {
e.printStackTrace();
}
}
return targetStruct;
}
@SuppressLint("NewApi")
private Constructor<?> loadUexDexClass(ClassLoader dexCl, String name) {
Class<?> target = null;
Constructor<?> targetStruct = null;
if (name != null) {
try {
target = dexCl.loadClass(name);
Class<?>[] paramTypes = {Context.class, EBrowserView.class};
targetStruct = target.getConstructor(paramTypes);
} catch (Exception e) {
e.printStackTrace();
}
}
return targetStruct;
}
private Constructor<?> loadEngineEventClass(String name) {
Class<?> target = null;
Constructor<?> targetStruct = null;
if (name != null) {
try {
target = Class.forName(name);
Class<?>[] noneParam = {};
targetStruct = target.getConstructor(noneParam);
} catch (Exception e) {
e.printStackTrace();
}
}
return targetStruct;
}
private boolean CopyAssets(Context context, String assetDir, String dir) {
boolean isSuccess = false;
String[] files;
try {
files = context.getResources().getAssets().list(assetDir);
} catch (IOException e1) {
isSuccess = false;
return isSuccess;
}
File mWorkingPath = new File(dir);
// if this directory does not exists, make one.
if (!mWorkingPath.exists()) {
if (!mWorkingPath.mkdirs()) {
BDebug.e("--CopyAssetsPlugins--", "cannot create directory.");
}
}
for (int i = 0; i < files.length; i++) {
try {
String fileName = files[i];
// we make sure file name not contains '.' to be a folder.
if (!fileName.contains(".")) {
if (0 == assetDir.length()) {
CopyAssets(context, fileName, dir + fileName + "/");
} else {
CopyAssets(context, assetDir + "/" + fileName, dir
+ "/" + fileName + "/");
}
continue;
}
File outFile = new File(mWorkingPath, fileName);
if (outFile.exists())
outFile.delete();
InputStream in = null;
if (0 != assetDir.length())
in = context.getAssets().open(assetDir + "/" + fileName);
else
in = context.getAssets().open(fileName);
OutputStream out = new FileOutputStream(outFile);
// Transfer bytes from in to out
byte[] buf = new byte[1024];
int len;
while ((len = in.read(buf)) > 0) {
out.write(buf, 0, len);
}
in.close();
out.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
isSuccess = true;
return isSuccess;
}
private void copyDynamicApk() {
BDebug.i("DL", "copyDynamicApk");
long time = System.currentTimeMillis();
long cost = 0;
// if (ESystemInfo.getIntence().mIsDevelop) {
// //TODO 如果是IDE调试版就把插件复制到sd卡
// }
SharedPreferences sp = mContext.getSharedPreferences(
F_SP_NAME_PLUGIN_LOADING, Context.MODE_PRIVATE);
boolean isFinished = false;
String curVersion = "";// 记录当前apk版本号
try {
PackageManager pm = mContext.getPackageManager();
PackageInfo pinfo = pm.getPackageInfo(mContext.getPackageName(),
PackageManager.GET_CONFIGURATIONS);
curVersion = pinfo.versionName;
} catch (Exception e) {
e.printStackTrace();
}
try {
isFinished = sp.getBoolean(F_SP_KEY_NAME_PLUGIN_COPY_FINISHED,
false);
if (isFinished) {
String lastCopyPkgVer = sp.getString(
F_SP_KEY_NAME_PLUGIN_COPY_LAST_PKG_VERSION, "");
// 若当前版本号与上次拷贝版本号不相同,则要重新拷贝动态库插件
isFinished = lastCopyPkgVer.equals(curVersion);
}
} catch (Exception e) {
// TODO BDebug
e.printStackTrace();
}
if (!isFinished) {
isFinished = CopyAssets(mContext, dexApk, libsParentDir
+ File.separator
+ dexApk);
// copy完成,记录状态以及当前apk版本
Editor edit = sp.edit();
edit.putBoolean(F_SP_KEY_NAME_PLUGIN_COPY_FINISHED, isFinished);
edit.putString(F_SP_KEY_NAME_PLUGIN_COPY_LAST_PKG_VERSION,
curVersion);
edit.commit();
} else {
BDebug.i("DL", "copyDynamicApk is already done");
}
cost = System.currentTimeMillis() - time;
BDebug.i("DL", "copyDynamicApk costs " + cost);
}
// 因为之前的方法无法替换子进程的classloader,故改成以下的方式。由于每一个进程初始化的时候都会初始化一次他的application,而且默认的classloader是和application的classloader一样的
// 故在application初始化的时候,替换掉application的classloader。之前只有在主进程中才替换掉application的loader,所以子进程还是无法加载动态插件
private void initClassLoader() {
try {
pluginJars = mContext.getAssets().list(dexJar);
if (pluginJars != null && pluginJars.length > 0) {
// create the dexPath
int PluginCount = pluginJars.length;
String dexPath = libsParentDir + File.separator + dexJar;
StringBuilder sb = new StringBuilder();
for (int i = 0; i < PluginCount; i++) {
sb.append(dexPath).append(File.separator)
.append(pluginJars[i]).append(File.pathSeparator);
}
dexPath = sb.toString();
// create the optPath
String optPath = libsParentDir + File.separator + optFile;
File dirFile = new File(optPath);
if (!dirFile.exists()) {
dirFile.mkdirs();
}
String libPath = libsParentDir + File.separator + dexLib;
// create the dexclassloader
DexClassLoader dexCl = new DexClassLoader(dexPath, optPath,
libPath, mContext.getClassLoader());
replaceCurrentClassLoader(dexCl);
}
/*
* Field mMainThread =
* Activity.class.getDeclaredField("mMainThread");
* mMainThread.setAccessible(true); Object mainThread =
* mMainThread.get((EBrowserActivity) context); Class threadClass =
* mainThread.getClass(); Field mPackages =
* threadClass.getDeclaredField("mPackages");
* mPackages.setAccessible(true); WeakReference<?> ref; Map<String,
* ?> map = (Map<String, ?>) mPackages.get(mainThread); ref =
* (WeakReference<?>) map.get(context.getPackageName()); Object apk
* = ref.get(); Class apkClass = apk.getClass();
*
* Field mClassLoader = apkClass.getDeclaredField("mClassLoader");
* mClassLoader.setAccessible(true); mClassLoader.set(apk, dexCl);
*/
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* use reflection tech replace the current classloader
*
* @param dexCl
* @throws Exception
*/
private void replaceCurrentClassLoader(DexClassLoader dexCl)
throws Exception {
Context mBase = new Smith<Context>(mContext, "mBase").get();
Object mPackageInfo = new Smith<Object>(mBase, "mPackageInfo").get();
Smith<ClassLoader> sClassLoader = new Smith<ClassLoader>(mPackageInfo,
"mClassLoader");
sClassLoader.set(dexCl);
}
}