Android開発をしたことがある方は、Activity, FragmentなどでgetApplication
を使いApplicationを呼び出しCustomApplicationにCastしたkことがあるのではないかと思います。
そのときなんでApplicationをCustomApplicationにCastしたときjava.lang.ClassCastException
が発生しないんだろう?どういう仕組なんだろう?と疑問に感じたことは無いでしょうか?
今回はその仕組について説明したいと思います。
疑問
そもそもCustomApplicationは
public class CustomApplication extends Application {
@Override
public final void onCreate() {
super.onCreate();
}
}
の様にApplicationのサブクラスになるため、直接生成したApplicationをCustomApplicationにキャストしようとすると java.lang.ClassCastException
が発生するはずです。
また Application
は
public class Application extends ContextWrapper implements ComponentCallbacks2 {
....
}
public class ContextWrapper extends Context {
...
}
となっているため、同様に直接生成したContextをCustomApplicationにCastするとエラーが発生するはずです。
薄々気づいてるかもしれませんが、ダウンキャスト時にエラーが発生しないということは内部でCustomApplication->Application->CustomApplication又はCustomApplication->Context->CustomApplicationの順でキャストがなされているはずです。
どのように行われているか見ていきます。
仕組み
Activity内でgetApplicationをした時
Activity.java
Return the application that owns this activity.
public final Application getApplication() {
return mApplication;
}
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor,
Window window) {
attachBaseContext(context);
mFragments.attachHost(null );
mWindow = new PhoneWindow(this, window);
mWindow.setWindowControllerCallback(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
mWindow.setSoftInputMode(info.softInputMode);
}
if (info.uiOptions != 0) {
mWindow.setUiOptions(info.uiOptions);
}
mUiThread = Thread.currentThread();
mMainThread = aThread;
mInstrumentation = instr;
mToken = token;
mIdent = ident;
mApplication = application;
mIntent = intent;
mReferrer = referrer;
mComponent = intent.getComponent();
mActivityInfo = info;
mTitle = title;
mParent = parent;
mEmbeddedID = id;
mLastNonConfigurationInstances = lastNonConfigurationInstances;
if (voiceInteractor != null) {
if (lastNonConfigurationInstances != null) {
mVoiceInteractor = lastNonConfigurationInstances.voiceInteractor;
} else {
mVoiceInteractor = new VoiceInteractor(voiceInteractor, this, this,
Looper.myLooper());
}
}
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
if (mParent != null) {
mWindow.setContainer(mParent.getWindow());
}
mWindowManager = mWindow.getWindowManager();
mCurrentConfig = config;
}
全体コード(http://tools.oesf.biz/android-7.1.1_r1.0/xref/frameworks/base/core/java/android/app/Activity.java)
Activityを継承したクラスでgetApplicationをするとActivity内のメンバ変数mApplication
が返されていることがわかります。
またmApplication
はattach
メソッドが呼び出された時に初期化されています。
またActivity.attach
はActivityThread.performLaunchActivity
で呼び出されており
ActivityThread.java
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
2518
2519
2520 ActivityInfo aInfo = r.activityInfo;
2521 if (r.packageInfo == null) {
2522 r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo,
2523 Context.CONTEXT_INCLUDE_CODE);
2524 }
2525
2526 ComponentName component = r.intent.getComponent();
2527 if (component == null) {
2528 component = r.intent.resolveActivity(
2529 mInitialApplication.getPackageManager());
2530 r.intent.setComponent(component);
2531 }
2532
2533 if (r.activityInfo.targetActivity != null) {
2534 component = new ComponentName(r.activityInfo.packageName,
2535 r.activityInfo.targetActivity);
2536 }
2537
2538 Activity activity = null;
2539 try {
2540 java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
2541 activity = mInstrumentation.newActivity(
2542 cl, component.getClassName(), r.intent);
2543 StrictMode.incrementExpectedActivityCount(activity.getClass());
2544 r.intent.setExtrasClassLoader(cl);
2545 r.intent.prepareToEnterProcess();
2546 if (r.state != null) {
2547 r.state.setClassLoader(cl);
2548 }
2549 } catch (Exception e) {
2550 if (!mInstrumentation.onException(activity, e)) {
2551 throw new RuntimeException(
2552 "Unable to instantiate activity " + component
2553 + ": " + e.toString(), e);
2554 }
2555 }
2556
2557 try {
2558 Application app = r.packageInfo.makeApplication(false, mInstrumentation);
2559
2560 if (localLOGV) Slog.v(TAG, "Performing launch of " + r);
2561 if (localLOGV) Slog.v(
2562 TAG, r + ": app=" + app
2563 + ", appName=" + app.getPackageName()
2564 + ", pkg=" + r.packageInfo.getPackageName()
2565 + ", comp=" + r.intent.getComponent().toShortString()
2566 + ", dir=" + r.packageInfo.getAppDir());
2567
2568 if (activity != null) {
2569 Context appContext = createBaseContextForActivity(r, activity);
2570 CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
2571 Configuration config = new Configuration(mCompatConfiguration);
2572 if (r.overrideConfig != null) {
2573 config.updateFrom(r.overrideConfig);
2574 }
2575 if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity "
2576 + r.activityInfo.name + " with config " + config);
2577 Window window = null;
2578 if (r.mPendingRemoveWindow != null && r.mPreserveWindow) {
2579 window = r.mPendingRemoveWindow;
2580 r.mPendingRemoveWindow = null;
2581 r.mPendingRemoveWindowManager = null;
2582 }
2583 activity.attach(appContext, this, getInstrumentation(), r.token,
2584 r.ident, app, r.intent, r.activityInfo, title, r.parent,
2585 r.embeddedID, r.lastNonConfigurationInstances, config,
2586 r.referrer, r.voiceInteractor, window);
2587
2588 if (customIntent != null) {
2589 activity.mIntent = customIntent;
2590 }
2591 r.lastNonConfigurationInstances = null;
2592 activity.mStartedActivity = false;
2593 int theme = r.activityInfo.getThemeResource();
2594 if (theme != 0) {
2595 activity.setTheme(theme);
2596 }
2597
2598 activity.mCalled = false;
2599 if (r.isPersistable()) {
2600 mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
2601 } else {
2602 mInstrumentation.callActivityOnCreate(activity, r.state);
2603 }
2604 if (!activity.mCalled) {
2605 throw new SuperNotCalledException(
2606 "Activity " + r.intent.getComponent().toShortString() +
2607 " did not call through to super.onCreate()");
2608 }
2609 r.activity = activity;
2610 r.stopped = true;
2611 if (!r.activity.mFinished) {
2612 activity.performStart();
2613 r.stopped = false;
2614 }
2615 if (!r.activity.mFinished) {
2616 if (r.isPersistable()) {
2617 if (r.state != null || r.persistentState != null) {
2618 mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state,
2619 r.persistentState);
2620 }
2621 } else if (r.state != null) {
2622 mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state);
2623 }
2624 }
2625 if (!r.activity.mFinished) {
2626 activity.mCalled = false;
2627 if (r.isPersistable()) {
2628 mInstrumentation.callActivityOnPostCreate(activity, r.state,
2629 r.persistentState);
2630 } else {
2631 mInstrumentation.callActivityOnPostCreate(activity, r.state);
2632 }
2633 if (!activity.mCalled) {
2634 throw new SuperNotCalledException(
2635 "Activity " + r.intent.getComponent().toShortString() +
2636 " did not call through to super.onPostCreate()");
2637 }
2638 }
2639 }
2640 r.paused = true;
2641
2642 mActivities.put(r.token, r);
2643
2644 } catch (SuperNotCalledException e) {
2645 throw e;
2646
2647 } catch (Exception e) {
2648 if (!mInstrumentation.onException(activity, e)) {
2649 throw new RuntimeException(
2650 "Unable to start activity " + component
2651 + ": " + e.toString(), e);
2652 }
2653 }
2654
2655 return activity;
2656 }
全体コード(http://tools.oesf.biz/android-7.1.0_r1.0/xref/frameworks/base/core/java/android/app/ActivityThread.java)
2558 Application app = r.packageInfo.makeApplication(false, mInstrumentation);
2583 activity.attach(appContext, this, getInstrumentation(), r.token,
2584 r.ident, app, r.intent, r.activityInfo, title, r.parent,
2585 r.embeddedID, r.lastNonConfigurationInstances, config,
2586 r.referrer, r.voiceInteractor, window);
2588行目で作成したApplication型のインスタンスをactivity.attach
の引数で渡していることがわかります。
この時makeApplication
の中身が気になるので調べてみると
LoadedApk.java
772 public Application makeApplication(boolean forceDefaultAppClass,
773 Instrumentation instrumentation) {
774 if (mApplication != null) {
775 return mApplication;
776 }
777
778 Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "makeApplication");
779
780 Application app = null;
781
782 String appClass = mApplicationInfo.className;
783 if (forceDefaultAppClass || (appClass == null)) {
784 appClass = "android.app.Application";
785 }
786
787 try {
788 java.lang.ClassLoader cl = getClassLoader();
789 if (!mPackageName.equals("android")) {
790 Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
791 "initializeJavaContextClassLoader");
792 initializeJavaContextClassLoader();
793 Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
794 }
795 ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
796 app = mActivityThread.mInstrumentation.newApplication(
797 cl, appClass, appContext);
798 appContext.setOuterContext(app);
799 } catch (Exception e) {
800 if (!mActivityThread.mInstrumentation.onException(app, e)) {
801 Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
802 throw new RuntimeException(
803 "Unable to instantiate application " + appClass
804 + ": " + e.toString(), e);
805 }
806 }
807 mActivityThread.mAllApplications.add(app);
808 mApplication = app;
809
810 if (instrumentation != null) {
811 try {
812 instrumentation.callApplicationOnCreate(app);
813 } catch (Exception e) {
814 if (!instrumentation.onException(app, e)) {
815 Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
816 throw new RuntimeException(
817 "Unable to create application " + app.getClass().getName()
818 + ": " + e.toString(), e);
819 }
820 }
821 }
822
823
824 SparseArray<String> packageIdentifiers = getAssets(mActivityThread)
825 .getAssignedPackageIdentifiers();
826 final int N = packageIdentifiers.size();
827 for (int i = 0; i < N; i++) {
828 final int id = packageIdentifiers.keyAt(i);
829 if (id == 0x01 || id == 0x7f) {
830 continue;
831 }
832
833 rewriteRValues(getClassLoader(), packageIdentifiers.valueAt(i), id);
834 }
835
836 Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
837
838 return app;
839 }
全体コード(http://tools.oesf.biz/android-7.1.0_r1.0/xref/frameworks/base/core/java/android/app/LoadedApk.java)
796 app = mActivityThread.mInstrumentation.newApplication(
797 cl, appClass, appContext);
となっておりInstrument.newApplication
からApplication
を作成していることがわかります。
また引数として渡しているappClass
は
780 Application app = null;
781
782 String appClass = mApplicationInfo.className;
783 if (forceDefaultAppClass || (appClass == null)) {
784 appClass = "android.app.Application";
785 }
となっており、forceDefaultAppClass
(makeApplicationの引数)がtrue
又はmApplicationInfo.className
がnull
の時にandroid.app.Application
が代入されるのがわかります。
ちなみにこの時のmApplicationInfo.classNameにはAndroidManifest.xmlにかかれている
<application
android:name=".CustomApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
のandroid:name
に指定された値が入ってきます。
989 public Application newApplication(ClassLoader cl, String className, Context context)
990 throws InstantiationException, IllegalAccessException,
991 ClassNotFoundException {
992 return newApplication(cl.loadClass(className), context);
993 }
994
1004 static public Application newApplication(Class<?> clazz, Context context)
1005 throws InstantiationException, IllegalAccessException,
1006 ClassNotFoundException {
1007 Application app = (Application)clazz.newInstance();
1008 app.attach(context);
1009 return app;
1010 }
全体コード(http://tools.oesf.biz/android-7.1.0_r1.0/xref/frameworks/base/core/java/android/app/Instrumentation.java)
1007 Application app = (Application)clazz.newInstance();
遂にたどり着きました。ココでappClass
を元にCustomApplicationを作成し、それをApplicationにダウンキャストしていることがわかります。
このようにしてgetApplication()で取得するApplicationはCustomApplicationにキャストできるわけですね。