Proxy思想に基づくAndroidプラグインフレーム
72231 ワード
http://zjmdp.github.io/2014/07/22/a-plugin-framework-for-android/
本論文のコードは全部Githubにあります.android-plugin
意味
プラグインのフレームワークの意味は以下の点にあります.は、インストールパッケージの体積を減少させ、ネットワークを介して選択的にプラグインの下で を発行する.はモジュール化され、ネットワークトラフィックを低減する .サイレントアップグレード、ユーザが感知しない場合にアップグレードする .低バージョンの機種の方法数が制限を超えてインストールできない問題を解決しました. コードデカップリング 現状
Androidではプラグインのフレームワークに関する技術について多くの議論と実装がなされており、プラグインは通常、appkまたはdexとしてパッケージ化されている.
dex形式のプラグインはしばしば、JavaのJar形式と似ている機能的なインターフェースを提供します.AndroidのDalvik VMはJavaのByte Codeを直接動的にロードできないため、Dalvik Byte Codeを提供する必要があります.dexはDalvik Byte Code形式のjarです.
appk形式のプラグインは、dex形式よりも多くの機能を提供しており、例えばリソースをappkにパッケージ化しても良いし、プラグイン内のActivityやServiceなどのシステムコンポーネントを実現しても良い.
本稿では主にappk形式のプラグインフレームについて議論し、appk形式についてはインストールとインストールの2つの方法がある.は、appをインストールする方式で比較的簡単に実現されています.主な原理は、プラグインappとメインプログラムを共有することによって、メインプログラムは は、appkをインストールしないで、ユーザーが手動でインストールした欠点を解決しました.しかし、実現するのは複雑で、主に DexClass loader
ここでは「Java仮想マシンを深く理解する:JVM高級特性とベストプラクティス」の第二版の中でjava類キャリアに対する一部の説明を引用します.
仮想マシン設計チームは、クラスローディング段階の「1つのクラスの全限定名来を通じて、このようなバイナリバイトストリームを記述する」という動作をJava仮想マシンの外部に実行して、アプリケーション自身が必要なクラスをどのように取得するかを決定する.この動作を実現するコードモジュールを「クラスキャリア」と呼びます.
Android仮想マシンの実装は、javaのJVMを参照しているので、Androidにおいても、クラス加載器の概念を使用している.ただし、JVMにおける加載器ローディングclassファイルに対して、AndroidのDalvik仮想マシンはDexフォーマットであり、Dexローディングを完了するのは主に
プラグイン内のシステムコンポーネントの呼び出し
Android Fraamewarkには
ActivityがAndroid Manifest.xmlで声明を出す必要があることはよく知られていますが、appkはインストール時
起動プラグインの中のActivityは必ずメインプログラムのAndroid Manifest.xmlでこのActivityを宣言しますが、プラグインのフレームの柔軟性を確保するために、どのようなActivityがプラグインにあるかは予知できません.
上記の問題を解決するために、ここではProxy思想に基づく解決方法を紹介します.大体の原理は、メインプログラムのAndroid Manifest.xmlにおいて一部の
Plugin SDK
すべてのプラグインおよびメインプログラムは、PluginSDKに依存して開発される必要があり、すべてのプラグインのActivityは、PlugiSDKの
まず、
上のコードの一部のセグメントは、プラグインの枠組みの核心的なロジックを示しています.他のコードは、このような論理的なサービスを実現するために、後はプロジェクト全体のソースコードを提供します.
プラグイン内のリソースの取得
プラグインappk内のリソースをロードするためのアイデアの一つは、プラグインappkのパスをメインプログラムリソース検索のパスに追加することであり、以下のコードは、この方法を示している.
本論文では、Proxy思想に基づくプラグインの枠組みを紹介します.コードはすべてGithubの中にあります.コードはフレーム全体の核心部分を抽出しただけです.もし生産環境に使うならば、
本論文のコードは全部Githubにあります.android-plugin
意味
プラグインのフレームワークの意味は以下の点にあります.
Androidではプラグインのフレームワークに関する技術について多くの議論と実装がなされており、プラグインは通常、appkまたはdexとしてパッケージ化されている.
dex形式のプラグインはしばしば、JavaのJar形式と似ている機能的なインターフェースを提供します.AndroidのDalvik VMはJavaのByte Codeを直接動的にロードできないため、Dalvik Byte Codeを提供する必要があります.dexはDalvik Byte Code形式のjarです.
appk形式のプラグインは、dex形式よりも多くの機能を提供しており、例えばリソースをappkにパッケージ化しても良いし、プラグイン内のActivityやServiceなどのシステムコンポーネントを実現しても良い.
本稿では主にappk形式のプラグインフレームについて議論し、appk形式についてはインストールとインストールの2つの方法がある.
createPackageContext
を介してプラグインのcontextを構築し、contextを通じてプラグインappのリソースにアクセスできます.この方法の欠点は、ユーザーが手動でインストールする必要があります.体験はあまり良くないです.DexClassloader
によって実現されます.同時に、プラグインのActivityなどのAndroidシステムコンポーネントをどうやって起動するかを解決します.プラグインフレームの本当の難点はここにあります.ここでは「Java仮想マシンを深く理解する:JVM高級特性とベストプラクティス」の第二版の中でjava類キャリアに対する一部の説明を引用します.
仮想マシン設計チームは、クラスローディング段階の「1つのクラスの全限定名来を通じて、このようなバイナリバイトストリームを記述する」という動作をJava仮想マシンの外部に実行して、アプリケーション自身が必要なクラスをどのように取得するかを決定する.この動作を実現するコードモジュールを「クラスキャリア」と呼びます.
Android仮想マシンの実装は、javaのJVMを参照しているので、Androidにおいても、クラス加載器の概念を使用している.ただし、JVMにおける加載器ローディングclassファイルに対して、AndroidのDalvik仮想マシンはDexフォーマットであり、Dexローディングを完了するのは主に
PathClassloader
およびDexclassloader
である.PathClassloader
は、デフォルトでは/data/dalvik-cache
にキャッシュされたdexファイルを読み、インストールされていないappkがPathClassloader
でロードされると、/data/dalvik-cache
ディレクトリの下で対応するdexが見つからないので、ClassNotFoundException
を抛り出す.DexClassloader
は、任意の経路の下にdexとappkファイルを含む、odex生成の経路を指定することにより、インストールされていないappkファイルをロードすることができる.次のコードは、DexClassloader
の使用方法を示している.final File optimizedDexOutputPath = context.getDir("odex", Context.MODE_PRIVATE);
try{
DexClassLoader classloader = new DexClassLoader("apkPath",
optimizedDexOutputPath.getAbsolutePath(),
null, context.getClassLoader());
Class<?> clazz = classloader.loadClass("com.plugindemo.test");
Object obj = clazz.newInstance();
Class[] param = new Class[2];
param[0] = Integer.TYPE;
param[1] = Integer.TYPE;
Method method = clazz.getMethod("add", param);
method.invoke(obj, 1, 2);
}catch(InvocationTargetException e){
e.printStackTrace();
}catch(NoSuchMethodException e){
e.printStackTrace();
}catch(IllegalAccessException e){
e.printStackTrace();
}catch(ClassNotFoundException e){
e.printStackTrace();
}catch (InstantiationException e){
e.printStackTrace();
}
DexClassloader
は、クラスのロード問題を解決しました.プラグインappkにいくつかの簡単なAPI呼び出しがある場合、上のコードは需要を満たすことができますが、ここで論じたプラグインフレームは、リソースアクセスとAndroidシステムコンポーネントの呼び出しを解決する必要があります.プラグイン内のシステムコンポーネントの呼び出し
Android Fraamewarkには
Activity
、Service
、Content Provider
、BroadcastReceiver
などの4つのシステムコンポーネントが含まれています.ここでは主にメインプログラムでプラグインのActivityを起動する方法について議論しています.他の3つのコンポーネントの呼び出し方法は同様です.ActivityがAndroid Manifest.xmlで声明を出す必要があることはよく知られていますが、appkはインストール時
PackageManagerService
でappkのAndroid dManifest.xmlファイルを解析します.この時、プログラムに含まれているActivityはどれかを決定しました.起動プラグインの中のActivityは必ずメインプログラムのAndroid Manifest.xmlでこのActivityを宣言しますが、プラグインのフレームの柔軟性を確保するために、どのようなActivityがプラグインにあるかは予知できません.
上記の問題を解決するために、ここではProxy思想に基づく解決方法を紹介します.大体の原理は、メインプログラムのAndroid Manifest.xmlにおいて一部の
ActivityNotFound
を宣言し、プラグインのActivityを起動すると、メインプログラムの一つProxyActivity
に変わります.最後の効果は起動のこのActivityは実際にメインプログラムで宣言されたActivityですが、関連コードはプラグインActivityのコードです.これはプラグインActivityが宣言されていないと起動できない問題を解決しました.上から見て起動するのはプラグインのActivityです.全体の過程を具体的に分析します.Plugin SDK
すべてのプラグインおよびメインプログラムは、PluginSDKに依存して開発される必要があり、すべてのプラグインのActivityは、PlugiSDKの
ProxyActivity
から継承され、ProxyActivity
はPluginBaseActivity
から継承され、PluginBaseActivity
インターフェースを実現している.public interface IActivity {
public void IOnCreate(Bundle savedInstanceState);
public void IOnResume();
public void IOnStart();
public void IOnPause();
public void IOnStop();
public void IOnDestroy();
public void IOnRestart();
public void IInit(String path, Activity context, ClassLoader classLoader, PackageInfo packageInfo);
}
public class PluginBaseActivity extends Activity implements IActivity {
...
private Activity mProxyActivity;
...
@Override
public void IInit(String path, Activity context, ClassLoader classLoader) {
mProxy = true;
mProxyActivity = context;
mPluginContext = new PluginContext(context, 0, path, classLoader);
attachBaseContext(mPluginContext);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
if (mProxy) {
mRealActivity = mProxyActivity;
} else {
super.onCreate(savedInstanceState);
mRealActivity = this;
}
}
@Override
public void setContentView(int layoutResID) {
if (mProxy) {
mContentView = LayoutInflater.from(mPluginContext).inflate(layoutResID, null);
mRealActivity.setContentView(mContentView);
} else {
super.setContentView(layoutResID);
}
}
...
@Override
public void IOnCreate(Bundle savedInstanceState) {
onCreate(savedInstanceState);
}
@Override
public void IOnResume() {
onResume();
}
@Override
public void IOnStart() {
onStart();
}
@Override
public void IOnPause() {
onPause();
}
@Override
public void IOnStop() {
onStop();
}
@Override
public void IOnDestroy() {
onDestroy();
}
@Override
public void IOnRestart() {
onRestart();
}
}
public class ProxyActivity extends Activity {
IActivity mPluginActivity;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle bundle = getIntent().getExtras();
if(bundle == null){
return;
}
mPluginName = bundle.getString(PluginConstants.PLUGIN_NAME);
mLaunchActivity = bundle.getString(PluginConstants.LAUNCH_ACTIVITY);
File pluginFile = PluginUtils.getInstallPath(ProxyActivity.this, mPluginName);
if(!pluginFile.exists()){
return;
}
mPluginApkFilePath = pluginFile.getAbsolutePath();
try {
initPlugin();
super.onCreate(savedInstanceState);
mPluginActivity.IOnCreate(savedInstanceState);
} catch (Exception e) {
mPluginActivity = null;
e.printStackTrace();
}
}
@Override
protected void onResume() {
super.onResume();
if(mPluginActivity != null){
mPluginActivity.IOnResume();
}
}
@Override
protected void onStart() {
super.onStart();
if(mPluginActivity != null) {
mPluginActivity.IOnStart();
}
}
...
private void initPlugin() throws Exception {
PackageInfo packageInfo = PluginUtils.getPackgeInfo(this, mPluginApkFilePath);
if (mLaunchActivity == null || mLaunchActivity.length() == 0) {
mLaunchActivity = packageInfo.activities[0].name;
}
ClassLoader classLoader = PluginUtils.getClassLoader(this, mPluginName, mPluginApkFilePath);
if (mLaunchActivity == null || mLaunchActivity.length() == 0) {
if (packageInfo == null || (packageInfo.activities == null) || (packageInfo.activities.length == 0)) {
throw new ClassNotFoundException("Launch Activity not found");
}
mLaunchActivity = packageInfo.activities[0].name;
}
Class<?> mClassLaunchActivity = classLoader.loadClass(mLaunchActivity);
getIntent().setExtrasClassLoader(classLoader);
mPluginActivity = (IActivity) mClassLaunchActivity.newInstance();
mPluginActivity.IInit(mPluginApkFilePath, this, classLoader);
}
...
@Override
public void startActivityForResult(Intent intent, int requestCode) {
boolean pluginActivity = intent.getBooleanExtra(PluginConstants.IS_IN_PLUGIN, false);
if (pluginActivity) {
String launchActivity = null;
ComponentName componentName = intent.getComponent();
if(null != componentName) {
launchActivity = componentName.getClassName();
}
intent.putExtra(PluginConstants.IS_IN_PLUGIN, false);
if (launchActivity != null && launchActivity.length() > 0) {
Intent pluginIntent = new Intent(this, getProxyActivity(launchActivity));
pluginIntent.putExtra(PluginConstants.PLUGIN_NAME, mPluginName);
pluginIntent.putExtra(PluginConstants.PLUGIN_PATH, mPluginApkFilePath);
pluginIntent.putExtra(PluginConstants.LAUNCH_ACTIVITY, launchActivity);
startActivityForResult(pluginIntent, requestCode);
}
} else {
super.startActivityForResult(intent, requestCode);
}
}
Activity
およびIActivity
は、プラグインのフレーム全体のコアで、コードを簡単に分析する.まず、
PluginBaseActivity
を見てください.@Override
protected void onResume() {
super.onResume();
if(mPluginActivity != null){
mPluginActivity.IOnResume();
}
}
変数ProxyActivity
のタイプはProxyActivity#onResume
であり、プラグインActivityがmPluginActivity
インターフェースを実現しているため、IActivity
が最終的に実行したのはプラグインActivityのIActivity
のコードであると推測され、以下にこの推測を確認する.mPluginActivity.IOnResume()
はonResume
インターフェースを実現していますが、これらのインターフェースは具体的にどのように実現されますか?コードを見る:@Override
public void IOnCreate(Bundle savedInstanceState) {
onCreate(savedInstanceState);
}
@Override
public void IOnResume() {
onResume();
}
@Override
public void IOnStart() {
onStart();
}
@Override
public void IOnPause() {
onPause();
}
...
インターフェースの実現はとても簡単で、インターフェースに対応するコールバック関数だけを呼び出しました.ここのコールバック関数は最終的にどこに移動しますか?前述したすべてのプラグインActivityはPluginBaseActivity
から継承されます.つまり、ここのコールバック関数は最終的にプラグインActivityに対応するコールバックに変更されます.例えばIActivity
が実行しているのはプラグインActivityのPluginBaseActivity
のコードです.これも以前の推測を確認しました.上のコードの一部のセグメントは、プラグインの枠組みの核心的なロジックを示しています.他のコードは、このような論理的なサービスを実現するために、後はプロジェクト全体のソースコードを提供します.
プラグイン内のリソースの取得
プラグインappk内のリソースをロードするためのアイデアの一つは、プラグインappkのパスをメインプログラムリソース検索のパスに追加することであり、以下のコードは、この方法を示している.
private AssetManager getSelfAssets(String apkPath) {
AssetManager instance = null;
try {
instance = AssetManager.class.newInstance();
Method addAssetPathMethod = AssetManager.class.getDeclaredMethod("addAssetPath", String.class);
addAssetPathMethod.invoke(instance, apkPath);
} catch (Throwable e) {
e.printStackTrace();
}
return instance;
}
プラグインActivityをリソースにアクセスさせるために、私たちがカスタマイズしたContectを使用します.IOnResume
の初期化にはいくつかの処理が必要です.public void IInit(String path, Activity context, ClassLoader classLoader, PackageInfo packageInfo) {
mProxy = true;
mProxyActivity = context;
mContext = new PluginContext(context, 0, mApkFilePath, mDexClassLoader);
attachBaseContext(mContext);
}
onResume
において、プラグインapp検索パスを含むContextは、PluginBaseActivity
を再ロードすることによって実現される.public PluginContext(Context base, int themeres, String apkPath, ClassLoader classLoader) {
super(base, themeres);
mClassLoader = classLoader;
mAsset = getPluginAssets(pluginFilePath);
mResources = getPluginResources(base, mAsset);
mTheme = getPluginTheme(mResources);
}
private AssetManager getPluginAssets(String apkPath) {
AssetManager instance = null;
try {
instance = AssetManager.class.newInstance();
Method addAssetPathMethod = AssetManager.class.getDeclaredMethod("addAssetPath", String.class);
addAssetPathMethod.invoke(instance, apkPath);
} catch (Throwable e) {
e.printStackTrace();
}
return instance;
}
private Resources getPluginAssets(Context ctx, AssetManager selfAsset) {
DisplayMetrics metrics = ctx.getResources().getDisplayMetrics();
Configuration con = ctx.getResources().getConfiguration();
return new Resources(selfAsset, metrics, con);
}
private Theme getPluginTheme(Resources selfResources) {
Theme theme = selfResources.newTheme();
mThemeResId = getInnerRIdValue("com.android.internal.R.style.Theme");
theme.applyStyle(mThemeResId, true);
return theme;
}
@Override
public Resources getResources() {
return mResources;
}
@Override
public AssetManager getAssets() {
return mAsset;
}
...
締め括りをつける本論文では、Proxy思想に基づくプラグインの枠組みを紹介します.コードはすべてGithubの中にあります.コードはフレーム全体の核心部分を抽出しただけです.もし生産環境に使うならば、
PluginContext
とgetAssets
の部品のProxy類は実現されていません.ActivityのProxy実現も不完全です.同時に私もこの枠組みに致命的な欠陥がないと保証できません.本文は主に総括、学習と交流を目的としています.