你足夠了解Context嗎?

你足夠了解Context嗎?


寫在前面:

當我還是一個24K純Android新手的時候(現(xiàn)在是也是個小Android萌新),拿著工具書對著電腦敲敲打打,那個時候我就有一個非常大的疑問:Context到底為何這么牛?show一個Dialog,啟動一個Activity,Service,發(fā)送一個Broadcast,還有各種方法需要傳入的參數(shù)。幾乎在Android中,Context的身影處處可見,所以弄懂它,似乎是一件迫在眉睫的事,所以深呼吸,整理思路,來看看Context到底是什么。

轉(zhuǎn)載請注明出處。MeloDev

零:官方定義

好吧如果你無法翻墻,推薦你兩個可以看官網(wǎng)文檔的網(wǎng)站:

Android官方文檔國內(nèi)鏡像站點

Android中文API

我們來看看官方文檔中,Context的解釋

Interface to global information about an application environment. This is an abstract class whose implementation is provided by the Android system. It allows access to application-specific resources and classes, as well as up-calls for application-level operations such as launching activities, broadcasting and receiving intents, etc.

  • 一個應用環(huán)境的全局信息,字面意思是上下文的意思;
  • Context是一個抽象類;
  • 允許我們通過Context獲取各種資源,服務,或者去啟動一個Activity,發(fā)送一個廣播,等等;

怎么去理解Context呢?其實Context就是給Android應用程序提供了一個可以實現(xiàn)各種操作的土壤環(huán)境,Context為Android提供了各種資源、功能、服務。如果說編寫一個Android程序像搭建一座房子,那Context就為Android提供了土地,木材,和染料(啟動一個Activity,彈出一個Dialog),并且能提供呼叫各種將房屋建得更完善的其他幫助(發(fā)送一個廣播,啟動一個服務等)。

一:繼承關系

Context繼承關系

通過繼承關系可以看到,Context直接子類為ContextIml(實現(xiàn)類)和ContextWrapper(包裝類)

再看看ContextWrapper的子類有什么,看到熟悉的Service和Application了吧,不過看到這里你一定有個疑問,為什么Activity和他們哥倆不在一個繼承層級呢?而是Activity又繼承了ContextThemeWrapper,那么ContextWrapper和ContextThemeWrapper的區(qū)別在哪里呢?

看到這兩個類的名字,相信你心里已經(jīng)有了答案,對,區(qū)別在Theme

該類內(nèi)部包含了主題(Theme)相關的接口,即android:theme屬性指定的。
只有Activity需要主題,Service不需要主題,
所以Service直接繼承于ContextWrapper類。而Activity因為含有Theme屬性的緣故,所以繼承自ContextThemeWrapper。

所以說,Context所調(diào)用的資源是不同的了?保留這個疑問,繼續(xù)向下看。

二:源碼閱讀

看到了繼承結(jié)構(gòu),我們分別來看看Context及其子類的一些源碼

  • Context
public abstract class Context {

    // 獲取應用程序包的AssetManager實例
    public abstract AssetManager getAssets();
 
    // 獲取應用程序包的Resources實例
    public abstract Resources getResources();

    // 獲取PackageManager實例,以查看全局package信息    
    public abstract PackageManager getPackageManager();

    // 獲取應用程序包的ContentResolver實例
    public abstract ContentResolver getContentResolver();
    
    // 它返回當前進程的主線程的Looper,此線程分發(fā)調(diào)用給應用組件(activities, services等)
    public abstract Looper getMainLooper();

    // 返回當前進程的單實例全局Application對象的Context     
    public abstract Context getApplicationContext();

    // 從string表中獲取本地化的、格式化的字符序列
    public final CharSequence getText(int resId) {
        return getResources().getText(resId);
    }

    // 從string表中獲取本地化的字符串
    public final String getString(int resId) {
        return getResources().getString(resId);
    }

    public final String getString(int resId, Object... formatArgs) {
        return getResources().getString(resId, formatArgs);
    }

