AS 3.1 多library合併打包成aar的正確方式(fat-aar)

 

前言

  主要參考fat-aar來合併打包。java

  可是這個fat-aar好久沒維護了,若是直接使用它會有不少問題。因爲對gradle腳本也不是太熟,就只能順着它的意思,將gradle降級成2.2.3的版本。android

  一開始我本地有2.3.3,能夠打包,可是打包出來的aar找不到R資源,還有一些Class根本沒有被打包進去。後面我將gradle降級成2.2.3,一切正常了。git

 

前提準備

  首先說一下個人demo工程。github

  有4個library,library1,library2,library3,main-library。顧名思義,就是將前3個library打包進main-library中。express

  須要更改一下gradle。有兩處須要更改。windows

  • 在工程的build.gradle中,更改gradle版本爲: 
dependencies {
        classpath 'com.android.tools.build:gradle:2.2.3'
 }
  • 在工程的gradle文件夾->wrapper文件夾->gradle-wrapper.properties文件
#Sat Jun 16 22:38:31 CST 2018
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip

  這裏最好是2.14.1,其餘版本可能會出現錯誤。app

   

library1,需合併的第一個Module  

  裏面我寫了3個類。而後libs中有一個jar,便於測試libs的合併。less

  1.Library1Activity->一個活動,顯示一張圖片。 dom

public class Library1Activity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_library1);
    }
}
View Code

  Library1Activity的佈局文件。jvm

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
    xmlns:app="https://schemas.android.com/apk/res-auto"
    xmlns:tools="https://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.xingfu.library1.Library1Activity">

    <ImageView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:src="@drawable/library1"
        android:layout_centerInParent="true"
        android:scaleType="centerCrop"
        />

</RelativeLayout>
View Code

 

  2.PrePareActivity->一個活動,分頁顯示3張gif圖片,這裏調用了一個第三方gif庫。

package com.xingfu.library1;

import android.os.Bundle;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.support.v7.app.AppCompatActivity;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.TextView;

import java.util.ArrayList;
import java.util.List;

import pl.droidsonroids.gif.GifImageView;

public class PrePareActivity extends AppCompatActivity {

    private static final int PAGE_NUM = 3;

    private ViewPager previewPager;

    private TextView btnKnow;
    private ImageView btnClose;
    private int currentItem=0;

    private ViewPager.OnPageChangeListener pageChangeListener = new ViewPager.OnPageChangeListener() {

        @Override
        public void onPageSelected(int arg0) {
            currentItem=arg0;
            if (arg0 == PAGE_NUM - 1) {
                btnKnow.setText("開始拍攝");
                btnKnow.setVisibility(View.VISIBLE);

            } else {
                btnKnow.setText("下一步");
                btnKnow.setVisibility(View.VISIBLE);
            }

        }

        @Override
        public void onPageScrolled(int arg0, float arg1, int arg2) {

        }

        @Override
        public void onPageScrollStateChanged(int arg0) {

        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.prepare_activity);
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);

        previewPager =(ViewPager)findViewById(R.id.pa_vp_preview);
        previewPager.setOnPageChangeListener(pageChangeListener);

        btnKnow = (TextView) findViewById(R.id.pa_tv_know);
        btnClose=(ImageView) findViewById(R.id.pa_iv_close);

        btnKnow.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {
                if(btnKnow.getText().toString().equals("開始拍攝")){
                    finish();
                }else{
                    previewPager.setCurrentItem(++currentItem);
                }
            }
        });

        btnClose.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {
                finish();
            }
        });

        createStepView();
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        return keyCode == KeyEvent.KEYCODE_BACK;
    }

    @Override
    public void onDestroy() {
        //SharedPreferencesUtils.setParam(this,Constants.isFirstLaunch,false);
        super.onDestroy();
    }

    /**
     * 建立拍攝準備圖片
     */
    private void createStepView() {
        ArrayList<View> views = new ArrayList<View>();
        int images[] = new int[]{R.drawable.prepare1,
                R.drawable.prepare2,R.drawable.prepare3};
        GifImageView gifImageView;
        for (int i = 0; i < images.length; i++) {
            View view = LayoutInflater.from(PrePareActivity.this).inflate(
                    R.layout.item_prepare_layout, null);
            gifImageView = (GifImageView) view.findViewById(R.id.item_gif_imageview);
            gifImageView.setImageResource(images[i]);
            gifImageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
            //view.setBackgroundColor(0xFFFFFFFF);
            views.add(view);
        }
        previewPager.setAdapter(new SetepAdapter(views));

    }

    static class SetepAdapter extends PagerAdapter {

        private List<View> imageViews;

        public SetepAdapter(List<View> imageViews) {
            this.imageViews = imageViews;
        }


        @Override
        public int getCount() {

            return imageViews.size();
        }

        @Override
        public boolean isViewFromObject(View arg0, Object arg1) {
            return arg0 == arg1;
        }

        @Override
        public void destroyItem(ViewGroup container, int position, Object object) {
            container.removeView(imageViews.get(position));

        }

        @Override
        public int getItemPosition(Object object) {
            return super.getItemPosition(object);
        }

        @Override
        public Object instantiateItem(ViewGroup container, int position) {
            ViewPager.LayoutParams params = new ViewPager.LayoutParams();
            params.gravity = Gravity.CENTER_HORIZONTAL;

            container.addView(imageViews.get(position), 0);

            return imageViews.get(position);
        }

    }
}
View Code

  PrePareActivity須要的兩個佈局資源。 

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="https://schemas.android.com/apk/res/android"
    xmlns:tools="https://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:ignore="ContentDescription">

    <pl.droidsonroids.gif.GifImageView
        android:id="@+id/item_gif_imageview"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:layout_alignParentBottom="true"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:layout_alignParentRight="true"
        android:layout_alignParentEnd="true"
        android:layout_alignParentTop="true"
        android:visibility="visible"
        android:scaleType="centerCrop"

        />


