插件式开发

参考

        潭州学院的公开课视频

效果

        在本应用中打开某个未安装的apk文件中的某个界面。一个apk中,最多只能有65535个方法,因此在大型项目中,将一些次要功能做成插件形式的,是很有必要的。

        做成插件有两种方式:一种是将插件通过静默安装的方式装到手机中,然后在本应用中启动插件中的某个activity,但静默安装需要root权限;另一种就是通过类加载器,加载dex文件中的某个fragment,但这种方法只能加载一个fragment,如果在该fragment中启动activity就会报activity找不到异常,这是因为插件应用中的activity并没有在本应用中进行注册。

原理

        android中显示界面有两个方法,一个是加载activity,一个是加载fragment。对于activity来说,系统在启动它时会进行一系列的初始化,而且它的生命周期也不好控制,所以为了方便选择使用fragment。

        在java中,想要加载某个类,可以使用类加载器进行加载,而android本身提供了一个DexClassLoader类加载器,可以使用它加载dex文件中的某个类。使用该类加载器进行加载时,需要将dex文件移到"/data/data/包名"目录下。

        在加载fragment时,该fragment可能会使用到一些资源文件,因此需要将资源文件等加载到本应用中。这里就需要通过反射使用AssetManager中的addAssetPath方法。

代码

//一个用来承载别的fragment的activity
public class PluginActivity extends Activity {
    public static final String KEY = "apk";
    private APK apk;//自定义bean
    private AssetManager as;
    private Resources resources;
    private Resources.Theme theme;
    private DexClassLoader classLoader;
    private String dexFilePath;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_plugin);
        initLocalData();
    }

    private void initLocalData() {
        apk = getIntent().getParcelableExtra(KEY);
        File tem = new File(getApplication().getFilesDir(),"dexout");
        tem.mkdir();
        String optimizedDirectory = tem.getAbsolutePath();
        moveAPK();
        /**
         * 第一个参数为从何处获得dex文件(apk中本身就有一个dex文件)
         * 第二个参数为将dex加载到哪个目录下
         * 第三个为c/c++库的目录
         * 第四个传入系统的classLoader即可
         */
        classLoader = new DexClassLoader(dexFilePath,optimizedDirectory,null,super.getClassLoader());
        try {
            as = AssetManager.class.newInstance();//通过反射获取AssetManager的实例
            as.getClass().getMethod("addAssetPath",String.class).invoke(as,apk.getPath());
            resources = new Resources(as,super.getResources().getDisplayMetrics(),super.getResources().getConfiguration());
            theme = resources.newTheme();
            theme.setTo(super.getTheme());
            //通过反射拿到要加载的fragment的实例
            Fragment f = (Fragment) getClassLoader().loadClass("com.example.hufeng.pluin.InFragment").newInstance();
            getFragmentManager().beginTransaction().add(R.id.root,f).commit();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void moveAPK() {
        File parent = new File(getApplication().getFilesDir(),"plugin");//将插件移动到该目录下,plugin名字可随意
        parent.mkdir();
        File dex = new File(parent,apk.getName());
        dexFilePath = dex.getAbsolutePath();
        try {
            FileOutputStream out = new FileOutputStream(dex);
            FileInputStream in = new FileInputStream(apk.getPath());
            byte[] b = new byte[1024];
            int len = -1;
            while ((len = in.read(b))!= -1){
                out.write(b,0,len);
                out.flush();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public ClassLoader getClassLoader() {
        return classLoader !=null?classLoader:super.getClassLoader();
    }

    @Override
    public AssetManager getAssets() {
        return as == null?super.getAssets():as;
    }

    @Override
    public Resources getResources() {
        return resources == null?super.getResources():resources;
    }

    @Override
    public Resources.Theme getTheme() {
        return theme == null?super.getTheme():theme;
    }
}


你可能感兴趣的:(插件式开发)