    // 返回一個可用于獲取包中類信息的class loader
    public abstract ClassLoader getClassLoader();

    // 返回應用程序包名
    public abstract String getPackageName();

    // 返回應用程序信息
    public abstract ApplicationInfo getApplicationInfo();

    // 根據(jù)文件名獲取SharedPreferences
    public abstract SharedPreferences getSharedPreferences(String name,
            int mode);

    // 其根目錄為: Environment.getExternalStorageDirectory()
    
    public abstract File getExternalFilesDir(String type);

    // 返回應用程序obb文件路徑
    public abstract File getObbDir();

    // 啟動一個新的activity 
    public abstract void startActivity(Intent intent);

    // 啟動一個新的activity 
    public void startActivityAsUser(Intent intent, UserHandle user) {
        throw new RuntimeException("Not implemented. Must override in a subclass.");
    }

    // 啟動一個新的activity 
    // intent: 將被啟動的activity的描述信息
    // options: 描述activity將如何被啟動
    public abstract void startActivity(Intent intent, Bundle options);

    // 啟動多個新的activity
    public abstract void startActivities(Intent[] intents);

    // 啟動多個新的activity
    public abstract void startActivities(Intent[] intents, Bundle options);

    // 廣播一個intent給所有感興趣的接收者,異步機制 
    public abstract void sendBroadcast(Intent intent);

    // 廣播一個intent給所有感興趣的接收者,異步機制 
    public abstract void sendBroadcast(Intent intent,String receiverPermission);

    public abstract void sendOrderedBroadcast(Intent intent,String receiverPermission);
 
    public abstract void sendOrderedBroadcast(Intent intent,
            String receiverPermission, BroadcastReceiver resultReceiver,
            Handler scheduler, int initialCode, String initialData,
            Bundle initialExtras);

    public abstract void sendBroadcastAsUser(Intent intent, UserHandle user);

    public abstract void sendBroadcastAsUser(Intent intent, UserHandle user,
            String receiverPermission);
  
    // 注冊一個BroadcastReceiver,且它將在主activity線程中運行
    public abstract Intent registerReceiver(BroadcastReceiver receiver,
                                            IntentFilter filter);

    public abstract Intent registerReceiver(BroadcastReceiver receiver,
            IntentFilter filter, String broadcastPermission, Handler scheduler);

    public abstract void unregisterReceiver(BroadcastReceiver receiver);
 
    // 請求啟動一個application service
    public abstract ComponentName startService(Intent service);

    // 請求停止一個application service
    public abstract boolean stopService(Intent service);
 
    // 連接一個應用服務,它定義了application和service間的依賴關系
    public abstract boolean bindService(Intent service, ServiceConnection conn,
            int flags);

    // 斷開一個應用服務,當服務重新開始時,將不再接收到調(diào)用, 
    // 且服務允許隨時停止
    public abstract void unbindService(ServiceConnection conn);
 
    public abstract Object getSystemService(String name);
 
    public abstract int checkPermission(String permission, int pid, int uid);
 
    // 返回一個新的與application name對應的Context對象
    public abstract Context createPackageContext(String packageName,
            int flags) throws PackageManager.NameNotFoundException;
    
    // 返回基于當前Context對象的新對象,其資源與display相匹配
    public abstract Context createDisplayContext(Display display);
 }  

Context的源碼算上注釋有3000行之多,這里貼出一些重要代碼,可以看到,Context幾乎包含了所有你能想到的,一個Android程序需要的資源和操作,Context自己就像一個App一樣,啟動Activity、Service,發(fā)送Broadcast,拿到assets下的資源,獲取SharedPreferences等等。

但Context只是一個頂層接口啊,又是誰幫他實現(xiàn)了操作呢?是ContextWrapper嗎?

  • ContextWrapper
public class ContextWrapper extends Context {
    Context mBase; //該屬性指向一個ContextIml實例

    public ContextWrapper(Context base) {
        mBase = base;
    }

