APK动态加载框架DL库分析

APK动态加载框架

优点:
---简化宿主程序的大小,动态加载模块功能,减小原始apk包大小,解决R文件中int定义超过65535问题。

限制性:
---插件和宿主app之间必须受某种规范的约束,才能将其装载,进而在一个进程内相互传值。
---目前只支持动态注册广播。首次调用apk时不支持加载Fragment,必须是context形式的启动类。
   解释 一下:是通过反射找到apk启动的Context来唤起apk的。
--插件中.so文件还不支持调用!

调用插件的实现流程:


第一步:
1、在插件apk中引入lib文件,在插件apk中add宿主工程





插件apk的启动context(service或者Activity)必须继承至
DLBasePluginService、DLBasePluginActivity或者DLBasePluginFragmentActivity

第二步:
插件apk,以下简称plug要重写onCreate()方法
  @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

第三步:
使用that作为代理Context的实例(为什么这样做?先不管,后面做解释)
比如toast时这样定义:
Toast.makeText(that, "HostExtra----->" +HostExtra, Toast.LENGTH_SHORT ).show();


第四步:
*调用传值

1、宿主向plug传值:

          DLPluginManager pluginManager = DLPluginManager.getInstance(this);
          DLIntent dlIntent= new  DLIntent(
                   packageInfo.packageName , item.launcherActivityName);//目标插件启动类(以启动activity为例)
          dlIntent.putExtra( "HostExtra", "TestIntent--->HostExtra" );
          pluginManager.startPluginActivity( this,dlIntent);


2、plug接受传值:
       String HostExtra;
        if(this.getIntent()!= null){
          HostExtra =this.getIntent().getStringExtra("HostExtra");
          Log. w("HostExtra----->", HostExtra);
          Toast. makeText(that, "HostExtra----->" +HostExtra , Toast.LENGTH_SHORT).show();
        }

3、plug向宿主传值与上面方法相同。

4、plug调用宿主中的方法:
        Button button=;
        button.setOnClickListener( new OnClickListener() {
            @Override
            public void onClick(View v) {
                TestHostClass testHostClass = new TestHostClass();//宿主中类(前提是第一步中add宿主工程)
                testHostClass.testMethod( that);
            }
        });
     --宿主中该类是这样定义的:
                     
            
       public class TestHostClass {

          public void testMethod(Context context) {//context是上面的that
           Toast. makeText(context, "Successed invoke host method", Toast.LENGTH_SHORT).show();
           }
       }
5、plug和plug之间(两个plug apk)相互启动唤醒
    Context context = getActivity();
            DLIntent dlIntent = new DLIntent(mPluginPackageName , MainActivity.class);
            DLPluginManager.getInstance(context).startPluginActivity(context, dlIntent);
6、plug中加载Fragment:
            FragmentManager manager = getSupportFragmentManager();
            FragmentTransaction transaction = manager.beginTransaction();
            //Fragment中要使用Toast或启动Activity等组件时要传值包名
            transaction.add(R.id. fragment_container,
                    new TestFragment().setPluginPackageName(getPackageName()));
            transaction.addToBackStack( "TestFragment#1");
            transaction.commit();

//目标Fragment中接受到包名 提供给DLIntent 使用
 @Override
    public void onClick(View v) {
        if (v == button1 ) {
            Context context = getActivity();
            DLIntent dlIntent = new DLIntent(mPluginPackageName , MainActivity.class);
            DLPluginManager.getInstance(context).startPluginActivity(context, dlIntent);
        }
    }
