前言
主要参考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