Androidの高度な進歩-プラグイン化開発の原理と実践

9756 ワード

前に書いてありますが、プラグイン化開発とは何ですか?
プラグイン化開発とは、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();
    }
  • 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を取得する方法を見てみましょう.
                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