</LinearLayout>
View Code
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
    xmlns:auto="https://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.v4.view.ViewPager
        android:id="@+id/pa_vp_preview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_alignParentTop="true"
        android:layout_centerHorizontal="true"
        />

    <ImageView
        android:id="@+id/pa_iv_close"
        android:layout_width="35dp"
        android:layout_height="35dp"
        android:layout_alignParentEnd="true"
        android:layout_alignParentRight="true"
        android:layout_marginEnd="8dp"
        android:layout_marginRight="8dp"
        android:layout_marginTop="8dp"
        android:clickable="true"
        android:contentDescription="@string/app_name"
        android:src="@drawable/delete" />


    <TextView
        android:id="@+id/pa_tv_know"
        android:layout_width="250dp"
        android:layout_height="45dp"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:layout_marginBottom="16dp"
        android:contentDescription="@string/app_name"
        android:gravity="center"
        android:background="@drawable/btn_background"
        android:text="下一步"
        android:textSize="24sp"
        android:textColor="@color/colorPrimary" />

</RelativeLayout>
View Code

 

  3.TimeUtil->一個工具類,主要是爲了測試用的,打包後,測試這個類是否能成功使用。

package com.xingfu.library1;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;

/**
 * Created by jasonjan on 2018/6/13.
 */

public class TimeUtil {
    private static final String TAG = "TimeUtil";

    public static String computePastTime(String time) {
        // Log.v(TAG, "computePastTime: " + time);
        String result = "剛剛";
        //2017-02-13T01:20:13.035+08:00
        time = time.replace("T", " ");
        time = time.substring(0, 22);
        // Log.v(TAG, "computePastTime time: " + time);
        SimpleDateFormat simpleDateFormat =
                new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.SIMPLIFIED_CHINESE);
        try {
            Date t = simpleDateFormat.parse(time);
            Date now = new Date(System.currentTimeMillis());
            long diff = (now.getTime() - t.getTime()) / 1000;
            if (diff < 60) {
                result = "剛剛";
            } else if ((diff /= 60) < 60) {
                result = diff + "分鐘前";
            } else if ((diff /= 60) < 24) {
                result = diff + "小時前";
            } else if ((diff /= 24) < 30) {
                result = diff + "天前";
            } else if ((diff /= 30) < 12) {
                result = diff + "月前";
            } else {
                diff /= 12;
                result = diff + "年前";
            }
        } catch (ParseException e) {
            e.printStackTrace();
        }
        // Log.v(TAG, "computePastTime result: " + result);
        return result;
    }

    public static String formatTime(String time) {
        // Log.v(TAG, "formatTime: " + time);
        //2017-02-13T01:20:13.035+08:00
        time = time.replace("T", " ");
        time = time.substring(0, 16);
        // Log.v(TAG, "formatTime result: " + time);
        return time;
    }
}
View Code

 

library2,需合併的第二個Module

  這裏面也有兩個類。而後libs有一個jar,爲了測試libs的合併。

  1.Library2Activity類->顯示一張圖片的活動。佈局和library1中的一致。

public class Library2Activity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_library2);
    }
}
View Code

  Library2Activity的佈局資源。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
    xmlns:app="https://schemas.android.com/apk/res-auto"
    xmlns:tools="https://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.xingfu.library2.Library2Activity">

    <ImageView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:src="@drawable/library2"
        android:layout_centerInParent="true"
        android:scaleType="centerCrop"
        />

</RelativeLayout>
View Code

 

  2.ToastUtils類->一個工具類,便於測試。

package com.xingfu.library2;

import android.content.Context;
import android.widget.Toast;

/**
 * Created by jasonjan on 2018/6/13.
 */

public class ToastUtils {
    private Toast mToast;
    private static ToastUtils mToastUtils;

    private ToastUtils(Context context) {
        mToast = Toast.makeText(context.getApplicationContext(), null, Toast.LENGTH_SHORT);
    }

