快过年了,项目进度放换了,看android 热修复方案的时候,顺便把多dex拆分方案,看了一下。


1.前提执行备案

     方案需要大家了解只是:

  1.ant编译android项目

step 1: 下载安装

        step 2: 跟android项目添加ant编译方式。参考   android update proejct -p ./



        step 3: 然后在项目的根目录中运行:ant debug 或者ant releces



  2.android 编译apk过程

  大概流程:

step 1. 收集所有的java文件,包括:R.java(通过android aapt生成),.java文件,.aidl文件(通过android AIDL 工具生成)


step 2.将生成的java文件,调用javac编译为.class文件


step  3.执行代码混淆


step 4.将所有的.class文件,通过android dex工具,打包为一个.dex文件()。


step 5. 将res,asset,dex,打包为apk


step 6. 签名


以上是android apk编译的大概流程。


2. 拆分dex原理:


        相关原来以及问题,个人感觉: android 多dex方案 整理的不错,就不在赘述。



3.实现方案


      整体方案参考 上面连接描述的。这个方案并没有自己重写android apk编译流程的build.xml文件。只是将android 原来的(%ANDROID_HOME%/tools/ant/build.xml,复制到项目中,将原来的文件做了些小的添加,



     整体方案如下:


     1.在android 编译流程中 step 3代码混淆执行完成后,将需要拆分的class文件移动别的文件夹中



     2. 在android 编译流程中 step 4中,当主dex文件执行完成后,在将需要拆分的class文件执行一次dex



     3.在android编译流程中 step 5之前,将拆分出来的dex文件复制到asset/dex(路径可以随意指定)目录中



     4.编译结束后,将复制到asset/dex文件中的dex文件删除



     5. 在apk的Application的oncreate中通过,反射,将asset/dex文件夹中的所有dex文件,加载到ClassLoader中,见:MultiDex.java


    


     下面进入主题:


    


      step 1: 配置项目可以使用ant编译,参考上面ant使用


                   在生成的build.xml中添加:


    <!-- 拆分dex,从build_apk.xml移动到这里 -->

    <property name="out.dir" value="bin" />

    <property name="out.absolute.dir" location="${out.dir}" />

    <property name="asset.dir" value="assets" />

    <property name="asset.absolute.dir" location="${asset.dir}" />

Android Studio合并app下载出来是两个app android合并dex_Android 65535 多dex拆分




      step 2 :将(%ANDROID_HOME%/tools/ant/build.xml 复制到项目的跟目录下,名称可以随意命名


                  A.我将android默认的build文件修改为:build_apk.xml



< import file ="build_apk.xml" />




                  C.定义代码混淆后执行的target,代码位置在<target name="-obfuscate">前面加入:


                        <!-- add by shehonghao ,拆分dex文件,开始-->
                        <!-- 代码混淆后执行 -->
                        <target name="-post-obfuscate"/>
                     <!-- add by shehonghao ,拆分dex文件,结束-->


Android Studio合并app下载出来是两个app android合并dex_xml_02




                  D.在target:<target name="-obfuscate">的最后面加入:


                       <!-- add by shehonghao,拆分dex文件,开始-->
                      <!-- 代码混淆结束后,开始拆封class文件 --> 


                     <antcall target="-post-obfuscate"></antcall>
                     <!-- add by shehonghao,拆分dex文件,结束-->




Android Studio合并app下载出来是两个app android合并dex_android_03



                     在代码之前添加:


                      <!-- add by shehonghao ,拆分dex文件,开始-->
     <!-- 打包dex后执行 -->
<target name="-post-dex" />
                        <!-- 创建资源后执行 -->
                       <target name="-pre-crunch"></target>
                      <!-- add by shehonghao ,拆分dex文件,结束-->


 

Android Studio合并app下载出来是两个app android合并dex_Android 65535 多dex拆分_04


                   

  
                   F.找到: <macrodef name="dex-helper">,在<dex executalbe="${ds}的之后添加:


                               <antcall target="-post-dex"></antcall>


      


                   

Android Studio合并app下载出来是两个app android合并dex_android_05


   

    step 3: 创建多拆分包build_multi.xml,并在build.xml中引入:

          

Android Studio合并app下载出来是两个app android合并dex_加载_06

         文件内容:

<?xml version="1.0" encoding="UTF-8"?>

<project

    name="MultiDemo_"

    default="help" >


    <!-- 不需要修改的 开始 -->

    <!-- dex在asset目录中的位置, -->

    <property name="dex.asset.dir" value="dex" />

     <!-- aseent/dex目录中 -->

     <property name="mulite.abs.dir" value="${asset.absolute.dir}/${dex.asset.dir}" />

     <!-- 不需要修改的 结束 -->

     

     

     <!--  拆分dex 1 -->

    <!-- dex在dex.asset.dir文件夹中的文件名称 -->

    <property name="dex.file.name1" value="classes1"></property>

    <!-- 保存被拆分出来的class文件的路径-->

    <property name="out.multi.classes.absolute.dir1" location="${out.dir}/${dex.file.name1}" />

    <!-- 编译后,生成的dex文件的绝对路径,包含文件名 -->

    <property name="intermediate.dex.file1" location="${out.absolute.dir}/${dex.file.name1}.dex" />

     

    

      <!--  拆分dex 2 -->

     <property name="dex.file.name2" value="classes2"></property>

    <!-- 保存被拆分出来的class文件的路径-->

    <property name="out.multi.classes.absolute.dir2" location="${out.dir}/${dex.file.name2}" />

    <!-- 编译后,生成的dex文件的绝对路径,包含文件名 -->

    <property name="intermediate.dex.file2" location="${out.absolute.dir}/${dex.file.name2}.dex" />

     

    

    <!-- 创建保存拆分class文件的路径,每个dex文件需要一个路径 , 需要修改 -->

   <target name="-init" >

    <!-- 创建第一个需要被拆分的文件路径 -->

    <mkdir dir="${out.multi.classes.absolute.dir1}" ></mkdir>

    

     <mkdir dir="${out.multi.classes.absolute.dir2}" ></mkdir>

    

    </target>

    

    <!-- 代码混淆后执行移动class文件策略,不需要修改 -->

    <target name="-post-obfuscate"   depends="-init" >

        <!-- 将需要拆分的class文件,移动到相应的路径中 -->

          <antcall target="-move-mulit-class"></antcall>

    </target>

    

    <!-- 移动class文件,不需要修改 -->

    <macrodef name="move-class">

        <attribute name="srcDir" description="源class做在的相对路径,bin/目录开始"></attribute>

        <attribute name="disDir" description="目标class做在的相对路径,bin/目录开始"></attribute>

        <sequential>

<if>

<condition>

<and>

<length string="@{srcDir}" trim="true" when="greater" length="0" />

</and>

<and>

<length string="@{disDir}" trim="true" when="greater" length="0" />

</and>

</condition>

<then>

<move file="@{srcDir}" todir="@{disDir}" ></move>

</then>

<else>

<echo> 为设置远文件,或目标文件</echo>

</else>

</if>

            

        </sequential>

    </macrodef>

    

      <!-- 移动需要拆分的class文件 ,需要修改-->

    <target name="-move-mulit-class">

        <!-- 将所有需要拆封在一个dex文件中的class移动一个文件夹中 -->

        <move-class srcDir="${out.dir}/classes/com/multi/dex1" disDir="${out.multi.classes.absolute.dir1}/com/multi/"></move-class>

        

        <!-- dex 2 中包含的class文件 -->

        <move-class srcDir="${out.dir}/classes/com/multi/dex2" disDir="${out.multi.classes.absolute.dir2}/com/multi/"></move-class>

    </target>

    

    

    <!-- 主dex编译结束后,编译拆分的dex,需要修改 -->

    <target name="-post-dex">

             <!--打包第一个被拆分dex文件 -->

<echo>编译拆分dex 1</echo>

<dex executable="${dx}"

                    output="${intermediate.dex.file1}"

nolocals="@{nolocals}"

forceJumbo="${dex.force.jumbo}"

                    disableDexMerger="${dex.disable.merger}"

verbose="${verbose}">

                <path path="${out.multi.classes.absolute.dir1}"/>

</dex>

            <!-- 打包第二个被拆分的dex文件 -->

<echo>编译拆分dex 2</echo>

<dex executable="${dx}"

                    output="${intermediate.dex.file2}"

nolocals="@{nolocals}"

forceJumbo="${dex.force.jumbo}"

                    disableDexMerger="${dex.disable.merger}"

verbose="${verbose}">

                <path path="${out.multi.classes.absolute.dir2}"/>

</dex>

    </target>

    

    <!-- 将分拆的dex文件,复制到assents/dex目录,需要修改 -->

     <target name="-pre-crunch">

         <!-- copy dex1 -->

<echo>复制拆分dex 1</echo>

         <move file="${intermediate.dex.file1}" todir="${mulite.abs.dir}"></move>

          <!-- copy dex2 -->

<echo>复制拆分dex 2</echo>

         <move file="${intermediate.dex.file2}" todir="${mulite.abs.dir}"></move>

     </target>

     

     

     <!-- 编译结束,删除压缩的dex文件, 需要修改-->

     <target name="-post-build">

          <!-- 删除dex1 -->

         <delete file="${mulite.abs.dir}/${dex.file.name1}.dex"></delete>

         <!-- 删除dex2 -->

         <delete file="${mulite.abs.dir}/${dex.file.name2}.dex"></delete>

     </target>

     

  


 

   

</project>



通过项目三步,编译好的apk的asset/dex目录下,就会存在被拆分出来的dex文件。


最后一步,将拆分出来的dex文件添加到主classload中


   方案:



     1.通过反射将当前已经加载进来的dex文件对接保存到内存中:


       getDexElementsByReflect(getPathListByReflect(pathClassLoader));



     2.将指定路径中的dex文件加载到内存中:


 /**

dex到内存中

     * 

@param

@param

@param

@return

     */

private


        // 加载指定路径的dex文件

        DexClassLoader dexClassLoader =

new DexClassLoader(dexPath, context.getDir("dex", 0).getAbsolutePath(), dexPath, pathClassLoader);

        // 反射获取DexClasLoader的pathList属性

        Object classLoaderPathList = getPathListByReflect(dexClassLoader);

if (classLoaderPathList == null) {

if (is_debug) {

TAG, "路径--》" + dexPath + " 加载到任何的class,请确认文件格式是否正确");

            }

return null;

        }

        // 反射获取dalvik.system.DexPathList的dexElements熟悉

return

    }


  3.将所有的dex,合并为一个数组,然后通过反射设置给主classloader。这样就完成,代码如下:

    

 // 合并所有的dex文件

        Object array = createObjectTypeArray(allDexElements[0].getClass(), dexNum);


        Object[] list = (Object[]) array;

        dexNum = 0;

for (int i = 0; i < allDexElements.length; i++) {

if (allDexElements[i] != null) {

                elementList = (Object[]) allDexElements[i];

length);

length;

            }


        }

        Object pathList = getPathListByReflect(pathClassLoader);

//

"dexElements", list);