    /**
     * @param base The new base context for this wrapper.
     * 創(chuàng)建Application、Service、Activity,會調(diào)用該方法給mBase屬性賦值
     */
    protected void attachBaseContext(Context base) {
        if (mBase != null) {
            throw new IllegalStateException("Base context already set");
        }
        mBase = base;
    }

    @Override
    public Looper getMainLooper() {
        return mBase.getMainLooper();
    }

    @Override
    public Object getSystemService(String name) {
        return mBase.getSystemService(name);
    }

    @Override
    public void startActivity(Intent intent) {
        mBase.startActivity(intent);
    }
}

好吧,ContextWrapper好像很懶的樣子,它把所有操作都丟給了mBase,mBase又是誰呢?在構(gòu)造方法和attachBaseContext方法中,指向了一個Context實例,ContextImpl,我們趕緊來看看ContextImpl的源碼!

  • ContextImpl
/**
 * Common implementation of Context API, which provides the base
 * context object for Activity and other application components.
 */
class ContextImpl extends Context {
    private final static String TAG = "ContextImpl";
    private final static boolean DEBUG = false;

    private static final HashMap<String, SharedPreferencesImpl> sSharedPrefs =
            new HashMap<String, SharedPreferencesImpl>();

    /*package*/ LoadedApk mPackageInfo; // 關鍵數(shù)據(jù)成員
    private String mBasePackageName;
    private Resources mResources;
    /*package*/ ActivityThread mMainThread; // 主線程

    @Override
    public AssetManager getAssets() {
        return getResources().getAssets();
    }

    @Override
    public Looper getMainLooper() {
        return mMainThread.getLooper();
    }

    @Override
    public Object getSystemService(String name) {
        ServiceFetcher fetcher = SYSTEM_SERVICE_MAP.get(name);
        return fetcher == null ? null : fetcher.getService(this);
    }

    @Override
    public void startActivity(Intent intent, Bundle options) {
        warnIfCallingFromSystemProcess();
        if ((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) {
            throw new AndroidRuntimeException(
                    "Calling startActivity() from outside of an Activity "
                    + " context requires the FLAG_ACTIVITY_NEW_TASK flag."
                    + " Is this really what you want?");
        }
        mMainThread.getInstrumentation().execStartActivity(
            getOuterContext(), mMainThread.getApplicationThread(), null,
            (Activity)null, intent, -1, options);
    }
}

其實ContextImpl才是在Context的所有繼承結(jié)構(gòu)中唯一一個真正實現(xiàn)了Context中方法的類。其它Context的子類,Application,Activity,Service,都是與ContextImpl相關聯(lián),去獲取資源和服務,并沒有真正自己去實現(xiàn),這里就不貼上ContextThemeWrapper的源碼了,它是為Activity添加了一些Theme的屬性,不再贅述。

思路越來越清晰,我們現(xiàn)在就是要去尋找,Activity,Service,Application是何時與ContextImpl完成綁定關聯(lián)的。

三:關聯(lián)時機

我們都知道ActivityThreadmain方法,是整個Android程序的入口,所以去探究ActivityThread類,也是一件非常重要的事。

推薦一篇文章,去了解下ActivityThread

ActivityThread簡介

貼出ActivityThread的main方法部分重要的代碼

public final class ActivityThread {  

    static ContextImpl mSystemContext = null;  
    
    static IPackageManager sPackageManager;
    
    // 創(chuàng)建ApplicationThread實例,以接收AMS指令并執(zhí)行  
    final ApplicationThread mAppThread = new ApplicationThread();  
  
    final Looper mLooper = Looper.myLooper();  
    
    final HashMap<IBinder, ActivityClientRecord> mActivities  
            = new HashMap<IBinder, ActivityClientRecord>();  
            
    final HashMap<IBinder, Service> mServices  
            = new HashMap<IBinder, Service>();  
            
    final H mH = new H();  

    Application mInitialApplication;  
  
    final ArrayList<Application> mAllApplications  
            = new ArrayList<Application>();  
  
