前言
主要參考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); } }
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>
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); } } }
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>

<?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>
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; } }
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); } }
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>
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; } }
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); } }
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; } }
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("\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)) } }
這個腳本是非常關鍵的,必須要學會看里面的細節,不然如果出錯了,都不知道修改哪里。
附上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; } } }
布局文件:

<?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>
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