プラグイン化を学ぶ前に、以前の記事を読む必要があります。
動的ローディングテクノロジー:
プログラムの実行中、プログラムに存在しない一部の実行可能ファイルが動的にロードされて実行されます。アプリケーションテクノロジーの開発に伴い、動的ロードテクノロジーは、ホットフィックスとプラグインの2つのブランチを徐々に導き出します。
- ホットフィックス:バグの修正に使用
- プラグイン:巨大なアプリケーションを解決し、機能モジュールを分離し、他のapkのコードを再利用します
プラグインのアイデア:
再利用したapkをプラグインとして使用し、別のapkに挿入します。たとえば、Taobaoに塩漬けの魚のページがあり、Taobaoを使用して塩漬けの魚を排水します。プラグインテクノロジーを使用すると、塩漬けの魚のapkのdexファイルを直接使用して保存できます。塩漬けの魚のページのセットを再度開発し、Taobaoapkの結合度を効果的に減らすためのコスト。
アクティビティプラグインの原則:
プラグインアクティビティの目的は、別のapkのアクティビティを直接使用することであり、アクティビティの起動とライフサイクル管理はAMSで処理する必要があります。他のapkのアクティビティは、このプロジェクトのマニフェストに登録されていないため、合格してはなりません。午前の検証をバイパスするには、startActivityプロセスをフックする必要があります。このプロジェクトではピットアクティビティを使用できます。午前に送信する前に、プラグインアクティビティをピットアクティビティに置き換えて、午前の検証に合格します。検証が完了すると、実際の起動が繰り返されます。プラグインアクティビティを元に戻します。
ステップ:
- このプロジェクトのピットアクティビティを事前に準備してください
- ピットアクティビティを使用してams検証をバイパスします
- プラグインアクティビティを復元する
1.アクティビティに参加する準備をします
元のプロジェクトで直接空白のアクティビティを準備し、マニフェストに登録することを忘れないでください。以下で彼をSubActivityと呼びます。
2.プラグインアクティビティを使用してピットアクティビティを置き換えます
検証のためにamsプロセスに渡す前に、ユーザープロセスはInstrumentationとiActivityManagerの2つのクラスを通過します。両方のクラスをフックポイントとして使用できます。iActivityManagerをフックする方法は次のとおりです。
2.1フックポイントのプロキシクラスiActivityManagerProxyを作成します
public class IActivityManagerProxy implements InvocationHandler {
private Object realActivityManager;
public IActivityManagerProxy(Object realActivityManager) {
this.realActivityManager = realActivityManager;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("startActivity".equals(method.getName())){
// 首先找到,原本需要启动的插件activity的原始intent
Intent originIntent = null;
int index = 0;
for (int i = 0;i<args.length;i++){
if (args[i] instanceof Intent){
originIntent = (Intent) args[i];
index = i;
break;
}
}
// 新建欺骗ams的占坑activity的intent
Intent fakeIntent = new Intent();
fakeIntent.setClass("xxx.xxx.xxx",SubActivity.class);
// 将真实的intent保存在fakeIntent中用于第三步的还原操作
fakeIntent.putExtra("real_intent",originIntent);
// 将fakeIntent写回原来的arges数组中
args[index] = fakeIntent;
}
return method.invoke(realActivityManager,args);
}
}
ここで使用される動的プロキシは、iActivityManagerプロキシを作成し、最初に開始されたプラグインアクティビティのインテントを見つけてから、サブアクティビティを開始してそれを置き換える新しいインテントを作成します。
2.2元のiActivityManagerを置き換えます。
public void hookAMS() throws Exception {
// 获取ActivityManager getService 返回的单例
Class ActivityManagerClazz = ActivityManager.class;
Field IActivityManagerSingletonField = ActivityManagerClazz.getDeclaredField("IActivityManagerSingleton");
Object IActivityManagerSingleton = IActivityManagerSingletonField.get(ActivityManagerClazz);
// 通过单例.get()获取iActivityManager, 这两步需要参考源码的iActivityManager的获取
Class singleClazz = IActivityManagerSingleton.getClass();
Method getMethod = singleClazz.getDeclaredMethod("get");
Object iActivityManager = getMethod.invoke(IActivityManagerSingleton,null);
// 生成动态代理对象
Object proxyInstance = Proxy.newProxyInstance(
ActivityManagerClazz.getClassLoader(),
ActivityManagerClazz.getInterfaces(),
new IActivityManagerProxy(iActivityManager));
// 将代理对象设置到单例上
Field mInstanceField = singleClazz.getField("mInstance");
mInstanceField.set(IActivityManagerSingleton,proxyInstance);
}
- このメソッドは、startActivityの前に呼び出す必要があります
3.プラグインアクティビティを復元します
ams検証をバイパスした後、実際にTargetActivityを開始する必要があります。また、Handlerメカニズムを学習した後、メッセージの処理順序は、最初に現在のmessage.callbackにロジックがあるかどうかを判断し、最初にコールバックを実行することであることがわかります。Messageをフックとして使用できます。ポイント
3.1カスタムCallBackを作成し、handleMessageが処理される前に、fakeIntentを実際のインテントに置き換えます
class MCallBack implements android.os.Handler.Callback {
@Override
public boolean handleMessage(Message msg) {
try {
Object activityClientRecord = msg.obj;
// 获取fakeIntent
Class acrClazz = activityClientRecord.getClass();
Field intentField = acrClazz.getDeclaredField("intent");
Intent intent = (Intent) intentField.get(activityClientRecord);
// 取出targetActivity的Intent
Intent realIntent = intent.getParcelableExtra("real_intent");
// 将realIntent的内容设置到fakeIntent
intent.setComponent(realIntent.getComponent());
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
msg.getTarget().handleMessage(msg);
return true;
}
}
3.2アクティビティスレッドをフックし、メインスレッドのH(ハンドラー)のCallBack属性を変更します。原理参考dispatchMessage方法
private void hookActivityThread() throws Exception {
Class activityThreadClass = Class.forName("android.app.ActivityThread");
Field singleInstanceField = activityThreadClass.getDeclaredField("sCurrentActivityThread");
Object activityThreadInstance = singleInstanceField.get(null);
Field mHField = activityThreadClass.getDeclaredField("mH");
Handler handler = (Handler) mHField.get(activityThreadInstance);
// 修改handler 的callback
Class handlerClazz = handler.getClass();
Field callbackField = handlerClazz.getDeclaredField("mCallback");
callbackField.set(handler,new MCallBack());
}
ハンドラメカニズムには2つのコールバックがあります。1つはHandler.mCallbackで、もう1つはMessage.callbackです。
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
メッセージがループでポーリングされると、dispatchMessageが呼び出されます。Message.callbackがnullでない場合、Runnableコールバックが処理されて終了します。msg.callbackがnullの場合、ハンドラーのmCallbackが最初に実行され、ハンドラーのmCallback.handleMessageに従います。戻り値は、Handler.handleMessageを実行するかどうかを決定します。
根据上面的流程,我们可以在ActivityThread的H处理startActivity这个Message的handleMessage前,在H的Callback中插入修改intent的代码,做到真实的开启TargetActivity
3.3プラグインアクティビティのライフサイクル管理:
上記の操作は、アクティビティ、プラグインアクティビティのライフサイクルの管理方法のみを有効にし、AMSはトークンを使用してアクティビティを識別および管理し、プラグインアクティビティトークンのバインドは影響を受けないため、プラグインアクティビティにはライフサイクルがあります。 ;
サービスプラグインの原則
エージェント配布の実装:
プラグインサービスが開始されると、プロキシサービスが最初に開始され、プロキシサービスが実行されると、プラグインサービスがonStartCommandで開始されます。
ステップ:
- エージェントサービスはプロジェクトで準備ができています
- フックiActivityManagerがプロキシサービスを開始します
- エージェントの配布:
- ProxyServiceはプラグインサービスの配布に長い時間がかかるため、再作成するにはSTART_STICKYProxyServiceを返す必要があります
- プラグインサービスを作成し、アタッチし、onCreateします。
1.プロジェクトにProxyServiceを作成し、マニフェストに登録します。
2. iActivityManagerをフックし、開始するTargetServiceをProxyServiceに置き換えます
2.1カスタムiActivityManagerProxyを作成する
public class IActivityManagerProxy implements InvocationHandler {
private Object realActivityManager;
public IActivityManagerProxy(Object realActivityManager) {
this.realActivityManager = realActivityManager;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("startService".equals(method.getName())){
Intent targetIntent = null;
int index = 0;
for (int i = 0;i<args.length;i++){
if (args[i] instanceof Intent){
targetIntent = (Intent) args[i];
index = i;
break;
}
}
Intent proxyIntent = new Intent();
proxyIntent.setClassName("com.xx.xx","com.xx.xx.ProxyService");
proxyIntent.putExtra("target_intent",targetIntent);
args[index] = proxyIntent;
}
return method.invoke(realActivityManager,args);
}
}
2.2フックAMSは、元のIActivityManagerを置き換えます 同上
public void hookAMS() throws Exception {
// 获取ActivityManager getService 返回的单例
Class ActivityManagerClazz = ActivityManager.class;
Field IActivityManagerSingletonField = ActivityManagerClazz.getDeclaredField("IActivityManagerSingleton");
Object IActivityManagerSingleton = IActivityManagerSingletonField.get(ActivityManagerClazz);
// 通过单例.get()获取iActivityManager, 这两步需要参考源码的iActivityManager的获取
Class singleClazz = IActivityManagerSingleton.getClass();
Method getMethod = singleClazz.getDeclaredMethod("get");
Object iActivityManager = getMethod.invoke(IActivityManagerSingleton,null);
// 生成动态代理对象
Object proxyInstance = Proxy.newProxyInstance(
ActivityManagerClazz.getClassLoader(),
ActivityManagerClazz.getInterfaces(),
new IActivityManagerProxy(iActivityManager));
// 将代理对象设置到单例上
Field mInstanceField = singleClazz.getField("mInstance");
mInstanceField.set(IActivityManagerSingleton,proxyInstance);
}
このコードがstartServiceの前に呼び出されている限り、proxyServiceが開始されます。次に、proxyServiceでtargetServiceを配布します。
2.3 proxyServiceでTargetServiceを開始します。
- コンテキストをバインドするためにattachを呼び出します
- onCreateを呼び出す
public class ProxyService extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
try {
// 准备attach方法的参数
Class activityThreadClazz = null;
activityThreadClazz = Class.forName("android.app.ActivityThread");
Method getApplicationMethod = activityThreadClazz.getDeclaredMethod("getApplicationMethod");
Field sCurrentActivityThreadField = activityThreadClazz.getDeclaredField("sCurrentActivityThread");
sCurrentActivityThreadField.setAccessible(true);
// activityThread
Object activityThread = sCurrentActivityThreadField.get(null);
// applicationThread
Object applicationThread = getApplicationMethod.invoke(activityThread, null);
Class iInterFaceClazz = Class.forName("android.os.IInterface");
Method asBinderMethod = iInterFaceClazz.getDeclaredMethod("asBinder");
asBinderMethod.setAccessible(true);
// token
Object token = asBinderMethod.invoke(applicationThread);
// iActivityManager
Class ActivityManagerClazz = ActivityManager.class;
Field IActivityManagerSingletonField = ActivityManagerClazz.getDeclaredField("IActivityManagerSingleton");
Object IActivityManagerSingleton = IActivityManagerSingletonField.get(ActivityManagerClazz);
Class singleClazz = IActivityManagerSingleton.getClass();
Method getMethod = singleClazz.getDeclaredMethod("get");
Object iActivityManager = getMethod.invoke(IActivityManagerSingleton, null);
// targetService
Class serviceClazz = Class.forName("android.app.Service");
Service targetService = (Service) serviceClazz.newInstance();
// attach
Method attachMethod = serviceClazz.getDeclaredMethod("attach");
attachMethod.invoke(targetService, this,
activityThread, intent.getComponent().getClassName(),
token, getApplication(), iActivityManager);
targetService.onCreate();
} catch (Exception e) {
e.printStackTrace();
}
return START_STICKY;
}
}
- まず、アタッチに必要なパラメータを準備し、リフレクションを通じて取得します
- targetServiceのattachメソッドを呼び出します
- targetServiceのonCreateメソッドを呼び出します
リソースのプラグイン化
こちらのスキニングの記事を参照してください:Androidスキニングの原則
ドンのプラグイン練習
1.プラグインの機能:
プラグインの目的は、プラグインプロジェクト代码/类
とメインプロジェクトを使用することです资源
1.1プラグインの4つの主要コンポーネントの処理:
プラグインアクティビティの使用など、プラグインの4つの主要なコンポーネントクラスを使用する場合は、特別な処理を行う必要があります。一般的な処理方法は次のとおりです。
- AMSをフックし、4つの主要コンポーネントのAMS検証をバイパスし、ライフサイクルを手動で管理します
- メインプロジェクトのマニフェストにプラグインアクティビティを直接登録します
方法1は、実装が非常に困難で柔軟性が高いという特徴がありますが、システムの非sdk呼び出しに対するGoogleの制限により、この方法は将来失敗する可能性があります。
方法2は、単純な実装が特徴ですが、十分な柔軟性がないため、マニフェストに記述する必要があります。デッド
某东采用的方式2,直接在manifest中写死插件的四大组件注册
1.2プラグインでのクラスのロードと使用:
各プラグインはClassLoaderでセットアップされます。目的は、プラグインクラス全体がローダーによってロードされ、すべてのプラグインのClassLoaderが親委任内の別のClassLoaderを継承することです。この目的は、メインプロジェクトの統合管理を容易にすることです。
1.3 DelegateClassLoaderの置き換え:
LoadedAPKのClassLoaderをDelegateClassLoaderに置き換えます。
1.4プラグインリソースへの参照
AssetManagerへのパスを追加するだけです。詳細については上記を参照してください
2.プラグインをメインプロジェクトにパッケージ化する方法、つまり、プラグインパッケージをメインプロジェクトに統合する方法:
- プラグインapkをアセットディレクトリに配置し、assetManagerを介してロードします
- プラグインapkファイルの変更されたサフィックスを.soのlib / armeabiディレクトリに配置すると、メインプロジェクトapkは、インストール時にこのディレクトリ内のファイルをdata / data / <package_name> / lib /ディレクトリに自動的にロードし、直接取得できます。
某东使用的第二种以so的形式放入lib目录自动加载,因为在运行时去使用AssetManager加载asset资源会影响程序的运行时速度
3.プラグインとメインプロジェクト間の通信方法
主にから借りたairbnbのDeepLinkDispatch
DeepLinkDispatchは、Androidのネイティブスキームプロトコルに似ており、MeituanでGaodeマップを開いたり、WeChatでJDを開いたりするなど、別のAPPページにジャンプするために使用されます。
DeepLinkDispatchの実現アイデア:最初にプラグインプロジェクトで開く必要のあるアクティビティに注釈を追加し、次にメインプロジェクトの入力注釈でDeepLinkDispatch.startActivityDirect()
設定されたパラメータを呼び出し、最後にシステムapiを介してContext.startActivity()
ページを開きます
4.プラグインの未来
GoogleのシステムAPIに対する制限はますます厳しくなり、開発者が調整できるように、ブラックリスト、ダークグレーリスト、ライトグレーリストに分割されました。プラグインには未来がないはずです。プラグインの目的について考えてみましょう。 :
- 開発効率を向上させるための独立したコンパイル
- モジュールの分離、コードの再利用
東洞の解決策:
组件化:
従来のコンポーネントは、新しいAndroidライブラリを作成し、開発、デバッグ、実際の参照中にアプリケーションとライブラリを切り替えることです。Dongdongのコンポーネント化は、各コンポーネントを個別のプロジェクトにして、アプリケーションとライブラリをプロジェクト構造に保持することです。 Androidライブラリ、ライブラリはコンポーネント機能の実装に使用され、アプリは開発とデバッグに使用され、メインプロジェクトが使用される場合、クラウドからのmaven同期に直接依存します。