通过代理Activity模式,以移花接木的方式,加载sd卡目录下的apk界面
动态加载、插件化开发很重要
当今360手机助手(DroidPlugin),个人开源(VirtualApp)、百度DL、携程DynamicAPK都用到了该技术
本例的大概思路是:
1、apk1初始化就一个主界面MainActivity,主界面只有一个Button按钮,点击后,弹出Toast,然后我们把编译好的apk1放到手机根目录SD卡下
2、apk2有一个MainActivity界面,界面上也有一个Button,点击按钮后,去加载SD目录下的apk1,调起来apk1,点击apk1中的button,弹出Toast即可
以上就是一个简单的逻辑?其实呢这里面问题好多,这里先简单说下问题点。
1、其实点击apk2的button启动的不是apk1的界面,而是将apk1的界面托管给一个静态代理类Activity,然后以静态代理Activity去构建类似于apk1的button,继而在静态代理Activity的上下文环境下,弹出Toast
2、这个例子只是在静态代理Activity类里,进行了简单的反射调用apk1的onCreate方法,被反射的apk1的主界面类,其实本质是一个java类,它没有Activity里面的逻辑,比如你拿不到里面的layout等资源,所以这个demo也就是一个对动态加载的一个小小的理解,没有涉及到Activity4大组建的动态代理、binder机制等
首先来看下apk的代码把
MainActivity
package com.example.targetproject;
import android.annotation.SuppressLint;
import android.app.ActionBar.LayoutParams;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.Toast;public class MainActivity extends Activity {public static final String KEY_APK_PATH = "apkPath";public static final String KEY_CLASS = "class";public static final String DEX_PATH = android.os.Environment.getExternalStorageDirectory().getPath() + "/TargetProject.apk";protected Activity mProxyActivity;public void setProxy(Activity proxyActivity) {mProxyActivity = proxyActivity;}@SuppressLint("NewApi")@Overrideprotected void onCreate(Bundle savedInstanceState) {if (mProxyActivity == null) {super.onCreate(savedInstanceState);mProxyActivity = this;}Button button = new Button(mProxyActivity);button.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT));button.setText("按我");button.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {Toast.makeText(mProxyActivity, "你点击了按钮啦!", Toast.LENGTH_SHORT).show();}});if (mProxyActivity == this) {super.setContentView(button);} else {mProxyActivity.setContentView(button);}}
}
简单说下里面的逻辑关系
这个界面做了什么呢?就是创建了一个以某个Activity为环境的Button,然后点击button,会产生一个以某个Activity为环境的Toast,我们先抛开让测试的apk进行通过动态加载的方式,以代理Activity调起的情况,首先它是一个可以独立运行编译的apk文件,所以说,我们先来分析下它需要的Activity,我们先定义一个Activity类
protected Activity mProxyActivity;
在onCreate方法中
if (mProxyActivity == null) {super.onCreate(savedInstanceState);mProxyActivity = this;}
意思就是如果没有托管的Activity类,就使用原生的Activity,那么如果有托管的Activity呢?我们就进行如下的设置
public void setProxy(Activity proxyActivity) {mProxyActivity = proxyActivity;}
在apk的主界面,加入一个代理类Activity,然后在代理类Activity环境下去创建button,Toast
以下就是创建Button的代码
Button button = new Button(mProxyActivity);button.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT));button.setText("按我");button.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {Toast.makeText(mProxyActivity, "你点击了按钮啦!", Toast.LENGTH_SHORT).show();}});
创建Button\Toast完毕呢,就是需要设置到某个Activity环境下,这里还得需要判断是原生的Activity环境,还是代理Activity环境?
if (mProxyActivity == this) {super.setContentView(button);} else {mProxyActivity.setContentView(button);}
然后编译后,运行apk没问题,就放到手机的sd卡根目录下,以下是我手机nexus5的路径目录
然后我们就来看测试apk的代码逻辑把
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context="com.example.test.MainActivity" ><Button android:id="@+id/btn"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="@string/load" /></RelativeLayout>
MainActivity
package com.example.test;import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;public class MainActivity extends Activity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);Button btn = (Button) this.findViewById(R.id.btn);btn.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {String root = android.os.Environment.getExternalStorageDirectory().getPath()+ "/TargetProject.apk";Intent intent = new Intent(MainActivity.this,ProxyActivity.class);intent.putExtra(ProxyActivity.KEY_APK_PATH, root);startActivity(intent);}});}
}
先来看下主界面的代码,这里就是一个button,点击后带过去一个sd卡跟目录下那个apk1的绝对路径,然后调到ProxyActivity类
String root = android.os.Environment.getExternalStorageDirectory().getPath()+ "/TargetProject.apk";
然后就看我们的代理Activity类
package com.example.test;import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.os.Bundle;
import android.util.Log;
import dalvik.system.DexClassLoader;/*** 代理类* @author safly**/
public class ProxyActivity extends Activity{public static final String KEY_APK_PATH = "apkPath"; public static final String KEY_CLASS = "class"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);//获取指定的apk文件路径和启动类名String mDexPath = getIntent().getStringExtra(KEY_APK_PATH); String mClass = getIntent().getStringExtra(KEY_CLASS);if (mClass == null) {PackageInfo packageInfo = getPackageManager().getPackageArchiveInfo(mDexPath, 1); if ((packageInfo.activities != null) && (packageInfo.activities.length > 0)) { Log.e("ProxyActivity", ""+packageInfo.activities[0].name);mClass = packageInfo.activities[0].name; } }launchTargetActivity(mDexPath,mClass); } /*** 利用ClassLoader,DexClassLoader和反射将apk中的界面启动* @param mDexPath apk 动态加载的apk本地路径* @param className 要打开的动态加载类的类名*/protected void launchTargetActivity(String mDexPath,String className) {Log.e("ProxyActivity", "launchTargetActivity");File dexOutputDir = this.getDir("dex", 0); final String dexOutputPath = dexOutputDir.getAbsolutePath(); ClassLoader localClassLoader = ClassLoader.getSystemClassLoader(); DexClassLoader dexClassLoader = new DexClassLoader(mDexPath, dexOutputPath, null, localClassLoader); try { Class<?> localClass = dexClassLoader.loadClass(className); Constructor<?> localConstructor = localClass.getConstructor(new Class[] {}); Object instance = localConstructor.newInstance(new Object[] {});//利用反射机制获取到设置代理Activity的方法Method setProxy = localClass.getMethod("setProxy",new Class[] { Activity.class }); setProxy.setAccessible(true); setProxy.invoke(instance, new Object[] { this }); //利用反射机制调用onCreate方法Method onCreate = localClass.getDeclaredMethod("onCreate",new Class[] { Bundle.class }); onCreate.setAccessible(true); onCreate.invoke(instance, new Object[] { null }); } catch (Exception e) { e.printStackTrace(); } } }
来说下上面的代码意思
ProxyActivity类onCreate方法中获取传递过来的KEY_APK_PATH(sd卡下apk1的绝对路径),我们还需要一个类,就是apk1的主界面的全类名,因为我们需要调用里面的onCreate方法,然后去添加button、toast控件
if (mClass == null) {PackageInfo packageInfo = getPackageManager().getPackageArchiveInfo(mDexPath, 1); if ((packageInfo.activities != null) && (packageInfo.activities.length > 0)) { Log.e("ProxyActivity", ""+packageInfo.activities[0].name);mClass = packageInfo.activities[0].name; } }
log输出如下
ProxyActivity(23526): com.example.targetproject.MainActivity
然后就看下launchTargetActivity里面的代码
File dexOutputDir = this.getDir("dex", 0);
dexOutputPath--/data/data/com.example.test/app_dex
以上是dex解压释放后的目录 ,log输出的目录,以下是截图
然后获取一个DexClassLoader,这里面参数为sd卡apk1的绝对路径、app_dex路径,然后还有一个ClassLoader.getSystemClassLoader()对象
参数如下
(String dexPath, String optimizedDirectory, String libraryPath, ClassLoader parent)
如下方法就是获取apk1中MainActivity的构造然后new一个实例
Class<?> localClass = dexClassLoader.loadClass(className); Constructor<?> localConstructor = localClass.getConstructor(new Class[] {}); Object instance = localConstructor.newInstance(new Object[] {});
以下就是反射去获取setProxy,onCreate方法,进行设置代理Activity类,然后在代理Activity类中进行设置button\toast控件
//利用反射机制获取到设置代理Activity的方法Method setProxy = localClass.getMethod("setProxy",new Class[] { Activity.class }); setProxy.setAccessible(true); setProxy.invoke(instance, new Object[] { this }); //利用反射机制调用onCreate方法Method onCreate = localClass.getDeclaredMethod("onCreate",new Class[] { Bundle.class }); onCreate.setAccessible(true); onCreate.invoke(instance, new Object[] { null });
以上就是这个小例子的逻辑,也算是对自己开启动态加载学习的一个小入门理解把
通过代理Activity模式,以移花接木的方式,加载sd卡目录下的apk界面相关推荐
- android sd卡列目录文件_Android加载SD卡目录,文件夹遍历,图片设置,设置文件对应打开方式等...
public class GridViewFile extends Activity implementsView.OnClickListener {privateContext context;pr ...
- Android动态加载进阶 代理Activity模式
基本信息 作者:kaedea 项目:android-dynamical-loading 技术背景 简单模式中,使用ClassLoader加载外部的Dex或Apk文件,可以加载一些本地APP不存在的类, ...
- 使用jQuery和YQL,以Ajax方式加载外部内容
我们来看看怎样使用jQuery,以Ajax方式加载外部(其他域上)的内容.这里的所有代码都可以从GitHub下载,也可以在这个演示页面中获取,因而不用复制粘贴了. OK,Ajax通过jQuery是很容 ...
- 《ArcGIS Runtime SDK for Android开发笔记》——(13)、图层扩展方式加载Google地图...
1.前言 http://mt2.google.cn/vt/lyrs=m@225000000&hl=zh-CN&gl=cn&x=420&y=193&z=9& ...
- 使用SDWebImage淡入淡出的方式加载图片
使用SDWebImage淡入淡出的方式加载图片 效果: 请通过以下方式下载源码: 找到它修改文件的地方: 以下是使用源码: // // ViewController.m // SDWebImageFa ...
- 【js】【cornerstone】cornerstone使用url方式加载图像
[js][cornerstone]cornerstone使用url方式加载图像 引入cornerstoneWebImageLoader loadImage 引入cornerstoneWebImageL ...
- post方式加载iframe
我们平常使用iframe时,直接设定src属性只能是get请求方式 ,get请求的参数大小有限制 如何实现即使用iframe又能通过post请求 两种方式 ajax使用post请求返回页面,直接将返回 ...
- Unity两中方式加载图片
看到草羊发的图片,也是真懒. 想起来当时写天气预报现在都忘干净了,好记性不如烂笔头,还是应该多记.自己打下来好了 using System; using System.Collections; usi ...
- layui信息加载流的方式加载数据
SSM项目中使用layui信息加载流的方式加载数据 这里首先jsp页面来一个div容器,这里数数据显示的地方 //css样式: <style type="text/css"& ...
最新文章
- 渥太华大学计算机硕士课程,渥太华大学计算机工程专业解析
- 【Android】AsyncTask异步类
- 《Python数据分析与挖掘实战》读书笔记
- Apache Hadoop 2.4.1 单节点安装
- 在mac上搭建octopress+github pages博客
- java里面add报错_java.util.Arrays$ArrayList addAll报错
- jquery :nth-child()选择器的简单应用
- BZOJ2006:[NOI2010]超级钢琴——题解
- 免费培训后包就业,还月薪上万,深扒BI数据工程师培训套路
- 第三次面试题目 (反省中!!)
- 期末知识点复习——概率论与数理统计(5)
- 一个很不错的H5动画网站
- 不可思议有氧机器人_不思议迷宫奇怪的机器人 不思议迷宫奇怪的机器人获取方式一览...
- 基于Android的房屋租赁系统
- Python: 向量、矩阵和多维数组(基于NumPy库)
- python用 requests 模块从 Web 下载文件
- control设备的注册流程
- Elastic的Workplace Search如何使用Gmail或Google Drive等数据源
- efi格式linux启动u盘启动不了,Grub2 EFI U盘启动
- TwinCAT3之Ads通讯——1、控制器和控制器间通讯