从源码角度看各种Context

简介

做应用开发的对Context的熟悉度应该是仅次于Activity和Service的。Context,英文名上下文场景,代表着对当前运行场景下的各种信息的一种封装。例如,需要调用四大组件进行工作都要调用到Context,同时,通过Context也可以获取到Resource, Display, Theme, AssetManager, WallPaperManager这些资源对象。因为Context的使用方法实在太普及,实在可以说的上是安卓的第一课,这篇就不给实例了。

Context创建方法

想要知道都有哪几种不同的Context,从ContextImpl代码中寻找是最好的办法,以下列举了各种不同的Context创建接口及其调用场景:

接口 调用场景
createActivityContext startActivity时和新创建的Activity绑定
createAppContext startService和bindApplication时和新创建的service和application绑定
createApplicationContext 远程widget和notification时会用到
createConfigurationContext 用来获取或设置Resources, Theme等资源
createPackageContext installProvider时和新创建的ContentProvider绑定
createSystemContext system_server启动时被初始化,传入PackageManagerService中
createDisplayContext 不常用

四大组件与Application的Context

上面的表格已经概括出各种类别的Context, 但是我准备按照四大组件的角度去看看不同组件的Context是如何被初始化的以及区别是什么。

类图

Activity继承自ContextThemeWrapper; Service、Application继承自ContextWrapper。ContextWrapper的操作是聚合了Context的实例来实现的。真正的功能是在ContextImpl中进行实现。BroadcastReceiver与ContentProvider有些特殊,其实BroadcastReceiver是在调用onReceive方法时传入了Context对象,而ContentProvider聚合了Context对象并没有直接继承ContextWrapper。

Activity

Activity是最常用也是在四大组件中最复杂的一个

ActivityThread.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
...
// 通过反射创建一个Activity实例
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);
r.intent.prepareToEnterProcess();
if (r.state != null) {
r.state.setClassLoader(cl);
}
} catch (Exception e) {
...
}
try {
// 获取Aapplication实例
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
if (activity != null) {
// 创建一个ContextImpl实例
Context appContext = createBaseContextForActivity(r, activity);
CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
Configuration config = new Configuration(mCompatConfiguration);
// 将Activity, Application, ContextImpl三者绑定
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.referrer, r.voiceInteractor);
}
}
...
private Context createBaseContextForActivity(ActivityClientRecord r, final Activity activity) {
...
ContextImpl appContext = ContextImpl.createActivityContext(
this, r.packageInfo, displayId, r.overrideConfig);
appContext.setOuterContext(activity);
Context baseContext = appContext;
...
return baseContext;
}
}

ContextImpl.java

1
2
3
4
5
6
static ContextImpl createActivityContext(ActivityThread mainThread,
LoadedApk packageInfo, int displayId, Configuration overrideConfiguration) {
if (packageInfo == null) throw new IllegalArgumentException("packageInfo");
return new ContextImpl(null, mainThread, packageInfo, null, null, false,
null, overrideConfiguration, displayId);
}

Application

ActivityThread.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
ContextImpl instrContext = ContextImpl.createAppContext(this, pi);
try {
java.lang.ClassLoader cl = instrContext.getClassLoader();
mInstrumentation = (Instrumentation)
cl.loadClass(data.instrumentationName.getClassName()).newInstance();
} catch (Exception e) {
throw new RuntimeException(
"Unable to instantiate instrumentation "
+ data.instrumentationName + ": " + e.toString(), e);
}
// 初始化instrumentation,绑定Context
mInstrumentation.init(this, instrContext, appContext,
new ComponentName(ii.packageName, ii.name), data.instrumentationWatcher,
data.instrumentationUiAutomationConnection);
...
Application app = data.info.makeApplication(data.restrictedBackupMode, null);
mInitialApplication = app;
...
try {
mInstrumentation.onCreate(data.instrumentationArgs);
} catch (Exception e) {
throw new RuntimeException(
"Exception thrown in onCreate() of "
+ data.instrumentationName + ": " + e.toString(), e);
}
try {
// 创建Application并调用onCreate方法
mInstrumentation.callApplicationOnCreate(app);
} catch (Exception e) {
if (!mInstrumentation.onException(app, e)) {
throw new RuntimeException(
"Unable to create application " + app.getClass().getName()
+ ": " + e.toString(), e);
}
}

