序文
ホットリペアテクノロジーは、現在のAndroid開発において比較的高度で人気のある知識ポイントであり、中級開発者が高度な開発に進むために習得しなければならないスキルです。同時に、Android業界では、ホットリペア技術が盛んになっており、大手メーカーが独自のホットリペアソリューションを立ち上げており、使用する技術ソリューションも異なります。もちろん、ソリューションごとに限界があります。この記事を組み合わせて詳しく説明することで、これらの熱修復スキームの比較と実装の原則を理解し、熱修復技術の本質を習得し、実際のプロジェクトに実践を適用して、学んだことを適用できるようになることを願っています(研究ノートは記事の最後で共有されます)。
ホットフィックスとは
簡単に言えば、オンラインの問題を修正するために、パッチ適用プロセスを再発行する必要はありません。
ホットフィックスを学ぶ理由
通常のソフトウェア開発プロセスでは、オフライン開発->オンライン->バグが見つかりました->緊急修理およびオンライン。ただし、この方法はコストがかかりすぎるため、次の問題を回避することはできません。
- オンライン版の開発はバグがないことを保証できますか?
- 修復されたバージョンは、ユーザーが時間内に更新されることを保証できますか?
- オンラインバグがビジネスに与える影響を最小限に抑える方法は?
対照的に、ホットフィックス開発プロセスはより柔軟で、再リリースの必要がなく、リアルタイムで効率的なホットフィックス、新しいアプリケーションをダウンロードする必要がなく、低コストであり、最も重要なことはバグを時間内に修正することです。また、ホットリペア技術の開発により、コードだけでなく、リソースファイルやSOライブラリもリペアできるようになりました。
適切な熱修理技術ソリューションを選択する方法は?
この記事は、現在、主要な工場が独自の熱修理ソリューションを立ち上げていると述べたところから始まりました。では、学習に適した一連の熱修理技術をどのように選択するのでしょうか。次に、現在の主流のホットリペアソリューションを比較して、答えを示します。
国内の主流の熱修理技術ソリューション
1.アリ部
名前 | 説明 |
---|---|
AndFix | リアルタイムで効果的なオープンソース |
HotFix | Ali Baichuan、オープンソースではなく、無料、リアルタイムで効果的 |
ソフィクス | オープンソースではない、商用料金、リアルタイムの有効/コールドスタート修理 |
HotFixはAndFixの最適化されたバージョンであり、SophixはHotFixの最適化されたバージョンです。ソフィックスは現在、アリの主な推進力です。
2.テンセント
名前 | 説明 |
---|---|
Qzoneスーパーパッチ | QQスペース、オープンソースではなく、コールドスタート修理 |
QFix | ハンドQチーム、オープンソース、コールドスタート修理 |
いじくり回す | WeChatチーム、オープンソース、コールドスタート修理。配布管理を提供し、基本バージョンは無料です |
3.その他
名前 | 説明 |
---|---|
壮健 | 美団、オープンソース、リアルタイム修理 |
ぬわ | ディアンピング、オープンソース、コールドスタート修理 |
友達 | お腹が空いた、オープンソース、コールドスタート修理ですか |
さまざまな熱修復スキームの比較
適切な熱修理計画を選択する方法は
?これは、すべてが需要に依存しているとしか言えません。会社の総合力が強い場合は、自己調査を検討することは問題ありませんが、コストとメンテナンスを考慮する必要があります。次の2つの提案を以下に示します。
- プロジェクトの要件
- 簡単なメソッドレベルのバグ修正のみが必要ですか?
- リソースなどのライブラリの修復が必要ですか?
- プラットフォームの互換性と成功率の要件は何ですか?
- 配布を制御し、監視データの統計を実行し、パッチパッケージを管理する必要がありますか?
- 会社のリソースは商業支払いをサポートしていますか?
- 学習と使用コスト
- 統合の難しさ
- コードの煩わしさ
- 試運転とメンテナンス
- 大廠を選択
- 保証された技術的性能
- 維持する誰かがいる
- 人気が高く、活発なオープンソースコミュニティ
- 支払いを検討している場合は、AliのSophixを選択することをお勧めします。Sophixは、完全な機能、シンプルで透過的な開発、および配布と監視の管理を備えた包括的な最適化の製品です。支払いを検討しない場合は、メソッドレベルのバグ修正のみをサポートする必要があります。リソースなどはサポートされていません。堅牢なものをお勧めします。両方のリソースなどをサポートする必要があると考える場合は、Tinkerをお勧めします。最後に、会社の総合力が強い場合は、最も柔軟で制御可能な自己調査を検討することができます。
ホットリペア技術ソリューションの原則
技術分類
NativeHookの原則
原理と実現
NativeHookの原理は、メソッドの構造情報をネイティブレイヤーで直接交換することで、古いメソッドと古いメソッドの完全な置き換えを実現し、それによってホットリペア機能を実現します。
以下は、AndFixのjniコードの一部を使用した説明です。
void replace_6_0(JNIEnv* env, jobject src, jobject dest) {
// 通过Method对象得到底层Java函数对应ArtMethod的真实地址
art::mirror::ArtMethod* smeth =
(art::mirror::ArtMethod*) env->FromReflectedMethod(src);
art::mirror::ArtMethod* dmeth =
(art::mirror::ArtMethod*) env->FromReflectedMethod(dest);
reinterpret_cast<art::mirror::Class*>(dmeth->declaring_class_)->class_loader_ =
reinterpret_cast<art::mirror::Class*>(smeth->declaring_class_)->class_loader_; //for plugin classloader
reinterpret_cast<art::mirror::Class*>(dmeth->declaring_class_)->clinit_thread_id_ =
reinterpret_cast<art::mirror::Class*>(smeth->declaring_class_)->clinit_thread_id_;
reinterpret_cast<art::mirror::Class*>(dmeth->declaring_class_)->status_ = reinterpret_cast<art::mirror::Class*>(smeth->declaring_class_)->status_-1;
//for reflection invoke
reinterpret_cast<art::mirror::Class*>(dmeth->declaring_class_)->super_class_ = 0;
//把旧函数的所有成员变量都替换为新函数的
smeth->declaring_class_ = dmeth->declaring_class_;
smeth->dex_cache_resolved_methods_ = dmeth->dex_cache_resolved_methods_;
smeth->dex_cache_resolved_types_ = dmeth->dex_cache_resolved_types_;
smeth->access_flags_ = dmeth->access_flags_ | 0x0001;
smeth->dex_code_item_offset_ = dmeth->dex_code_item_offset_;
smeth->dex_method_index_ = dmeth->dex_method_index_;
smeth->method_index_ = dmeth->method_index_;
smeth->ptr_sized_fields_.entry_point_from_interpreter_ =
dmeth->ptr_sized_fields_.entry_point_from_interpreter_;
smeth->ptr_sized_fields_.entry_point_from_jni_ =
dmeth->ptr_sized_fields_.entry_point_from_jni_;
smeth->ptr_sized_fields_.entry_point_from_quick_compiled_code_ =
dmeth->ptr_sized_fields_.entry_point_from_quick_compiled_code_;
LOGD("replace_6_0: %d , %d",
smeth->ptr_sized_fields_.entry_point_from_quick_compiled_code_,
dmeth->ptr_sized_fields_.entry_point_from_quick_compiled_code_);
}
void setFieldFlag_6_0(JNIEnv* env, jobject field) {
art::mirror::ArtField* artField =
(art::mirror::ArtField*) env->FromReflectedField(field);
artField->access_flags_ = artField->access_flags_ & (~0x0002) | 0x0001;
LOGD("setFieldFlag_6_0: %d ", artField->access_flags_);
}
各Javaメソッドは、アートのArtMethodに対応します。ArtMethodは、アクセス権やコード実行アドレスなど、このJavaメソッドのすべての情報を記録します。env-> FromReflectedMethodを使用して、メソッドに対応するArtMethodの実際の開始アドレスを取得し、ArtMethodポインターに強制して、そのすべてのメンバーを変更します。
このように、将来このメソッドが呼び出されると、新しいメソッドの実現に直接進み、ホットリペアの効果を実現します。
利点
- 即時効果
- パフォーマンスのオーバーヘッド、エディターのインストルメンテーションやコードの書き換えはありません
不利益
- 安定性と互換性の問題があります。ArtMethodの構造は基本的にGoogleのオープンソースコードを参照しています。主要メーカーのROMが変更されている可能性があり、構造の不整合や修復の失敗を引き起こす可能性があります。
- 変数とクラスを追加することはできず、メソッドレベルのバグのみを修正でき、新しい関数をリリースすることはできません。
javaHookの原理
原理と実現
Robust of Meituanを例にとると、Robustの原理は次のように簡単に説明できます。
1.基本パッケージを構築するときにスタブを挿入し、各メソッドの前にChangeQuickRedirect静的変数タイプのロジックを挿入します。挿入プロセスはビジネス開発に対して完全に透過的です。
2.パッチをロードするときは、パッチパッケージから置換するクラスと特定の置換メソッドの実装を読み取り、パッチdexをロードするための新しいClassLoaderを作成します。changeQuickRedirectがnullでない場合、accessDispatchを実行して以前の古いロジックを置き換え、修正の目的を達成することができます。
以下のロバストなソースコードを分析してみましょう。
まず、次のように、基本パッケージに挿入されているコードロジックを確認します。
public static ChangeQuickRedirect u;
protected void onCreate(Bundle bundle) {
//为每个方法自动插入修复逻辑代码,如果ChangeQuickRedirect为空则不执行
if (u != null) {
if (PatchProxy.isSupport(new Object[]{bundle}, this, u, false, 78)) {
PatchProxy.accessDispatchVoid(new Object[]{bundle}, this, u, false, 78);
return;
}
}
super.onCreate(bundle);
...
}
Robustのコア修復ソースコードは次のとおりです。
public class PatchExecutor extends Thread {
@Override
public void run() {
...
applyPatchList(patches);
...
}
/**
* 应用补丁列表
*/
protected void applyPatchList(List<Patch> patches) {
...
for (Patch p : patches) {
...
currentPatchResult = patch(context, p);
...
}
}
/**
* 核心修复源码
*/
protected boolean patch(Context context, Patch patch) {
...
//新建ClassLoader
DexClassLoader classLoader = new DexClassLoader(patch.getTempPath(), context.getCacheDir().getAbsolutePath(),
null, PatchExecutor.class.getClassLoader());
patch.delete(patch.getTempPath());
...
try {
patchsInfoClass = classLoader.loadClass(patch.getPatchesInfoImplClassFullName());
patchesInfo = (PatchesInfo) patchsInfoClass.newInstance();
} catch (Throwable t) {
...
}
...
//通过遍历其中的类信息进而反射修改其中 ChangeQuickRedirect 对象的值
for (PatchedClassInfo patchedClassInfo : patchedClasses) {
...
try {
oldClass = classLoader.loadClass(patchedClassName.trim());
Field[] fields = oldClass.getDeclaredFields();
for (Field field : fields) {
if (TextUtils.equals(field.getType().getCanonicalName(), ChangeQuickRedirect.class.getCanonicalName()) && TextUtils.equals(field.getDeclaringClass().getCanonicalName(), oldClass.getCanonicalName())) {
changeQuickRedirectField = field;
break;
}
}
...
try {
patchClass = classLoader.loadClass(patchClassName);
Object patchObject = patchClass.newInstance();
changeQuickRedirectField.setAccessible(true);
changeQuickRedirectField.set(null, patchObject);
} catch (Throwable t) {
...
}
} catch (Throwable t) {
...
}
}
return true;
}
}
利点
- 高い互換性(ロバストは通常DexClassLoaderのみを使用)、高い安定性、および修復成功率は99.9%と高い
- パッチはリアルタイムで有効になり、再起動する必要はありません
- 静的メソッドを含むメソッドレベルの修復をサポートする
- メソッドとクラスの追加をサポート
- ProGuardの難読化、インライン化、最適化、およびその他の操作をサポートします
不利益
- コードは煩わしいものであり、関連するコードが元のクラスに追加されます
- そのため、リソースの交換は一時的にサポートされていません
- apkのサイズが増加します。平均して、関数は17.47バイト増加し、100,000関数は167万増加します。
javamulitdexの原理
原理と実現
Androidは、BaseDexClassLoader、PathClassLoader、およびDexClassLoaderの3つのクラスローダーを使用して、DEXファイルからクラスデータを読み取ります。PathClassLoaderとDexClassLoaderはすべてBaseDexClassLoaderから継承されます。dexファイルはdexFileオブジェクトに変換され、Element []配列に格納され、findclassはElement配列を順番にトラバースしてDexFileを取得し、DexFileのfindclassを実行します。ソースコードは次のとおりです。
// 加载名字为name的class对象
public Class findClass(String name, List<Throwable> suppressed) {
// 遍历从dexPath查询到的dex和资源Element
for (Element element : dexElements) {
DexFile dex = element.dexFile;
// 如果当前的Element是dex文件元素
if (dex != null) {
// 使用DexFile.loadClassBinaryName加载类
Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
if (clazz != null) {
return clazz;
}
}
}
if (dexElementsSuppressedExceptions != null) {
suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
}
return null;
}
したがって、このソリューションの原則は、ClassLoader.pathList.dexElements []をフックし、パッチを適用したdexを配列のフロントエンドに挿入することです。ClassLoaderのfindClassは、dexElements []でdexをトラバースすることによってクラスを見つけるためです。したがって、修復されたクラスが最初に見つかります。修理の効果を出すために。
以下は、Nuwaの主要な実装ソースコードを使用して次のように説明しています。
public static void injectDexAtFirst(String dexPath, String defaultDexOptPath) throws NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
//新建一个ClassLoader加载补丁Dex
DexClassLoader dexClassLoader = new DexClassLoader(dexPath, defaultDexOptPath, dexPath, getPathClassLoader());
//反射获取旧DexElements数组
Object baseDexElements = getDexElements(getPathList(getPathClassLoader()));
//反射获取补丁DexElements数组
Object newDexElements = getDexElements(getPathList(dexClassLoader));
//合并,将新数组的Element插入到最前面
Object allDexElements = combineArray(newDexElements, baseDexElements);
Object pathList = getPathList(getPathClassLoader());
//更新旧ClassLoader中的Element数组
ReflectionUtils.setField(pathList, pathList.getClass(), "dexElements", allDexElements);
}
private static PathClassLoader getPathClassLoader() {
PathClassLoader pathClassLoader = (PathClassLoader) DexUtils.class.getClassLoader();
return pathClassLoader;
}
private static Object getDexElements(Object paramObject)
throws IllegalArgumentException, NoSuchFieldException, IllegalAccessException {
return ReflectionUtils.getField(paramObject, paramObject.getClass(), "dexElements");
}
private static Object getPathList(Object baseDexClassLoader)
throws IllegalArgumentException, NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
return ReflectionUtils.getField(baseDexClassLoader, Class.forName("dalvik.system.BaseDexClassLoader"), "pathList");
}
private static Object combineArray(Object firstArray, Object secondArray) {
Class<?> localClass = firstArray.getClass().getComponentType();
int firstArrayLength = Array.getLength(firstArray);
int allLength = firstArrayLength + Array.getLength(secondArray);
Object result = Array.newInstance(localClass, allLength);
for (int k = 0; k < allLength; ++k) {
if (k < firstArrayLength) {
Array.set(result, k, Array.get(firstArray, k));
} else {
Array.set(result, k, Array.get(secondArray, k - firstArrayLength));
}
}
return result;
}
利点
- dalvik仮想マシンとart仮想マシンの適応を検討する必要はありません
- コードは邪魔にならず、apkのサイズにはほとんど影響しません
不利益
- 次回修理が必要
- クラスがCLASS_ISPREVERIFIEDに追加されるのを避けるために、パフォーマンスの低下は大きく、インストルメンテーションを使用し、他のクラスが呼び出すために別のヘルパークラスを別のdexに配置します。
dexの交換
原理と実現
dexインストルメンテーションによって引き起こされるパフォーマンスの低下を回避するために、dex置換は別のアプローチを取ります。原則は、dex差分パッケージを提供し、dex全体を置き換えることです。違いによってpatch.dexを指定し、patch.dexとアプリケーションclasses.dexを完全なdexにマージし、完全なdexをロードし、パラメーターとしてdexFileオブジェクトを取得して、Elementオブジェクトを作成し、古いdex-Elements配列を全体。
これは、WeChat Tinker、およびTinkerが独自に開発したDexDiff / DexMergeアルゴリズムで採用されているスキームでもあります。Tinkerは、リソースとSoパッケージの更新もサポートしています。SoパッチパッケージはBsDiffによって生成され、リソースパッチパッケージはファイルmd5比較によって直接生成されます。比較的大きなリソース(デフォルトは100KBより大きい場合は大きなファイル)の場合、 BsDiffは、ファイルQuantityパッチの違いを生成するために使用されます。
Tinkerの実装ソースコードを見てみましょう。もちろん、特定の実装アルゴリズムは非常に複雑です。主要な実装のみを確認します。最終的な修正は、UpgradePatchのtryPatchメソッドです。
@Override
public boolean tryPatch(Context context, String tempPatchPath, PatchResult patchResult) {
//省略一堆校验
... ....
//下面是关键的diff算法及合并实现,实现相对复杂,感兴趣可以再仔细阅读源码
//we use destPatchFile instead of patchFile, because patchFile may be deleted during the patch process
if (!DexDiffPatchInternal.tryRecoverDexFiles(manager, signatureCheck, context, patchVersionDirectory, destPatchFile)) {
TinkerLog.e(TAG, "UpgradePatch tryPatch:new patch recover, try patch dex failed");
return false;
}
if (!BsDiffPatchInternal.tryRecoverLibraryFiles(manager, signatureCheck, context, patchVersionDirectory, destPatchFile)) {
TinkerLog.e(TAG, "UpgradePatch tryPatch:new patch recover, try patch library failed");
return false;
}
if (!ResDiffPatchInternal.tryRecoverResourceFiles(manager, signatureCheck, context, patchVersionDirectory, destPatchFile)) {
TinkerLog.e(TAG, "UpgradePatch tryPatch:new patch recover, try patch resource failed");
return false;
}
// check dex opt file at last, some phone such as VIVO/OPPO like to change dex2oat to interpreted
if (!DexDiffPatchInternal.waitAndCheckDexOptFile(patchFile, manager)) {
TinkerLog.e(TAG, "UpgradePatch tryPatch:new patch recover, check dex opt file failed");
return false;
}
if (!SharePatchInfo.rewritePatchInfoFileWithLock(patchInfoFile, newInfo, patchInfoLockFile)) {
TinkerLog.e(TAG, "UpgradePatch tryPatch:new patch recover, rewrite patch info failed");
manager.getPatchReporter().onPatchInfoCorrupted(patchFile, newInfo.oldVersion, newInfo.newVersion);
return false;
}
TinkerLog.w(TAG, "UpgradePatch tryPatch: done, it is ok");
return true;
}
利点
- 高い互換性
- 小さなパッチ
- 透過的な開発、邪魔にならないコード
不利益
- コールドスタート修理、次のスタート修理
- DexマージメモリはVMヘッドで消費され、OOMしやすく、最終的にマージの失敗を引き起こします
リソース回復の原則
インスタントラン
1.新しいAssetManagerを構築し、リフレクションを介してaddAssertPathを呼び出して、この完全な新しいリソースパッケージをAssetManagerに追加します。これにより、すべての新しいリソースを含むAssetManagerが取得されます
2.元のAssetManagerへのすべての貴重な参照を見つけ、リフレクションを通じて参照をAssetManagerに置き換えます
public static void monkeyPatchExistingResources(Context context,
String externalResourceFile, Collection activities) {
if (externalResourceFile == null) {
return;
}
try {
//反射一个新的 AssetManager
AssetManager newAssetManager = (AssetManager) AssetManager.class
.getConstructor(new Class[0]).newInstance(new Object[0]);
//反射 addAssetPath 添加新的资源包
Method mAddAssetPath = AssetManager.class.getDeclaredMethod("addAssetPath", new Class[]{String.class});
mAddAssetPath.setAccessible(true);
if (((Integer) mAddAssetPath.invoke(newAssetManager,
new Object[]{externalResourceFile})).intValue() == 0) {
throw new IllegalStateException(
"Could not create new AssetManager");
}
Method mEnsureStringBlocks = AssetManager.class.getDeclaredMethod("ensureStringBlocks", new Class[0]);
mEnsureStringBlocks.setAccessible(true);
mEnsureStringBlocks.invoke(newAssetManager, new Object[0]);
//反射得到Activity中AssetManager的引用处,全部换成刚新构建的AssetManager对象
if (activities != null) {
for (Activity activity : activities) {
Resources resources = activity.getResources();
try {
Field mAssets = Resources.class.getDeclaredField("mAssets");
mAssets.setAccessible(true);
mAssets.set(resources, newAssetManager);
} catch (Throwable ignore) {
Field mResourcesImpl = Resources.class.getDeclaredField("mResourcesImpl");
mResourcesImpl.setAccessible(true);
Object resourceImpl = mResourcesImpl.get(resources);
Field implAssets = resourceImpl.getClass().getDeclaredField("mAssets");
implAssets.setAccessible(true);
implAssets.set(resourceImpl, newAssetManager);
}
Resources.Theme theme = activity.getTheme();
try {
try {
Field ma = Resources.Theme.class.getDeclaredField("mAssets");
ma.setAccessible(true);
ma.set(theme, newAssetManager);
} catch (NoSuchFieldException ignore) {
Field themeField = Resources.Theme.class.getDeclaredField("mThemeImpl");
themeField.setAccessible(true);
Object impl = themeField.get(theme);
Field ma = impl.getClass().getDeclaredField("mAssets");
ma.setAccessible(true);
ma.set(impl, newAssetManager);
}
Field mt = ContextThemeWrapper.class.getDeclaredField("mTheme");
mt.setAccessible(true);
mt.set(activity, null);
Method mtm = ContextThemeWrapper.class.getDeclaredMethod("initializeTheme", new Class[0]);
mtm.setAccessible(true);
mtm.invoke(activity, new Object[0]);
Method mCreateTheme = AssetManager.class.getDeclaredMethod("createTheme", new Class[0]);
mCreateTheme.setAccessible(true);
Object internalTheme = mCreateTheme.invoke(newAssetManager, new Object[0]);
Field mTheme = Resources.Theme.class.getDeclaredField("mTheme");
mTheme.setAccessible(true);
mTheme.set(theme, internalTheme);
} catch (Throwable e) {
Log.e("InstantRun",
"Failed to update existing theme for activity "
+ activity, e);
}
pruneResourceCaches(resources);
}
}
Collection references;
if (Build.VERSION.SDK_INT >= 19) {
Class resourcesManagerClass = Class.forName("android.app.ResourcesManager");
Method mGetInstance = resourcesManagerClass.getDeclaredMethod("getInstance", new Class[0]);
mGetInstance.setAccessible(true);
Object resourcesManager = mGetInstance.invoke(null, new Object[0]);
try {
Field fMActiveResources = resourcesManagerClass.getDeclaredField("mActiveResources");
fMActiveResources.setAccessible(true);
ArrayMap arrayMap = (ArrayMap) fMActiveResources.get(resourcesManager);
references = arrayMap.values();
} catch (NoSuchFieldException ignore) {
Field mResourceReferences = resourcesManagerClass.getDeclaredField("mResourceReferences");
mResourceReferences.setAccessible(true);
references = (Collection) mResourceReferences.get(resourcesManager);
}
} else {
Class activityThread = Class.forName("android.app.ActivityThread");
Field fMActiveResources = activityThread.getDeclaredField("mActiveResources");
fMActiveResources.setAccessible(true);
Object thread = getActivityThread(context, activityThread);
HashMap map = (HashMap) fMActiveResources.get(thread);
references = map.values();
}
for (WeakReference wr : references) {
Resources resources = (Resources) wr.get();
if (resources != null) {
try {
Field mAssets = Resources.class.getDeclaredField("mAssets");
mAssets.setAccessible(true);
mAssets.set(resources, newAssetManager);
} catch (Throwable ignore) {
Field mResourcesImpl = Resources.class.getDeclaredField("mResourcesImpl");
mResourcesImpl.setAccessible(true);
Object resourceImpl = mResourcesImpl.get(resources);
Field implAssets = resourceImpl.getClass().getDeclaredField("mAssets");
implAssets.setAccessible(true);
implAssets.set(resourceImpl, newAssetManager);
}
resources.updateConfiguration(resources.getConfiguration(), resources.getDisplayMetrics());
}
}
} catch (Throwable e) {
throw new IllegalStateException(e);
}
}
だから修理の原則
インターフェイス呼び出しの置換
SDKは、システムのデフォルトのロードのインターフェースを置き換えるインターフェースを提供します。
SOPatchManger.loadLibrary(String libName)
替换
System.loadLibrary(String libName)
SOPatchManger.loadLibraryインターフェイスがsoライブラリをロードすると、最初にSDKの指定されたディレクトリにsoをロードしようとします。存在しない場合は、インストールapkディレクトリにsoライブラリをロードします
利点:異なるSDKバージョンと互換性がある必要がないため、SDKバージョンはすべてSystem.loadLibraryインターフェイスです。
短所:ビジネスコードに侵入する必要があり、システムのデフォルトの読み込みを置き換える必要があるため、ライブラリインターフェイス
反射注入
同様のタイプの修復リフレクションインジェクション方式が採用されています。パッチsoライブラリのパスがnativeLibraryDirectories配列の前に挿入されている限り、元のsoライブラリディレクトリの代わりにsoライブラリがロードされるようにすることができます。修理を達成するために。
public String findLibrary(String libraryName) {
String fileName = System.mapLibraryName(libraryName);
for (NativeLibraryElement element : nativeLibraryPathElements) {
String path = element.findNativeLibrary(fileName);
if (path != null) {
return path;
}
}
return null;
}
利点:ユーザーインターフェイス呼び出しに侵入する必要がない
短所:バージョンの互換性制御を行う必要がある、互換性が低い
ホットリペア技術を使用する際に注意が必要な問題は何ですか?
バージョン管理
ホットフィックステクノロジの使用後のリリースプロセスの変更により、対応するブランチ管理を制御に採用する必要があります。
通常、モバイル開発のブランチ管理では、次のように機能ブランチを使用します。
ブランチ | 説明 |
---|---|
主人 | オンラインバージョンの管理に使用されるメインブランチ(マージのみ、コミットではなく、権限の設定)、対応するタグを時間内に設定 |
開発者 | 開発ブランチでは、バージョン番号に従ってマスターブランチに基づいて新しいバージョンの研究開発が作成されます。テストが検証に合格すると、オンラインになり、マスターブランチにマージされます。 |
関数X | 機能ブランチ、需要に応じて設定。開発ブランチに基づいて作成し、関数開発が完了した後に開発開発ブランチにマージします |
ホットフィックスが接続された後、次のブランチ戦略が推奨されます:
ブランチ | 説明 |
---|---|
主人 | オンラインバージョンを管理し、対応するタグを時間内に設定するために使用されるメインブランチ(マージのみ、コミットではなく、権限の設定)(通常は3桁のバージョン番号) |
hot_fix | ホットフィックスブランチ。マスターブランチに基づいて作成され、緊急の問題を修正した後、プッシュをテストした後、hot_fixをマスターブランチにマージします。マスターブランチに再度タグを付けます。(通常4桁のバージョン番号) |
開発者 | 開発ブランチでは、バージョン番号に従ってマスターブランチに基づいて新しいバージョンの研究開発が作成されます。テストが検証に合格すると、オンラインになり、マスターブランチにマージされます。 |
関数X | 機能ブランチ、需要に応じて設定。開発ブランチに基づいて作成し、関数開発が完了した後に開発開発ブランチにマージします |
ホットフィックスブランチのテストおよびリリースプロセスは、品質を確保するために通常のバージョンプロセスと一致していることに注意してください。
流通監視
TinkerやSophixなどの現在の主流のホットリペアソリューションは、パッチの配布と監視を提供します。これは、熱修理技術ソリューションを選択する際に考慮する必要がある重要な要素の1つでもあります。結局のところ、オンライン版の品質を確保するためには、配布管理とリアルタイム監視が不可欠です。
やっと
ホットフィックスを詳細に理解するには、クラスの読み込みメカニズム、インスタント実行、multidexとjavaの低レベルの実装の詳細、JNI、AAPT、仮想マシンの知識を理解する必要があります。もちろん、理解するには膨大な知識が必要です。 AndroidFramworkの実装の詳細は非常に重要です。ホットリペアの原則に精通していると、独自のプログラミングレベルを提供し、問題を解決する能力を向上させることができます。最終的に、ホットリペアは単純なクライアントSDKではなく、セキュリティメカニズムとサーバー側の制御ロジックも含まれています。リンク全体ではありません短時間で素早くできます。
そのため、友人がAndroidのホットリペア技術をより直感的かつ迅速に学習および習得できるようにするために、ビデオと電子書籍のホットリペアシリーズの一連の学習資料を収集して整理します。ビデオチュートリアルは、iQiyiのシニアエンジニアであるランス氏によって提供され、ホットリペア技術を包括的に説明するための例として、Qzoneホットリペアの実際のプロジェクトを取り上げました。この電子書籍は、Aliの「Androidホットリペアテクノロジーの原理の詳細な調査」から派生したもので、ホットリペアテクノロジーの非常に詳細な解釈が含まれています。
スペース上の理由から、ここには一部のスクリーンショットのみが表示されています。完全な情報が必要な友達は、いいねとコメントの後にここをクリックすると、無料でスクリーンショットを入手できます。