    public static synchronized ToastUtils getInstanc(Context context) {
        if (null == mToastUtils) {
            mToastUtils = new ToastUtils(context);
        }
        return mToastUtils;
    }
    /**
     * 顯示toast
     *
     * @param toastMsg
     */
    public void showToast(int toastMsg) {
        mToast.setText(toastMsg);
        mToast.show();
    }

    /**
     * 顯示toast
     *
     * @param toastMsg
     */
    public void showToast(String toastMsg) {
        mToast.setText(toastMsg);
        mToast.show();
    }

    /**
     * 取消toast,在activity的destory方法中調用
     */
    public void destory() {
        if (null != mToast) {
            mToast.cancel();
            mToast = null;
        }
        mToastUtils = null;
    }
}
View Code

 

library3,需合併的第三個Module

  這裏面一樣有2個類,而後有一個jniLibs,爲了測試jni的合併。

  1.Library3Activity->顯示一張圖片的活動。 佈局文件和library1中一致。

public class Library3Activity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_library3);
    }
}
View Code

  2.DisplayUtils->一個通用工具,方便測試。

package com.xingfu.library3;

import android.app.Activity;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.util.DisplayMetrics;
import android.view.Window;

/**
 * Created by jasonjan on 2018/6/13.
 */

public class DisplayUtils {
    /**
     * 是否橫屏
     *
     * @param context
     * @return
     */
    public static boolean isLandscape(Context context) {
        return context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE;
    }

    /**
     * 是否豎屏
     *
     * @param context
     * @return
     */
    public static boolean isPortrait(Context context) {
        return context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
    }

    /**
     * Get screen width, in pixels
     *
     * @param context
     * @return
     */
    public static int getScreenWidth(Context context) {
        DisplayMetrics dm = context.getResources().getDisplayMetrics();
        return dm.widthPixels;
    }

    /**
     * Get screen height, in pixels
     *
     * @param context
     * @return
     */
    public static int getScreenHeight(Context context) {
        DisplayMetrics dm = context.getResources().getDisplayMetrics();
        return dm.heightPixels;
    }

    /**
     * Get screen density, the logical density of the display
     *
     * @param context
     * @return
     */
    public static float getScreenDensity(Context context) {
        DisplayMetrics dm = context.getResources().getDisplayMetrics();
        return dm.density;
    }

    /**
     * Get screen density dpi, the screen density expressed as dots-per-inch
     *
     * @param context
     * @return
     */
    public static int getScreenDensityDPI(Context context) {
        DisplayMetrics dm = context.getResources().getDisplayMetrics();
        return dm.densityDpi;
    }

    /**
     * Get titlebar height, this method cannot be used in onCreate(),onStart(),onResume(), unless it is called in the
     * post(Runnable).
     *
     * @param activity
     * @return
     */
    public static int getTitleBarHeight(Activity activity) {
        int statusBarHeight = getStatusBarHeight(activity);
        int contentViewTop = activity.getWindow().findViewById(Window.ID_ANDROID_CONTENT).getTop();
        int titleBarHeight = contentViewTop - statusBarHeight;
        return titleBarHeight < 0 ? 0 : titleBarHeight;
    }

    /**
     * Get statusbar height, this method cannot be used in onCreate(),onStart(),onResume(), unless it is called in the
     * post(Runnable).
     *
     * @param activity
     * @return
     */
    public static int getStatusBarHeight(Activity activity) {
        Rect rect = new Rect();
        activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(rect);
        return rect.top;
    }

    /**
     * Get statusbar height
     *
     * @param activity
     * @return
     */
    public static int getStatusBarHeight2(Activity activity) {
        int statusBarHeight = getStatusBarHeight(activity);
        if (0 == statusBarHeight) {
            Class<?> localClass;
            try {
                localClass = Class.forName("com.android.internal.R$dimen");
                Object localObject = localClass.newInstance();
                int id = Integer.parseInt(localClass.getField("status_bar_height").get(localObject).toString());
                statusBarHeight = activity.getResources().getDimensionPixelSize(id);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (NumberFormatException e) {
                e.printStackTrace();
            } catch (IllegalArgumentException e) {
                e.printStackTrace();
            } catch (SecurityException e) {
                e.printStackTrace();
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            }
        }
        return statusBarHeight;
    }

    /**
     * Convert dp to px by the density of phone
     *
     * @param context
     * @param dp
     * @return
     */
    public static int dip2px(Context context, float dp) {
        if (context == null) {
            return -1;
        }
        return (int) (dipToPx(context, dp) + 0.5f);
    }

    /**
     * Convert dp to px
     *
     * @param context
     * @param dp
     * @return
     */
    private static float dipToPx(Context context, float dp) {
        if (context == null) {
            return -1;
        }
        float scale = context.getResources().getDisplayMetrics().density;
        return dp * scale;
    }

    /**
     * Convert px to dp by the density of phone
     *
     * @param context
     * @param px
     * @return
     */
    public static int px2dip(Context context, float px) {
        if (context == null) {
            return -1;
        }
        return (int) (pxToDip(context, px) + 0.5f);
    }

    /**
     * Convert px to dp
     *
     * @param context
     * @param px
     * @return
     */
    private static float pxToDip(Context context, float px) {
        if (context == null) {
            return -1;
        }
        float scale = context.getResources().getDisplayMetrics().density;
        return px / scale;
    }

    /**
     * Convert px to sp
     *
     * @param context
     * @param px
     * @return
     */
    public static int px2sp(Context context, float px) {
        return (int) (pxToSp(context, px) + 0.5f);
    }

    /**
     * Convert px to sp
     *
     * @param context
     * @param px
     * @return
     */
    private static float pxToSp(Context context, float px) {
        float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
        return px / fontScale;
    }

    /**
     * Convert sp to px
     *
     * @param context
     * @param sp
     * @return
     */
    public static int sp2px(Context context, float sp) {
        return (int) (spToPx(context, sp) + 0.5f);
    }

    /**
     * Convert sp to px
     *
     * @param context
     * @param sp
     * @return
     */
    private static float spToPx(Context context, float sp) {
        float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
        return sp * fontScale;
    }
}
View Code

 

 

main-library,將3個Module合併,並獲取最終的aar文件        

