PS:専門的な知識を見たくないなら、興味のある知識を見てください。とにかく、あまり怠けないようにしましょう。
前の記事では、コンポーネント化開発のいくつかの基本的な問題を概括的にまとめました。本記事では、コンポーネント化の学習を続け、主に以下の 3 つの側面からコンポーネント化における Application について紹介します。
- Application の役割
- Application の統合
- 動的な Application の設定
Application の役割#
Android アプリケーションが起動する際に最初に起動するのが Application です。各アプリは実行時に唯一の Application を作成し、そのライフサイクルはアプリのライフサイクルです。Application でよく使われるコールバックメソッドは以下の通りです。
- onCreate:アプリケーションが作成されるときにコールバックされ、コールバックのタイミングはどの Activity よりも早いです。
- onTerminate:アプリケーションが終了するときに呼び出されますが、必ず呼び出されるとは限りません。
- onLowMemory:バックグラウンドのアプリケーションが終了し、フォアグラウンドのアプリケーションのメモリが不足しているときに呼び出されるメソッドで、このメソッド内で不要なリソースを解放してこの状況に対処できます。
- onConfigurationChanged:設定が変更されたときにこのメソッドがコールバックされます。例えば、携帯電話の画面が回転するなどです。
- onTrimMemory:アプリケーションの異なるメモリ状況を通知します。以下のメモリレベルの説明は、Carson_Hoからのものです。
以下に、Carson_Hoがまとめた onTrimMemory に関連するメモリレベルの説明を添付します。
Application はアプリ全体のシングルトンオブジェクトとして、以下の役割を果たします。
- アプリの入り口として、基本設定を初期化するために使用できます。例えば、サードパーティの SDK の初期化などです。
- Application 内で全体で使用する変数を定義できますが、アプリが強制終了された場合、空ポインタの問題が発生する可能性があり、再度アプリを開くとクラッシュすることがあります。このように使用することが確定している場合は、この状況を適切に処理する必要があります。
- Application を利用して Activity のライフサイクル状態を管理し、アプリがフォアグラウンドかバックグラウンドかを判断できます。また、メモリの優先度に応じて自アプリが占有するメモリを減少させ、システムによる強制終了の可能性を低減できます。
Application の統合#
AndroidManifest は各 Module の宣言設定ファイルであり、アプリを生成する際には対応する AndroidManifest ファイルも必要です。複数の Module が互いに依存している場合、子 Module の AndroidManifest ファイルの内容を親 Module の AndroidManifest ファイルに統合する必要があります。最終的には build ディレクトリに最終的な AndroidManifest ファイルが生成され、コンパイル生成された AndroidManifest ファイルの具体的なパスは以下の通りです。
app\build\intermediates\manifests\full\debug\AndroidManifest.xml
子 Module の AndroidManifest ファイルを統合する際、コンパイラは use-sdk の情報や未設定の属性を補完します。統合後、Activity などのコンポーネント内の name 属性はパッケージ名 + ファイル名で指定されます。
AndroidManifest ファイルを統合する際には Application を統合する必要があり、Application の統合ルールは以下の通りです。
- 子 Module にカスタム Application があり、親 Module にカスタム Application がない場合、子 Module の Application が最終的な AndroidManifest ファイルに統合されます。
- 親 Module にカスタム Application があり、子 Module にカスタム Application がない場合、最終的に統合された AndroidManifest ファイルでは親 Module の Application が使用されます。
- 複数の子 Module にカスタム Application がある場合、衝突を解決した後、最終的に統合された AndroidManifest ファイルでは最後にコンパイルされた Module の Application が使用されます。
- 子 Module にカスタム Application があり、子 Module にもカスタム Application がある場合、この時も親 Module の AndroidManifest ファイルに tools属性を追加する必要があることが示されます。コンパイルが完了した後、統合された AndroidManifest ファイルでは親 Module のカスタム Application が使用されます。
統合プロセスで 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.
例えば、ここでは子 Module の AndroidManifest ファイルの application タグの下に tools属性を追加する必要があります。
tools:replace="android:name"
動的な Application の設定#
Application を統合する必要があるだけでなく、コンポーネント化プロセスにおいて各 Module の初期化も非常に重要です。リフレクションを使用して各 Module の初期化を完了させることができます。つまり、親 Module 内で子 Module の初期化オブジェクトをリフレクションで取得し、その初期化メソッドを呼び出します。便利さのために、子 Module の初期化クラスを管理するクラスを定義します。以下を参照してください。
/**
* 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);
}
各子 Module がこの初期化基底クラスを便利に使用できるように、基底 Module に配置します。基底クラスはすべての Module に依存しているため、各子 Module で BaseAppInit を継承して自分の Module の初期化クラスを実装します。以下を参照してください。
/**
* 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;
}
}
最後に、親 Module のカスタム Application 内でリフレクションを使用して各子 Module の初期化クラスオブジェクトを作成し、その初期化メソッドを呼び出します。以下を参照してください。
/**
* 高優先度初期化
*/
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
また、基底 Module 内に初期化基底クラスと BaseApplication を作成し、BaseApplication 内でリフレクションを使用して具体的な初期化メソッドを呼び出すこともできます。根本的にはリフレクションを使用するだけで、別の実装方法です。まず、基底 Module 内に 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){}
}
基底 Module 内に 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);
}
}
}
次に、子 Module 内で具体的な初期化クラスを実装します。以下を参照してください。
/**
* 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");
}
}
最後に、親 Module 内で BaseApplication を継承してカスタム Application を実装し、各子 Module の初期化ファイルを登録します。以下を参照してください。
/**
* 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 つの方法はどちらもリフレクションを使用しており、リフレクションはデカップリングを実現する一方で、アプリケーションのパフォーマンスをある程度低下させます。もちろん、コンポーネント化の目的は、各コンポーネントや各 Module 間のデカップリングを可能な限り実現することです。パフォーマンスを少し犠牲にしてでも、デカップリングを最大化できるのであれば、それは受け入れられます。