zoukankan      html  css  js  c++  java
  • 【Android开发学习笔记】【高级】【随笔】插件化——资源加载

    前言


      上一节我们针对插件最基本的原理进行了一个简单的demo实现,但是由于插件的Context对象被宿主所接管,因此无法加载插件程序的资源。那么如何解决这个问题捏?

      有人提出这样的方案:将apk中的资源解压到某个目录下,然后通过读文件的方法进行资源加载,虽然理论上可以实现,但是实际操作起来难度很大,首先资源文件格式不一(xml、图片等),其次针对不同分辨率的手机,Android设备也会加载同名不同路径下的图片,因此这种方案实现起来难度非常大。也有人说,可以将插件资源复制一份到宿主工程中,然而如果这样做了,我们在插件更新的时候,必须宿主也得更新资源后更新,同时这样也会增加宿主工程安装包的大小,这与我们当时使用插件化来解决问题的思想不符,因此这种方案也不可使用。那么我们应该使用什么样的原理呢?

    原理


       通过看Android的源代码,我们发现Context下有这两个抽象的方法:

      

      而Context对象获取资源就是靠这两个函数来完成的,而这两个抽象的方法在ContextImpl.java中实现是这个样子的:

        

      于是我们需要一个Resources的对象,查阅代码之后,最终发现了AssetManager.java下的这个函数:

      

      看英文注释,我们发现这个函数,我们可以传递给一个zip包或者是资源的路径,同时这个函数还是隐藏的函数,因此我们还是使用反射来,调用这个函数后就可以得到Resources的对象了。

    实现


      所以我们需要在宿主中实现一个 loadResources() 这样的函数,用来加载插件的资源:

     1 package com.bryan.host;
     2 
     3 import java.io.File;
     4 import java.lang.reflect.Constructor;
     5 import java.lang.reflect.InvocationTargetException;
     6 import java.lang.reflect.Method;
     7 
     8 import dalvik.system.DexClassLoader;
     9 
    10 import android.annotation.SuppressLint;
    11 import android.app.Activity;
    12 import android.content.pm.PackageInfo;
    13 import android.content.pm.PackageManager;
    14 import android.content.res.AssetManager;
    15 import android.content.res.Resources;
    16 import android.content.res.Resources.Theme;
    17 import android.os.Bundle;
    18 import android.provider.MediaStore.Video;
    19 
    20 public class ProxyActivity extends Activity
    21 {
    22     ......
    23     
    24     /* 用来加载资源的*/
    25     protected AssetManager mAssetManager;
    26     protected Resources mResources = null;
    27     protected Theme mTheme = null;
    28     
    29     ....
    30     
    31     /* 加载插件的指定activity*/
    32     @SuppressLint("NewApi") protected void OpenAppointActivity(final String className)
    33     {
    34         ....
    35         
    36         try 
    37         {
    38             loadResources();            
    39             ...
    40             
    41             /* 反射 调用插件中的设置代理 */
    42             ...
    43             
    44             /* 反射告诉插件是被宿主调起的*/
    45             ....
    46             
    47         } catch (Exception e) 
    48         {
    49             e.printStackTrace();  
    50         }
    51     }
    52     
    53     
    54     protected void loadResources()
    55     {
    56         try 
    57         {
    58             AssetManager assetManager = AssetManager.class.newInstance();
    59             Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
    60             addAssetPath.invoke(assetManager, mExtraDexPath);
    61             mAssetManager = assetManager;    
    62         } catch (Exception e) {
    63             e.printStackTrace();
    64         }
    65         Resources superResources = super.getResources();
    66         mResources = new Resources(mAssetManager, superResources.getDisplayMetrics(), superResources.getConfiguration());
    67         mTheme = mResources.newTheme();
    68         mTheme.setTo(super.getTheme());
    69     }    
    70     
    71     /* 重写这两个加载资源的函数 */
    72     @Override  
    73     public AssetManager getAssets() 
    74     {
    75         if (mAssetManager != null) 
    76         {
    77             return mAssetManager;
    78         }
    79         return super.getAssets();
    80     }  
    81       
    82     @Override  
    83     public Resources getResources() {  
    84         if (mResources != null) 
    85         {
    86             return mResources;            
    87         }
    88         return super.getResources();
    89     }
    90 
    91 }

      注意这个函数的位置,一定要放到反射插件的OnCreate之前,其次重写 getAssets() 和 getResources() 即可。

      

      插件工程我们需要在BaseActivity中处理 getAssets() 和 getResources() 这两个函数,方法与之前类似,考虑插件自己启动还是被宿主加载的两种情况:

     1 package com.bryan.plugin;
     2 
     3 
     4 import android.app.Activity;
     5 import android.content.Intent;
     6 import android.content.res.AssetManager;
     7 import android.content.res.Resources;
     8 import android.os.Bundle;
     9 import android.view.View;
    10 import android.view.ViewGroup.LayoutParams;
    11 
    12 public class BaseActivity extends Activity
    13 {
    14     /* 宿主工程中的代理Activity*/
    15     protected Activity mProxyActivity;
    16     
    17     /* 判断是被谁调起的,如果是宿主调起的为1 */
    18     int Who = 0;
    19     
    20     public void setProxy(Activity proxyActivity)
    21     {
    22         mProxyActivity = proxyActivity;
    23     }
    24     
    25     @Override
    26     protected void onCreate(Bundle savedInstanceState) 
    27     {
    28         
    29 
    30         if (savedInstanceState != null) 
    31         {
    32             Who = savedInstanceState.getInt("Host", 0);
    33         }
    34         if (Who == 0) 
    35         {
    36             super.onCreate(savedInstanceState);
    37             mProxyActivity = this;
    38         }
    39     }
    40         
    41     ......
    42     
    43     /* 重写几个重要的添加布局的类 */
    44     ......
    45 
    46 
    47     /* 重写加载资源的方法  */
    48     @Override
    49     public Resources getResources() {
    50         if (Who == 0) 
    51         {
    52             return super.getResources();    
    53         }
    54         else 
    55         {
    56             return mProxyActivity.getResources();
    57         }
    58     }
    59     
    60     
    61     @Override
    62     public AssetManager getAssets() {
    63         if (Who == 0) 
    64         {
    65             return super.getAssets();
    66         }
    67         else 
    68         {
    69             return mProxyActivity.getAssets();
    70         }    
    71     }
    72     
    73     
    74 }

      Base中处理完成之后,我们在插件主Activity中尝试一下加载资源的代码:

     1 package com.bryan.plugin;
     2 
     3 import android.content.Context;
     4 import android.graphics.Color;
     5 import android.graphics.drawable.BitmapDrawable;
     6 import android.os.Bundle;
     7 import android.view.View;
     8 import android.view.View.OnClickListener;
     9 import android.view.ViewGroup.LayoutParams;
    10 import android.widget.Button;
    11 import android.widget.ImageView;
    12 import android.widget.LinearLayout;
    13 
    14 public class MainActivity extends BaseActivity {
    15 
    16     @Override
    17     protected void onCreate(Bundle savedInstanceState) {
    18         super.onCreate(savedInstanceState);
    19         
    20         // 初始化处理布局
    21         InitView();
    22     }
    23     
    24     private void InitView()
    25     {
    26         View view = CreateView(mProxyActivity); 
    27         mProxyActivity.setContentView(view);
    28         
    29         System.out.println("MainActivity.InitView()" + "activity name is :"+mProxyActivity.getClass().getSimpleName());
    30     }
    31     
    32     private View CreateView(final Context context)
    33     {
    34         LinearLayout linearLayout = new LinearLayout(context);
    35         
    36         linearLayout.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
    37         linearLayout.setBackgroundColor(Color.parseColor("#F4F4D6"));        
    38         Button button = new Button(context);  
    39         button.setText("plugin button");  
    40         linearLayout.addView(button, LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT); 
    41         ImageView image = new ImageView(context);
    42         image.setImageBitmap(new BitmapDrawable( getResources().openRawResource(R.drawable.qq)).getBitmap());
    43        linearLayout.addView(image, LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT); 
    44         button.setOnClickListener(new OnClickListener() {            
    45             @Override
    46             public void onClick(View v) {
    47                 StartActivityByProxy("com.bryan.plugin.TestActivity");
    48             }
    49         });
    50         return linearLayout;    
    51     }
    52 }

      我们在插件的资源中插入一张qq的图片,资源名字命名为qq,然后在加载资源的地方加入41-43行的代码,这通过这样的方式来进行加载资源。

     

    结果


      首先运行插件工程:

      

      其次运行宿主工程:

       

      可以看到在宿主工程中成功加载了插件工程的资源。

      

      看样子我们已经解决了一个很棘手的问题,还剩一个Activity生命周期函数失效的问题,下一节我们来讲这个。

  • 相关阅读:
    leetcode-滑动窗口
    leetcode刷题-双指针
    nlp
    机器学习
    tf-idf算法
    RNN和LSTM的理解
    DDD落地实践-战术实现心得
    DDD落地实践-战略设计心得
    测试平台系列(66) 数据驱动之基础Model
    Python小知识之对象的比较
  • 原文地址:https://www.cnblogs.com/by-dream/p/5031143.html
Copyright © 2011-2022 走看看