  這裏面沒有任何類,主要是有一個fat-aar.gradle文件。

/**
 * This is free and unencumbered software released into the public domain.

 Anyone is free to copy, modify, publish, use, compile, sell, or
 distribute this software, either in source code form or as a compiled
 binary, for any purpose, commercial or non-commercial, and by any
 means.

 In jurisdictions that recognize copyright laws, the author or authors
 of this software dedicate any and all copyright interest in the
 software to the public domain. We make this dedication for the benefit
 of the public at large and to the detriment of our heirs and
 successors. We intend this dedication to be an overt act of
 relinquishment in perpetuity of all present and future rights to this
 software under copyright law.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
 OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 OTHER DEALINGS IN THE SOFTWARE.

 For more information, please refer to <https://unlicense.org/>
 */


import com.android.annotations.NonNull
import com.android.manifmerger.ManifestMerger2
import com.android.manifmerger.ManifestMerger2.Invoker
import com.android.manifmerger.ManifestMerger2.MergeType
import com.android.manifmerger.MergingReport
import com.android.manifmerger.PlaceholderEncoder
import com.android.manifmerger.XmlDocument
import com.android.utils.ILogger
import com.google.common.base.Charsets
import com.google.common.io.Files

/**
 * Fat AAR Lib generator v 0.2.1
 * Target Gradle Version :: 2.2.0
 *
 * Latest version available at https://github.com/adwiv/android-fat-aar
 * Please report issues at https://github.com/adwiv/android-fat-aar/issues
 *
 * This code is in public domain.
 *
 * Use at your own risk and only if you understand what it does. You have been warned ! :-)
 */

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:manifest-merger:25.3.2'
    }
}

configurations {
    embedded
}

dependencies {
    compile configurations.embedded
}

// Paths to embedded jar files
//合併Jar
ext.embeddedJars = new ArrayList()
// Paths to embedded aar projects
//合併aar路徑
ext.embeddedAarDirs = new ArrayList()
// Embedded aar files dependencies
//合併aar文件
ext.embeddedAarFiles = new ArrayList<ResolvedArtifact>()
// List of embedded R classes
//合併R文件
ext.embeddedRClasses = new ArrayList()

// Change backslash to forward slash on windows
//設置全局參數
ext.build_dir = buildDir.path.replace(File.separator, '/');
ext.root_dir = project.rootDir.absolutePath.replace(File.separator, '/');
ext.exploded_aar_dir = "$build_dir/intermediates/exploded-aar";
ext.classs_release_dir = "$build_dir/intermediates/classes/release";
ext.bundle_release_dir = "$build_dir/intermediates/bundles/release";
ext.manifest_aaapt_dir = "$build_dir/intermediates/manifests/aapt/release";
ext.generated_rsrc_dir = "$build_dir/generated/source/r/release";

ext.base_r2x_dir = "$build_dir/fat-aar/release/";

def gradleVersionStr = GradleVersion.current().getVersion();
ext.gradleApiVersion = gradleVersionStr.substring(0, gradleVersionStr.lastIndexOf(".")).toFloat();

println "Gradle version: " + gradleVersionStr;

