摯愛篤深 發表於 2019-11-25 17:28:00

Android 插件化开发(三):资源插件化

<p>在前面的文章中我们成功的加载了外部的Dex(Apk)并执行了插件的Bean代码。这时我们会想,能不能加载并运行插件Apk的Activity。答案当然是能,否则后续我们的研究就没意义了,但是想实现Activity的插件化运行,我们必须要解决一个问题——<strong>如何使用插件中的资源</strong>。</p>
<p>本文我们就讲一下插件的资源加载机制,并讲述一下如何实现资源的插件化。</p>
<h2>一、资源的加载机制</h2>
<p>Android的资源文件分为两类:</p>
<p>第一类是res目录下存放的可编辑的资源文件,这类文件在编译时系统会自动在R文件中生成资源文件的16进制值。</p>
<p>例如:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">final</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> R {
    </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">final</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> anim {
    </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">final</span> <span style="color: rgba(0, 0, 255, 1)">int</span> abc_fade_in=0x7f050000<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">      public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">final</span> <span style="color: rgba(0, 0, 255, 1)">int</span> abc_fade_in=0x7f050000<span style="color: rgba(0, 0, 0, 1)">;
    ...
    }
}</span></pre>
</div>
<p>我们在平时的开发时,访问这类资源比较简单,使用Context的getResource方法即可得到res下的各种资源。如下面代码所示:</p>
<div class="cnblogs_code">
<pre>String content = mContext.getResource().getString(R.string.content);</pre>
</div>
<p>第二类是assets目录下存放的原始资源文件。apk在编译时不会编译assets下的文件,我们不能使用R.的方式访问,只能使用AssetsManager类的open方法来获取assets目录下的文件资源。</p>
<p>而AssetsManager又源于Resources类的getAssets方法,如下面代码所示:</p>
<div class="cnblogs_code">
<pre>Resource resource =<span style="color: rgba(0, 0, 0, 1)"> getResource();
AssetsManager am </span>=<span style="color: rgba(0, 0, 0, 1)"> getResource().getAssets();
InputStream is</span>= getResource().getAssets().open("filename");</pre>
</div>
<p>通过上面的分析,我们可以初步做出一个结论:我们能使用Resources类是一个很重要的类,通过此类提供的相关API,我们能操作资源的加载。</p>
<h2>二、资源插件化的解决方案</h2>
<p>谈及资源插件化,我们不得不对AssetsManager的API多说一些。</p>
<p>AssetsManager中有一个addAssetsPath(String Path)方法,App启动的时候就会将当前的apk路径传进去,接下来AssetsManager和Resources就能访问当前apk的所有资源了。</p>
<p>AssetsManager的addAssetsPath方法不对外,但是我们可以通过反射的方式,把插件apk的路径传到这个方法,这样就把插件的资源添加到一个资源池中了。App有几个插件,我们就调用几次addAssetsPath方法,把插件的资源都塞到池子里。</p>
<p>这里我们以加载插件Apk里面的字符串资源为目标,实战一下资源插件化:</p>
<p>首先我们在插件app的string.xml里面定义字符串资源:</p>
<div class="cnblogs_code">
<pre> <span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">string </span><span style="color: rgba(255, 0, 0, 1)">name</span><span style="color: rgba(0, 0, 255, 1)">="myplugin1_hello_world"</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span>Hello Plugin<span style="color: rgba(0, 0, 255, 1)">&lt;/</span><span style="color: rgba(128, 0, 0, 1)">string</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span></pre>
</div>
<p>然后我们在宿主app编写如下代码:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span> MainActivity <span style="color: rgba(0, 0, 255, 1)">extends</span><span style="color: rgba(0, 0, 0, 1)"> Activity {

    </span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> AssetManager mAssetManager;

    </span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> Resources mResources;

    </span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> Resources.Theme mTheme;

    </span><span style="color: rgba(0, 0, 255, 1)">private</span> String dexPath = <span style="color: rgba(0, 0, 255, 1)">null</span>;    <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">apk文件地址</span>

    <span style="color: rgba(0, 0, 255, 1)">private</span> File fileRelease = <span style="color: rgba(0, 0, 255, 1)">null</span>;<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">释放目录</span>

    <span style="color: rgba(0, 0, 255, 1)">protected</span> DexClassLoader classLoader = <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">;

    </span><span style="color: rgba(0, 0, 255, 1)">private</span> String pluginName = "plugin1.apk"<span style="color: rgba(0, 0, 0, 1)">;

    TextView mTextView;

    @Override
    </span><span style="color: rgba(0, 0, 255, 1)">protected</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> attachBaseContext(Context newBase) {
      </span><span style="color: rgba(0, 0, 255, 1)">super</span><span style="color: rgba(0, 0, 0, 1)">.attachBaseContext(newBase);
      extractAssets(newBase, pluginName);
    }

    @SuppressLint(</span>"NewApi"<span style="color: rgba(0, 0, 0, 1)">)
    @Override
    </span><span style="color: rgba(0, 0, 255, 1)">protected</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> onCreate(Bundle savedInstanceState) {
      </span><span style="color: rgba(0, 0, 255, 1)">super</span><span style="color: rgba(0, 0, 0, 1)">.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);

      File extractFile </span>= <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.getFileStreamPath(pluginName);
      dexPath </span>=<span style="color: rgba(0, 0, 0, 1)"> extractFile.getPath();

      fileRelease </span>= getDir("dex", 0<span style="color: rgba(0, 0, 0, 1)">);

      classLoader </span>= <span style="color: rgba(0, 0, 255, 1)">new</span> DexClassLoader(dexPath, fileRelease.getAbsolutePath(), <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">, getClassLoader());

      mTextView </span>=<span style="color: rgba(0, 0, 0, 1)"> findViewById(R.id.tv);

      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">带资源文件的调用</span>
      findViewById(R.id.btn_6).setOnClickListener(<span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> View.OnClickListener() {
            @Override
            </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> onClick(View arg0) {
                loadResources();
                </span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> {
                  Class mLoadClassDynamic </span>= classLoader.loadClass("com.plugin1.Dynamic"<span style="color: rgba(0, 0, 0, 1)">);
                  Object dynamicObject </span>=<span style="color: rgba(0, 0, 0, 1)"> mLoadClassDynamic.newInstance();
                  IDynamic dynamic </span>=<span style="color: rgba(0, 0, 0, 1)"> (IDynamic) dynamicObject;
                  String content </span>= dynamic.getStringForResId(MainActivity.<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">);
                  mTextView.setText(content);
                  Toast.makeText(getApplicationContext(), content </span>+ ""<span style="color: rgba(0, 0, 0, 1)">, Toast.LENGTH_LONG).show();
                } </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (Exception e) {
                  e.printStackTrace();
                }
            }
      });
    }

    </span><span style="color: rgba(0, 0, 255, 1)">protected</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> loadResources() {
      </span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> {
            AssetManager assetManager </span>= AssetManager.<span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">.newInstance();
            Method addAssetPath </span>= assetManager.getClass().getMethod("addAssetPath", String.<span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">);
            addAssetPath.invoke(assetManager, dexPath);
            mAssetManager </span>=<span style="color: rgba(0, 0, 0, 1)"> assetManager;
      } </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (Exception e) {
            e.printStackTrace();
      }
      Resources superRes </span>= <span style="color: rgba(0, 0, 255, 1)">super</span><span style="color: rgba(0, 0, 0, 1)">.getResources();
      superRes.getDisplayMetrics();
      superRes.getConfiguration();
      mResources </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Resources(mAssetManager, superRes.getDisplayMetrics(), superRes.getConfiguration());
      mTheme </span>=<span style="color: rgba(0, 0, 0, 1)"> mResources.newTheme();
      mTheme.setTo(</span><span style="color: rgba(0, 0, 255, 1)">super</span><span style="color: rgba(0, 0, 0, 1)">.getTheme());
    }

    @Override
    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> AssetManager getAssets() {
      </span><span style="color: rgba(0, 0, 255, 1)">return</span> mAssetManager == <span style="color: rgba(0, 0, 255, 1)">null</span> ? <span style="color: rgba(0, 0, 255, 1)">super</span><span style="color: rgba(0, 0, 0, 1)">.getAssets() : mAssetManager;
    }

    @Override
    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> Resources getResources() {
      </span><span style="color: rgba(0, 0, 255, 1)">return</span> mResources == <span style="color: rgba(0, 0, 255, 1)">null</span> ? <span style="color: rgba(0, 0, 255, 1)">super</span><span style="color: rgba(0, 0, 0, 1)">.getResources() : mResources;
    }

    @Override
    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> Resources.Theme getTheme() {
      </span><span style="color: rgba(0, 0, 255, 1)">return</span> mTheme == <span style="color: rgba(0, 0, 255, 1)">null</span> ? <span style="color: rgba(0, 0, 255, 1)">super</span><span style="color: rgba(0, 0, 0, 1)">.getTheme() : mTheme;
    }


    </span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
   * 把Assets里面得文件复制到 /data/data/files 目录下
   *
   * </span><span style="color: rgba(128, 128, 128, 1)">@param</span><span style="color: rgba(0, 128, 0, 1)"> context
   * </span><span style="color: rgba(128, 128, 128, 1)">@param</span><span style="color: rgba(0, 128, 0, 1)"> sourceName
   </span><span style="color: rgba(0, 128, 0, 1)">*/</span>
    <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> extractAssets(Context context, String sourceName) {
      AssetManager am </span>=<span style="color: rgba(0, 0, 0, 1)"> context.getAssets();
      InputStream is </span>= <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">;
      FileOutputStream fos </span>= <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">;
      </span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> {
            is </span>=<span style="color: rgba(0, 0, 0, 1)"> am.open(sourceName);
            File extractFile </span>=<span style="color: rgba(0, 0, 0, 1)"> context.getFileStreamPath(sourceName);
            fos </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> FileOutputStream(extractFile);
            </span><span style="color: rgba(0, 0, 255, 1)">byte</span>[] buffer = <span style="color: rgba(0, 0, 255, 1)">new</span> <span style="color: rgba(0, 0, 255, 1)">byte</span>;
            </span><span style="color: rgba(0, 0, 255, 1)">int</span> count = 0<span style="color: rgba(0, 0, 0, 1)">;
            </span><span style="color: rgba(0, 0, 255, 1)">while</span> ((count = is.read(buffer)) &gt; 0<span style="color: rgba(0, 0, 0, 1)">) {
                fos.write(buffer, </span>0<span style="color: rgba(0, 0, 0, 1)">, count);
            }
            fos.flush();
      } </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (IOException e) {
            e.printStackTrace();
      } </span><span style="color: rgba(0, 0, 255, 1)">finally</span><span style="color: rgba(0, 0, 0, 1)"> {
            closeSilently(is);
            closeSilently(fos);
      }
    }
</span>
    <span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> closeSilently(Closeable closeable) {
      </span><span style="color: rgba(0, 0, 255, 1)">if</span> (closeable == <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) {
            </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)">;
      }
      </span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> {
            closeable.close();
      } </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (Throwable e) {
            e.printStackTrace();
      }
    }
}</span></pre>
</div>
<p>以上代码分为四个逻辑部分:</p>
<p>1).&nbsp; loadResources方法。通过反射创建AssetManager对象,调用addAssetPath方法,把插件Plugin1路径添加到这个AssetManager对象中。从此这个AssetManager就只为插件Plugin1服务了。在这个AssetManager对象的基础上,创建对应的Resource和Theme对象。</p>
<p>2). 重写Activity的getAsset,getResource和getTheme方法。重写逻辑见上面的代码。</p>
<p>3). 加载外部的插件,生成这个插件对应的ClassLoader。</p>
<p>4). 通过反射,获取插件中的类,构造出插件类的对象,然后就可以让插件类读取插件中的资源了。</p>
<p>&nbsp;</p>
<p>上述内容代码仓库地址为:https://github.com/renhui/RHPluginProgramming/tree/master/DexResAccess-master</p>
<p>基于这个思路,我们可以尝试使用插件资源替换当前显示的内容,实现换肤效果,核心思想是一样的,这里就不过多赘述了。</p>
<p>&nbsp;</p><br><br>
来源:https://www.cnblogs.com/renhui/p/11907493.html
頁: [1]
查看完整版本: Android 插件化开发(三):资源插件化