共计 13101 个字符,预计需要花费 33 分钟才能阅读完成。
Android 温习笔记目录
- 唠唠工作栈,返回栈和生命周期
- 唠唠 Activity 的生命周期
- 唠唠 Context
本文永恒更新地址: https://xiaozhuanlan.com/topi…
目录
- 什么是 Context?
- 四大组件和 Context
- Application 和 Context
- 为什么 Application 的 Context 不能够创立 Dialog?
- 未完待遇 …
文章结尾,先来看一段代码:
public class ContextActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);
setContentView(R.layout.activity_context);
Log.e("context", "getApplication in Activity:" + getApplication().getClass().getName());
Log.e("context", "getApplicationContext in Activity:" + getApplicationContext().getClass().getName());
Log.e("context", "getBaseContext in Activity:" + getBaseContext().getClass().getName());
startService(new Intent(this,ContextService.class));
}
}
你能精确的说出这三行打印语句的执行后果吗?如果不能,你须要认真浏览这篇文章。
什么是 Context?
Context
是一个抽象类。既然是抽象类,那么它就代表了一类具体对象的通用特色。先来看一下 Context 的类图:
其中看到了咱们很相熟的 Activity、Service、Application,这些都是 Context
的具体实现类,也就是说 Context 形象了这些类的通用特色和性能:
- 获取系统资源,
getResources()
,getAssets()
等 - 启动各种零碎组件
- 获取零碎服务
- ……
这些与零碎环境非亲非故的性能都是由 Context 提供的,所以个别将其称为 上下文,它其实就是对以后运行环境的具体形容,为零碎组件的失常运行提供必要的环境和资源。
在下面的类图中,可能有两个读者比拟生疏的类,ContextWraaper 和 ContextImpl。
ContextImpl
很好了解,它就是 Context 的具体实现类。Context 类中的所有形象办法都是在 ContextImpl 中实现的。
class ContextImpl extends Context {
......
@Override
public AssetManager getAssets() {return getResources().getAssets();}
@Override
public Resources getResources() {return mResources;}
@Override
public PackageManager getPackageManager() {if (mPackageManager != null) {return mPackageManager;}
IPackageManager pm = ActivityThread.getPackageManager();
if (pm != null) {
// Doesn't matter if we make more than one instance.
return (mPackageManager = new ApplicationPackageManager(this, pm));
}
return null;
}
@Override
public ContentResolver getContentResolver() {return mContentResolver;}
@Override
public Looper getMainLooper() {return mMainThread.getLooper();
}
......
}
ContextWraaper 其实也很简略,间接看它的实现代码:
public class ContextWrapper extends Context {
@UnsupportedAppUsage
Context mBase;
public ContextWrapper(Context base) {mBase = base;}
/**
* 在这个办法中给 mBase 赋值
*/
protected void attachBaseContext(Context base) {if (mBase != null) {throw new IllegalStateException("Base context already set");
}
mBase = base;
}
public Context getBaseContext() {return mBase;}
@Override
public AssetManager getAssets() {return mBase.getAssets();
}
@Override
public Resources getResources() {return mBase.getResources();
}
......
}
这是一个典型的 装璜者模式 ,也叫做 润饰模式,一下来自维基百科:
润饰模式,是面向对象编程畛域中,一种动静地往一个类中增加新的行为的设计模式。就性能而言,润饰模式相比生成子类更为灵便,这样能够给某个对象而不是整个类增加一些性能。
通过应用润饰模式,能够在运行时裁减一个类的性能。原理是:减少一个润饰类包裹原来的类,包裹的形式个别是通过在将原来的对象作为润饰类的构造函数的参数。装璜类实现新的性能,然而,在不须要用到新性能的中央,它能够间接调用原来的类中的办法。润饰类必须和原来的类有雷同的接口。
润饰模式是类继承的另外一种抉择。类继承在编译时候减少行为,而装璜模式是在运行时减少行为。
当有几个互相独立的性能须要裁减时,这个区别就变得很重要。在有些面向对象的编程语言中,类不能在运行时被创立,通常在设计的时候也不能预测到有哪几种性能组合。这就意味着要为每一种组合创立一个新类。相同,润饰模式是面向运行时候的对象实例的, 这样就能够在运行时依据须要进行组合。一个润饰模式的示例是 JAVA 里的 Java I/O Streams 的实现。
Context 是根本的抽象类,无论是实现类,还是装璜类,都间接或间接的实现它。ContextImpl 是 Context 的间接实现类,但各个组件并不是间接继承 ContextImpl,而是通过装璜类 ContextWrapper 来持有 ContextImpl。这是为什么呢?对于 Activity 和 Service 来说,它们都须要零碎上下文运行环境,但它们又是不同的。Activity 须要显示到前台,它有页面,它须要主题,于是有了继承自 ContextWrapper 的 ContextThemeWrapper
,扩大了性能,给 Activity 提供了主题。同时,Activity、Service、Application 这些具体组件自身又扩大出了不同的生命周期性能。
所以,装璜器模式通过组合和扩大装璜类,来给不同的具体对象提供了不同的性能扩大。
Activity
、Service
、Application
最终都是继承自装璜类 ContextWrapper
,ContextWrapper
通过 attachBaseContext()
办法来获取理论做事的 ContextImpl
对象。所以这些组件的创立过程中,肯定会在某一机会调用 attachBaseContext()
办法对 mBase
对象进行赋值,让咱们从源码外面找找答案。
四大组件和 Context
Activity 和 Context
先说 Activity
,Activity 的启动过程极其简单,咱们就间接从 ActivityThread
的 performLaunchActivity()
办法看起。
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
ActivityInfo aInfo = r.activityInfo;
if (r.packageInfo == null) {
// 1. 获取 LoadedApk 对象
r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo,
Context.CONTEXT_INCLUDE_CODE);
}
......
// 2. 创立 ContextImpl 对象
ContextImpl appContext = createBaseContextForActivity(r);
Activity activity = null;
try {java.lang.ClassLoader cl = appContext.getClassLoader();
// 3. 反射创立 Activity 对象
activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
......
} catch (Exception e) {......}
try {
// 4. 创立 Application 对象
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
if (activity != null) {
......
appContext.setOuterContext(activity);
// 5. 绑定 activity
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, window, r.configCallback);
......
int theme = r.activityInfo.getThemeResource();
if (theme != 0) {
// 设置主题
activity.setTheme(theme);
}
// 6. 回调 onCreate()
if (r.isPersistable()) {mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {mInstrumentation.callActivityOnCreate(activity, r.state);
}
......
r.activity = activity;
}
r.setState(ON_CREATE);
mActivities.put(r.token, r);
} catch (SuperNotCalledException e) {throw e;} catch (Exception e) {......}
return activity;
}
整顿一下大抵的执行流程:
- 获取 LoadedApk 对象,示意加载过的 Apk,通常一个 App 对应着一个 LoadedApk
- 通过
createBaseContextForActivity()
办法创立 ContextImpl 对象 - 反射创立 Activity 对象
- 创立 Application 对象,这里也是用的反射。如果开发者没有申明本人的 Application 的话,就是默认的
androoid.app.Application
- 调用
activity.attach()
,这个办法很重要,前面具体说 - 回调
onCreate()
接着就是 Activity 失常的生命周期流程了。
重点看一下 createBaseContextForActivity()
办法和 attach()
办法。
private ContextImpl createBaseContextForActivity(ActivityClientRecord r) {
ContextImpl appContext = ContextImpl.createActivityContext(this, r.packageInfo, r.activityInfo, r.token, displayId, r.overrideConfig);
......
return appContext;
}
调用了 ContextImpl.createActivityContext()
办法。
static ContextImpl createActivityContext(ActivityThread mainThread,
LoadedApk packageInfo, ActivityInfo activityInfo, IBinder activityToken, int displayId,
Configuration overrideConfiguration) {
......
// 创立 ContextImpl 对象
ContextImpl context = new ContextImpl(null, mainThread, packageInfo, activityInfo.splitName,
activityToken, null, 0, classLoader);
......
final ResourcesManager resourcesManager = ResourcesManager.getInstance();
context.setResources(resourcesManager.createBaseActivityResources(activityToken,
packageInfo.getResDir(),
splitDirs,
packageInfo.getOverlayDirs(),
packageInfo.getApplicationInfo().sharedLibraryFiles,
displayId,
overrideConfiguration,
compatInfo,
classLoader));
context.mDisplay = resourcesManager.getAdjustedDisplay(displayId,
context.getResources());
return context;
}
装璜类 ContextWrapper 真正须要的 ContextImpl 对象当初曾经创立进去了,然而还没有绑定到 Activity。持续看 Activity.attach()
办法,留神 attach()
办法的第一个参数就是刚刚创立进去的 ContextImpl 对象。
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, ActivityConfigCallback activityConfigCallback) {// 回调 attachBaseContext()
attachBaseContext(context);
......
// 创立 PhoneWindow
mWindow = new PhoneWindow(this, window, activityConfigCallback);
......
mWindow.setWindowManager((WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
......
}
你对 attachBaseContext()
办法应该还有印象。ContextWrapper 正是通过这个办法给 mBase
对象赋值,拿到真正的 ContextImpl 对象。到这里,整个逻辑就通顺了。
留神
attach()
办法中的setWindowManager()
办法中的 mToken 参数,这决定了 Application Context 无奈创立和显示 Dialog。后续会进行详细分析。
再回头看看文章结尾的问题。
Log.e("context", "getApplication in Activity:" + getApplication().getClass().getName());
Log.e("context", "getApplicationContext in Activity:" + getApplicationContext().getClass().getName());
Log.e("context", "getBaseContext in Activity:" + getBaseContext().getClass().getName());
第一个 getApplication()
,看下源码就晓得了:
public final Application getApplication() {return mApplication;}
getApplication()
返回的是以后的 Application 对象。开发者没有申明本人实现的 Application 的话,就是零碎默认的 android.app.Application
。
第二个 getApplicationContext()
,它并不是 Activity 中的办法,而是 ContextWrapper 的。间接看源码:
@Override
public Context getApplicationContext() {return mBase.getApplicationContext();
}
调用的是 ContextImpl.getApplicationContext()
。
@Override
public Context getApplicationContext() {return (mPackageInfo != null) ?
mPackageInfo.getApplication() : mMainThread.getApplication();
}
所以返回的同样是 Application 对象。
第三个,getBaseContext()
,同样是 ContextWrapper 中的办法:
public Context getBaseContext() {return mBase;}
所以这里返回的是 ContextImpl 对象。
最初的打印语句是:
E/context: getApplication in Activity: luyao.android.App
E/context: getApplicationContext in Activity: luyao.android.App
E/context: getBaseContext in Activity: android.app.ContextImpl
对于 Activity 就说这么多了。上面来看看 Service。
Service 和 Context
Service 其实和 Activity 的整体流程基本一致,创立服务的次要逻辑在 ActivityThread.handleCreateService()
办法中。这里我就不贴源码了,简略叙述一下:
- 创立 LoadedApk 对象
- 反射创立 Service 对象
- 调用 ContextImpl.createAppCntext() 创立 ContextImpl 对象
- 创立 Application 对象
- 调用 service.attach() 进行绑定
- 回调 service 的 onCreate() 办法
间接看一下 Service.attach()
办法:
public final void attach(
Context context,
ActivityThread thread, String className, IBinder token,
Application application, Object activityManager) {attachBaseContext(context);
......
}
又看到了相熟的 attachBaseContext()
办法。
Activity
和 Service
都是继承自 ContextWrapper
的,最初都是通过 attachBaseContext()
对 ContextImpl 类型的 mBase
赋值。而 ContentProvider
和 BroadcastReceiver
都没有继承 Context,所以它们获取 Context 的形式会有一点不一样。
ContentProvider 和 Context
先来看 ContentProvider
,创立 Provider 的逻辑在 Activity.installProvider()
办法中:
private ContentProviderHolder installProvider(Context context,
ContentProviderHolder holder, ProviderInfo info,
boolean noisy, boolean noReleaseNeeded, boolean stable) {
ContentProvider localProvider = null;
IContentProvider provider;
// 创立 LoadedApk 和 ContextImpl
c = context.createPackageContext(ai.packageName,Context.CONTEXT_INCLUDE_CODE);
try {
......
// 创立 ContentProvider
localProvider = packageInfo.getAppFactory()
.instantiateProvider(cl, info.name);
provider = localProvider.getIContentProvider();
......
// 绑定 Context
localProvider.attachInfo(c, info);
} catch (java.lang.Exception e) {......}
......
return retHolder;
}
最初在 ContentProvider.attachInfo()
办法中进行了 ContextImpl 的赋值操作。
private void attachInfo(Context context, ProviderInfo info, boolean testing) {if (mContext == null) {
// 给 mContext 赋值
mContext = context;
......
// 回调 onCreate()
ContentProvider.this.onCreate();}
}
这样 ContentProvider 也能拿到 Context 对象了。
BroadcastReceiver 和 Context
最初就是 BroadcastReceiver 了,对应 ActivityThread.handleReceiver()
办法:
private void handleReceiver(ReceiverData data) {
......
// 创立 LoadedApk 对象
LoadedApk packageInfo = getPackageInfoNoCheck(data.info.applicationInfo, data.compatInfo);
Application app;
BroadcastReceiver receiver;
ContextImpl context;
try {
// 创立 Application 对象
app = packageInfo.makeApplication(false, mInstrumentation);
// 创立 ContextImpl 对象
context = (ContextImpl) app.getBaseContext();
......
// 创立 BroadcastReceiver 对象
receiver = packageInfo.getAppFactory()
.instantiateReceiver(cl, data.info.name, data.intent);
} catch (Exception e) {......}
try {
......
// 回调 onReceive()
receiver.onReceive(context.getReceiverRestrictedContext(),
data.intent);
} catch (Exception e) {......} finally {sCurrentBroadcastIntent.set(null);
}
......
}
大多数步骤和 Activity 还是相似的,只是到最初回调 onReceive()
办法的时候,才会把 ContextImpl 对象传过来。留神,这里并不是间接返回原生的 ContextImpl 对象,而是调用 context.getReceiverRestrictedContext()
返回一个 受限制 的 ReceiverRestrictedContext
,你无奈应用这个 Context 对象启动 Service。
这不正是 装璜者模式 的体现?想给播送的 Context 对象加点限度,那就再来一个装璜类 ReceiverRestrictedContext
,它继承了 ContextWrapper
, 重写局部办法以限度利用场景。 通过减少和组合装璜类,而不是减少子类,来实现性能扩大。
Application 和 Context
四大组件说完了,别忘了 Application
也是 Context
的间接子类。
Application 的创立机会得从利用过程的创立开始说起。Zygote 过程在接管到客户端申请创立利用过程的 socket 申请之后,会 fork 出子过程,并反射调用 ActivityThread 的动态 main() 办法。接着是 AMS 和客户端的一系列 Binder 调用以及 Handler 通信,最终主线程在收到 BIND_APPLICATION
音讯之后回调 handleBindApplication()
办法,到这里就是咱们须要的逻辑了:
private void handleBindApplication(AppBindData data){
......
// 获取 ContextImpl
final ContextImpl appContext = ContextImpl.createAppContext(this, data.info);
......
// 创立 Application 对象
app = data.info.makeApplication(data.restrictedBackupMode, null);
......
// 调用 Application 的 onCreate() 办法
mInstrumentation.callApplicationOnCreate(app);
}
你可能会纳闷怎么没有回调 attBaseContext()
办法,别急,看看 LoadedApk.makeApplication()
办法是如何创立 Application 的。
public Application makeApplication(boolean forceDefaultAppClass,
Instrumentation instrumentation) {
......
// 创立 ContextImpl
ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
// 反射创立 Application
app = mActivityThread.mInstrumentation.newApplication(cl, appClass, appContext);
appContext.setOuterContext(app);
}
通过 Instrumentation.newApplication()
办法创立 Application。
public Application newApplication(ClassLoader cl, String className, Context context)
throws InstantiationException, IllegalAccessException,
ClassNotFoundException {
// 反射创立
Application app = getFactory(context.getPackageName())
.instantiateApplication(cl, className);
// 重点
app.attach(context);
return app;
}
重点就在 Application.attach()
办法。
final void attach(Context context) {attachBaseContext(context);
mLoadedApk = ContextImpl.getImpl(context).mPackageInfo;
}
在这里调用了 attachBaseContext()
办法进行赋值,也验证了 attachBaseContext()
确实比 onCreate()
先调用。
为什么 Application 的 Context 不能够创立 Dialog?
应用 Application 的 Context 创立 Dialog 并显示,会报如下谬误:
Caused by: android.view.WindowManager$BadTokenException: Unable to add window -- token null is not valid; is your activity running?
at android.view.ViewRootImpl.setView(ViewRootImpl.java:951)
at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:387)
at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:96)
at android.app.Dialog.show(Dialog.java:344)
留神错误信息 token null is not valid,还记得文章后面说到 Activity 和 Context 的时候,有这么一段代码:
mWindow.setWindowManager((WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
留神其中的 mToken 参数不为 null,是不是就阐明了 Application 的 token 参数为空呢?
原本筹备接着说说这个问题,但可能造成文章篇幅过长,所以 Android 温习笔记 下一篇会独自来唠唠这个问题。
Android 温习笔记 将会是一个付费专栏,因为须要肯定的曝光度,后面几(N)期文章会收费公布。
如果你感觉文章还不错,那么无妨到 小专栏 反对我,同时关注我的微信公众号:秉心说 TM