afterEvaluate {
    // the list of dependency must be reversed to use the right overlay order.
    //獲取全部的依賴
    def dependencies = new ArrayList(configurations.embedded.resolvedConfiguration.firstLevelModuleDependencies)
    //反向遍歷
    dependencies.reverseEach {

        def aarPath;
        if (gradleApiVersion >= 2.3f)
            aarPath = "${root_dir}/${it.moduleName}/build/outputs/default"
        else
            aarPath = "${exploded_aar_dir}/${it.moduleGroup}/${it.moduleName}/${it.moduleVersion}"

        //遍歷每一個module
        it.moduleArtifacts.each {
            artifact ->

                println "ARTIFACT 3 : "
                println artifact
                //處理aar
                if (artifact.type == 'aar') {
                    if (!embeddedAarFiles.contains(artifact)) {
                        embeddedAarFiles.add(artifact)
                    }
                    if (!embeddedAarDirs.contains(aarPath)) {
                        if (artifact.file.isFile()) {
                            println artifact.file
                            println aarPath

                            copy {
                                from zipTree(artifact.file)
                                into aarPath
                            }
                        }
                        embeddedAarDirs.add(aarPath)
                    }
                } else if (artifact.type == 'jar') {
                    //若是有jar
                    def artifactPath = artifact.file
                    if (!embeddedJars.contains(artifactPath))
                        embeddedJars.add(artifactPath)
                } else {
                    throw new Exception("Unhandled Artifact of type ${artifact.type}")
                }
        }
    }

    //如何還有依賴
    if (dependencies.size() > 0) {
        // Merge Assets
        //前者依賴後者
        generateReleaseAssets.dependsOn embedAssets
        //embedAssets依賴prepareReleaseDependencies
        embedAssets.dependsOn prepareReleaseDependencies

        // Embed Resources by overwriting the inputResourceSets
        packageReleaseResources.dependsOn embedLibraryResources
        embedLibraryResources.dependsOn prepareReleaseDependencies

        // Embed JNI Libraries
        bundleRelease.dependsOn embedJniLibs

        if (gradleApiVersion >= 2.3f) {

            embedJniLibs.dependsOn transformNativeLibsWithSyncJniLibsForRelease
            ext.bundle_release_dir = "$build_dir/intermediates/bundles/default"
        } else {
            embedJniLibs.dependsOn transformNative_libsWithSyncJniLibsForRelease
            ext.bundle_release_dir = "$build_dir/intermediates/bundles/release";
        }

        // Merge Embedded Manifests
        bundleRelease.dependsOn embedManifests
        embedManifests.dependsOn processReleaseManifest

        // Merge proguard files
        embedLibraryResources.dependsOn embedProguard
        embedProguard.dependsOn prepareReleaseDependencies

        // Generate R.java files
        compileReleaseJavaWithJavac.dependsOn generateRJava
        generateRJava.dependsOn processReleaseResources

        // Bundle the java classes
        bundleRelease.dependsOn embedJavaJars
        embedJavaJars.dependsOn compileReleaseJavaWithJavac

        // If proguard is enabled, run the tasks that bundleRelease should depend on before proguard
        if (tasks.findByPath('proguardRelease') != null) {
            proguardRelease.dependsOn embedJavaJars
        } else if (tasks.findByPath('transformClassesAndResourcesWithProguardForRelease') != null) {
            transformClassesAndResourcesWithProguardForRelease.dependsOn embedJavaJars
        }
    }
}

//執行任務-合併庫的資源
task embedLibraryResources << {
    println "Running FAT-AAR Task :embedLibraryResources"
    //待修改,已經註釋
    def oldInputResourceSet = packageReleaseResources.inputResourceSets
    packageReleaseResources.conventionMapping.map("inputResourceSets") {
        getMergedInputResourceSets(oldInputResourceSet)
    }
}

private List getMergedInputResourceSets(List inputResourceSet) {
    //We need to do this trickery here since the class declared here and that used by the runtime
    //are different and results in class cast error
    def ResourceSetClass = inputResourceSet.get(0).class

    //資源集合
    List newInputResourceSet = new ArrayList(inputResourceSet)

    println "getMergedInputResourceSets"

    println embeddedAarDirs
    //遍歷這個aar路徑
    embeddedAarDirs.each { aarPath ->
        try {
            println aarPath
            def resname
            if (gradleApiVersion >= 2.3f) {
                def parentProject = project.rootProject.name.toString()
                println "parent: "
                println parentProject

                def startIndex = aarPath.indexOf('/' + parentProject)
                def endIndex = aarPath.indexOf('/build/')

                println "start"
                println startIndex
                println "end"
                println endIndex
                if (startIndex < 1 || endIndex < 1)
                    return;
                resname = aarPath.substring(startIndex, endIndex).replace('/', ':')
            } else
                resname = (aarPath.split(exploded_aar_dir)[1]).replace('/', ':');
            def rs = ResourceSetClass.newInstance([resname, true] as Object[])
            rs.addSource(file("$aarPath/res"))
            println "ResourceSet is " + rs
            println resname
            newInputResourceSet += rs
        } catch (Exception e) {
            e.printStackTrace();
            throw e;
        }
    }

    return newInputResourceSet
}

/**
 * Assets are simple files, so just adding them to source set seems to work.
 */
task embedAssets << {
    println "Running FAT-AAR Task :embedAssets"
    embeddedAarDirs.each { aarPath ->

        println "當前的aarPath爲:" + aarPath + " $aarPath"

        if ("$aarPath".endsWith("library1/build/outputs/default")
                || "$aarPath".endsWith("library2/build/outputs/default")
                || "$aarPath".endsWith("library3/build/outputs/default")
                ) {

            println "進入了" + aarPath
            android.sourceSets.main.assets.srcDirs += file("$aarPath/assets")

        }

    }
}

