APK动态加载框架解析(一)

来源:互联网 发布:tf卡测试软件 编辑:程序博客网 时间:2024/05/19 23:26

既然项目中刚好用到了动态加载框架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/
0 0
原创粉丝点击