    static final ThreadLocal<ActivityThread> sThreadLocal = new ThreadLocal<ActivityThread>();  
    
    Instrumentation mInstrumentation;  
  
    static Handler sMainThreadHandler;  // set once in main()  

    private class H extends Handler {  
  
        public void handleMessage(Message msg) {  
            if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));  
            switch (msg.what) {  
                case LAUNCH_ACTIVITY: {  
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");  
                    ActivityClientRecord r = (ActivityClientRecord)msg.obj;  
  
                    r.packageInfo = getPackageInfoNoCheck(  
                            r.activityInfo.applicationInfo, r.compatInfo);  
                    handleLaunchActivity(r, null);  
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);  
                } break;  
                ...  
            }  
            if (DEBUG_MESSAGES) Slog.v(TAG, "<<< done: " + codeToString(msg.what));  
        }  
         
        ...  
    }  

    private class ApplicationThread extends ApplicationThreadNative {  
  
        private void updatePendingConfiguration(Configuration config) {  
            synchronized (mPackages) {  
                if (mPendingConfiguration == null ||  
                        mPendingConfiguration.isOtherSeqNewer(config)) {  
                    mPendingConfiguration = config;  
                }  
            }  
        }  
  
        public final void schedulePauseActivity(IBinder token, boolean finished,  
                boolean userLeaving, int configChanges) {  
            queueOrSendMessage(  
                    finished ? H.PAUSE_ACTIVITY_FINISHING : H.PAUSE_ACTIVITY,  
                    token,  
                    (userLeaving ? 1 : 0),  
                    configChanges);  
        }  
  
        // we use token to identify this activity without having to send the  
        // activity itself back to the activity manager. (matters more with ipc)  
        public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,  
                ActivityInfo info, Configuration curConfig, CompatibilityInfo compatInfo,  
                Bundle state, List<ResultInfo> pendingResults,  
                List<Intent> pendingNewIntents, boolean notResumed, boolean isForward,  
                String profileName, ParcelFileDescriptor profileFd, boolean autoStopProfiler) {  
            ActivityClientRecord r = new ActivityClientRecord();  
  
            r.token = token;  
            r.ident = ident;  
            r.intent = intent;  
            r.activityInfo = info;  
            r.compatInfo = compatInfo;  
            r.state = state;  
  
            r.pendingResults = pendingResults;  
            r.pendingIntents = pendingNewIntents;  
  
            r.startsNotResumed = notResumed;  
            r.isForward = isForward;  
  
            r.profileFile = profileName;  
            r.profileFd = profileFd;  
            r.autoStopProfiler = autoStopProfiler;  
  
            updatePendingConfiguration(curConfig);  
  
            queueOrSendMessage(H.LAUNCH_ACTIVITY, r);  
        }  
  
        ...  
    }
    public static void main(String[] args) {  
        SamplingProfilerIntegration.start();  
  
        // CloseGuard defaults to true and can be quite spammy.  We  
        // disable it here, but selectively enable it later (via  
        // StrictMode) on debug builds, but using DropBox, not logs.  
        CloseGuard.setEnabled(false);  
  
        Environment.initForCurrentUser();  
  
        // Set the reporter for event logging in libcore  
        EventLogger.setReporter(new EventLoggingReporter());  
  
        Process.setArgV0("<pre-initialized>");  
  
        Looper.prepareMainLooper();  
  
        // 創(chuàng)建ActivityThread實例  
        ActivityThread thread = new ActivityThread();  
        thread.attach(false);  
  
        if (sMainThreadHandler == null) {  
            sMainThreadHandler = thread.getHandler();  
        }  
  
        AsyncTask.init();  
  
        if (false) {  
            Looper.myLooper().setMessageLogging(new  
                    LogPrinter(Log.DEBUG, "ActivityThread"));  
        }  
  
        Looper.loop();  
  
        throw new RuntimeException("Main thread loop unexpectedly exited");  
    } 
    public void handleMessage(Message msg) {  
        if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));  
        switch (msg.what) {  
            case LAUNCH_ACTIVITY: { // 創(chuàng)建Activity對象  
                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");  
                ActivityClientRecord r = (ActivityClientRecord)msg.obj;  
  
                r.packageInfo = getPackageInfoNoCheck(  
                        r.activityInfo.applicationInfo, r.compatInfo);  
                handleLaunchActivity(r, null);  
                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);  
            } break;  
  
            case BIND_APPLICATION: // 創(chuàng)建Application對象  
                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication");  
                AppBindData data = (AppBindData)msg.obj;  
                handleBindApplication(data);  
                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);  
                break;  
  
            case CREATE_SERVICE: // 創(chuàng)建Service對象  
                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceCreate");  
                handleCreateService((CreateServiceData)msg.obj);  
                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);  
                break;  
              
            case BIND_SERVICE:  // Bind Service對象  
                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceBind");  
                handleBindService((BindServiceData)msg.obj);  
                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);  
                break;  
        }  
    }    
}