/**
 * Merge proguard.txt files from all library modules
 * @author Marian Klühspies
 */
task embedProguard << {
    println "Running FAT-AAR Task :embedProguard"

    def proguardRelease = file("$bundle_release_dir/proguard.txt")
    embeddedAarDirs.each { aarPath ->
        try {
            def proguardLibFile = file("$aarPath/proguard.txt")
            if (proguardLibFile.exists())
                proguardRelease.append("\n" + proguardLibFile.text)
        } catch (Exception e) {
            e.printStackTrace();
            throw e;
        }
    }
}


task generateRJava << {
    println "Running FAT-AAR Task :generateRJava"

    // Now generate the R.java file for each embedded dependency
    def mainManifestFile = android.sourceSets.main.manifest.srcFile;
    def libPackageName = "";

    if (mainManifestFile.exists()) {

        libPackageName = new XmlParser().parse(mainManifestFile).@package
    }

    embeddedAarDirs.each { aarPath ->

        //if("$aarPath".endsWith(""))
        def manifestFile = file("$aarPath/AndroidManifest.xml");
        if (!manifestFile.exists()) {
            manifestFile = file("./src/main/AndroidManifest.xml");
        }

        if (manifestFile.exists()) {
            def aarManifest = new XmlParser().parse(manifestFile);
            def aarPackageName = aarManifest.@package

            String packagePath = aarPackageName.replace('.', '/')

            // Generate the R.java file and map to current project's R.java
            // This will recreate the class file
            def rTxt = file("$aarPath/R.txt")
            def rMap = new ConfigObject()

            if (rTxt.exists()) {
                rTxt.eachLine {
                    line ->
                        //noinspection GroovyUnusedAssignment
                        def (type, subclass, name, value) = line.tokenize(' ')
                        rMap[subclass].putAt(name, type)
                }
            }

            def sb = "package $aarPackageName;" << '\n' << '\n'
            sb << 'public final class R {' << '\n'

            rMap.each {
                subclass, values ->
                    sb << "  public static final class $subclass {" << '\n'
                    values.each {
                        name, type ->
                            sb << "    public static $type $name = ${libPackageName}.R.${subclass}.${name};" << '\n'
                    }
                    sb << "    }" << '\n'
            }

            sb << '}' << '\n'

            mkdir("$generated_rsrc_dir/$packagePath")
            file("$generated_rsrc_dir/$packagePath/R.java").write(sb.toString())

            embeddedRClasses += "$packagePath/R.class"
            embeddedRClasses += "$packagePath/R\$*.class"
        }

    }
}

task collectRClass << {
    println "COLLECTRCLASS"
    delete base_r2x_dir
    mkdir base_r2x_dir

    copy {
        from classs_release_dir
        include embeddedRClasses
        into base_r2x_dir
    }
}

task embedRClass(type: org.gradle.jvm.tasks.Jar, dependsOn: collectRClass) {
    println "EMBED R CLASS"

    destinationDir file("$bundle_release_dir/libs/")
    println destinationDir
    from base_r2x_dir
    println base_r2x_dir
}

/**
 * To embed the class files, we need to change the R.class to X.class, so we explode it in another
 * location, proguard it to modify R to X, and then finally copy it to build location
 */
task embedJavaJars(dependsOn: embedRClass) << {
    println "Running FAT-AAR Task :embedJavaJars"

    embeddedAarDirs.each { aarPath ->

        // Explode all classes.jar files to classes so that they can be proguarded
        def jar_dir
        if (gradleApiVersion >= 2.3f)
            jar_dir = "$aarPath"
        else
            jar_dir = "$aarPath/jars"

        if (embeddedAarFiles.size() > 0) {

            embeddedAarFiles.each {
                artifact ->
                    FileTree aarFileTree = zipTree(artifact.file.getAbsolutePath());

                    def aarFile = aarFileTree.files.find { it.name.contains("classes.jar") }

                    copy {
                        from zipTree(aarFile)
                        into classs_release_dir
                    }
            }

        } else {

            println jar_dir
            println classs_release_dir
            println bundle_release_dir
            println embeddedJars

            copy {
                from zipTree(jar_dir + "/classes.jar")
                into classs_release_dir
            }
        }

        // Copy all additional jar files to bundle lib
        FileTree jars = fileTree(dir: jar_dir, include: '*.jar', exclude: 'classes.jar')
        jars += fileTree(dir: jar_dir + "/libs", include: '*.jar')
        jars += fileTree(dir: "$aarPath/libs", include: '*.jar')

        copy {
            from jars
            into file("$bundle_release_dir/libs")
        }

        // Copy all embedded jar files to bundle lib
        copy {
            from embeddedJars
            into file("$bundle_release_dir/libs")
        }
    }
}

/**
 * For some reason, adding to the jniLibs source set does not work. So we simply copy all files.
 */
