既然项目中刚好用到了动态加载框架DL,我就索性研究了一下DL的实现,现在已有小成,否则也不敢出来写博客,那么今天我们就看一下DL最简单的实现,直接上代码,代码中有足够清楚的解释,这段代码只能做到启动Plugin,暂时还不能获取plugin中的各种资源
先看一下宿主程序的Manifest配置
权限:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
Activity节点:
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity android:name=".ProxyActivity">
</activity>
宿主程序的启动页:
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private String mAbsolutePath;
private TextView mTv_go;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
initSD();
}
//不再赘述
private void initView() {
mTv_go = (TextView) findViewById(R.id.tv_go);
mTv_go.setOnClickListener(this);
}
//Android 6.0 以后提高了安全性,对SD的读写加上这段代码就行了
private void initSD() {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
if (!ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 0);
}
}
}
@Override
public void onClick(View v) {
PackageInfo packageInfo;
//获取外部存储路径,注意:需要提前在手机SD卡中新建DL文件夹,将打包后的插件APK放到这个文件夹中
String pluginFolder = Environment.getExternalStorageDirectory() + "/DL";
File pluginFile = new File(pluginFolder);
//如果 pluginFile这个文件不存在,就新建
if (!pluginFile.exists()) {
pluginFile.mkdir();
}
//获取pluginFile文件夹下的所有文件,建议只放一个APK文件,否则还要自己加代码进去
File[] files = pluginFile.listFiles();
//如果pluginFile中没有插件,显示未发现
if (files.length == 0) {
mTv_go.setText("没有发现插件");
return;
}
//如果有,遍历 也就只有一个
for (File file : files) {
//APK所在的绝对路径
mAbsolutePath = file.getAbsolutePath();
//获取 PackageManager
PackageManager packageManager = this.getPackageManager();
try {
//获取包信息 PackageInfo
packageInfo = packageManager.getPackageArchiveInfo(mAbsolutePath, PackageManager.GET_ACTIVITIES | PackageManager.GET_SERVICES);
if (packageInfo.activities != null && packageInfo.activities.length > 0) {
//获取启动Activity的name
String name = packageInfo.activities[0].name;
mTv_go.setText("name:" + name + ";path:" + mAbsolutePath);
}
} catch (Exception e) {
e.printStackTrace();
}
}
//
Intent intent = new Intent(this, ProxyActivity.class);
intent.putExtra(ProxyActivity.EXTRA_DEX_PATH, mAbsolutePath);
startActivity(intent);
}
}
宿主程序的代理Activity
/**
* 这里虽然写的是proxy,但是现在还不算是proxy,根据原著的想法是通过代理模式来托管插件的生命周期
* 以及获取插件的相关资源,后期再说,现在先不管。
*/
public class ProxyActivity extends AppCompatActivity {
public static final String EXTRA_DEX_PATH = "extra.dex.path";
public static final String FROM = "extra.from";
public static final int FROM_EXTERNAL = 0;
private String mDexPath;
private String mClass;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//接收的值,这里只传递了路径
mDexPath = getIntent().getStringExtra(EXTRA_DEX_PATH);
mClass = getIntent().getStringExtra(EXTRA_CLASS);
if (mClass == null) {
launchTargetActivity();
} else {
launchTargetActivity(mClass);
}
}
protected void launchTargetActivity() {
PackageInfo packageInfo = getPackageManager().getPackageArchiveInfo(
mDexPath, PackageManager.GET_ACTIVITIES);
if ((packageInfo.activities != null)
&& (packageInfo.activities.length > 0)) {
mClass = packageInfo.activities[0].name;
launchTargetActivity(mClass);
}
}
//这里是主要的逻辑,基本实现就是Java的反射、类装载以及Android的DexClassLoader
protected void launchTargetActivity(final String className) {
Log.i("aaa","start launchTargetActivity, className=" + className);
//创建一个dex差分包的路径
File dexOutputDir = this.getDir("dex", Context.MODE_PRIVATE);
//获取dex的绝对路径
final String dexOutputPath = dexOutputDir.getAbsolutePath();
//获取系统的类装载器
ClassLoader localClassLoader = ClassLoader.getSystemClassLoader();
//APK进行处理,生成dex差分文件
DexClassLoader dexClassLoader = new DexClassLoader(mDexPath,
dexOutputPath, null, localClassLoader);
try {
//装载插件的启动页Activity,并返回启动页类对象,className为插件APK启动页Activity所在的绝对路径,
Class<?> localClass = dexClassLoader.loadClass(className);
//利用反射获取构造方法
Constructor<?> localConstructor = localClass
.getConstructor();
//实例化启动页对象
Object instance = localConstructor.newInstance();
//利用反射获取setProxy方法
Method setProxy = localClass.getMethod("setProxy",
Activity.class);
setProxy.setAccessible(true);
//调用setProxy方法
setProxy.invoke(instance, this);
//利用发射获取protected方法
Method onCreate = localClass.getDeclaredMethod("onCreate",
Bundle.class);
onCreate.setAccessible(true);
Bundle bundle = new Bundle();
bundle.putInt(FROM, FROM_EXTERNAL);
//调用onCreate方法,并传递值
onCreate.invoke(instance, bundle);
} catch (Exception e) {
e.printStackTrace();
}
}
}
插件程序的代码:
public class BaseActivity extends AppCompatActivity {
public static final String PROXY_VIEW_ACTION = "com.cy.tplugin.VIEW";
public static final String DEX_PATH = "/storage/emulated/0/DL/app-debug.apk";
public static final String EXTRA_DEX_PATH = "extra.dex.path";
public static final String FROM = "extra.from";
public static final int FROM_EXTERNAL = 0;
public static final int FROM_INTERNAL = 1;
protected int mFrom = FROM_INTERNAL;
protected Activity mProxyActivity;
public void setProxy(Activity proxyActivity) {
mProxyActivity = proxyActivity;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
//这里做了一个判断,如果是通过插件启动,则用mProxyActivity.setContentView(generateContentView(mProxyActivity));
//这个布局,如果不是则直接调用原始的R.layout.
if (savedInstanceState != null) {
mFrom = savedInstanceState.getInt(FROM, FROM_INTERNAL);
}
if (mFrom == FROM_INTERNAL) {
super.onCreate(savedInstanceState);
mProxyActivity = this;
}
Log.i("aaa","onCreate: from= " + mFrom);
if (mFrom==FROM_EXTERNAL){
mProxyActivity.setContentView(generateContentView(mProxyActivity));
}else {
setContentView(R.layout.main);
}
}
//由于我们只是仅仅实验启动插件程序,并没有去想办法获取其资源文件,所以我们不能用XML,只能通过代码来设置布局
private View generateContentView(final Context context) {
LinearLayout layout = new LinearLayout(context);
layout.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
layout.setBackgroundColor(Color.parseColor("#ef563a"));
Button button = new Button(context);
button.setText("button");
layout.addView(button, ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(context, "you clicked button",
Toast.LENGTH_SHORT).show();
}
});
return layout;
}
protected void startActivityByProxy(String className) {
if (mProxyActivity == this) {
Intent intent = new Intent();
intent.setClassName(this, className);
this.startActivity(intent);
} else {
Intent intent = new Intent(PROXY_VIEW_ACTION);
intent.putExtra(EXTRA_DEX_PATH, DEX_PATH);
intent.putExtra(EXTRA_CLASS, className);
mProxyActivity.startActivity(intent);
}
}
}
最后一定要尊重原著
http://blog.csdn.net/singwhatiwanna/article/details/39937639/