也許你不能完全看懂、理解這些代碼,不過沒關系,直接告訴你結(jié)論吧,ActivityThread的一個內(nèi)部類H,里面定義了activity、service等啟動、銷毀等事件的響應,也就是說activity、service的啟動、銷毀都是在ActivityThread中進行的。

當然了,一個Activity或者Service的從創(chuàng)建到啟動是相當復雜的,其中還涉及的Binder機制等等原理,推薦給大家兩篇博文,去慢慢研讀消化吧。

Activity啟動原理詳解

Service啟動原理分析

準備工作不知不覺就做了這么多,差點忘了正事,我們還是要繼續(xù)尋找ApplicationActivityService是何時與ContextImpl進行關聯(lián)的。

  • Application
   // ActivityThread.java
   private void handleBindApplication(AppBindData data) { 
      try {
            // If the app is being launched for full backup or restore, bring it up in
            // a restricted environment with the base application class.
            Application app = data.info.makeApplication(data.restrictedBackupMode, null);
            mInitialApplication = app;
            ...
        } finally {
            StrictMode.setThreadPolicy(savedPolicy);
        }
   }

   // LoadedApk.java
   public Application makeApplication(boolean forceDefaultAppClass,
            Instrumentation instrumentation) {
        if (mApplication != null) {
            return mApplication;
        }

        Application app = null;

        String appClass = mApplicationInfo.className;
        if (forceDefaultAppClass || (appClass == null)) {
            appClass = "android.app.Application";
        }

        try {
            java.lang.ClassLoader cl = getClassLoader();
            ContextImpl appContext = new ContextImpl(); // 創(chuàng)建ContextImpl實例
            appContext.init(this, null, mActivityThread);
            app = mActivityThread.mInstrumentation.newApplication(
                    cl, appClass, appContext);
            appContext.setOuterContext(app); // 將Application實例傳遞給Context實例
        } catch (Exception e) {
            ...
        }
        mActivityThread.mAllApplications.add(app);
        mApplication = app;

        return app;
    }

