【Android】 getApplication() をCustomApplicationにCastできる仕組み
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 /*parent*/); 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 // System.out.println("##### [" + System.currentTimeMillis() + "] ActivityThread.performLaunchActivity(" + r + ")"); 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 }
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 // Rewrite the R 'constants' for all library apks. 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 }
1007 Application app = (Application)clazz.newInstance();
遂にたどり着きました。ココでappClass
を元にCustomApplicationを作成し、それをApplicationにダウンキャストしていることがわかります。
このようにしてgetApplication()で取得するApplicationはCustomApplicationにキャストできるわけですね。