Android 复习笔记目录java
本文永久更新地址: xiaozhuanlan.com/topic/03675…android
文章开头,先来看一段代码:编程
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 的类图:微信
其中看到了咱们很熟悉的 Activity、Service、Application,这些都是 Context
的具体实现类,也就是说 Context 抽象了这些类的通用特征和功能:markdown
getResources()
,getAssets()
等这些与系统环境息息相关的功能都是由 Context 提供的,因此通常将其称为 上下文,它其实就是对当前运行环境的具体描述,为系统组件的正常运行提供必要的环境和资源。app
在上面的类图中,可能有两个读者比较陌生的类,ContextWraaper 和 ContextImpl。socket
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 其实也很简单,直接看它的实现代码:ide
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
对象进行赋值,让咱们从源码里面找找答案。
先说 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;
}
复制代码
整理一下大体的执行流程:
createBaseContextForActivity()
方法建立 ContextImpl 对象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 其实和 Activity 的总体流程基本一致,建立服务的主要逻辑在 ActivityThread.handleCreateService()
方法中。这里我就不贴源码了,简单叙述一下:
直接看一下 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
,建立 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 了,对应 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 的建立时机得从应用进程的建立开始提及。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 并显示,会报以下错误:
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 复习笔记 下一篇会单独来唠唠这个问题。
若是你以为文章还不错,关注个人微信公众号:秉心说TM