PS:専門的な知識を見たくないなら、興味のある知識を見てください。とにかく、あまり怠けないようにしましょう。
前の記事では、コンポーネント化開発のいくつかの基本的な問題を概説しました。本記事では、コンポーネント化の学習を続け、主に以下の 3 つの側面からアプリケーションにおけるコンポーネント化を紹介します。
- アプリケーションの役割
- アプリケーションの統合
- アプリケーションの動的設定
アプリケーションの役割#
Android アプリケーションの起動時に最初に起動するのはアプリケーションです。各アプリは実行時に唯一のアプリケーションを作成し、そのライフサイクルはアプリのライフサイクルです。アプリケーションで一般的に使用されるコールバックメソッドは以下の通りです。
- onCreate:アプリケーションが作成されるときにコールバックされ、コールバックのタイミングはどの Activity よりも早いです。
- onTerminate:アプリケーションが終了するときに呼び出されますが、必ずしも呼び出されるとは限りません。
- onLowMemory:バックグラウンドアプリケーションが終了したが、フォアグラウンドアプリケーションのメモリが不足しているときにこのメソッドが呼び出されます。このメソッド内で不要なリソースを解放してこの状況に対処できます。
- onConfigurationChanged:設定が変更されたときにこのメソッドがコールバックされます。例えば、携帯電話の画面が回転するなどです。
- onTrimMemory:アプリケーションの異なるメモリ状況を通知します。以下のメモリレベルの説明は、Carson_Hoによる onTrimMemory に関連するメモリレベルの説明です。
アプリケーションは全体のアプリのシングルトンオブジェクトとして、以下の役割を果たします。
- アプリの入り口として、基本的な設定を初期化するために使用できます。例えば、サードパーティの SDK の初期化などです。
- アプリケーション内で全体で使用される変数を定義できますが、アプリが強制終了された場合、ヌルポインタの問題が発生する可能性があり、再度アプリを開くとクラッシュすることがあります。このように使用することが確定している場合は、この状況を適切に処理する必要があります。
- アプリケーションを利用して Activity のライフサイクル状態を管理したり、アプリがフォアグラウンドにいるかバックグラウンドにいるかを判断したりできます。メモリの優先度に応じて、自身のアプリが占めるメモリを減少させ、システムによる強制終了の可能性を減らすことができます。
アプリケーションの統合#
AndroidManifest は各モジュールの宣言設定ファイルであり、アプリを生成する際には対応する AndroidManifest ファイルも必要です。複数のモジュールが互いに依存している場合、サブモジュールの AndroidManifest ファイルの内容を主モジュールの AndroidManifest ファイルに統合する必要があります。最終的には build ディレクトリに最終的な AndroidManifest ファイルが生成されます。コンパイル生成された AndroidManifest ファイルの具体的なパスは以下の通りです。
app\build\intermediates\manifests\full\debug\AndroidManifest.xml
サブモジュールの AndroidManifest ファイルを統合する際、コンパイラは use-sdk の情報や未設定の属性を補完します。統合後、Activity などのコンポーネント内の name 属性はパッケージ名 + ファイル名で指定されます。
AndroidManifest ファイルを統合する際には、アプリケーションを統合する必要があります。アプリケーションの統合ルールは以下の通りです。
- サブモジュールにカスタムアプリケーションがあり、主モジュールにカスタムアプリケーションがない場合、サブモジュールのアプリケーションが最終的な AndroidManifest ファイルに統合されます。
- 主モジュールにカスタムアプリケーションがあり、サブモジュールにカスタムアプリケーションがない場合、最終的に統合された AndroidManifest ファイルでは主モジュールのアプリケーションが使用されます。
- 複数のサブモジュールにカスタムアプリケーションがある場合、衝突を解決した後、最終的に統合された AndroidManifest ファイルでは最後にコンパイルされたモジュールのアプリケーションが使用されます。
- サブモジュールにカスタムアプリケーションがあり、サブモジュールにもカスタムアプリケーションがある場合、主モジュールの AndroidManifest ファイルに tools属性を追加する必要があることが通知されます。コンパイルが完了した後、統合された AndroidManifest ファイルでは主モジュールのカスタムアプリケーションが使用されます。
統合プロセスで tools属性を追加しない場合、tools属性を追加するように通知され、エラーメッセージは以下の通りです。
Manifest merger failed : Attribute application@name value=(com.manu.module_one.OneApplication) from [:moduel_one] AndroidManifest.xml:13:9-58
is also present at [:module_two] AndroidManifest.xml:13:9-58 value=(com.manu.module_two.TwoApplication).
Suggestion: add 'tools:replace="android:name"' to <application> element at AndroidManifest.xml:6:5-21:19 to override.
例えば、ここではサブモジュールの AndroidManifest ファイルの application タグの下に tools属性を追加する必要があります。
tools:replace="android:name"
アプリケーションの動的設定#
アプリケーションの統合に加えて、コンポーネント化プロセスにおける各モジュールの初期化も非常に重要です。リフレクションを使用して各モジュールの初期化を完了することができます。これは、主モジュール内でサブモジュールの初期化オブジェクトをリフレクションで取得し、その初期化メソッドを呼び出すことを意味します。サブモジュールの初期化クラスを管理するためにクラスを定義することが便利です。以下のようにします。
/**
* Created by jzman
* Powered by 2019/04/15 0022.
*/
public class ModuleConfig {
private static final String moduleOneInit = "com.manu.module_one.ModuleOneAppInit";
private static final String moduleTwoInit = "com.manu.module_two.ModuleTwoAppInit";
public static String[] moduleInits = {
moduleOneInit,
moduleTwoInit
};
}
初期化の基底インターフェースを以下のように作成します。
/**
* 統一App初期化インターフェース
* Created by jzman
* Powered by 2019/04/15 0022.
*/
public interface BaseAppInit {
/**
* 高優先度で初期化される
* @param application
* @return
*/
boolean onInitHighPriority(Application application);
/**
* 低優先度で初期化される
* @param application
* @return
*/
boolean onInitLowPriority(Application application);
}
各サブモジュールがこの初期化基底クラスを便利に使用できるように、基底モジュールに配置します。基底クラスはすべてのモジュールに依存しているため、各サブモジュールで BaseAppInit を継承して自分のモジュールの初期化クラスを実装します。以下のようにします。
/**
* module_one初期化ファイル
* Created by jzman
* Powered by 2019/04/15 0022.
*/
public class ModuleOneAppInit implements BaseAppInit {
private static final String TAG = ModuleOneAppInit.class.getSimpleName();
@Override
public boolean onInitHighPriority(Application application) {
Log.i(TAG, "ModuleOneAppInit---onInitHighPriority");
return true;
}
@Override
public boolean onInitLowPriority(Application application) {
Log.i(TAG, "ModuleOneAppInit---onInitLowPriority");
return true;
}
}
最後に、主モジュールのカスタムアプリケーション内でリフレクションを使用して各サブモジュールの初期化クラスオブジェクトを作成し、その初期化メソッドを呼び出します。以下のようにします。
/**
* 高優先度初期化
*/
private void initModuleHighPriority(){
for (String init: ModuleConfig.moduleInits){
try {
Class<?> clazz = Class.forName(init);
BaseAppInit appInit = (BaseAppInit) clazz.newInstance();
appInit.onInitHighPriority(this);
} catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {
e.printStackTrace();
}
}
}
/**
* 低優先度初期化
*/
private void initModuleLowPriority(){
for (String init: ModuleConfig.moduleInits){
try {
Class<?> clazz = Class.forName(init);
BaseAppInit appInit = (BaseAppInit) clazz.newInstance();
appInit.onInitLowPriority(this);
} catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {
e.printStackTrace();
}
}
}
実行ログは以下の通りです。
ModuleOneAppInit---onInitHighPriority
ModuleTwoAppInit---onInitHighPriority
ModuleOneAppInit---onInitLowPriority
ModuleTwoAppInit---onInitLowPriority
また、基底モジュール内に初期化基底クラスと BaseApplication を作成し、BaseApplication 内で具体的な初期化メソッドをリフレクションで呼び出すこともできます。根本的にはリフレクションを使用しますが、別の実装方法です。まず、基底モジュール内に BaseAppInit を以下のように作成します。
/**
* Created by jzman
* Powered by 2019/04/15 0022.
*/
public abstract class BaseAppInit {
private Application mApplication;
public BaseAppInit() {
}
public void setApplication(@NonNull Application application) {
this.mApplication = application;
}
public void onCreate(){}
public void OnTerminate(){}
public void onLowMemory(){}
public void configurationChanged(Configuration configuration){}
}
基底モジュール内に BaseApplication を以下のように作成します。
/**
* Created by jzman
* Powered by 2019/04/15 0023.
*/
public abstract class BaseApplication extends Application {
private List<Class<? extends BaseAppInit>> classInitList = new ArrayList<>();
private List<BaseAppInit> appInitList = new ArrayList<>();
@Override
public void onCreate() {
super.onCreate();
appInit();
initCreate();
}
protected abstract void appInit();
protected void registerApplicationInit(Class<? extends BaseAppInit> classInit) {
classInitList.add(classInit);
}
private void initCreate() {
for (Class<? extends BaseAppInit> classInit : classInitList) {
try {
BaseAppInit appInit = classInit.newInstance();
appInitList.add(appInit);
appInit.onCreate();
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
}
}
@Override
public void onTerminate() {
super.onTerminate();
for (BaseAppInit appInit : appInitList) {
appInit.OnTerminate();
}
}
@Override
public void onLowMemory() {
super.onLowMemory();
for (BaseAppInit appInit : appInitList) {
appInit.onLowMemory();
}
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
for (BaseAppInit appInit : appInitList) {
appInit.configurationChanged(newConfig);
}
}
}
次に、サブモジュールで具体的な初期化クラスを実装します。以下のようにします。
/**
* Created by jzman
* Powered by 2019/04/15 0023.
*/
public class ModuleThreeAppInit extends BaseAppInit {
private static final String TAG = ModuleThreeAppInit.class.getSimpleName();
@Override
public void onCreate() {
Log.i(TAG, "ModuleThreeAppInit---onCreate");
}
}
最後に、主モジュールで BaseApplication を継承してカスタムアプリケーションを実装し、各サブモジュールの初期化ファイルを登録します。以下のようにします。
/**
* Created by jzman
* Powered by 2019/04/15 0023.
*/
public class MApplication extends BaseApplication{
@Override
protected void appInit() {
registerApplicationInit(ModuleThreeAppInit.class);
registerApplicationInit(ModuleForeAppInit.class);
}
}
実行ログは以下の通りです。
ModuleThreeAppInit---onCreate
ModuleForeAppInit---onCreate
上記の 2 つの方法はどちらもリフレクションを使用しており、リフレクションはデカップリングを実現する一方で、アプリケーションのパフォーマンスをある程度低下させます。もちろん、コンポーネント化の目的は、各コンポーネントや各モジュール間のデカップリングをできるだけ実現することです。パフォーマンスを少し犠牲にしてでも、デカップリングを最大化できるのであれば、それも受け入れられます。