三件事:Aar, Manifest和Activity-Alias

前几天在做需求的时候,接触了一些之间没有太了解的东西,于是今天找个机会写下来给大家分享一下,说不定大家以后也会有这种需求,可以提前了解一下,主要有三个点:aar文件的类型,Manifest文件的自动合并以及Activity-Alias

关于aar文件

为什么会存在这种文件格式,和jar文件有什么区别?先来句综述:aar文件是建立在jar文件的基础之上,aarjar文件的一个变种。其实他们本质上没有什么区别,它们都是压缩包,只是能包含的内容不一样,aar包括的东西更多一些,jar也能包含资源文件,不过是文本资源和图片资源,不能包含Android平台下的drawable以及各种xml文件,aar相对来说更包容一些,aar文件结构其实更类似我们应用的apk安装包,我们通过Gradle compile进来的库也都是以aar文件引入进来的。

具体怎么生成一个aar文件以及引入本地aar文件,可以参考Android Developer的文档进行操作,因为官方的文档已经描述的很清楚了,所以我在这里也没必要在赘述什么,有兴趣的可以直接传送。传送门(自备梯子)

Manifest 文件合并

刚才其实忘了说,aar文件中除了必要的class文件,还有一些必要的文件,其中就包括AndroidManifest文件,但是我们也知道,一个应用只能有一个Manifest文件,那当我们引入其他依赖库的Manifest文件是怎么处理的呢?没错,就是合并,最终所有的Manifest文件都会被合并成一个Manifest文件,那么我们要讨论的问题就出现了,按照什么顺序合并呢?合并出现冲突怎么解决呢?接下来慢慢解释:

合并优先级

优先级从高到低依次为:

  1. build.gradle 配置
  2. app module 的Manifest文件,也就是我们主项目中的Manifest文件
  3. 其他依赖库的Manifest文件

下面这张图解释了优先级的作用

除了合并的优先级,还有一些潜在的合并规则也需要注意一下:

  1. <manifest>节点中的属性都会已最高优先级指定的值为准,所以不会出现值不一样导致的冲突。这也解释了为什么我们只要在build.gradle文件中指定了versionCode或者versionName,那么项目的Manifest文件中配置的相应属性总是会被覆盖。

  2. <uses-feature><uses-library>节点中的android:required属性会在多个配置中采用或运算,也就是说,只有有一个配置设置了true,那这条属性就是true,表示这个feature或者library是必需的。

  3. <uses-sdk>中的属性,一般情况下,也是以最高优先级的配置为准,不过也有两种特殊情况:

    1. 如果低优先级的库的minSdkVersion更高,这时候需要处理一下冲突,可以使用overrideLibrary来覆盖因入库的minSdkVersion,这样冲突是解决了,但是这样会存在隐患问题,一般第三方库的minSdkVersion用来表示最低可运行版本,你如果通过覆盖的方式解决了冲突,但是在低版本运行的时候,可能会出问题,所以更安全的解决方式就是调高自己的minSdkVersion或者换用其他库
    2. 低优先级的库的targetSdkVersion更低,表示这个库还没有为更高的版本做好适配的准备,这时候虽然合并Mainfest文件并并不会出现冲突,但是也会存在一些隐患问题,比如在targetSdkVersion低于15,并且声明了READ_CONTACTS的权限,那么在更高的版本上需要额外的增加READ_CALL_LOG权限,以让应用正常运行,而这件事,合并工具会自动帮我们完成。
  4. <intent-filter>标签不会被匹配,它们每一个都会被认为是独一无二的,然后一起加在相同的父标签里。

除了通过既定的规则,我们可以猜到最后合并的Manifest文件大概是什么样子,我们还可以直接在Android Studio中直观地实时看到合并以后的Manifest文件,就是在Manifest文件的底部有一个Tab可以看到Merged Manifest,在这里,我们可以清楚的看到我们最终的Manifest文件是什么结构,看有没有恶意的第三方库在我们的Manifest文件中动手脚。
官方示意图

除了这些基本的规则,我们还可以在Manifest文件中定义自己的规则,以防止冲突的出现或者当冲突出现的时候,用来解决冲突,因为具体规则比较多,所以就不在这里展开,大家都兴趣或者有需求的可以直接上Android Developer上看官方的文档,传送门(自备梯子)

Activity-alias

1
2
3
4
5
6
<activity android:name="me.shaohui.shareutil._ShareActivity"
android:theme="@android:style/Theme.Translucent.NoTitleBar" />
<activity-alias
android:name="$me.shaohui.shareutil.wxapi.WXEntryActivity"
android:exported="true"
android:targetActivity="me.shaohui.shareutil._ShareActivity"/>

大家通过字面意思就能看得出来,这个元素就是给TargetActivity属性所指定的Activity设定一个别名,之前一直不知道在什么情况需要给Activity指定别名,这次在做一个社会化分享库的时候,突然明白了Activity-Alias存在的意义了。

Android微信的分享SDK为了接受微信请求的回调以及返回值,需要在应用包名相应的目录下新建一个wxapi目录,并且在该目录下新增一个名为WXEntryActivityActivity,处理好以后,会在发起微信请求以及请求完成以后,拉起这个Activity。这对我们使用者来说,单独为微信创建一个目录存放一个特定的Activity是个很蛋疼的一个操作,这样会打乱我们的项目结构,而且看起来也很是不优雅,苦思冥想许久,怎么能避免这样的尴尬,最后遇到了Activity-Alias这种解决方案,觉得简直就是碰到了亲人,

最后的解决方案就是大家前面看到的那段代码,我只需要在普通目录下定义我们接收微信回调的Activity,然后再给这个Activity定义一个me.shaohui.shareutil.wxapi.WXEntryActivity别名,这样就既满足了微信的调用需求,还不用给这个Activity进行特殊化处理,只要微信通过这个别名就能找到我们的目标Activity,我们的目标Activity可以随意按照我们既定的目录结构存放,不需要特殊处理,皆大欢喜。

Activity-Alias也有一些自己的属性,大部分都是和Activity节点的属性类似,只有一个targetActivity需要我们重点关注,它定义了这个别名是给哪个Activity设置的,而且要求指定的Activity必须在Activity-Alias之前被声明,否则最后应用安装的时候会出问题。

目前想到的就这些,以后再有新的想法再慢慢扩充,如果有哪写的不对的地方,欢迎大家指出,谢谢!

参考链接

  1. https://developer.android.com/studio/projects/android-library.html
  2. https://developer.android.com/studio/build/manifest-merge.html
Fork me on GitHub