每個應用程序在第一次啟動時,都會首先創(chuàng)建一個Application對象。從startActivity流程可知,創(chuàng)建Application的時機在handleBindApplication()方法中,該函數(shù)位于 ActivityThread.java類

  • Activity
      private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        ...
        Activity a = performLaunchActivity(r, customIntent); // 到下一步

        if (a != null) {
            r.createdConfig = new Configuration(mConfiguration);
            Bundle oldState = r.state;
            handleResumeActivity(r.token, false, r.isForward,
                    !r.activity.mFinished && !r.startsNotResumed);
            ...
        }
        ...
     }

    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        ...    
        Activity activity = null;
        try {
            java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
            StrictMode.incrementExpectedActivityCount(activity.getClass());
            r.intent.setExtrasClassLoader(cl);
            if (r.state != null) {
                r.state.setClassLoader(cl);
            }
        } catch (Exception e) {
            ...
        }

        try {
            Application app = r.packageInfo.makeApplication(false, mInstrumentation);

            if (activity != null) {
                Context appContext = createBaseContextForActivity(r, activity); // 創(chuàng)建Context
                CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
                Configuration config = new Configuration(mCompatConfiguration);
                if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity "
                        + r.activityInfo.name + " with config " + config);
                activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config);

                if (customIntent != null) {
                    activity.mIntent = customIntent;
                }
                r.lastNonConfigurationInstances = null;
                activity.mStartedActivity = false;
                int theme = r.activityInfo.getThemeResource();
                if (theme != 0) {
                    activity.setTheme(theme);
                }


            mActivities.put(r.token, r);

        } catch (SuperNotCalledException e) {
            ...

        } catch (Exception e) {
            ...
        }

        return activity;
    }

    private Context createBaseContextForActivity(ActivityClientRecord r,
            final Activity activity) {
        ContextImpl appContext = new ContextImpl();  // 創(chuàng)建ContextImpl實例
        appContext.init(r.packageInfo, r.token, this);
        appContext.setOuterContext(activity);

        // For debugging purposes, if the activity's package name contains the value of
        // the "debug.use-second-display" system property as a substring, then show
        // its content on a secondary display if there is one.
        Context baseContext = appContext;
        String pkgName = SystemProperties.get("debug.second-display.pkg");
        if (pkgName != null && !pkgName.isEmpty()
                && r.packageInfo.mPackageName.contains(pkgName)) {
            DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance();
            for (int displayId : dm.getDisplayIds()) {
                if (displayId != Display.DEFAULT_DISPLAY) {
                    Display display = dm.getRealDisplay(displayId);
                    baseContext = appContext.createDisplayContext(display);
                    break;
                }
            }
        }
        return baseContext;
    }

通過startActivity()或startActivityForResult()請求啟動一個Activity時,如果系統(tǒng)檢測需要新建一個Activity對象時,就會回調(diào)handleLaunchActivity()方法,該方法繼而調(diào)用performLaunchActivity()方法,去創(chuàng)建一個Activity實例,并且回調(diào)onCreate(),onStart()方法等,函數(shù)位于 ActivityThread.java類。

  • Service
    private void handleCreateService(CreateServiceData data) {
        // If we are getting ready to gc after going to the background, well
        // we are back active so skip it.
        unscheduleGcIdler();

        LoadedApk packageInfo = getPackageInfoNoCheck(
                data.info.applicationInfo, data.compatInfo);
        Service service = null;
        try {
            java.lang.ClassLoader cl = packageInfo.getClassLoader();
            service = (Service) cl.loadClass(data.info.name).newInstance();
        } catch (Exception e) {
            if (!mInstrumentation.onException(service, e)) {
                throw new RuntimeException(
                    "Unable to instantiate service " + data.info.name
                    + ": " + e.toString(), e);
            }
        }

        try {
            if (localLOGV) Slog.v(TAG, "Creating service " + data.info.name);

            ContextImpl context = new ContextImpl(); // 創(chuàng)建ContextImpl實例
            context.init(packageInfo, null, this);

            Application app = packageInfo.makeApplication(false, mInstrumentation);
            context.setOuterContext(service);
            service.attach(context, this, data.info.name, data.token, app,
                    ActivityManagerNative.getDefault());
            service.onCreate();
            mServices.put(data.token, service);
            try {
                ActivityManagerNative.getDefault().serviceDoneExecuting(
                        data.token, 0, 0, 0);
            } catch (RemoteException e) {
                // nothing to do.
            }
        } catch (Exception e) {
            if (!mInstrumentation.onException(service, e)) {
                throw new RuntimeException(
                    "Unable to create service " + data.info.name
                    + ": " + e.toString(), e);
            }
        }
    }

通過startService或者bindService時,如果系統(tǒng)檢測到需要新創(chuàng)建一個Service實例,就會回調(diào)handleCreateService()方法,完成相關數(shù)據(jù)操作。handleCreateService()函數(shù)位于 ActivityThread.java類

