Android插件混淆解决方法以及MultiDex的配置
windowsfxg
8年前
<p>最近在公司做的工作都是插件化相关,所以看了很多插件化的框架。整个插件化的方案现在是比较成熟的,怎样处理ClassLoader,怎么替换Activity生命周期,怎么去处理Receiver和Service,几个主流的框架基本上都是大同小异。我们团队选用了AndroidPluginFramework这个框架,具体的BenchMark其实在很多框架下面都可以看到。如何选取还是取决于自身需求,在插件化这块其实主流的需求一般是两种:</p> <ol> <li>完全独立的插件。就是给一个APK和宿主没有关系,宿主可以不安装的情况下调起这个APK,让用户无感知。</li> <li>非独立插件。这个其实是大部分公司的需求,就是随着公司业务的发展,客户端承载的业务越来越多,这个时候无论是从团队合作的角度还是动态化的角度,都希望各个业务之间解耦,发布能更加独立和动态。这种模式下,一般会抽出一个公共库,给各个组件提供基本功能,比如手淘还未开源的Atlas的结构。<br> 借用一张架构图,公共库抽象出中间件那个模块,提供给各个组件基本的能力<br> <img src="https://simg.open-open.com/show/f6fb3f34dfeea8fc375cd319b7109a9d.png"> <p>atlas.png</p> </li> </ol> <p>AndroidPluginFramework这个框架是支持这两种场景的,但我们实际业务场景是第二种,非独立插件。公共库中包含里基本的网络,缓存,以及UI框架。当然独立插件最后出来也是独立的APK。基本背景介绍完了,接下来开始讲讲本文的主题吧,关于插件化时代码混淆的问题。这个问题应该很容易想到,我们公共库中提供出去给插件使用的类应该只在宿主中有一份,宿主打包的时候把公共库打包到宿主的APK中,插件只应该在编译过程中用到,gradle中以provide的方式依赖这些代码,比如我们工程中mobilebase是公共依赖库</p> <pre> <code class="language-java">provide files(project(':mobilebase').getBuildDir().absolutePath + '/intermediates/bundles/release/classes.jar') provide files(project(':mobilebase').getBuildDir().absolutePath + '/outputs/rClasses.jar')</code></pre> <p>当然就会遇到一个问题是宿主在打release包的时候,会混淆mobilebase类,此时插件是不知道混淆的规则的,所以当插件想去调用公共库时就会ClassNotFound或者method不对。如何解决这个问题,有两种思路</p> <ol> <li>完全不混淆mobilebase,keep住mobilebase中的所有东西。这个方案适用于你的公共库够薄的情况,比如你各个组件之间公用的东西很少,那适用这个方案。</li> <li>使用相同的混淆规则。这个其实听上去相对合理一点的方案,宿主和插件使用相同的混淆的规则,理所当然能解决上面的问题。</li> </ol> <p>我们其实公共库里的东西还是有点多的,所以准备用第二种方案。</p> <p>Proguard在开启混淆时,会在app的 <strong> <strong>/build/outpust/mapping</strong> </strong> 目录下生成四个文件</p> <pre> <code class="language-java">dump.txt 说明 APK 中所有类文件的内部结构。 mapping.txt 提供原始与混淆过的类、方法和字段名称之间的转换。 seeds.txt 列出未进行混淆的类和成员。 usage.txt 列出从 APK 移除的代码。</code></pre> <p>其中mapping文件是混淆的规则,故我们只需要把这个文件用到插件的混淆配置中即可。所以拷贝这个文件到插件的目录,在插件的proguard-rules中添加</p> <pre> <code class="language-java">-applymapping mapping.txt</code></pre> <p>表示复用mappting,但由于混淆规则中的很多类插件是没有的,所以会有很多的Warning,所以我们配置一下ignore掉这些w,最终插件的混淆配置如下</p> <pre> <code class="language-java">-optimizationpasses 5 -dontusemixedcaseclassnames -dontskipnonpubliclibraryclasses -dontpreverify -verbose -optimizations !code/simplification/arithmetic,!field/*,!class/merging/* -ignorewarnings -printseeds -applymapping mapping.txt</code></pre> <p>OK,到这儿本以为能混淆的配置可以了,但运行时发现,插件中调用到公共库的地方并没有被正常混淆,还是找不到混淆后的方法。调研了发现provide的包gradle不会去混淆。。。</p> <h3>接下来就得去折腾multidex了</h3> <p>首先现在的问题是,我们不能把公共库打包到插件的apk中,但是以provide方式依赖又会出现无法混淆的问题。看了下AndroidPluginFramework官方提供的混淆建议(作者表示也没有试过)</p> <pre> <code class="language-java">具体方法: 1、开启混淆编译宿主,保留mapping文件 2、将插件的build.gradle文件中的provided配置换成compile, 因为provided方式提供的包不会被混淆 3、在插件的混淆配置中apply编译宿主时产生的mapping文件。 4、接着在插件编译脚本中开启multdex编译。并配置multdex的mainlist,使得原先所有provided的包的class被打入到副dex中。 这样插件编译完成后,会有2个dex,1个是插件自己需要的代码,1个是原先provided后来改成了compile的那些包。 5、再将这个原provided的包形成的dex,也就是副dex从apk中删除,再对插件apk重新签名。</code></pre> <p>简单来说就是先全部混淆,再利用multidex把之前provide的类全部打到第二个dex中,再删除第二个dex,再重新签名得到混淆后的插件APK。</p> <p>那就去试用一下multidex这个官方的拆包的库,至于这个multidex的原理以及大量的坑网上都能搜到很多的分析文章,美团也有很多技术分析 <a href="/misc/goto?guid=4959732713257974075" rel="nofollow,noindex">文章</a></p> <p>我说说我在实施这个过程中的坑吧,就是如何实现把指定的class打包到Class2.dex中。因为我们需要把插件的类打到主dex,其余provide的类打包到第二个dex中。</p> <p>这个问题网上最多的答案是在你插件的build.gradle中插入如下脚本</p> <pre> <code class="language-java">afterEvaluate { tasks.matching { it.name.startsWith('dex') }.each { dx -> def listFile = project.rootDir.absolutePath + '/plugintest/maindexlist.txt' println "root dir:" + project.rootDir.absolutePath println "dex task found:" + dx.name if (dx.additionalParameters == null) { dx.additionalParameters = [] } dx.additionalParameters += '--multi-dex' dx.additionalParameters += '--main-dex-list=' + listFile dx.additionalParameters += '--minimal-main-dex' dx.additionalParameters += '--set-max-idx-number=20000' } }</code></pre> <p>这段脚本的意思是当你插件的gradle的task graph扫描完成的时候,在dexXXX的任务中插入几个参数,</p> <ol> <li>--main-dex-list= 这个是一个txt文件指明你想哪些类打包到主dex</li> <li>--minimal-main-dex 最小化主dex,保证主dex中只有上面参数指定的类</li> <li>--set-max-idx-number 每个dex中最多的方法数(不太确定,大概是这个意思,默认值65535)</li> </ol> <p>网上的答案大部分是这段脚本,但你发现 <strong> <em>**</em> </strong> 并不会生效 <strong> <em>**</em> </strong> 。因为dexXXXDebug这个任务只在gradle1.5以下才有,之后就被隐藏了。</p> <p>我现在版本是2.2应该怎么配置?</p> <pre> <code class="language-java">android{ dexOptions { additionalParameters += '--main-dex-list=maindexlist.txt' additionalParameters += '--minimal-main-dex' additionalParameters += '--set-max-idx-number=20000' } }</code></pre> <p>在Android配置中添加这段即可,当然gradle1.5之后开始提供更多的第三方接口,所以也可以尝试使用</p> <p><a href="/misc/goto?guid=4959675829814668892" rel="nofollow,noindex">https://github.com/ceabie/DexKnifePlugin</a> 这个分包插件来完成。</p> <p>配置上以上混淆配置和multidex后,再打包插件,会发现成功混淆,分包也正确,混淆规则和宿主一致。</p> <p>以上就是整个插件打包和混淆的过程,由于刚接触插件化不久,如果有更合理的混淆方案,请告知一下,搞这块还是挺蛋疼的,记录一下!</p> <p> </p> <p>来自:http://www.jianshu.com/p/791d7f9673da</p> <p> </p>