Android 上下文Context
源码基于Android8.0 API 26
1 Context
的关联类
在Android
中,Activity
可以直接new
吗?
Activity mActivity = new Activity();
Android
应用程序开发基于Java
语言,Activity
本质上也是一个对象,那么👆的写法有什么问题?Android
应用程序不像Java
应用程序那样,随便创建一个类,写一个main
方法就可以运行,Android
是基于组件的应用设计模式,组件的运行需要有一个完整的Android
工程环境。 只有在这个环境下,Activity
、Service
、BroadcastReceiver
等组件才可以正常工作。这个支持组件运行上下文环境就是Context
。 可以这样讲,Context
是一个维持Android
应用中各个组件能够正常工作的一个核心工程类。
如果想要在Android Studio
中运行一个.java
文件的main
函数,需要在Project Root/.idea/gradle.xml
的标签GradleProjectSettings
添加:
<option name="delegatedBuild" value="false" />
在开发过程中,Context
的使用场景总的来说可以分为两大类:
- 使用
Context
调用方法,比如启动Activity
、启动Service
、发送广播、操作数据库、访问资源、调用系统服务等 - 调用方法时传入
Context
,比如弹出Toast
、创建Dialog
等
这些行为意味着需要访问系统。
那么Context
是从哪来的呢?从AMS
,AMS
是系统级进程,拥有访问系统的权利,应用程序的启动受AMS
的调控,在程序启动的过程中,AMS
会把一个“凭证”通过跨进程通信给应用程序,程序会把这个“凭证”封装程Context
,并提供一系列的接口,这样我程序也就可以很方便的访问系统资源了。 这样做的好处是:系统可以对应用程序的操作进行调控,限制各种情境下的权限,同时也可以防止恶意攻击。
以下为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.
*
* 提供了关于应用程序环境的全局信息的接口。
* 它是一个被Android系统支持的抽象类。它允许获取和应用程序相关的资源和类,包括应用级别操作,比如启动Activity、
* 发送广播、接收Intent等操作
*/
public abstract class Context { }
1.1 ContextImpl
和ContextWrapper
既然Context
是抽象类,在它的内部定义了很多方法以及静态常量,它的具体实现类为ContextImpl
。 和Context
相关联的类,除了ContextImpl
,还有ContextWrapper
、ContextThemeWrapper
、Application
、Service
等。
从上图中看,ContextImpl
和ContextWrapper
继承自Context
。
1.1.1 ContextImpl
ContextImp
是Context
功能的实现类,应用程序中所调用的各种Context
类的方法,其实现均来自该类。 因为外界需要使用并拓展ContextImpl
的功能,因此设计上使用了装饰模式。
以下是ContextImpl
的源码:
class ContextImpl extends Context { }
1.1.2 ContextWrapper
ContextWrapper
是上下文功能的封装类,它对ContextImpl
进行包装,主要起方法传递作用。ContextWrapper
中几乎所有的方法都是调用ContextImpl
来实现的,因此,在ContentWrapper
的构造函数中包含一个真正的Context
的引用——mBase
,mBase
具体指向ContextImpl
,同时ContextWrapper
提供了attachBaseContext()
用于给ContextWrapper
对象中指定真正的Context
对象,调用ContextWrapper
的方法都会被转向给其所包含的真正的Context
对象。
以下是ContextWrapper
的源码:
public class ContextWrapper extends Context {
@UnsupportedAppUsage
Context mBase;
public ContextWrapper(Context base) {
mBase = base;
}
protected void attachBaseContext(Context base) {
if (mBase != null) {
throw new IllegalStateException("Base context already set");
}
mBase = base;
}
public Context getBaseContext() {
return mBase;
}
@Override
public Resources getResources() {
return mBase.getResources();
}
@Override
public ContentResolver getContentResolver() {
return mBase.getContentResolver();
}
@Override
public Looper getMainLooper() {
return mBase.getMainLooper();
}
@Override
public Context getApplicationContext() {
return mBase.getApplicationContext();
}
@Override
public String getPackageName() {
return mBase.getPackageName();
}
@Override
public void startActivity(Intent intent) {
mBase.startActivity(intent);
}
@Override
public void sendBroadcast(Intent intent) {
mBase.sendBroadcast(intent);
}
@Override
public Intent registerReceiver(
BroadcastReceiver receiver, IntentFilter filter) {
return mBase.registerReceiver(receiver, filter);
}
@Override
public void unregisterReceiver(BroadcastReceiver receiver) {
mBase.unregisterReceiver(receiver);
}
@Override
public ComponentName startService(Intent service) {
return mBase.startService(service);
}
@Override
public boolean stopService(Intent name) {
return mBase.stopService(name);
}
@Override
public boolean bindService(Intent service, ServiceConnection conn, int flags) {
return mBase.bindService(service, conn, flags);
}
@Override
public void unbindService(ServiceConnection conn) {
mBase.unbindService(conn);
}
}
其实,在ContextWrapper
中的方法是非常多的,但是,实现都非常统一,就是调用了mBase
对象中对应当前方法名的方法。
ContextWrapper
有三个直接的子类,ContextThemeWrapper
、Service
和Application
。其中,ContextThemeWrapper
是一个带主题的封装类,它有一个直接子类就是Activity
。
ContextThemeWrapper
类,其内容包含了主题Theme
相关的接口,这里所说的主题是指在AndroidManifest.xml
中通过android:theme
为Application
元素或者Activity
元素制定的主题。当然只有Activity
才需要主题,而Service
是不需要主题的,因为Service
是没有界面的后台场景,所以Service
直接继承于ContextWrapper
,Application
同理。
Context
的关联类采用了装饰模式,主要有以下优点:
- 使用者(比如
Service
)能够更方便的使用Context
- 如果
ContextImpl
发生了变化,它的装饰类ContextWrapper
不需要做任何修改 ContextImpl
的实现不会暴露给使用者,使用者也不必关系ContextImpl
的实现- 通过组合而非继承的方式,拓展
ContextImpl
的功能,在运行时选择不同的装饰类,实现不同的功能
总结:Context
的两个子类分工明确,其中ContextImpl
是Context
的具体实现类,ContextWrapper
是Context
的包装类。Activity
,Application
,Service
虽然都继承自ContentWrapper
(Activity
继承自ContextWrapper
的子类ContextThemeWrapper
),但它们初始化的过程中都会创建ContextImpl
对象mBase
,由ContextImpl
实现Context
中的方法。
由此可知,Context
一共有三种类型,分别是Application
、Activity
和Service
。这三个类分别各自承担了不同的作用,而具体的功能则是由ContextImpl
类去实现的。
因此在大多数的场景下,Activity
、Service
和Application
这三种类型的Context
都是可以通用的。不过有几种场景比较特殊,比如启动Activity
,还有弹出Dialog
。出于安全因素的考虑,Android
是不允许Activity
或Dialog
凭空出现的,一个Activity
的启动必须建立在另一个Activity
的基础之上,也就是以此形成的返回栈。而Dialog
则必须在一个Activity
上面弹出(除非是System Alert
类型的Dialog
),因此在这种场景下,只能使用Activity
类型的Context
,否则将会出错。
Activity
常用于与UI
有关的操作,如添加Window
,常规使用可以直接用Activity.this
。Service
可以和Activity
一样直接使用Service.this
来使用Context
,和Activity
不同的是,Service
没有界面,也不需要主题。ContentProvider
使用的是Application
的Context
,Broadcast
使用Activity
的Context
。
1.2 Context
的数量
那么一个应用程序中到底有多少个Context
呢?Context
有Application
、Activity
和Service
三种类型,因此一个应用程序中Context
数量的计算公式可以这样写:Context
数量 = Activity
数量 + Service
数量 + 1
,1
代表Application
的数量, 因为一个应用程序可以有多个Activity
和多个Service
,但只能有一个Application
。
那么在四大组件中,为什么只有Activity
、Service
持有Context
,那么Broadcast Receiver
,Content Provider
并不是Context
的子类,它们所持有的Context
都是其他地方传过去的,所以并不计入Context
总数。
1.3 Context
的作用域
Context
的作用域还是有一些限制的。由于Context
的具体实例是由ContextImpl
类去实现的,因此在绝大多数的场景下,Activity
、Service
和Application
这三种类型的Context
都是可以通用的。不过有几种场景比价特殊,比如启动Activity
、还有弹出Dialog
。出于安全原因的考虑,Android
是不允许Activity
或者Dialog
凭空出现的,一个Activity
的启动必须建立在另一个Activity
的基础之上,也就是以此形成的返回栈。而Dialog
则必须在一个Activity
上面弹出(除非是System Alert
类型的Dialog
),因此在这种场景下,只能用Activity
类型的Context
,否则将会出错。
从上图中可以看出Activity
所持有的Context
的作用域最广,因为Activity
继承自ContextThemeWrapper
,而Application
和Service
继承自ContentWrapper
,很显然ContextThemeWrapper
在ContextWrapper
的基础上又做了一些操作使得Activity
变得更加强大。
2 Application Context
2.1 Application Context
的创建过程
在一个应用程序启动完成后,应用程序就会有一个全局的Application Context
。以下是Application Context
的创建过程的时序图:
ActivityThread
类作为应用程序进程的主线程管理类,它会调用它的内部类ApplicationThread
的scheduleLaunchActivity
方法来启动Activity
。
在scheduleLaunchActivity
方法中向H
类发送LAUNCH_ACTIVITY
类型的消息,目的是将启动Activity
的逻辑放在主线程的消息队列中,这样启动Activity
的逻辑会在主线程中执行。
H
继承自Handler
,是ActivityThread
的内部类。在H
类的handleMessage
方法对LAUNCH_ACTIVITY
类型的消息的处理。
2.2 Application Context
的获取
一个应用程序启动完成后,就会有一个全局的Application Context
。通过getApplicationContext()
方法可以获得应用程序全局的Application Context
,getApplicationContext()
方法在ContextWrapper
中实现,如下所示:
@Override
public Context getApplicationContext() {
return mBase.getApplicationContext();
}
mBase
指的是ContextImpl
,ContextImpl
的getApplicationContext()
方法:
@Override
public Context getApplicationContext() {
return (mPackageInfo != null) ?
mPackageInfo.getApplication() : mMainThread.getApplication();
}
如果LoadApk
类型的mPackageInfo
不为null
,则调用LoadedApk
的getApplication
方法,否则调用ActivityThread
的getApplication
方法。由于应用程序这时已经启动,因此LoadedApk
不会为null
,则会调用LoadedApk
的getApplication
方法,如下所示:
Application getApplication() {
return mApplication;
}
这样就可以通过getApplicationContext()
方法获取到Application Context
。
那么,getApplication()
和getApplicationContext()
方法获得的是否是同一个对象呢?
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MyApplication myApp = (MyApplication) getApplication();
Log.d("TAG", "getApplication is " + myApp);
Context appContext = getApplicationContext();
Log.d("TAG", "getApplicationContext is " + appContext);
}
}
// getApplication is com.cah.androidtest.MyApplication@fafb347
// getApplicationContext is com.cah.androidtest.MyApplication@fafb347
// kotlin
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Log.e("CAH", "onCreate: $application")
Log.e("CAH", "onCreate: $applicationContext")
}
// onCreate: android.app.Application@a95ffff
// onCreate: android.app.Application@a95ffff
打印的内容是一样的,内存地址也是相同的,是同一个对象。 其实这个结果很好理解,Application
本身就是一个Context
,所以这里获取getApplicationContext()
得到的结果就是MyApplication
本身的实例。
那么,既然这两个方法得到的结果都是相同的,为什么Android
还要提供两个功能重复的方法呢?实际上这两个方法在作用域上有比较大的区别。getApplication()
方法的语义性非常强,是用来获取Application
实例的,但是这个方法只有在Activity
和Service
中才能调用的。如果在一些其他的场景,比如BroadcastReceiver
中也想获得Application
的实例,这时就可以借助getApplicationContext()
方法了,如下所示:
public class MyReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
MyApplication myApp = (MyApplication) context.getApplicationContext();
Log.d("TAG", "myApp is " + myApp);
}
}
也就是说,getApplicationContext()
方法的作用域会更广一些,任何一个Context
的实例,只要调用getApplicationContext()
方法都可以拿到Application
对象。
getBaseContext()
除了getApplicationContext()
和getApplication()
,还有一个getBaseContext()
方法,以下为getBaseContext()
的源码:
// ContentWrapper.java
public class ContextWrapper extends Context {
@UnsupportedAppUsage
Context mBase;
protected void attachBaseContext(Context base) {
if (mBase != null) {
throw new IllegalStateException("Base context already set");
}
mBase = base;
}
public Context getBaseContext() {
return mBase;
}
}
打印一下这三个方法的获得的值:
Context baseContext = getBaseContext();
Log.d("TAG", "getBaseContext is " + baseContext);
// getApplication is com.cah.androidtest.MyApplication@fafb347
// getApplicationContext is com.cah.androidtest.MyApplication@fafb347
// getBaseContext is android.app.ContextImpl@6592774
这次得到不同的对象,getBaseContext()
方法得到的是一个ContextImpl
对象。ContextImpl
正是上下文功能的实现类。也就是说像Application
、Activity
、Service
这样的类其实并不会去具体实现Context
的功能,而仅仅是做了一层接口封装而已,Context
的具体功能都是由ContextImpl
类去完成的。
ContextWrapper
中的方法还是非常多的,但是实现都非常统一,就是调用了mBase
对象中对应当前方法名的方法。
那么这个mBase
对象又是什么呢?在attachBaseContext()
方法中,这个方法传入一个base
参数,并把这个参数赋值给了mBase
对象。而attachBaseContext()
方法其实是由系统来调用的,它会把ContextImpl
对象作为参数传递到attachBaseContext()
方法当中,从而赋值给mBase
对象,之后ContextWrapper
中的所有方法其实都是通过这种委托的机制交由ContextImpl
去具体实现的,所有ContextImpl
是上下文功能的实现类是非常准确的。
在getBaseContext()
方法中,返回了mBase
对象,而mBase
对象其实就是ContextImpl
对象。
3 Activity
Context
Activity
的Context
会在Activity
的启动过程中被创建,以下是Activity
的Context
创建过程的时序图:
ActivityThread
是应用程序进程的主线程管理类,它的内部类ApplicationThread
会调用scheduleLaunchActivity
方法来启动Activity
,scheduleLaunchActivity
方法如下所示:
private class ActivityThread extends IApplicationThread.Stub {
final H mH = new H();
private void sendMessage(int what, Object obj) {
sendMessage(what, obj, 0, 0, false);
}
private void sendMessage(int what, Object obj, int arg1, int arg2, boolean async) {
Message msg = Message.obtain();
msg.what = what;
msg.obj = obj;
msg.arg1 = arg1;
msg.arg2 = arg2;
if (async) {
msg.setAsynchronous(true);
}
mH.sendMessage(msg);
}
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent,
String reason){
Activity a = performLaunchActivity(r, customIntent);
}
private class ApplicationThread extends IApplicationThread.Stub {
@Override
public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
ActivityInfo info, Configuration curConfig, Configuration overrideConfig,
CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor,
int procState, Bundle state, PersistableBundle persistentState,
List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) {
updateProcessState(procState, false);
ActivityClientRecord r = new ActivityClientRecord();
r.token = token;
r.ident = ident;
r.intent = intent;
r.referrer = referrer;
// ...
sendMessage(H.LAUNCH_ACTIVITY, r);
}
}
private class H extends Handler {
public static final int LAUNCH_ACTIVITY = 100;
public void handleMessage(Message msg) {
switch (msg.what) {
case LAUNCH_ACTIVITY: {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
r.packageInfo = getPackageInfoNoCheck(
r.activityInfo.applicationInfo, r.compatInfo);
handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
} break;
}
}
}
}
scheduleLaunchActivity
方法将启动Activity
的参数封装称为 ActivityClientRecord
, sendMessage
方法向H
类发送类型为LAUNCH_ACTIVITY
的消息,并将ActivityClientRecord
传递过去。sendMessage
方法的目的是将启动Acitivty
的逻辑放在主线程的消息队列中,这样,启动Activity
的逻辑就会在主线程中执行。
H
类的handleMessage
方法会对LAUNCH_ACTIVITY
类型的消息进行处理,其中调用了ActivityThread
的handleLaunchActivity
方法,而在handleLaunchActivity
方法中又调用了ActivityThread
的performLaunchActivity
方法。以下是performLaunchActivty
的源码:
private class ActivityThread extends IApplicationThread.Stub {
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
// 1. 创建Activity的ContextImpl
ContextImpl appContext = createBaseContextForActivity(r);
Activity activity = null;
try {
java.lang.ClassLoader cl = appContext.getClassLoader();
// 2. 创建Activity的实例
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 {
if (activity != null) {
// 3. 将此前创建的Activity实例赋值给ContextImpl的成员变量mOuterContext,这样ContextImpl也可以访问Activity的变量和方法
appContext.setOuterContext(activity);
// 4. 将ContextImpl传入activity的attach方法
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);
if (r.isPersistable()) {
// 5. mInstrumentation.callActivityOnCreate调用Activity的onCreate方法
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
mInstrumentation.callActivityOnCreate(activity, r.state);
}
}
r.paused = true;
mActivities.put(r.token, r);
} catch (SuperNotCalledException e) {
throw e;
} catch (Exception e) {
}
return activity;
}
}
注释4处是Activity.attach
方法的源码:
public class Activity extends ContextThemeWrapper
implements LayoutInflater.Factory2,
Window.Callback, KeyEvent.Callback,
OnCreateContextMenuListener, ComponentCallbacks2,
Window.OnWindowDismissedCallback, WindowControllerCallback,
AutofillManager.AutofillClient {
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) {
// 1. attachBaseContext方法在ContextThemeWrapper中实现
attachBaseContext(context);
mFragments.attachHost(null /*parent*/);
// 2. 创建PhoneWindow,代表程序窗口。PhoneWindow在运行期间会间接触发好多事件,比如点击、菜单弹出、屏幕焦点变化等事件,这些事件都需要转发给PhoneWindow关联的Activity,转发通过Window.callback接口实现,Activity实现了这个接口
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setWindowControllerCallback(this);
// 3. 将当前的Activity通过Window的setCallback方法传递给PhoneWindow
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
// 4. 为PhoneWindow设置WindowManager
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());
}
// 5. 获取WindowManager并赋予给Activity的成员变量mWindowManager,这样在Activity中就可以通过getWindowManager方法来获取WindowManager
mWindowManager = mWindow.getWindowManager();
mCurrentConfig = config;
mWindow.setColorMode(info.colorMode);
}
}
以下是ContextThemeWrapper.attachBaseContext
方法的源码:
public class ContextThemeWrapper extends ContextWrapper {
@Override
protected void attachBaseContext(Context newBase) {
super.attachBaseContext(newBase);
}
}
以下是ContextThemeWrapper
父类attachBaseContext
的attachBaseContext
方法的源码:
public class ContextWrapper extends Context {
Context mBase;
protected void attachBaseContext(Context base) {
if (mBase != null) {
throw new IllegalStateException("Base context already set");
}
// 一路传递过来的Activity的ContentImpl,将它赋值给ContextWrapper的成员变量mBase
mBase = base;
}
@Override
public Resources.Theme getTheme() {
return mBase.getTheme();
}
}
ContextWrapper
的功能就可以交给由ContextImpl
来处理。比如:调用ContextWrapper
的getTheme
方法实际上是调用ContextImpl
的getTheme
方法。
总结: 在启动Activity
的过程中创建ContextImpl
,并赋值给ContextWrapper
的成员变量mBase
,Activity
继承自ContextWrapper
的子类ContextThemeWrapper
,这样,在Activity
中就可以使用Context
定义的方法了。
4 Service
Context
Service
的Context
也是在Service
的启动过程中被创建,和Activity
的Context
创建过程类似,以下是Service
的创建时序图:
ActivityThread
的内部类ApplicationThread
会调用scheduleCreateService
方法启动Service
,以下是scheduleCreateService
的源码:
public final class ActivityThread {
final H mH = new H();
private void sendMessage(int what, Object obj) {
sendMessage(what, obj, 0, 0, false);
}
private void sendMessage(int what, Object obj, int arg1, int arg2, boolean async) {
Message msg = Message.obtain();
msg.what = what;
msg.obj = obj;
msg.arg1 = arg1;
msg.arg2 = arg2;
if (async) {
msg.setAsynchronous(true);
}
mH.sendMessage(msg);
}
private void handleCreateService(CreateServiceData data) {
try {
// 1. 通过ContextImpl的createAppContext方法创建ContextImpl,并将该ContextImpl传入注释2处service的attach方法中
ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
context.setOuterContext(service);
Application app = packageInfo.makeApplication(false, mInstrumentation);
// 2.
service.attach(context, this, data.info.name, data.token, app,
ActivityManager.getService());
service.onCreate();
mServices.put(data.token, service);
try {
ActivityManager.getService().serviceDoneExecuting(
data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
} catch (Exception e) {
}
}
private class ApplicationThread extends IApplicationThread.Stub {
public final void scheduleCreateService(IBinder token,
ServiceInfo info, CompatibilityInfo compatInfo, int processState) {
updateProcessState(processState, false);
CreateServiceData s = new CreateServiceData();
s.token = token;
s.info = info;
s.compatInfo = compatInfo;
sendMessage(H.CREATE_SERVICE, s);
}
}
private class H extends Handler {
public static final int CREATE_SERVICE = 114;
public void handleMessage(Message msg) {
switch (msg.what) {
case CREATE_SERVICE:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, ("serviceCreate: " + String.valueOf(msg.obj)));
handleCreateService((CreateServiceData)msg.obj);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
}
}
}
}
sendMessage
方法向H
类发送CREATE_SERVICE
类型的消息,H
类的handleMessage
方法会对CREATE_SERVICE
类型的消息进行处理,其中调用了ActivityThread
的handleCreateService
方法。
注释2处service
的attach
方法:
public abstract class Service extends ContextWrapper implements ComponentCallbacks2 {
public final void attach(Context context, ActivityThread thread, String className,
IBinder token, Application application, Object activityManager) {
attachBaseContext(context); // 1
mThread = thread; // NOTE: unused - remove?
mClassName = className;
mToken = token;
mApplication = application;
mActivityManager = (IActivityManager)activityManager;
mStartCompatibility = getApplicationInfo().targetSdkVersion
< Build.VERSION_CODES.ECLAIR;
}
}
注释1处调用了ContextWrapper
的attachBaseContext
方法:
public class ContextWrapper extends Context {
Context mBase;
protected void attachBaseContext(Context base) {
if (mBase != null) {
throw new IllegalStateException("Base context already set");
}
mBase = base;
}
}
这样base
一路传递过来的是ContextImpl
,将ContextImpl
赋值给ContextWrapper
的Context
类型的成员mBase
,这样在ContextWrapper
中就可以使用Context
方法,而Service
继承自ContextWrapper
,同样可以使用Context
的方法。
5 其他
5.1 Application
的设计
在开发过程中,基本上每一个应用程序都会有自己的一个Application
,并让它继承系统的Application
类,然后在自己的Application
类中去封装一些通用的操作。其实这并不是Google
所推荐的一种做法,因为这样只能把Application
当成一个通用工具类来使用,而实际上使用一个简单的单例也可以是想同样的功能。但是,也有太多的项目都是这样使用Application
的,当然,这种做法并没有什么副作用。
以下对Application
设计进行分析:
首先新建一个MyApplication
并让它继承自Application
,然后在AndroidManifest.xml
文件中对MyApplication
进行指定, 如下所示:
<application
android:name=".MyApplication"
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:theme="@style/AppTheme">
</application>
指定完成后,当程序启动时Android
系统会创建一个MyApplication
的实例,如果这里不指定的话就会默认创建一个Application
的实例。
有很多的Application
都是被当作通用工具类来使用的,那么既然作为一个通用工具类,要怎样才能获取到它的实例呢?——只需要调用getApplication()
方法就能拿到我们自定义的Application
的实例了。
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MyApplication myApp = (MyApplication) getApplication();
Log.d("TAG", "getApplication is" + myApp);
}
}
5.2 使用Application
的问题
虽说Application
的用法确实比较简单,但是我们平时的开发工作当中也着实存在着不少Application
误用场景。
Application
是Context
的其中一种类型,那么是否就意味着,只要是Application
的实例,就能随时使用Context
的各种方法呢?
public class MyApplication extends Application {
public MyApplication() {
String packageName = getPackageName();
Log.e("TAG", "package name is : " + packageName);
}
}
这是一个非常简单的自定义Application
,我们在MyApplication
的构造方法中获取了当前应用程序的包名,并打印欻里,获取包名使用了getPackageName()
方法,这个方法就是由Context
提供的,那么上面的代码能正常运行吗?
应用程序一启动就立刻崩溃了,报了一个空指针异常。如果把代码改成以下写法:
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
String packageName = getPackageName();
Log.e("TAG", "package name is : " + packageName);
}
}
// package name is : com.cah.androidtest
在构造方法中调用Context
的方法就会崩溃,在onCreate()
方法中调用Context
的方法就一切正常,那么这两个方法之间到底发生了什么事情呢?我们从新回顾一个ContextWrapper
类的源码,ContextWrapper
中有一个attachBaseContext()
方法,这个方法将会掺入一个Context
参数赋值给mBase
对象,之后mBase
对象就有值了。而我们又知道,所有Context
的方法都是调用Context
中的任何一个方法时,就会出现空指针异常,上面的代码就是这种情况。Application
中方法的执行顺序如下图所示:
Application
中在onCreate()
方法里去初始化各种全局的变量数据是一种比较推荐的做法,但是如果你想把初始化的时间点提前到极致,也可以去重写attachBaseContext()
,如下所示:
public class MyApplication extends Application {
@Override
protected void attachBaseContext(Context base) {
// 在这里,调用Context方法会崩溃
super.attachBaseContext(base);
// 在这里,可以正常调用Context方法
}
}
以上是我们平时在使用Application
时需要注意的一个点,下面来介绍另外一种非常普遍的Application
误用情况。
其实Android
官方并不太推荐我们使用自定义的Application
,基本上只有需要做一些全局初始化的时候可能才需要用到自定义的Application
,官方描述如下:
There is normally no need to subclass Application. In most situation, static singletons can provide the same functionality in a more modular way. If your singleton needs a global context (for example to register broadcast receivers), the function to retrieve it can be given a
Context
which internally usesContext.getApplicationContext()
when first constructing the singleton.
但是,就观察而言,现在自定义的Application
的使用情况基本上可以达到100%了,也就是我们平时自己写测试Demo
的时候可能不会使用,正式的项目几乎全部都会使用自定义的Application
。可使用归使用,有不少的项目对自定义Application
的用法不到位,正如官方文档中所描述的一样,多数项目只是把自定义Application
当成了一个通用工具类,而这个功能并不需要借助Application
来是想,使用单例可能是一种更加标准的方式。
不过自定义Application
也并没有什么副作用,它和单例模式二选一都可以实现同样的功能,有的项目也会把自定义Application
和单例模式混合到一起使用,这就过分了,以下是代码:
public class MyApplication extends Application {
public static MyApplication app;
public static MyApplication getInstance() {
if (app == null) {
app = new MyApplication();
}
return app;
}
}
就像单例模式一样,这里提供了一个getIntance()
方法,用于获取MyApplication
的实例,有了这个实例之后,就可以调用MyApplication
中的各种工具方法了。
但是这种写法是不对的,因为我们知道Application
是属于系统组件,系统组件大的实例就是要由系统去创建,如果这里我们自己new
一个MyApplication
的实例,它就只是一个普通的Java
对象而已,而不具备任何Context
的能力,有很多人反馈使用LitePal
时发生了空指针错误其实就是这个原因,因为你提供给LitePal
的只是一个普通的Java
对象,它无法通过这个对象来进行Context
操作。
那么如果真的想要提供一个获取MyApplication
实例的方法,比较标准的写法又是什么样的呢?其实这里我们只需要谨记一点,Application
全局只有一个,它本身就已经是单例了,无需在用单例模式去为它做多重实例保护了,代码如下:
public class MyApplication extends Application {
public static MyApplication app;
public static MyApplication getInstance() {
return app;
}
@Override
public void onCreate() {
super.onCreate();
app = this;
}
}
getInstance()
方法可以照常提供,但是里面不需要做任何逻辑判断,直接返回app
对象就可以了,而app
对象又是什么呢?在onCreate
方法中我们将app
对象赋值成this
,this
就是当前Application
的实例,那么app
也就是当前Application
的实例了。
5.3 Application
和Service
所不推荐的两种使用情况
- 如果用
ApplicationContext
去启动一个LaunchMode
为standard
的Acitivity
的时候会报错android.util.AndroidRuntimeException:Calling startActivity from outside of an Activity context require the FLAG_ACTIVITY_NEW_TAG falg. Is this really what you want?
这是因为非Activity
类型的Context
并没有所谓的任务栈,所以待启动的Activity
就找不到栈了。 解决这个问题的方法就是为待启动的Activity
指定FLAG_ACTIVITY_NEW_TASK
标记位,这样启动的时候就为它创建一个新的任务栈,而此时Activity
是以singleTask
模式启动的。所有这种用Application
启动Activity
的方式不推荐使用,Service
同Application
。 - 在
Application
和Service
中去layout inflate
也是合法的,但是会使系统默认的主题样式,如果你自定义了某些样式可能不会被使用,所以这种方式也不推荐使用。
一句话总结:凡是跟UI
相关的 ,都应该使用Acitivty
做为Context
来处理,其他的一些操作,Service
、Activity
、Application
等实例都可以,注意Context
引用的持有,防止内存泄漏。
如何获取Context
通常我们想要获取Context
对象,主要有以下四种方法:
View.getContext
,返回当前View
对象的Context
对象,通常是当前正在展示的Activity
对象Activity.getApplicationContext
,获取当前Activity
所在的(应用)进程的Context
对象,通常我们使用Context
对象时,要优先考虑这个全局进程Context
ContextWrapper.getBaseContext()
:用来获取一个ContextWrapper
进行装饰之前的Context
,可以使用这个方法,这个方法在实际开发中使用不多,也不建议使用Activity.this
返回当前Activity
实例,如果UI
控件不需要使用Activity
作为Context
对象,但是默认Toast
实际上使用ApplicationContext
也可以
5.4 Context
引起的内存泄漏
使用Context
最重要的问题之一是注意内存泄漏。 不同的Context
的生命周期不同,Application
是在应用存在的期间一直存在,而Activity
是会随着界面的销毁而销毁,如果我们的代码长时间持有Activity
的Context
,如静态引用或者单例类,那么会导致Activity
无法释放,
5.4.1 静态引用
如下面的代码:
object MyClass {
lateinit var mContext: Context
fun showToast(context: Context) {
mContext = context
}
}
单例类在应用持续的时间内会一直存在,这样Context
也会被一直持有,Activity
无法被回收,导致内存泄漏。
那,都换成Application
不就可以了,如下:
object MyClass {
lateinit var mContext: Context
fun showToast(context: Context) {
mContext = context.getApplicationContext
}
}
不可以。什么时候可以使用Application
,不涉及UI以及启动Activity
操作Activity
的Context
是拥有主题属性的,如果使用Application
来操作UI,那么会丢失自定义的主题,采用系统默认的主题。同时,有些UI操作只有Activity
可以执行,如弹出Dialog
,这涉及到Window
的token
问题。这也是官方对Context
不同权限的设计,没有界面的Context
,就不应该有操作界面的权利。使用Application
启动的Activity
必须制定task
以及标记singleTask
,因为Application
是没有任务栈的,需要重新开一个新的任务栈。因此,根据不同Context
的职责来执行不同的任务。
5.4.2 错误的单例模式
public class Singleton {
private static Singleton instance;
private Context context;
private Singleton(Context context) {
this.mContext = context;
}
public static Singleton getInstance (Context context) {
if(instance == null) {
instance = new Singleton(context);
}
return instance;
}
}
这是一个非线程安全的单例模式,instance
作为静态对象,其声明周期要长于普通的对象,其中也包含Activity
,假如Activity A
去getInstance
获得instance
对象,传入this
,常驻内存的Singleton
保存了你传入的Activity A
对象,并且一致持有,即使Activity
被销毁掉,但因为它的引用还存在于一个Singleton
中,就不可能被GC
掉,这样就导致了内存泄漏。
5.4.3 View
持有Acitivty
引用
public class MainActivity extends Activity {
private static Drawable mDrawable;
@Override
protected void onCreate(Bundle saveInstanceState) {
super.onCreate(saveInstanceState);
setContentView(R.layout.activity_main);
ImageView iv = new ImageView(this);
mDrawable = getResources().getDrawable(R.drawable.ic_launcher);
iv.setImageDrawable(mDrawable);
}
}
用一个静态Drawable
对象当ImageView
设置这个Drawable
时,ImageView
保存了mDrawable
的引用,而ImageView
传入的this
是MainActivity
的mContext
,因为被static
修饰的mDrawable
是常驻内存的,MainActivity
是它的间接引用,MainActivity
被销毁时,也不能被GC
掉,所以造成内存泄漏。
参考
https://zhuanlan.zhihu.com/p/24847247
https://blog.csdn.net/yanbober/article/details/45967639
https://juejin.cn/post/6887499574383116302/
https://blog.csdn.net/hfy8971613/article/details/107201238/