7、plug apk内部类(一个plug apk)之间的调用:
     Button button=;
     button.setOnClickListener( new OnClickListener() {
            @Override
            public void onClick(View v) {
                DLIntent intent = new DLIntent(getPackageName(), TestFragmentActivity. class);//plug中的activity
                // 传递Parcelable类型的数据,为什么? 参见:http://blog.csdn.net/djun100/article/details/9667283
                intent.putExtra( "person", new Person("plugin-a" , 22));
                intent.putExtra( "dl_extra", "from DL framework");
                startPluginActivityForResult(intent, 0);
            }
        });

      
        button2.setOnClickListener( new OnClickListener() {
            public void onClick(View v) {
                DLIntent intent = new DLIntent(getPackageName(), TestService.class );//plug中的service
                startPluginService(intent);
            }
        });
      对getPackageName()代码做解释:
    @Override
    public String getPackageName() {
        if (mFrom == DLConstants.FROM_INTERNAL) {
            return super .getPackageName();
        } else {
            return mPluginPackage .packageName ;
        }
    }
                           

8、plug关闭
         Button button=;
        button.setOnClickListener( new OnClickListener() {

            @Override
            public void onClick(View v) {
                Toast. makeText(that, "quit", Toast.LENGTH_SHORT).show();
                that.setResult( RESULT_FIRST_USER);
                that.finish();
            }
        });

     上面这种方式会关闭所有plug整体!!!!。that是代理,代理关闭,plug销毁。

        通过对getPackageName()代码的解释和plug关闭的事实可能已经感觉到了第四步中为什么要用that了。。。我的理解:apk解压装载到宿主之后、宿主以创建一个代理的形式做数据中转,满足了整体上看来仍然是一个进程的要求。进而欺上瞒下进行传值。



问题是apk怎样装载到宿主中的?


第一步:在宿主中指定plug下载的路径

          private String pluginFolder ;
          if (!Environment.MEDIA_MOUNTED.equals(Environment
                   . getExternalStorageState())) {
               //手机有tf外插卡
               pluginFolder = "/mnt/sdcard2/DynamicLoadHost" ;
          } else {
               // 在手机有内置存储卡的情况下使用如下路径:
               pluginFolder = Environment.getExternalStorageDirectory()
                        + "/DynamicLoadHost";
          }
第二步:

遍历该目录下所有文件,并进行解压获取该apk的重要信息:
           

遍历文件的方法如下;
      private ArrayList<PluginItem> mPluginItems = new ArrayList<PluginItem>();
     private void initData() {
          File file = new File(pluginFolder );
          File[] plugins = file.listFiles();
           if (plugins == null || plugins.length == 0) {
                mNoPluginTextView.setVisibility(View.VISIBLE);
               return;
          }

           mPluginItems.clear();
           for (File plugin : plugins) {
              PluginItem item = new PluginItem();
              item. pluginPath = plugin.getAbsolutePath();
              item. packageInfo = DLUtils.getPackageInfo(MainActivity. this, item.pluginPath);
                if(item.pluginPath ==null||item.packageInfo== null){
                    return;
              }
               if (item.packageInfo .activities != null
                        && item.packageInfo.activities .length > 0) {
                   item. launcherActivityName = item.packageInfo.activities [0].name ;
              }
               if (item.packageInfo .services != null
                        && item.packageInfo.services .length > 0) {
                   item. launcherServiceName = item.packageInfo.services [0].name ;
              }
               mPluginItems.add(item);
               DLPluginManager. getInstance(this).loadApk(item. pluginPath);//重点在这,装载apk
          }
     }
     在loadApk ()中过滤出apk文件。DLPluginManager是最终焦点,数据的焦点!装载了apk中的全部信息!为什么这么说解释如下


