这一点在 iOS 中尚未完成,(iOS OTA 的历史也不是特别的悠长)。可是 webview.apk 不是一个普普通通的 apk,首要它没有图标,不算是点击启动的“App”。一起,更新这个 APK,会让一切运用 webview 的运用都得到更新,哪怕是 web t Z Ibview 中的 UI ,比方前进撤退也相同,得到更新。
这一点是如何做到的呢?今天咱们来分析下 webviewU g . G 这个独特的 APK。
Android 资源和资源ID
假如开发过 Android 的小伙伴,对 R 这个类A ( X / H是了解得不能再了– , ? s A解了,一个 R 类,里边一切的“字符串”咱们都看得懂,可是一堆十六进制的数字,咱们或许并不是非常的了解,比方看见一个 R 长这样:
publib v a ` v 1 U ac class R {
public static class layout {
public static final int activity_main = 0x7f020000
}
}
后面那串十六进制的数字,咱O V D们一般称之为资源 ID (resId),假如你对 R 更了解一点,更能够知道资源 id 其实是有规则的,它的规则大概是
0xPPTTEEEE
咱们知道 android 针对不同机型以及不同场景,界说了许许多多 config,最经典的多语言场景:values/values-en/values-zh-CN 咱们运用一个字符串资源或许运用的是相同的 ID,可是拿T x ( o到的详细值是不同的9 X f ,。这个模型便是一个表模型 —— id 作为主键,查询到一行数据a 5 j,k a W D g再依据实际情况选择某一列,一行一列确认一个终究i P k ` a P V C b值:
这种模型对咱们在不同场景下需求运用“同一含义”的资源供给了非常大的便捷。Android 中= A ] T E 9 |有一个类叫 AssetManager 便是担任读取 R 中的 id 值,终究到一个叫 resour: H X 5 4ces.arsc 的表中找到详细资源的路径或许值回来给 App 的。
插件化中的资源固定
因而,咱们期望资源 iI 2 R + g ` Q ,d 一旦Y 5 S F X [ | R生成,就不要再动来动去了。可是这儿又有一个非常显眼的问题:假如 packageId 永远是 7f,那么显然是不够用的,咱们知道有一定的计划能够更改 packgeId,H I B – ) J只需在不同事务包中运S D S a T用不同的 packageId,这样能极大防止 id 碰撞的问题,为插件化运用外部资源供给了条件。
答案当然是必定的。
WebView APK 和 android 体系资源
咱们再看下大名鼎鼎的 android sdk 中的 android.jar 供给的资源。
咱们看到,android.jar 中资源的 packageId 是 01。直g ? v # i y }觉告知我,1 这个值也很特别,(2 看上去就不那么特别了)这个 01 的完成,其实靠猜也知道是怎样做的 —— 把 packageId 01 作为保留 id,androi] G g T 3 ? 1 Wd 体系中资源的u @ q – 0 / # V x id 永久固定,那么一切 app 拿到的 0x01 最初的资源永远是确认的,比方,咱们去检查 color/black 这个资源,检查上面那张表里的结果是 0x0106000c,那么我至少确认我这个版别一切 anc _ P @ – 7 e Gdroid 手机的a # M y 0 @android:color/black 这个资源的 id 全都是 0x0106000c。咱们能够做一个 demo 为证,我编译一个xml文件:
<?xml version="1.0" encoding="utf-8"?>
<ImageView
xmlns:android=~ 0 - F f 1 k K"http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android@ / G 9 % 3 C Z S:layout_height="match_parentn c G K n"
android:background="@android:cc Z y golor/black">H H W 1 r ~;
</ImageView>
然后检查_ D i P z编n ~ M t m ! Y d ?译出来的结果
咱们看见 android:background 的值变成了 @ref/0x0106000c。这个 apk 在 Android 手机上运转的时分,会在 AssetsManager 里边加载两个资源包,一个是自己的 App 资源7 – 7 g S h M r q包,一个是 android framework 资源包,这时分去找 0x0106000c 的时分,就会找p O . h v l到体系的资源里边去。
带着这些猎奇,我下载了 aapt 的源码,预备在真相世界( F X !里一探终究。
AAPT 源码,告知你一切
咱们首要能够先瞅一眼O 5 h & c $ o,R 下面值的界说为什么是 0xPPTTEEEE,这个界说在 ResourceType.h,一起咱们发现了以下几行代码
#define Res_GETPACKAGE(id) ((id>&. Z % ogt;24)-1)
#define Res_GETTYPE(id) (((id>P n H 1 K V;>16)&0xFF)-1)
#define Res_GETENTRY(id) (id&0xFFFF)
#define APP_PACKAGE_ID 0x7f
#define SYS_PACKAGE_ID 0x01
前三行是 id 的L 7 . | i k & l界说,后两行是特别 packagD = b `eId 实锤。好了,01 被认定是体系包资源,7f 被认定为 App 包资源。
知道1 w # w f | K z v这点今后,咱们运用 webview 中的资源的方法就变成如下比如:
<?xml version="1.0" encoding="utN k C Tf-8"?>
<Imm S { c | V g m CageView
xmlnK - L ^ 2 [ 1s:android="http://schemas.android.A k [com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@com.google.android.webview:drawable/icon_wK 9 t q 3 1 Rebview">
</ImaA N * T D K g hgeView>
res/layout/layoutactivity.xml:2: era I 6 = %ror: Error: Resource is not public. (at ‘src’
with value ‘@cD % E 7 . Y yom.google.android.webview:drawable/iconwebview’).
在运用 aa0 r C Y 2r 私有资源的时分,咱们只需能拼出悉数称号,是能够强行运用的。一起,apk,其实也有办法强行引证到这个资源,这一点我也是通过检查源码的方法得到定论的,详细在 ResourceTypes.cpp 中,有相关的代码:
b; Z U Y h y e P :ool createIfNotFound = false;
const char16_t* resourceRefName;
int resourceNameLen;
if (len > 2 &&? ( 9 } S y ` + G; s[1] == '+K @ * N m B @ n z') {
createIfNotFound = true;
resourceRefNa2 . Ime = s + 2;
resourceNameLen = len - 2;
} else iS | | y 7 S v E Yf (le? g `n > 2 && s[1] == '*{ + ~ ! A G B') {
enforcePrivate = false;
resourceRef3 ) . GName = s + 2;
resourceNameLen = len - 2;
} else {
createIfNotFound = false;
resourceRefName = s + 1;
resourceNameLen = len - 1;
}
String16 package, tB 7 [ 6 . Jype, name= 7 ] Z;
if (!expandResourceRef(resourceRefNP o @ 7 L & )ame,resou^ [ A c 7 O ( ) -rceN2 ` 4 qameLen, &package, &type, &name,
defType, defPackag] Q = e - O - 5e, &errorMsg)) {
if (accessor != NULL) {
accessor->reportError(accessorCookie, errorMsg);
}
return false;
}
uint32_t specFlags = 0;
uint32_t rid = identifierForName(name.string(), name.size(), type.string(),
type.size()m V U, packagv } qe.string(), package.size(), &specFlags);
if (rid != 0) {
if (enforcePrivate) {
if (accessor == NULL || accessor->getAssetsPackage() != package) {
if ((specFlags&ResTable_typeSpec::SPEC_PUBLIC) == 0) {
if (accessor != NULL) {
accessor->reportError(accesso( l R n p z - [ `rCookie, "ResZ E u -ource is notJ @ 2 a y f v public.y h F # J")w - s l V;
}
return false;
}
}= ^ a $
}
// ...
}
<?xml ve| V P p i C 0rsioU s } 9 U Y Xn="1.0" encoding="utf-8"?>
<ImageView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_pa] _ $ N F 4 Brent"
android:sr4 / o [ ^ ic="@*com.google.and( S : ` } % qroid.webview:drawa* / sble/icon_webU ( V # L Zview">
</ImageView>
留意 @ 和包名之间多了一个 *,K E V l T % m F这个星号,便是无视私有资源直接引证的意思,再一次运用 aapt 编译,资源编译成
功。检查编译出来的文件
DynamicRefTable
if (accessor) {
rid = Res_MAK` 4 y D ? , F EEID(
accessor->getRemappedPackage(Res_GETPACKAGE(rid)),
Re` w h * D y Q !s_GETTYPE(rid), Res_GETENTRY(rid));
if (kD) M Y ] 3 UebugTableNoisy) {
ALD ( I e &OGI("Incl %s:%s/%s: 0x%08x\n",
String8(packE - { kage).string(), String8(type).string(),
String8(name).string(), rid);
}
}
uint32_t packageId = Res_GETPACKAGE(rid) + 1;
if (packageId != APP_PACKAGE_IDY k | L x 5 && packagh g 1eId != SYS_PACKAGE_ID) {
outValue-R - m v ; w>dataType = Res_value::TYPE_DYNAMIC_REFERENCE;
}
outValue->data = rid;
这段代码告知咱们几件事:
-
刚刚的 webview 的 packageIdS b ) } t n 6 是通过 remapp 后的
-
它的类型变成了 TYPEDYNAMICREFERENCE
a & / E D @ ( rapt d--values resourcesout.apk
指令把资源E z ?信息打印出来,能够发现咱们查询 TYPEDYNAMICREFERENCE 和 DynamicRefT& b D @able 有关的代码,找到了这么一个函数,咱们看下界说:
status_t DynamicRe! K .fTable::look: l d 1upReso6 s w y SurceId(uint32_t* resId) const {
uint32_t res = *resId;
size_t packageId = Res_GETPACKAGE(res) + 1;
if (packageId == APP_PACKAGE_ID g l l h = P [ - &H M ( l P xamp;& !mAppAsLib) {
// No lookup needs to be done, app package IDs are absolute.
return NO_ERROR;
}
if (p7 p uackageId == 0 || (packageId == APPm D W 8_PY : ] 1 Q O $ ^ACKAGE_ID && mAppAsLib)) {
// Th) S P N S + 3 1e package ID is 0x00. That means that a shN p Sared library is accessingc $ _ % m
// its ownO - C , ` S local resource.
// Or if app resource is loaded as share` F g H Nd library, the resource which has
// app package Id is local resources.
// so we fix up those resources with the calling package ID.
*resId = (0xFFFFFF & (*re? [ F GsId)) | (((uint32_t) mAs] [ H k 5 RsignedPackageId) << 24);
ret$ ! Eurn NO_E 8 e , d T K f cERROR;
}
// Do a proper lookup.
uint8_t translatedId = mLookupTable[packageId];
if (translatedId == 0) {
ALOGW("Dynamp X 6icRefTablew B } B # d 1(0x%02x): No mapping for build-time package ID 0x%02x.",
(= O ) T % } r r Cuint8_t)mAssignedPackageId, (5 Q j v ! R . H $uint8_t)pack^ Z D H ?ageId);
for (size_t i = 0; i < 256; i++^ 8 j) {
if (mLookupTable[i] != 0) {
ALOGW("e[0x%02x] ->7 R y a; 0x%02x", (uint8_t)i, mLookupTable[i]);
}
}
retu& C & R 2 d d s 2rn UNKNOWN_ERROR;
}
*resId = (res & 0x00ffffff) | (R F f c 5 n z R ((uint32_t) translatedId) << 24);
return No & ^O_ERROR;
}
得到几个定论:
-
假如 packageId 是 0x7f 的话,不转化,本来的 ID 仍是本来的 ID
-
假如 packageId 是^ # % { , 4 0 或许 packageId 是 7f 且 mAppAsLib 是真的话,把 packgeId 换成 mAssig1 o X j L fnedPackageId
-
否则从 mLookupTA J 4able 这个表中做一个映射,换成 translatedId 回来。
void AssR ( ( Y f } J ^ @etManager2::BuildDynamicRefTablw o ne() {
package_groups_.cleare o 6 O % q B();
package_ids_.fill(0xff);
// 0x01 is reserved for the android package.
int next_package_id = 0x02;
const size_t apk_assets_count = apk_f W h kassets_.size();
for (size_t i = 0; i < apk_assets_count; i++) {
const ApkAssets* apk_asset = apk_assets_[i];
for (const std::uni^ 8 P Cque] { s_ptr<consZ ^ Z d | Z ! :t LoadedPackage>& package :
apk_asset->GetLoadedArsc()->GetPackages()) {
// Get the package ID or assign one if a shared library.
int package_id;
if (package->IsDynamic()) {
//S @ { M m在 LoadedArsc 中,发现假如 packageId == 0,就被界说为 DynamK H E 7 Z j 7icPackage
package_id = next_pacH u e / x P t /kage_id++;
} ec P Else {
//否则运用自己界说的 packageId (非0)
package_id = package->GetPackageId(` F);
}
// Add the mapping for package ID to index if not pM l %resent.
uint8_t idx = package_ids_[package_id];
if (idx == 0xff) {
// 把这个 packageId 记录下来,并赋值进内存中和 pa, k 7 5 a .ckage 绑定起来
package- 0 G_ids_[package_id] = idx = static_cast<uint8_t>(package_groups_.size());
package_groups_.push_back({});
package_groups_.back().dynamic_ref_table.mAssignedPackageId = package_id;
}
Pac] | 0 O & M H Z ^kageGroup* package_group =- , p Y &package_groups. R Z U_[idx];
// Add the package and to th[ C . k D @ ! .e set of packal h 9 5 oges with the same ID.
package_group-&F & Q t 5 - V *gt;packages_i g U n J } | 0.push_back(package.T ] F = a h z [ vget());
package_group->cookies_.push_back(static_cast<ApkAssetsCookie>(i));
// 一起更改 DynaU w YmicRefTable 中 包名 和 ph l O k , D tackageId 的对应联系
// Add the package name -> buiB [ . | /ld time ID mappings.
for (cons( , p 3 I X B &t DynamicPackageEntry& entry : package->GetDynamicPackD Z * Y M g xageMap()) {
String16 package_name(entry.package_ # p G = wname.P o ) Z 6 ( Y B cc_str(), entry.package_name.size());
package_group->dynamic_ref_table| ) R V g 3.S 2 . j ! a / ImEntries.replaceValueFor(
package_name,/ { a v ! ~ & static_cast<uint8_t>(entry.package_id));
}
}
}
// 运用 O(na R Y j^2) 的方法,把现已缓存的一切 DynamicRefTable 中的 包名 -> id 的联系悉数重映射一遍
// Nowd G X P F assign the runtime IDs so that we have a build-time to runtime ID map.
const auto package_groups_end = package_groups_.end();
for (auto iter = package_groups_.begin(); iter != p3 n `ackage_groups_ee ~ Q ? C ) d `nd; ++iter) {
const std::string& pa/ Y m rckage_name = iter->packages_[0]->GetPackageName();
for (auto iter2 = package_groups_.begin(); iter2 != package_groups_end; ++iter2) {
iter2->dynamic_ref_tablu | ] 5 B { R L Ce.addMapping(SW ? o ~tring16(package_name.c_str(I 8 L _), package_name.size()),
iter->dy% | J / znamc ) o I & ( & $ /ic_ref_table.mAssignedPackageI` # H *d);
}
}
}
上面的中文注释是我加的,这一段逻辑其实很简单,咱们通过这样的处理,完成了 buildId -> runtimeId 的映射。也便是说,WebView 的 packageId 是在运转时动态计算生成的!
总结
-
仍然需求手动办理 packageId。
-
把 aapt 中关于 d^ T Hynamic refeP i 2 7 / Crence 的当地改成 reference。