Androidの高度な進歩-プラグイン化開発の原理と実践
9756 ワード
前に書いてありますが、プラグイン化開発とは何ですか?
プラグイン化開発とは、APPの機能モジュールの一部を個別に引き出し、単独で実行可能なapkパッケージにパッケージ化すること(もちろん、ログイン状態やパラメータ環境が必要な場合は単独で実行できませんが、技術的には可能です)、APPプログラムがこれらのモジュールを実行する必要がある場合は、これらのモジュールapkを直接ロードして実行することができます.
分かりやすい例を挙げると、支付宝の内部には多くの機能モジュールが集積されており、その中にはチケットを買うような支付宝のapkパッケージをすべてパッケージ化することは不可能であり、パッケージの体積が大きすぎるだけでなく、機能モジュールの挿入にも不利である.
プラグイン化開発の全体的な考え方を示し、一つ一つ破壊します.
まず、ホストアプリはProxyActivityを提供し、ホストがプラグインパッケージのActivityを開く必要がある場合は、すべて起動したProxyActivityであり、ProxyActivityを起動するintentに、本当に開く必要があるプラグインパッケージのActivityのクラスのフルネームを携帯します.
ProxyActivityは正常に起動しますが、このProxyActivityは実際に開く必要があるActivityオブジェクトをonCreateコールバックで反射します.
今、instanceオブジェクトを手に入れました.もちろん、システムnewではありません.ライフサイクルメソッドには通知が必要です.では、ProxyActivityのすべてのライフサイクルメソッドを書き直し、instanceに対応するライフサイクルメソッドを手動で呼び出します.
もちろんProxyActivityにはmPluginActivityに対応する呼び出しを通知するコールバックメソッドもありますが、ここでは例を挙げません.
Activityでは重要な2つのコールバック方法があります. getClassLoaderはClassLoaderオブジェクトを返します.Activity内部では反射newオブジェクトを使用する場合、ここで返されるClassLoaderを使用して反射します.したがって、ProxyActivityで提供する必要があるのは、現在ロードされているプラグインパッケージに対応するClassLoaderです. getResourcesはResourcesオブジェクトを返します.Activity内部ではリソースファイルを使用する場合、ここで返されるResourcesを使用してリソースを取得します.したがって、ProxyActivityで提供する必要があるのは、現在プラグインパッケージをロードしているResourcesです.
ここまで言うと、プラグイン化開発がプラグインパッケージの中のActivityを開く原理を知っていると思います.私たちのホストアプリはProxyActivityを提供しています.プラグインパッケージActivityを開くのは実は開いているProxyActivityですが、ProxyActivityは他のことをしないで、プラグインパッケージActivityのインスタンス化だけを担当しています.さらに、ProxyActivityのすべてのイベントドライバをプラグインパッケージActivityに通知します.つまり、ProxyActivityはプラグインパッケージActivityのコードを呼び出してプラグインパッケージを実装する機能を実現します.
このとき、外部プラグインパッケージapkをロードし、classLoaderとresourceを取得する方法を見てみましょう.
上記のコードでは、PackageInfoは、メインActivityの全クラス名の取得など、プラグインパッケージmanifestに登録されているすべてのコンポーネント情報を取得できます.
OK、ここでプラグインパッケージを貼ることができます.すべてのActivityコードを継承する必要があります.
IPluginActivityは、attach()メソッドを宣言する以外はactivityがデフォルトで提供するメソッドです.プラグインパッケージのPluginBaseActivityがthatを非空判定する目的は、プラグインパッケージapkが単独でインストールされ、実行できるようにすることです.
さあ、ここまで書いてソースを捧げます.リンク:https://share.weiyun.com/5ne9oLj
プラグイン化開発とは、APPの機能モジュールの一部を個別に引き出し、単独で実行可能なapkパッケージにパッケージ化すること(もちろん、ログイン状態やパラメータ環境が必要な場合は単独で実行できませんが、技術的には可能です)、APPプログラムがこれらのモジュールを実行する必要がある場合は、これらのモジュールapkを直接ロードして実行することができます.
分かりやすい例を挙げると、支付宝の内部には多くの機能モジュールが集積されており、その中にはチケットを買うような支付宝のapkパッケージをすべてパッケージ化することは不可能であり、パッケージの体積が大きすぎるだけでなく、機能モジュールの挿入にも不利である.
プラグイン化開発の全体的な考え方を示し、一つ一つ破壊します.
まず、ホストアプリはProxyActivityを提供し、ホストがプラグインパッケージのActivityを開く必要がある場合は、すべて起動したProxyActivityであり、ProxyActivityを起動するintentに、本当に開く必要があるプラグインパッケージのActivityのクラスのフルネームを携帯します.
Intent intent = new Intent(context, ProxyActivity.class);
intent.putExtra("className", className);//className Activity
return intent;
ProxyActivityは正常に起動しますが、このProxyActivityは実際に開く必要があるActivityオブジェクトをonCreateコールバックで反射します.
Class activityClass = getClassLoader().loadClass(className);
Constructor constructor = activityClass.getConstructor(new Class[]{});
Activity instance = constructor.newInstance(new Object[]{});
今、instanceオブジェクトを手に入れました.もちろん、システムnewではありません.ライフサイクルメソッドには通知が必要です.では、ProxyActivityのすべてのライフサイクルメソッドを書き直し、instanceに対応するライフサイクルメソッドを手動で呼び出します.
public class ProxyActivity extends Activity {
private IPluginActivity mPluginActivity;// Activity
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String className = getIntent().getStringExtra("className");
try {
Class activityClass = getClassLoader().loadClass(className);
Constructor constructor = activityClass.getConstructor(new Class[]{});
Object instance = constructor.newInstance(new Object[]{});
mPluginActivity = (PluginBaseActivity) instance;
mPluginActivity.onCreate(savedInstanceState);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
protected void onStart() {
super.onStart();
mPluginActivity.onStart();
}
@Override
protected void onResume() {
super.onResume();
mPluginActivity.onResume();
}
@Override
protected void onPause() {
super.onPause();
mPluginActivity.onPause();
}
@Override
protected void onStop() {
super.onStop();
mPluginActivity.onStop();
}
@Override
protected void onDestroy() {
super.onDestroy();
mPluginActivity.onDestroy();
}
}
もちろんProxyActivityにはmPluginActivityに対応する呼び出しを通知するコールバックメソッドもありますが、ここでは例を挙げません.
Activityでは重要な2つのコールバック方法があります.
@Override
public ClassLoader getClassLoader() {
return PluginManager.getInstance().getPluginBean().getDexClassLoader();
}
@Override
public Resources getResources() {
return PluginManager.getInstance().getPluginBean().getResources();
}
ここまで言うと、プラグイン化開発がプラグインパッケージの中のActivityを開く原理を知っていると思います.私たちのホストアプリはProxyActivityを提供しています.プラグインパッケージActivityを開くのは実は開いているProxyActivityですが、ProxyActivityは他のことをしないで、プラグインパッケージActivityのインスタンス化だけを担当しています.さらに、ProxyActivityのすべてのイベントドライバをプラグインパッケージActivityに通知します.つまり、ProxyActivityはプラグインパッケージActivityのコードを呼び出してプラグインパッケージを実装する機能を実現します.
このとき、外部プラグインパッケージapkをロードし、classLoaderとresourceを取得する方法を見てみましょう.
File pluginFile;//
PackageManager packageManager = context.getPackageManager();
PackageInfo packageInfo = packageManager.getPackageArchiveInfo(pluginFile.getAbsolutePath(), PackageManager.GET_ACTIVITIES);
File dexOutFile = context.getDir("dex", Context.MODE_PRIVATE);
DexClassLoader dexClassLoader = new DexClassLoader(pluginFile.getAbsolutePath(), dexOutFile.getAbsolutePath()
, null, context.getClassLoader());
AssetManager assetManager = AssetManager.class.newInstance();
Method addAssetPath = AssetManager.class.getMethod("addAssetPath", String.class);
addAssetPath.invoke(assetManager, pluginFile.getAbsolutePath());
Resources resources = new Resources(assetManager, context.getResources().getDisplayMetrics(), context.getResources().getConfiguration());
上記のコードでは、PackageInfoは、メインActivityの全クラス名の取得など、プラグインパッケージmanifestに登録されているすべてのコンポーネント情報を取得できます.
packageInfo.activities[0].name
OK、ここでプラグインパッケージを貼ることができます.すべてのActivityコードを継承する必要があります.
public abstract class PluginBaseActivity extends AppCompatActivity implements IPluginActivity {
protected Activity that;// Activity
@Override
public void attach(ProxyActivity proxyActivity) {
if (that != null)
throw new RuntimeException("Plugin's activity already has been attached!");
this.that = proxyActivity;
attachBaseContext(proxyActivity);
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
if (that == null) {
super.onCreate(savedInstanceState);
}
}
@Override
public void onStart() {
if (that == null) {
super.onStart();
}
}
@Override
public void onResume() {
if (that == null) {
super.onResume();
}
}
@Override
public void onPause() {
if (that == null) {
super.onPause();
}
}
@Override
public void onStop() {
if (that == null) {
super.onStop();
}
}
@Override
public void onDestroy() {
if (that == null) {
super.onDestroy();
}
}
@Override
public void onSaveInstanceState(Bundle outState) {
if (that == null) {
super.onSaveInstanceState(outState);
}
}
@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
if (that == null) {
super.onRestoreInstanceState(savedInstanceState);
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (that == null) {
return super.onTouchEvent(event);
}
return false;
}
@Override
public void onBackPressed() {
if (that == null) {
super.onBackPressed();
}
}
@Override
public void setContentView(View view) {
if (that == null) {
super.setContentView(view);
} else {
that.setContentView(view);
}
}
@Override
public void setContentView(int layoutResID) {
if (that == null) {
super.setContentView(layoutResID);
} else {
that.setContentView(layoutResID);
}
}
@Override
public void startActivity(Intent intent) {
if (that == null) {
super.startActivity(intent);
} else {// intent proxyActivity
intent.putExtra("className", intent.getComponent().getClassName());
intent.setClassName(intent.getComponent().getPackageName(), ProxyActivity.class.getName());
that.startActivity(intent);
}
}
@Override
public ComponentName startService(Intent intent) {
if (that == null) {
return super.startService(intent);
} else {
intent.putExtra("className", intent.getComponent().getClassName());
intent.setClassName(intent.getComponent().getPackageName(), ProxyService.class.getName());
return that.startService(intent);
}
}
@Override
public View findViewById(int id) {
if (that == null) {
return super.findViewById(id);
} else {
return that.findViewById(id);
}
}
@Override
public Intent getIntent() {
if (that == null) {
return super.getIntent();
} else {
return that.getIntent();
}
}
@Override
public Window getWindow() {
if (that == null) {
return super.getWindow();
} else {
return that.getWindow();
}
}
@Override
public WindowManager getWindowManager() {
if (that == null) {
return super.getWindowManager();
} else {
return that.getWindowManager();
}
}
}
public interface IPluginActivity {
void attach(ProxyActivity proxyActivity);
void onCreate(@Nullable Bundle savedInstanceState);
void onStart();
void onResume();
void onPause();
void onStop();
void onDestroy();
void onSaveInstanceState(Bundle outState);
void onRestoreInstanceState(Bundle savedInstanceState);
boolean onTouchEvent(MotionEvent event);
void onBackPressed();
void setContentView(View view);
void setContentView(int layoutResID);
void startActivity(Intent intent);
ComponentName startService(Intent intent);
View findViewById(int id);
Intent getIntent();
Window getWindow();
WindowManager getWindowManager();
}
IPluginActivityは、attach()メソッドを宣言する以外はactivityがデフォルトで提供するメソッドです.プラグインパッケージのPluginBaseActivityがthatを非空判定する目的は、プラグインパッケージapkが単独でインストールされ、実行できるようにすることです.
さあ、ここまで書いてソースを捧げます.リンク:https://share.weiyun.com/5ne9oLj