task embedJniLibs << {
    println "Running FAT-AAR Task :embedJniLibs"

    embeddedAarDirs.each { aarPath ->
        println "======= Copying JNI from $aarPath"
        // Copy JNI Folders
       /* if ("$aarPath".endsWith("library3/unspecified")) {
            copy {
                println "進入library3拿jni中文件"
                from fileTree(dir: "$aarPath/jni")
                into file("$bundle_release_dir/jni")
            }
        }*/
        copy {
            println "進入library3拿jni中文件"
            from fileTree(dir: "$aarPath/jni")
            into file("$bundle_release_dir/jni")
        }
    }
}

task embedManifests << {
    println "Running FAT-AAR Task :embedManifests"

    ILogger mLogger = new MiLogger()
    List libraryManifests = new ArrayList<>()

    embeddedAarDirs.each { aarPath ->
        File dependencyManifest = file("$aarPath/AndroidManifest.xml")

        if (!libraryManifests.contains(aarPath) && dependencyManifest.exists()) {
            libraryManifests.add(dependencyManifest)
        }
    }

    File reportFile = file("${build_dir}/embedManifestReport.txt")

    File origManifest = file("$bundle_release_dir/AndroidManifest.xml")
    File copyManifest = file("$bundle_release_dir/AndroidManifest.orig.xml")
    File aaptManifest = file("$manifest_aaapt_dir/AndroidManifest.xml")

    if (!origManifest.exists()) {
        origManifest = file("./src/main/AndroidManifest.xml")
    }

    if (!origManifest.exists()) {
        return;
    }

    copy {
        from origManifest.parentFile
        into copyManifest.parentFile
        include origManifest.name
        rename(origManifest.name, copyManifest.name)
    }

    try {
        Invoker manifestMergerInvoker = ManifestMerger2.newMerger(copyManifest, mLogger, MergeType.APPLICATION)

        manifestMergerInvoker.addLibraryManifests(libraryManifests.toArray(new File[libraryManifests.size()]))

        // manifestMergerInvoker.setPlaceHolderValues(placeHolders)
        manifestMergerInvoker.setMergeReportFile(reportFile);

        MergingReport mergingReport = manifestMergerInvoker.merge();

        mLogger.info("Merging result:" + mergingReport.getResult());
        MergingReport.Result result = mergingReport.getResult();
        switch (result) {
            case MergingReport.Result.WARNING:
                mergingReport.log(mLogger);
        // fall through since these are just warnings.
            case MergingReport.Result.SUCCESS:
                XmlDocument xmlDocument = mergingReport.getMergedXmlDocument(MergingReport.MergedManifestKind.MERGED);
                try {
                    String annotatedDocument = mergingReport.getActions().blame(xmlDocument);
                    mLogger.verbose(annotatedDocument);
                } catch (Exception e) {
                    mLogger.error(e, "cannot print resulting xml");
                }
                save(xmlDocument, origManifest);
                mLogger.info("Merged manifest saved to " + origManifest);
                if (aaptManifest.exists()) {
                    new PlaceholderEncoder().visit(xmlDocument);
                    save(xmlDocument, aaptManifest);
                    mLogger.info("Merged aapt safe manifest saved to " + aaptManifest);
                }
                break;
            case MergingReport.Result.ERROR:
                mergingReport.log(mLogger);
                throw new RuntimeException(mergingReport.getReportString());
            default:
                throw new RuntimeException("Unhandled result type : " + mergingReport.getResult());
        }
    } catch (RuntimeException e) {
        // Unacceptable error
        e.printStackTrace()
        throw new RuntimeException(e);
    }
}