ContextImpl.java

1
2
3
4
5
static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo) {
if (packageInfo == null) throw new IllegalArgumentException("packageInfo");
return new ContextImpl(null, mainThread,
packageInfo, null, null, false, null, null, Display.INVALID_DISPLAY);
}

Service

ActivityThread.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
private void handleCreateService(CreateServiceData data) {
...
// 通过反射创建Service
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 = ContextImpl.createAppContext(this, packageInfo);
context.setOuterContext(service);
// 得到Application实例,如果没有被实例化过则会创建一个
Application app = packageInfo.makeApplication(false, mInstrumentation);
// 将Context, Application与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, SERVICE_DONE_EXECUTING_ANON, 0, 0);
} catch (RemoteException e) {
// nothing to do.
}
} catch (Exception e) {
...
}
}

BroadcastReceiver

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
static final class ReceiverDispatcher {
final BroadcastReceiver mReceiver;
ReceiverDispatcher(BroadcastReceiver receiver, Context context,
Handler activityThread, Instrumentation instrumentation,
boolean registered) {
...
mReceiver = receiver;
...
}
final class Args extends BroadcastReceiver.PendingResult implements Runnable {
...
public void run() {
final BroadcastReceiver receiver = mReceiver;
...
try {
// 通过反射调用BroadcastReceiver的onReceive并传入Context对象
ClassLoader cl = mReceiver.getClass().getClassLoader();
intent.setExtrasClassLoader(cl);
setExtrasClassLoader(cl);
receiver.setPendingResult(this);
receiver.onReceive(mContext, intent);
} catch (Exception e) {
...
}
...
}
}
}

ContextImpl.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
private ContextImpl(ContextImpl container, ActivityThread mainThread,
LoadedApk packageInfo, IBinder activityToken, UserHandle user, boolean restricted,
Display display, Configuration overrideConfiguration, int createDisplayWithId) {
mOuterContext = this;
...
}
public void sendOrderedBroadcastAsUser(Intent intent, UserHandle user,
String receiverPermission, int appOp, Bundle options, BroadcastReceiver resultReceiver,
Handler scheduler, int initialCode, String initialData, Bundle initialExtras) {
IIntentReceiver rd = null;
if (resultReceiver != null) {
if (mPackageInfo != null) {
...
} else {
...
// 初始化ReceiverDispatcher, 通过getOuterContext获取Context实例
rd = new LoadedApk.ReceiverDispatcher(resultReceiver, getOuterContext(),
scheduler, null, false).getIIntentReceiver();
}
}
...
}
final Context getOuterContext() {
return mOuterContext;
}

由代码可知,ReceiverDispatcher的初始化在它的构造器阶段完成,而如果没有调用过setOuterContext, getOutContext返回的是这个ContextImpl实例本身。由此可以判断,BroadcastReceiver的Context是根据调用者的Context来决定的。

ContentProvider

ActivityThread.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
private IActivityManager.ContentProviderHolder installProvider(Context context,
IActivityManager.ContentProviderHolder holder, ProviderInfo info,
boolean noisy, boolean noReleaseNeeded, boolean stable) {
ContentProvider localProvider = null;
IContentProvider provider;
if (holder == null || holder.provider == null) {
if (DEBUG_PROVIDER || noisy) {
Slog.d(TAG, "Loading provider " + info.authority + ": "
+ info.name);
}
Context c = null;
ApplicationInfo ai = info.applicationInfo;
// 如果传进来的Context和ContentProvider所在进程一致,则不再需要重新创建
if (context.getPackageName().equals(ai.packageName)) {
c = context;
} else if (mInitialApplication != null &&
mInitialApplication.getPackageName().equals(ai.packageName)) {
c = mInitialApplication;
} else {
try {
// 创建一个ContextImpl实例
c = context.createPackageContext(ai.packageName,
Context.CONTEXT_INCLUDE_CODE);
} catch (PackageManager.NameNotFoundException e) {
// Ignore
}
}
if (c == null) {
return null;
}
try {
// 反射创建ContentProvider
final java.lang.ClassLoader cl = c.getClassLoader();
localProvider = (ContentProvider)cl.
loadClass(info.name).newInstance();
provider = localProvider.getIContentProvider();
...
// Context与ContentProvider绑定
localProvider.attachInfo(c, info);
} catch (java.lang.Exception e) {
return null;
}
}
...
}

