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

前言

  主要参考fat-aar来合并打包。

  但是这个fat-aar很久没维护了,如果直接使用它会有很多问题。由于对gradle脚本也不是太熟,就只能顺着它的意思,将gradle降级成2.2.3的版本。

  一开始我本地有2.3.3,可以打包,但是打包出来的aar找不到R资源,还有一些Class根本没有被打包进去。后面我将gradle降级成2.2.3,一切正常了。

前提准备

  首先说一下我的demo工程。

  有4个library,library1,library2,library3,main-library。顾名思义,就是将前3个library打包进main-library中。

  需要更改一下gradle。有两处需要更改。

  • 在工程的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,其他版本可能会出现错误。

   

library1,需合并的第一个Module  

  里面我写了3个类。然后libs中有一个jar,便于测试libs的合并。

  1.Library1Activity->一个活动,显示一张图片。 

public class Library1Activity extends AppCompatActivity {

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

  Library1Activity的布局文件。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://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="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://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="http://schemas.android.com/apk/res/android"
    xmlns:auto="http://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="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://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 <http://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("
" + 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;" << '
' << '
'
            sb << 'public final class R {' << '
'

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

            sb << '}' << '
'

            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="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://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

原文地址:https://www.cnblogs.com/Jason-Jan/p/9192273.html