我们知道,在同一个Android虚拟机或者同一个应用市场上,每个apk都必须具有惟一的包名,即package name,现在也叫做applicationID,即包名是一个apk的唯一标识符。同时,每个apk都具有一个签名,签名则不要求唯一性,一般而言,同一个开发者、公司或组织所发布的apk,都会使用该开发者、公司或组织统一的签名,在进行apk升级时,首先通过包名确定要升级的apk,然后进行签名验证,签名验证通过之后才能执行升级。由此,包名保证了apk的唯一性,而签名则用于验证apk的合法性。
我昨天遇到一件怪事,公司开发的两个apk,安装其中一个以后,另一个无法安装。一些手机提示“安装错误”,另一些手机提示“包名冲突”。
首先想到的是,难道两个apk使用了同样的包名,而签名不一致从而导致安装存在冲突。读取这两个apk的包名发现,其包名并不一致,所谓的包名冲突并不成立。
后来发现,两个apk的内容提供器provider使用了同样的授权。
<provider
android:authorities="list"
android:enabled=["true" | "false"]
android:exported=["true" | "false"]
android:grantUriPermissions=["true" | "false"]
android:icon="drawable resource"
android:initOrder="integer"
android:label="string resource"
android:multiprocess=["true" | "false"]
android:name="string"
android:permission="string"
android:process="string"
android:readPermission="string"
android:syncable=["true" | "false"]
android:writePermission="string">
. . .
</provider>
android:authorities
标识内容提供器范围内的数据URI的授权列表,有多个授权时,要用分号来分离每个授权。为了避免冲突,授权名应该使用Java样式的命名规则(如:com.example.provider.cartoonprovider)。通常,用ContentProvider子类名称来设定这个属性。
这个属性没有默认值,至少要指定一个授权。
除了包名以外,每个apk的provider的授权也必须具备唯一性,一般使用该apk的包名+provider的名称,即ContentProvider子类名称来作为授权的名称,足可确保唯一性。
我们的开发人员由于项目拷贝时没有修改新项目的provider的授权名称,从而导致两个apk的provider名称冲突,无法安装到同一个Android虚拟机上。
如何避免这一问题呢?
首先是开发新项目时直接新建项目,不要偷懒直接拷贝旧项目然后修改。但这是很难避免的,因为一些项目确实有很多共同性,拷贝拷贝效率更高。
再就是要求拷贝之后除了修改包名之外,一定要记得修改provider的授权,而这就很依赖人了,而人是经常靠不住的,工作忙起来难免有遗漏。
我的想法是在配置provider的授权android:authorities时,不要直接给授权指定一个字符串,而是引入字符串变量,即将apk的包名作为一个变量直接引入授权,这样就可以保证不同apk,只要包名不一样,他们的provider授权也就不一样。
如何引入包名变量呢,一种自然而然的想法是通过@符读取一个代表包名的变量,但这样的话开发人员就得去维护多一个变量;更好的办法是通过${applicationId}直接引入apk的包名。
<provider
android:authorities="${applicationId}.provider"
android:enabled=["true" | "false"]
android:exported=["true" | "false"]
android:grantUriPermissions=["true" | "false"]
android:icon="drawable resource"
android:initOrder="integer"
android:label="string resource"
android:multiprocess=["true" | "false"]
android:name="string"
android:permission="string"
android:process="string"
android:readPermission="string"
android:syncable=["true" | "false"]
android:writePermission="string">
. . .
</provider>
android:authorities="${applicationId}.provider"
这样,在授权里直接读取包名,没有任何负担。