看到這里,相信你對Context的理解更進一步了,現(xiàn)在我們知道了Context是什么,它為Android提供了怎樣的資源、功能、和服務,又在什么時候?qū)pplication、Activity、Service與ContextImpl相關聯(lián),但是所請求的資源是不是同一套資源呢?

在這里你一定說:“當然不是,不同的Context對象明顯是有區(qū)別的,用法也不同”

但是其實他們訪問的,確確實實,是同一套資源。

三:Context資源詳解

來吧,看看不同Context對象的區(qū)別和用法的不同,參見以下表格。

Context用法區(qū)別

這張表格是不是又支持了你的觀點(也就是一直認為的,Context資源對象是不同的),但是還是要再次強調(diào)一次,它們所請求的,確確實實是同一塊資源,看看上面進行關聯(lián)的源碼,都走進了Context實現(xiàn)類的init方法,撥云見日,我們?nèi)タ纯?strong>init方法吧。

查看ContextImpl類源碼可以看到,getResources方法直接返回內(nèi)部的mResources變量

final void init(LoadedApk packageInfo,  
            IBinder activityToken, ActivityThread mainThread,  
            Resources container) {  
    mPackageInfo = packageInfo;  
    mResources = mPackageInfo.getResources(mainThread);  
  
    if (mResources != null && container != null  
            && container.getCompatibilityInfo().applicationScale !=  
                    mResources.getCompatibilityInfo().applicationScale) {  
        if (DEBUG) {  
            Log.d(TAG, "loaded context has different scaling. Using container's" +  
                    " compatiblity info:" + container.getDisplayMetrics());  
        }  
        mResources = mainThread.getTopLevelResources(  
                mPackageInfo.getResDir(), container.getCompatibilityInfo().copy());  
    }  
    mMainThread = mainThread;  
    mContentResolver = new ApplicationContentResolver(this, mainThread);  
  
    setActivityToken(activityToken);  
} 

mResources又是調(diào)用LoadedApkgetResources方法進行賦值。代碼如下。

public Resources getResources(ActivityThread mainThread) {  
    if (mResources == null) {  
        mResources = mainThread.getTopLevelResources(mResDir, this);  
    }  
    return mResources;  
} 

從代碼中可以看到,最終mResources的賦值是由AcitivtyThreadgetTopLevelResources方法返回。代碼如下。

Resources getTopLevelResources(String resDir, CompatibilityInfo compInfo) {  
    ResourcesKey key = new ResourcesKey(resDir, compInfo.applicationScale);  
    Resources r;  
    synchronized (mPackages) {  
        // Resources is app scale dependent.  
        if (false) {  
            Slog.w(TAG, "getTopLevelResources: " + resDir + " / "  
                    + compInfo.applicationScale);  
        }  
        WeakReference<Resources> wr = mActiveResources.get(key);  
        r = wr != null ? wr.get() : null;  
          
        if (r != null && r.getAssets().isUpToDate()) {  
            if (false) {  
                Slog.w(TAG, "Returning cached resources " + r + " " + resDir  
                        + ": appScale=" + r.getCompatibilityInfo().applicationScale);  
            }  
            return r;  
        }  
    }  
  
    AssetManager assets = new AssetManager();  
    if (assets.addAssetPath(resDir) == 0) {  
        return null;  
    }  
  
    DisplayMetrics metrics = getDisplayMetricsLocked(false);  
    r = new Resources(assets, metrics, getConfiguration(), compInfo);  
    if (false) {  
        Slog.i(TAG, "Created app resources " + resDir + " " + r + ": "  
                + r.getConfiguration() + " appScale="  
                + r.getCompatibilityInfo().applicationScale);  
    }  
      
    synchronized (mPackages) {  
        WeakReference<Resources> wr = mActiveResources.get(key);  
        Resources existing = wr != null ? wr.get() : null;  
        if (existing != null && existing.getAssets().isUpToDate()) {  
            // Someone else already created the resources while we were  
            // unlocked; go ahead and use theirs.  
            r.getAssets().close();  
            return existing;  
        }  
        // XXX need to remove entries when weak references go away  
        mActiveResources.put(key, new WeakReference<Resources>(r));  
        return r;  
    }  
}  

