Android插件开发 —— 通过预注册方式打开activity(记录我踩过的坑)

来源:互联网 发布:哈尔滨蓝网网络直播 编辑:程序博客网 时间:2024/06/04 01:27

Android插件开发 —— 通过预注册方式打开activity(记录我踩过的坑)

插件开发的原理简单的说就是将插件apk合并到宿主的ClassLoader中。我先简单说下如何使用插件中的资源,因为预注册时有些坑就跟这个有关系。

要使用apk中的资源,我们首先想到有个Resources就好了,先看下Resources的构造方法:

public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config) {    this(assets, metrics, config, null);}

所以接下来我们就要获取一个AssetManager的对象,通过反射,如下(其中dexPath为插件apk的路径):

private AssetManager createAssetManager(            String dexPath )    {        try        {            AssetManager assetManager = AssetManager.class.newInstance();            Method addAssetPath = assetManager.getClass().getMethod( "addAssetPath" , String.class );            addAssetPath.invoke( assetManager , dexPath );            return assetManager;        }        catch( Exception e )        {            e.printStackTrace();            return null;        }    }

现在AssetManager也有了,我们就可以生成Resources(下面的mApplicationContext是宿主的ApplicationContext)。

private Resources createResources(            AssetManager assetManager )    {        Resources superRes = mApplicationContext.getResources();        Resources resources = new Resources( assetManager , superRes.getDisplayMetrics() , superRes.getConfiguration() );        return resources;    }

然后我们可以自定义一个context,来管理我们新生成的这个AssetManager和Resources。

