主要參考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
dependencies { classpath 'com.android.tools.build:gradle:2.2.3' }
#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
裏面我寫了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); } }
Library1Activity的佈局文件。jvm
<?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; } }
這裏面也有兩個類。而後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; } }
這裏面一樣有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; } }
這裏面沒有任何類,主要是有一個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。
不過對於本工程,如今是沒有報錯了。主要修改的就是一些路徑問題了。這個看看就知道修改哪裏。全局參數那裏沒改,就用它原來默認的就好。
1.clean一下工程。
2.點擊Gradle。
點擊這個以後,就開始合併module了。
幸運的話,在main-library的build文件夾的outputs文件夾的aar文件夾下就生成了咱們要的 main-library-release.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,這裏使用了第三方庫(爲了測試遠程依賴是否成功打包)