iOSではまだ実装されていません。しかし、webview.apkは普通のapkではありません。 まず、アイコンがないので、クリックして起動する「アプリ」とはみなされません。同時に、このAPKをアップデートすると、webviewを使用しているすべてのアプリがアップデートされ、進む、戻るなどのwebviewのUIまでアップデートされます。
これはどのように機能するのでしょうか?今日はAPKウェブビューを分析します。
Android リソースID
Androidのパートナーを開発したことがある人なら、Rクラスに慣れているでしょう。Rクラスは、すべての「文字列」は理解できますが、16進数の束は、このようなR longを見るように、あまりなじみがないかもしれません:
public class R {
public static class layout {
public static final int activity_main = 0x7f
}
}
最後の16進数は一般的にリソースIDと呼ばれ、Rに詳しい方なら、リソースIDにはパターンがあることをご存知でしょう。
0xPPTTEEEE
このモデルは、異なるシナリオで「同じ意味」のリソースを使うのにとても便利です。AndroidにはAssetManagerというクラスがあり、Rからid値を読み込み、resources.arscというテーブルで特定のリソースのパスや値を探してアプリに返す役割を担っています。resources.arscと呼ばれるテーブルで特定のリソースのパスまたは値を見つけ、アプリに返します。
プラグイン化におけるリソース固定
そのため、リソース ID が一度生成されると、それを移動させることはできないということが期待されています。もしpackageIdが常に7fであるなら、packgeIdを変更する何らかの解決策があることを知るだけでは明らかに不十分で、異なるビジネスパッケージで異なるpackageIdを使用するだけで、id衝突の問題を大幅に回避でき、外部リソースのプラグイン使用の条件を提供できます。
もちろん、答えはイエスです。
WebView APKとアンドロイドのシステムリソース
自慢のandroid sdkのandroid.jarが提供するリソースを見てみましょう。
直感は私に、1この値も非常に特別な、この01の実装では、実際には、推測することによっても行う方法を知っている - リソースpackageIdのandroid.jarは01です参照してください予約IDとしてpackageId 01は、リソースのidのアンドロイドシステムは永久に固定されています!その後、すべてのアプリは、例えば、リソースの色/黒をチェックしに行く0x0106000cである上記の表の結果を確認し、私は少なくとも@アンドロイドの私のバージョンのすべてのアンドロイド携帯電話であることを確認して、0x01で始まるリソースは、常にOKになります得る:色/黒リソースのIDはすべて0x0106000cです。0x0106000c.私はそれを証明するためにデモを行うことができます、私はxmlファイルをコンパイルします:
<?xml version="1.0" encoding="utf-8"?>
<ImageView
xmlns:android="http://..///id"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/black">
</ImageView>
コンパイルされた結果をご覧ください。
android:backgroundの値が@ref/0x0106000cに変わっていますね。 このapkがAndroid携帯で実行されると、AssetsManager内に2つのリソースパッケージが読み込まれます。1つは独自のアプリリソースパッケージで、もう1つはandroidフレームワークリソースパッケージです。0x0106000cを探すと、システムのリソースが見つかります。
これらの好奇心を胸に、私はaaptのソースコードをダウンロードし、真実の世界を探求する準備をしました。
すべてがわかるAAPTソースコード
まず、ResourceType.hで定義されている0xPPTTEEEEである理由を知るために、R以下の値の定義を見てみましょう。
#define Res_GETPACKAGE(id) ((id>>24)-1)
#define Res_GETTYPE(id) (((id>>16)&0xFF)-1)
#define Res_GETENTRY(id) (id&0xFFFF)
#define APP_PACKAGE_ID 0x7f
#define SYS_PACKAGE_ID 0x01
最初の3行がidの定義で、最後の2行が特別なpackageIdファクトです。さて、01はシステムパッケージリソースとして識別され、7fはAppパッケージリソースとして識別されます。
これを理解した上で、ウェブビューのリソースを使用する方法は、以下の例のようになります:
<?xml version="1.0" encoding="utf-8"?>
<ImageView
xmlns:android="http://..///id"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@com.google.android.webview:drawable/icon_webview">
</ImageView>
res/layout/layoutactivity.xml:2: error: Error: Resource is not public ...
aarプライベートリソースを使用する場合、完全な名前を綴ることができる限り、それを強制することが可能です。同時に、apk、実際には、このリソースへの参照を強制する方法があり、私もソースコードを見て結論づけたのですが、具体的にはResourceTypes.cppに、関連するコードがあります:
bool createIfNotFound = false;
const char16_t* resourceRefName;
int resourceNameLen;
if (len > 2 && s[1] == '+') {
createIfNotFound = true;
resourceRefName = s + 2;
resourceNameLen = len - 2;
} else if (len > 2 && s[1] == '*') {
enforcePrivate = false;
resourceRefName = s + 2;
resourceNameLen = len - 2;
} else {
createIfNotFound = false;
resourceRefName = s + 1;
resourceNameLen = len - 1;
}
String16 package, type, name;
if (!expandResourceRef(resourceRefName,resourceNameLen, &package, &type, &name,
defType, defPackage, &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(), package.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(accessorCookie, "Resource is not public.");
}
return false;
}
}
}
// ...
}
<?xml version="1.0" encoding="utf-8"?>
<ImageView
xmlns:android="http://..///id"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@*com.google.android.webview:drawable/icon_webview">
</ImageView>
これはプライベートリソースへの直接参照を無視し、もう一度 aapt を使ってコンパイルすることで、リソースが正常にコンパイルされたことを意味します。コンパイルされたファイルの表示
DynamicRefTable
if (accessor) {
rid = Res_MAKEID(
accessor->getRemappedPackage(Res_GETPACKAGE(rid)),
Res_GETTYPE(rid), Res_GETENTRY(rid));
if (kDebugTableNoisy) {
ALOGI("Incl %s:%s/%s: 0x%08x
",
String8(package).string(), String8(type).string(),
String8(name).string(), rid);
}
}
uint32_t packageId = Res_GETPACKAGE(rid) + 1;
if (packageId != APP_PACKAGE_ID && packageId != SYS_PACKAGE_ID) {
outValue->dataType = Res_value::TYPE_DYNAMIC_REFERENCE;
}
outValue->data = rid;
このコードはいくつかのことを物語っています:
aapt d--values resourcesout.apk
リソース情報を出力するコマンドを使用すると、次のことがわかります。TYPEDYNAMICREFERENCEとDynamicRefTableに関連するコードをクエリすると、次の関数が見つかりました:
status_t DynamicRefTable::lookupResourceId(uint32_t* resId) const {
uint32_t res = *resId;
size_t packageId = Res_GETPACKAGE(res) + 1;
if (packageId == APP_PACKAGE_ID && !mAppAsLib) {
// No lookup needs to be done, app package IDs are absolute.
return NO_ERROR;
}
if (packageId == 0
(packageId == APP_PACKAGE_ID && mAppAsLib)) {
// The package ID is 0x00. That means that a shared library is accessing
// its own local resource.
// Or if app resource is loaded as shared library, the resource which has
// app package Id is local resources.
// so we fix up those resources with the calling package ID.
*resId = (0xFFFFFF & (*resId)) | (((uint32_t) mAssignedPackageId) << 24);
return NO_ERROR;
}
// Do a proper lookup.
uint8_t translatedId = mLookupTable[packageId];
if (translatedId == 0) {
ALOGW("DynamicRefTable(0x%02x): No mapping for build-time package ID 0x%02x.",
(uint8_t)mAssignedPackageId, (uint8_t)packageId);
for (size_t i = 0; i < 256; i++) {
if (mLookupTable[i] != 0) {
ALOGW("e[0x%02x] -> 0x%02x", (uint8_t)i, mLookupTable[i]);
}
}
return UNKNOWN_ERROR;
}
*resId = (res & 0x00ffffff) | (((uint32_t) translatedId) << 24);
return NO_ERROR;
}
結論をいくつか出してください:
packageIdが0x7fの場合、変換しなくても元のIDのままです。
そうでない場合は、mLookupTableテーブルからマップを作成し、translatedIdとして返します。
void AssetManager2::BuildDynamicRefTable() {
package_groups_.clear();
package_ids_.fill(0xff);
// 0x01 is reserved for the android package.
int next_package_id = 0x02;
const size_t apk_assets_count = apk_assets_.size();
for (size_t i = 0; i < apk_assets_count; i++) {
const ApkAssets* apk_asset = apk_assets_[i];
for (const std::unique_ptr<const LoadedPackage>& package :
apk_asset->GetLoadedArsc()->GetPackages()) {
// Get the package ID or assign one if a shared library.
int package_id;
if (package->IsDynamic()) {
//LoadedArscでは、もし packageId == 0と定義されているアプリは DynamicPackage
package_id = next_package_id++;
} else {
//そうでなければ、自分の packageId
package_id = package->GetPackageId();
}
// Add the mapping for package ID to index if not present.
uint8_t idx = package_ids_[package_id];
if (idx == 0xff) {
// packageIdを記録し、それをメモリに代入してパッケージにバインドする。
package_ids_[package_id] = idx = static_cast<uint8_t>(package_groups_.size());
package_groups_.push_back({});
package_groups_.back().dynamic_ref_table.mAssignedPackageId = package_id;
}
PackageGroup* package_group = &package_groups_[idx];
// Add the package and to the set of packages with the same ID.
package_group->packages_.push_back(package.get());
package_group->cookies_.push_back(static_cast<ApkAssetsCookie>(i));
// また、DynamicRefTableのパッケージ名とpackageIdの対応を変更する。
// Add the package name -> build time ID mappings.
for (const DynamicPackageEntry& entry : package->GetDynamicPackageMap()) {
String16 package_name(entry.package_name.c_str(), entry.package_name.size());
package_group->dynamic_ref_table.mEntries.replaceValueFor(
package_name, static_cast<uint8_t>(entry.package_id));
}
}
}
// O(n^2) そのためには、以下のメソッドを使って、すでにキャッシュしたDynamicRefTableのパッケージ名をすべて追加すればいい。 -> id 2つのアプリの関係はすべてリマップされている。
// Now 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 != package_groups_end; ++iter) {
const std::string& package_name = iter->packages_[0]->GetPackageName();
for (auto iter2 = package_groups_.begin(); iter2 != package_groups_end; ++iter2) {
iter2->dynamic_ref_table.addMapping(String16(package_name.c_str(), package_name.size()),
iter->dynamic_ref_table.mAssignedPackageId);
}
}
}