loadApk一共做了两件事:重要的事@!
 public DLPluginPackage loadApk( final String dexPath, boolean hasSoLib) {
        mFrom = DLConstants. FROM_EXTERNAL;

//第一件事:解压apk得到packageInfo
        PackageInfo packageInfo = mContext.getPackageManager().getPackageArchiveInfo(dexPath,
                PackageManager. GET_ACTIVITIES | PackageManager.GET_SERVICES );
        if (packageInfo == null) {
            return null ;
        }
//第二件事:装载apk,打包apk消息。
        DLPluginPackage pluginPackage = preparePluginEnv(packageInfo, dexPath);
        if (hasSoLib) {
            copySoLib(dexPath);
        }

        return pluginPackage;
    }

      loadApk ()调用getPackageInfo方法中的getPackageArchiveInfo 解压apk:
    public PackageInfo getPackageArchiveInfo(String archiveFilePath, int flags) {
        PackageParser packageParser = new PackageParser(archiveFilePath);
        DisplayMetrics metrics = new DisplayMetrics();
        metrics.setToDefaults();
        final File sourceFile = new File(archiveFilePath);
        PackageParser.Package pkg = packageParser.parsePackage(
                sourceFile, archiveFilePath, metrics, 0);
        if (pkg == null ) {
            return null ;
        }
        if ((flags & GET_SIGNATURES) != 0) {
            packageParser.collectCertificates(pkg, 0);
        }
        PackageUserState state = new PackageUserState();
        return PackageParser.generatePackageInfo(pkg, null, flags, 0, 0, null, state);//拿到了PackageInfo
    }
为什么会支持解压apk文件呢?

PackageParser是这定义的:
 public static boolean isPackageFilename(String name) {
    return name.endsWith(".apk");
  }


也就是说PackageParser这个类就是Google提供用来解压.apk后缀文件的。

//准备打包plug apk信息
  private DLPluginPackage preparePluginEnv(PackageInfo packageInfo, String dexPath) {

        DLPluginPackage pluginPackage = mPackagesHolder.get(packageInfo.packageName );
        if (pluginPackage != null) {
            return pluginPackage;
        }
        DexClassLoader dexClassLoader = createDexClassLoader(dexPath);
        AssetManager assetManager = createAssetManager(dexPath);
        Resources resources = createResources(assetManager);
        // create pluginPackage
        pluginPackage = new DLPluginPackage(dexClassLoader, resources, packageInfo);
        mPackagesHolder.put(packageInfo.packageName , pluginPackage);
        return pluginPackage;//最终返回的是DLPluginPackage
    }
----创建DexClassLoader
 private String dexOutputPath;

    private DexClassLoader createDexClassLoader(String dexPath) {
        File dexOutputDir = mContext.getDir( "dex", Context.MODE_PRIVATE );
        dexOutputPath = dexOutputDir.getAbsolutePath();
        //google提供的方法
        DexClassLoader loader = new DexClassLoader(dexPath, dexOutputPath , mNativeLibDir , mContext.getClassLoader());
        return loader;
    }


DLPluginManagerDLBasePluginService、DLBasePluginActivity或者DLBasePluginFragmentActivity这些类中是怎样使用的这里不再赘述了,请查看提供的lib源码及demo。


上面内容是对demo的注解,介绍内容为博主说明之外的内容。





目前装载包时遇到的问题:
1:
12-09 16:13:12.591: E/SoLibManager(23294): copy so lib failed: java.io.IOException: Error reading data for assets/library/armeabi/libpatch1.so near offset 112116
对应的lib中的类SoLibManager

2:



  运行应用
   首先android有一个PackageManager,这玩意功能很强大,功能就和它的意思一样
   
   假设:如果我们知道一个第三方Application的包的名称和Activity的名称,是否可以启动它的,答案当让市YES
   
   启动代码:
        PackageManager pm;  
   //初始化pm, 比如在activity里可以PackageManager pm = getPackageManager(); 
        PackageInfo pi = pm.getPackageInfo("包的名称", PackageManager.GET_ACTIVITIES);
   //PackageInfo  包含丰富的包的信息,这个'包的名称'是什么,在AndroidManifest.xml中有明确定义 
   //  比如 package="xxx.yyy.Portal.UI"
        ActivityInfo ai=pi.activities[0];  // ActivityInfo 同样道理 他是 Activity的信息
   //这里指向第一个包中的Activity, 大多数都是第一个Activity为启动Activity
        if(ai==null) throw new Exception(pkg+"不包含任何Activity");
        String sName=ai.name;  //这里就得到Activity的类名了
   启动它:
        Intent intent = new Intent();
        intent.setComponent(new ComponentName(pkg,sName));

 

阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页