public class DLPluginContext extends ContextWrapper{    protected AssetManager mAssetManager;    protected Resources mResources;    protected Theme mTheme;    protected ClassLoader classLoader;    //pluginPackageName是插件apk的包名    public DLPluginContext(            Context containerContext,String pluginPackageName )    {        super( containerContext );        String pluginPath = DLUtils.getPluginPath( containerContext , pluginPackageName );        mAssetManager = createAssetManager(pluginPath );        mResources = createResources(mAssetManager );          int mThemeResource = DLUtils.selectDefaultTheme(0,            containerContext.getApplicationInfo().targetSdkVersion);        if (mTheme == null) {            mTheme = mResources.newTheme();        }        mTheme.applyStyle(mThemeResource, true);        classLoader = pluginPackage.classLoader;    }    @Override    public Resources getResources()    {        if(this.mResources != null){            return this.mResources;        }        return super.getResources();    }    @Override    public AssetManager getAssets()    {        // TODO Auto-generated method stub        if(this.mAssetManager != null)            return this.mAssetManager;        return super.getAssets();    }    @Override    public Theme getTheme()    {        // TODO Auto-generated method stub        if(this.mTheme != null)            return this.mTheme;        return super.getTheme();    }    @Override    public ClassLoader getClassLoader()    {        if(this.classLoader != null)            return classLoader;        return super.getClassLoader();    }}

接下来:

android插件化启动activity的方式有两种方式

1、代理方式:参见“木质的旋律”大大的博客http://blog.csdn.net/h28496/article/details/50414873
2、预注册的方式:参见“木质的旋律”大大的博客http://blog.csdn.net/H28496/article/details/49966503

首先我们的宿主apk中有个管理类DLHost,其中有一个DLPlugin的对象,DLPlugin的是一个抽象类,具体实现是在插件中。DLHost的initPlugin()方法实现dexClassLoader的合并,并通过类名的反射获取到一个DLPlugin的对象。这样就实现了宿主和插件的通信。

public abstract class DLHost{    public static final String SUFFIX = ".apk";    protected Context containerContext;    protected DLPlugin plugin;    public DLHost(            Context containerContext )    {        this.containerContext = containerContext;    }    public Context getContext()    {        return containerContext;    }    public void start(            String packageName ,            String pluginClassName )    {        try        {            initPlugin( packageName , pluginClassName );        }        catch( Throwable e )        {            //Log.i( "" , " e.printStackTrace(); " );            e.printStackTrace();        }    }    private void initPlugin(            String packageName ,            String pluginClassName )    {        //      Log.d( "update" , "pluginPath 0" );        if( plugin != null )            return;        checkPluginFile( packageName );        String pluginPath = DLUtils.getPluginPath( containerContext , packageName );        PackageInfo packageInfo = DLUtils.getPackageInfo( containerContext , pluginPath );        if( packageInfo != null )        {            DLPluginManager mDLPluginManager = DLPluginManager.getInstance( containerContext );            mDLPluginManager.loadApk( pluginPath );            DLPluginPackage pluginPackage = mDLPluginManager.getPackage( packageName );            if( pluginPackage == null )            {                return;            }            Class<?> clazz = DLUtils.loadPluginClass( pluginPackage.classLoader , pluginClassName );            if( clazz == null )            {                return;            }            DLPlugin instance;            try            {                Constructor<?> m = null;                m = clazz.getConstructor( new Class[]{ DLHost.class } );                instance = (DLPlugin)m.newInstance( new Object[]{ this } );                plugin = instance;            }            catch( Exception e )            {                // TODO Auto-generated catch block                e.printStackTrace();            }        }    }}

TestHost继承DLHost,宿主MainActivity的button点击时,先完成dexclassloader的合并,然后启动activity。

public class MainActivity extends Activity{    @Override    protected void onCreate(            Bundle savedInstanceState )    {        super.onCreate( savedInstanceState );        setContentView( R.layout.activity_main );        findViewById( R.id.button ).setOnClickListener( new View.OnClickListener() {            @Override            public void onClick(                    View v )            {                // TODO Auto-generated method stub                TestHost.getInstance( getApplicationContext() );                Intent it = new Intent();                it.setClassName( MainActivity.this , "com.zjp.example.testplugin.PluginActivity" );                startActivity( it );            }        } );    }}

“com.zjp.example.testplugin.PluginActivity”是插件中的activity,插件apk中的这个activity的内容如下:

public class PluginActivity extends Activity{    @Override    protected void onCreate(            Bundle savedInstanceState )    {        super.onCreate( savedInstanceState );        TextView tv = new TextView( this );        tv.setText( "这是插件中的activity" );        setContentView( tv );    }}

这里写图片描述这里写图片描述

看到这里,大家可以看到,我在插件的activity中并没有使用到插件apk中的资源,如果不使用插件apk的这个资源,所有的view都靠new出来,到这里就可以结束啦。但正常的大家一般不会自己为难自己的,界面还是通过布局文件实现的。

下面开始在插件中使用资源文件。

我想通过插件中DLPluginContext生成LayoutInflater.from( pluginContext ).inflate( R.layout.xxx , null );一个view,然后通过setContentView(view)就实现了引用插件中的资源。
说到这里,就来看看我们的DLPlugin(上面DLHost中有一个DLPlugin的对象)。

public abstract class DLPlugin{    protected DLHost host;    protected Context pluginContext;    public DLPlugin(            DLHost host )    {        this.host = host;        pluginContext = new DLPluginContext( host.getContext() , getPackageName() );    }}

DLPlugin中生成了DLPluginContext的对象,而DLPlugin我们上面已经通过DLHost反射生成了实例,这样我们就可以直接用了。
但是却有空指针,plugin对象为空。
这里写图片描述
如下图1的log堆栈中,在host也就是宿主中通过反射调用了plugin(插件)种TestPlugin(继承DLPlugin)的初始化方法,并且赋值给一个静态变量instance,如下图2,但是在plugin中我直接调用这个TestPlugin.getInstance()静态方法却返回的是一个null,见如图1的最后一行log。
图1
图1
图2
图2

这个问题到现在我也没有搞明白是为什么,静态变量在同一个进程中(我打印过进程id,id值相等)不是可以大家一起用的么?如果有谁明白,请回复一下我。

public class PluginActivity extends Activity{    @Override    protected void onCreate(            Bundle savedInstanceState )    {        super.onCreate( savedInstanceState );        Context context = new DLPluginContext( this , "com.zjp.example.testplugin" );        View view = (ViewGroup)LayoutInflater.from( context ).cloneInContext( context ).inflate( R.layout.activity_main , null );        setContentView( view );        TextView tv = (TextView)findViewById( R.id.helloworld );        tv.setText( "这是插件布局的中的textview" );    }}

这里写图片描述
记住:这里通过LayoutInflater来生成布局一定要执行cloneInContext( context ),不然不能生成view。

你以为这篇流水账到这里就结束了么,还没有,请看官们接着往下看。

我在我的布局文件中加入了一个webview,也可以实现。
这里写图片描述
这里我遇到webview播放网络视频的问题,点击全屏按钮,能全屏播放。在4.2的机器上出现了视频全屏后不显示播放、暂停的按钮。但是另写了一个demo可以显示出来,然后就开始不停查找webview设置的属性是不是哪里不对,然后并没有结果。这时候就只能查看源码了。
在这里,我给大家安利一个很好的看源码的网站,各个不同的版本源码都有:http://androidxref.com/。
在4.2的源码上,通过surfaceview和mediacontrol生成两个view,放到framelayout中,通过webview的回调返回给webview,webview收到这个回调,将这个view加入到布局中实现布局。
这里写图片描述
现在的问题是MediaControl这个view不见了,接着查看MediaControl这个类生成view的地方,因为我们的webview是通过插件的布局文件生成的,WebView的Context是我们自定义的DLPluginContext,所以这里要生成view必须通过inflate.cloneInContext(),导致这个view没有生成成功。
这里写图片描述
所以我们可以通过new WebView(Context context)生成WebView,这个context传入的是我们当前的activity,然后加入到布局中就可以啦。

做了插件化代理方式、预注册方式,遇到好一些问题都是关于Context,宿主的Context,插件的Context,一定要用正确。

最后,关于预注册的代码下载见:http://download.csdn.net/download/shuishuixiaoping/9929970
当时没有注意,下载需要5积分,我也没有找到地方哪里可以改,有需要的请留言。

这篇流水账就到这里结束啦,欢迎大家吐槽。

阅读全文
0 0
原创粉丝点击