以上代碼中,mActiveResources對象內(nèi)部保存了該應用程序所使用到的所有Resources對象,其類型為WeakReference,所以當內(nèi)存緊張時,可以釋放Resources占用的資源,自然這不是我們探究的重點,ResourcesKey的構(gòu)造需要resDir和compInfo.applicationScale。resdDir變量的含義是資源文件所在路徑,實際指的是APK程序所在路徑,比如可以是:/data/app/com.haii.android.xxx-1.apk,該apk會對應/data/dalvik-cache目錄下的:data@app@com.haii.android.xxx-1.apk@classes.dex文件。

所以結(jié)論來了:

如果一個應用程序沒有訪問該程序以外的資源,那么mActiveResources變量中就僅有一個Resources對象。

總結(jié):

當ActivityThread類中創(chuàng)建Application、Service、Activity的同時,完成了與ContextImpl的關聯(lián)綁定,通過ContextImpl類中init方法,獲得了一個唯一的Resources對象,根據(jù)上述代碼中資源的請求機制,再加上ResourcesManager采用單例模式,這樣就保證了不同的ContextImpl訪問的是同一套資源。

如果這篇博客現(xiàn)在就結(jié)束了,你一定會殺了我 - -,現(xiàn)在我們就來分析下,是什么造成了唯一的這個Resources,卻展現(xiàn)出了“不同”。

舉個通俗易懂的例子,我和我老媽都拿到同一塊土豆,但是因為我們處理這個土豆的方法有區(qū)別,導致這個土豆最后表現(xiàn)出來的也不一樣,我想把它做成薯片,我媽媽把它炒成了土豆絲,:-D。

再具體一點,比如除了Activity可以創(chuàng)建一個Dialog,其它Context都不可以創(chuàng)建Dialog。比如在Application中創(chuàng)建Dialog會報錯,還有Application和Service可以啟動一個Activity,但是需要創(chuàng)建一個新的task。比如你在Application中調(diào)用startActivity(intent)時系統(tǒng)也會崩潰報錯。

報錯的原因并不是因為他們拿到的Context資源不同,拿到的都是一個Resoucres對象,但是在創(chuàng)建Dialog的時候會使用到Context對象去獲取當前主題信息,但是我們知道Application和Service是繼承自ContextWrapper,沒有實現(xiàn)關于主題的功能,然而Activity是繼承自ContextThemeWrapper,該類是實現(xiàn)了關于主題功能的,因此創(chuàng)建Dialog的時候必須依附于Activity的Context引用。

結(jié)論:

Application、Service、Activity,它們本身對Resources資源處理方法的不同,造成了這個Resoucres最后表現(xiàn)出來的不一樣,這么說大家就都懂了吧!

四:Context內(nèi)存泄漏

關于Context的內(nèi)存泄漏,找到一篇比較不錯的文章分享給大家。

Android開發(fā),中可能會導致內(nèi)存泄露的問題

寫在最后:

Context可能還有更多深層次的知識需要我們?nèi)チ私猓热鏑ontext這些封裝類,是具體如何通過Binder跟ContextImpl進行關聯(lián)的;資源對象都被存儲在ArrayMap,為什么ArrayMap中會有可能存在多個資源對象,如何訪問其他應用程序的Context資源等等,剩下的這些就靠大家慢慢發(fā)掘了~

轉(zhuǎn)載請注明出處。

如果大家覺得喜歡有價值,就關注我,點下贊哈,你們的支持是我持續(xù)原創(chuàng)的動力。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,622評論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,716評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,746評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,991評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,706評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,036評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,029評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,203評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,725評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,451評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,677評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,161評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,857評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,266評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,606評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,407評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,643評論 2 380

推薦閱讀更多精彩內(nèi)容