private void save(XmlDocument xmlDocument, File out) {
    try {
        Files.write(xmlDocument.prettyPrint(), out, Charsets.UTF_8);
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

class MiLogger implements ILogger {

    @Override
    void error(
            @com.android.annotations.Nullable Throwable t,
            @com.android.annotations.Nullable String msgFormat, Object... args) {
        System.err.println(String.format("========== ERROR : " + msgFormat, args))
        if (t) t.printStackTrace(System.err)
    }

    @Override
    void warning(@NonNull String msgFormat, Object... args) {
        System.err.println(String.format("========== WARNING : " + msgFormat, args))
    }

    @Override
    void info(@NonNull String msgFormat, Object... args) {
        System.out.println(String.format("========== INFO : " + msgFormat, args))
    }

    @Override
    void verbose(@NonNull String msgFormat, Object... args) {
        // System.out.println(String.format("========== DEBUG : " + msgFormat, args))
    }
}
View Code

  這個腳本是很是關鍵的,必需要學會看裏面的細節,否則若是出錯了,都不知道修改哪裏。

  附上fat-aar的原地址吧。fat-aar。   還有一個聽說能解決大部分問題的插件:fat-aar-plugin

  不過對於本工程,如今是沒有報錯了。主要修改的就是一些路徑問題了。這個看看就知道修改哪裏。全局參數那裏沒改,就用它原來默認的就好。

  

 

生成aar文件

  1.clean一下工程。

  2.點擊Gradle。

  

  點擊這個以後,就開始合併module了。

  幸運的話,在main-library的build文件夾的outputs文件夾的aar文件夾下就生成了咱們要的 main-library-release.aar文件了。

 

 

使用該aar文件

  這裏就須要新建一個工程來測試這個aar有沒有成功打包。

  包括合併libs,合併jni,合併R.java,合併資源文件等等。

  提示:若是你想看aar文件中解壓出來的東西,能夠先該後綴名爲zip,而後解壓就好了。

      若是你想直接改解壓中的文件,再壓縮成zip,再改後綴名爲aar是行不通的。就是說這是不可逆的過程。

 

  1.建好一個工程後,在libs中加入剛剛生成的aar。

    在build.gradle中引入:

    首先在android節點下加入:

repositories {
        flatDir {
            dirs 'libs'
        }
    }

    而後再dependencies節點下加入:

compile(name: 'main-library-release', ext: 'aar')

 

  2.再建立一個活動,三個按鈕。每一個按鈕對應使用前面library中定義的活動。

package com.xingfu.testdemo;

import android.content.ComponentName;
import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

import com.xingfu.library1.Library1Activity;
import com.xingfu.library1.PrePareActivity;
import com.xingfu.library2.Library2Activity;
import com.xingfu.library2.ToastUtils;
import com.xingfu.library3.DisplayUtils;
import com.xingfu.library3.Library3Activity;


public class TestActivity extends AppCompatActivity implements View.OnClickListener {

    private Button btn1;
    private Button btn2;
    private Button btn3;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test);

        btn1=findViewById(R.id.at_test1_btn);
        btn2=findViewById(R.id.at_test2_btn);
        btn3=findViewById(R.id.at_test3_btn);

        btn1.setOnClickListener(this);
        btn2.setOnClickListener(this);
        btn3.setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()){
            case R.id.at_test1_btn:
                ToastUtils.getInstanc(this).showToast("點擊了按鈕1,即將調整到活動1,同時使用了了庫1中的類返回值爲:");
                Intent intent1 = new Intent(android.content.Intent.ACTION_VIEW);
                intent1.setComponent(new ComponentName("com.xingfu.testdemo","com.xingfu.library1.Library1Activity"));
                startActivity(intent1);
              /* Intent intent1=new Intent(this, Library1Activity.class);
               startActivity(intent1);*/
                break;
            case R.id.at_test2_btn:
                ToastUtils.getInstanc(this).showToast("點擊了按鈕2,即將調整到活動2");
                Intent intent2 = new Intent(this, Library2Activity.class);
                startActivity(intent2);
               /* Intent intent2 = new Intent(android.content.Intent.ACTION_VIEW);
                intent2.setComponent(new ComponentName("com.xingfu.testdemo","com.xingfu.library2.Library2Activity"));
                startActivity(intent2);*/
                break;
            case R.id.at_test3_btn:
                /*ToastUtils.getInstanc(this).showToast("點擊了按鈕3,即將調整到活動3,同時使用了庫3中的類返回值爲:"
                        + DisplayUtils.dip2px(this,10));
                Intent intent3 = new Intent(this, Library3Activity.class);
                startActivity(intent3);*/
                Intent intent3=new Intent(this, PrePareActivity.class);
                startActivity(intent3);

                /*Intent intent3 = new Intent(android.content.Intent.ACTION_VIEW);
                intent3.setComponent(new ComponentName("com.xingfu.testdemo","com.xingfu.library3.Library3Activity"));
                startActivity(intent3);*/
                break;
        }
    }
}
View Code

  佈局文件: 

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="https://schemas.android.com/apk/res/android"
    xmlns:app="https://schemas.android.com/apk/res-auto"
    xmlns:tools="https://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.xingfu.testdemo.TestActivity">

    <Button
        android:id="@+id/at_test1_btn"
        android:text="點擊進入library1"
        android:layout_margin="16dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <Button
        android:id="@+id/at_test2_btn"
        android:text="點擊進入library2"
        android:layout_margin="16dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <Button
        android:id="@+id/at_test3_btn"
        android:text="點擊進入library3"
        android:layout_margin="16dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

</LinearLayout>
View Code

 

  3.聲明一下:

  在這個測試工程中,無需在res裏面添加任何資源,無需在AndroidManifest.xml添加任何權限,任何aar中用過的activity標籤。

  無需在build.gradle引入任何aar中用的遠程依賴。前提是在main-library中的遠程依賴,使用了embedded替換了compile。

  由於已經包含在aar文件中了,因此這裏直接使用便可,很是之方便。

 

 

使用效果

          

  第一幅gif==>點擊了前兩個按鈕,分別調轉到對應的活動頁面。

  第二幅gif==>點擊了第三個按鈕,調轉到PrepareActivity,這裏使用了第三方庫(爲了測試遠程依賴是否成功打包)

 

 

項目地址

  打包項目:https://github.com/JasonToJan/xfDemo

  測試aar項目:https://github.com/JasonToJan/TestDemo