几点区别

Activity, Application/Service ContextImpl 成员变量

Type Activity Application/Service ContentProvider
ContextImpl
ActivityThread
LoadedApk
IBinder
UserHandle
boolean(restricted) false false
Display
Configuration
int(createDisplayWithId)

一般我们开发期间实际到的Context一般就两类,一类是Application/Service的Context,另一类是Activity的Context

两种Context常用功能的区别

两种Context的区别主要在于能否显示dialog,因为要进行ui上的操作需要有显示dialog的window,所以application context不能实现。

如果尝试去使用getApplicationContext去显示dialog会报出以下错误:

1
2
3
4
5
6
7
Attempted to add window with non-application token WindowToken{4067a268 token=null}. Aborting.
D/AndroidRuntime( 1923): Shutting down VM
W/dalvikvm( 1923): threadid=1: thread exiting with uncaught exception (group=0x40015560)
E/AndroidRuntime( 1923): FATAL EXCEPTION: main
E/AndroidRuntime( 1923): java.lang.RuntimeException: Unable to create application
com.test.shrenik.ControlApplication: android.view.WindowManager$BadTokenException:
Unable to add window -- token null is not for an application

深入代码层去解析:

Dialog.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
...
final Window w = new PhoneWindow(mContext);
mWindow = w;
...
}
public void show() {
// 获取window的params
WindowManager.LayoutParams l = mWindow.getAttributes();
if ((l.softInputMode
& WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) == 0) {
WindowManager.LayoutParams nl = new WindowManager.LayoutParams();
// copyFrom
nl.copyFrom(l);
nl.softInputMode |=
WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
l = nl;
}
try {
// 加入到mDecorView中
mWindowManager.addView(mDecor, l);
mShowing = true;
sendShowMessage();
} finally {
}
}

WindowManager.java

1
2
3
4
5
6
7
public final int copyFrom(LayoutParams o) {
if (token == null) {
// NOTE: token only copied if the recipient doesn't
// already have one.
token = o.token;
}
}

WindowManagerImpl.java

1
2
3
4
5
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mDisplay, mParentWindow);
}

WindowManagerGlobal.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
...
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
if (parentWindow != null) {
parentWindow.adjustLayoutParamsForSubWindow(wparams);
} else {
final Context context = view.getContext();
if (context != null
&& (context.getApplicationInfo().flags
& ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
}
}
ViewRootImpl root;
View panelParentView = null;
...
try {
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
synchronized (mLock) {
final int index = findViewLocked(view, false);
if (index >= 0) {
removeViewLocked(index, true);
}
}
throw e;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
int res; /* = WindowManagerImpl.ADD_OKAY; */
...
try {
...
// 将LayoutParams加入到window session中
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mInputChannel);
} catch (RemoteException e) {
...
} finally {
if (restore) {
attrs.restore();
}
}
if (res < WindowManagerGlobal.ADD_OKAY) {
...
switch (res) {
...
throw new WindowManager.BadTokenException(
"Unable to add window -- token " + attrs.token
+ " is not for an application");
...
}
}
}

Session.java

1
2
3
4
5
6
7
@Override
public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets,
Rect outOutsets, InputChannel outInputChannel) {
return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
outContentInsets, outStableInsets, outOutsets, outInputChannel);
}

WindowManagerService.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public int addWindow(Session session, IWindow client, int seq,
WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
InputChannel outInputChannel) {
WindowToken token = mTokenMap.get(attrs.token);
if (token == null) {
if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) {
Slog.w(TAG, "Attempted to add application window with unknown token "
+ attrs.token + ". Aborting.");
return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
}
}
...
token = new WindowToken(this, attrs.token, -1, false);
addToken = true;
if (addToken) {
mTokenMap.put(attrs.token, token);
}
}
}
扫码支持0.99元,您的支持将鼓励我继续创作!