tsurutanのつぶやき

これが、破壊的イノベーションだっっっ!!

【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が返されていることがわかります。 またmApplicationattachメソッドが呼び出された時に初期化されています。

またActivity.attachActivityThread.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     }

全体コード(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         // 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.classNamenullの時に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にキャストできるわけですね。