Android中Context表示上下文,Context及其常用子类的关系如下图所示

Context
AOSP路径:/frameworks/base/core/java/android/content/Context.java
public abstract class Context {
public abstract PackageManager getPackageManager();
public abstract void startActivity(@RequiresPermission Intent intent);
...
}
Context是抽象类,定义了许多抽象方法,主要涉及Activity、Service、广播、文件、数据库、权限校验、获取系统服务等操作,如:startActivity、startService、sendBroadcast、registerReceiver、getContentResolver、getSharedPreferences、openFileInput、openFileOutput、openOrCreateDatabase、checkPermission、getSystemService。
ContextImpl
AOSP路径:/frameworks/base/core/java/android/app/ContextImpl.java
class ContextImpl extends Context {
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;
}
public void startActivity(Intent intent) {
warnIfCallingFromSystemProcess();
startActivity(intent, null);
}
...
}
ContextImpl继承Context抽象类,实现了Context类中的抽象方法,是Context的具体实现类。
ContextWrapper
AOSP路径:/frameworks/base/core/java/android/content/ContextWrapper.java
public class ContextWrapper extends Context {
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 PackageManager getPackageManager() {
return mBase.getPackageManager();
}
public void startActivity(Intent intent) {
mBase.startActivity(intent);
}
...
}
ContextWrapper继承Context抽象类,作为Context类的包装类,其内部维护了一个Context类型的成员变量mBase,mBase最终会指向一个ContextImpl对象,ContextWrapper的方法其内部依赖mBase。
Application
AOSP路径:/frameworks/base/core/java/android/app/Application.java
public class Application extends ContextWrapper
Application继承ContextWrapper,因此也拥有一个Context类型的成员变量mBase,mBase最终会指向一个ContextImpl对象,ContextImpl是Context的具体实现类,所以Application也就拥有了Context提供的所有功能。一个应用中只有一个Apllication实例。
在Activity和Service中可以通过getApplication()或getApplicationContext()来获取Application。
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity_Log";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Context baseContext = getBaseContext();
Application application = getApplication();
Context applicationContext = getApplicationContext();
Log.i(TAG, "onCreate: baseContext is " + baseContext);
Log.i(TAG, "onCreate: application is " + application);
Log.i(TAG, "onCreate: applicationContext is " + applicationContext);
}
}
public class MyService extends Service {
private static final String TAG = "MyService_Log";
public MyService() {
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Context baseContext = getBaseContext();
Application application = getApplication();
Context applicationContext = getApplicationContext();
Log.i(TAG, "onStartCommand: baseContext is " + baseContext);
Log.i(TAG, "onStartCommand: application is " + application);
Log.i(TAG, "onStartCommand: applicationContext is " + applicationContext);
return Service.START_STICKY_COMPATIBILITY;
}
}
日志打印如下:
2020-09-06 16:50:38.864 28689-28689/com.example.myapp1 I/MainActivity_Log: onCreate: baseContext is android.app.ContextImpl@2a78d78
2020-09-06 16:50:38.864 28689-28689/com.example.myapp1 I/MainActivity_Log: onCreate: application is android.app.Application@141fc51
2020-09-06 16:50:38.865 28689-28689/com.example.myapp1 I/MainActivity_Log: onCreate: applicationContext is android.app.Application@141fc51
2020-09-06 16:50:45.399 28689-28689/com.example.myapp1 I/MyService_Log: onStartCommand: baseContext is android.app.ContextImpl@fa019ee
2020-09-06 16:50:45.400 28689-28689/com.example.myapp1 I/MyService_Log: onStartCommand: application is android.app.Application@141fc51
2020-09-06 16:50:45.400 28689-28689/com.example.myapp1 I/MyService_Log: onStartCommand: applicationContext is android.app.Application@141fc51
在BroadcastReceiver中可以通过onReceive(Context context, Intent intent)中参数context调用getApplicationContext()来获取Application。
public class MyReceiver extends BroadcastReceiver {
private static final String TAG = "MyReceiver_Log";
@Override
public void onReceive(Context context, Intent intent) {
if (intent != null && "MyApp1.Action.MyReceiver".equals(intent.getAction())) {
Log.i(TAG, "onReceive: context is " + context);
Context applicationContext = context.getApplicationContext();
Log.i(TAG, "onReceive: applicationContext is " + applicationContext);
}
}
}
日志打印如下:
2020-09-06 16:50:47.124 28689-28689/com.example.myapp1 I/MyReceiver_Log: onReceive: context is android.app.ReceiverRestrictedContext@d7c0ea1
2020-09-06 16:50:47.124 28689-28689/com.example.myapp1 I/MyReceiver_Log: onReceive: applicationContext is android.app.Application@141fc51
由上面日志可知,在Activity和Service中通过getApplication()或getApplicationContext()获取的Application,和,在BroadcastReceiver中通过onReceive(Context context, Intent intent)中参数context调用getApplicationContext()获取的Application,是同一个对象android.app.Application@141fc51,由此说明一个应用中只有一个Apllication实例。
此外,应用也可以自定义Application,并添加额外的功能,当应用安装运行时,Android系统就会为应用创建自定义的Application,例如:
创建自定义MyApplication,继承Application
public class MyApplication extends Application {
private static final String TAG = "MyApplication_Log";
public void showInfo(){
Log.i(TAG, "showInfo: I am MyApplication");
}
}
在AndroidManifest.xml中对MyApplication进行指定
<application
android:name=".MyApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
在Activity中通过getApplication()或getApplicationContext()获取的自定义Application,此时Application不仅包含Context的功能,还包括了应用自定义功能
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity_Log";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Context baseContext = getBaseContext();
Application application = getApplication();
Context applicationContext = getApplicationContext();
Log.i(TAG, "onCreate: baseContext is " + baseContext);
Log.i(TAG, "onCreate: application is " + application);
Log.i(TAG, "onCreate: applicationContext is " + applicationContext);
Log.i(TAG, "onCreate: packageName is " + application.getPackageName());
Log.i(TAG, "onCreate: packageName is " + applicationContext.getPackageName());
MyApplication myApplication = (MyApplication) application;
MyApplication myApplicationContext = (MyApplication) applicationContext;
myApplication.showInfo();
myApplicationContext.showInfo();
}
}
日志打印如下:
2020-09-06 17:12:10.333 28903-28903/com.example.myapp1 I/MainActivity_Log: onCreate: baseContext is android.app.ContextImpl@2a78d78
2020-09-06 17:12:10.333 28903-28903/com.example.myapp1 I/MainActivity_Log: onCreate: application is com.example.myapp1.MyApplication@141fc51
2020-09-06 17:12:10.333 28903-28903/com.example.myapp1 I/MainActivity_Log: onCreate: applicationContext is com.example.myapp1.MyApplication@141fc51
2020-09-06 17:12:10.333 28903-28903/com.example.myapp1 I/MyApplication_Log: onCreate: packageName is com.example.myapp1
2020-09-06 17:12:10.333 28903-28903/com.example.myapp1 I/MyApplication_Log: onCreate: packageName is com.example.myapp1
2020-09-06 17:12:10.333 28903-28903/com.example.myapp1 I/MyApplication_Log: showInfo: I am MyApplication
2020-09-06 17:12:10.333 28903-28903/com.example.myapp1 I/MyApplication_Log: showInfo: I am MyApplication
由上面日志可知,获取的正是应用自定义的MyApplication。
自定义Apllication时,需要注意Application方法的执行顺序:构造方法、attachBaseContext方法、onCreate方法。以下我们通过一个简单案例说明:
public class MyApplication extends Application {
private static final String TAG = "MyApplication_Log";
public MyApplication(){
String packageName = getPackageName();
Log.i(TAG, "MyApplication: packageName is " + packageName);
}
}
直接安装应用,Android系统在为应用创建自定义的MyApplication时,会执行构造方法里面的getPackageName()。应用启动后,报错了:
java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.String android.content.Context.getPackageName()' on a null object reference
为什么在getPackageName()上报空指针呢?
我们知道:ContextWrapper继承Context抽象类,作为Context类的包装类,其内部维护了一个Context类型的成员变量mBase,mBase最终会指向一个ContextImpl对象,ContextWrapper的方法其内部依赖mBase。Application继承ContextWrapper,因此也拥有一个Context类型的成员变量mBase,mBase最终会指向一个ContextImpl对象,ContextImpl是Context的具体实现类,所以Application也就拥有了Context提供的所有功能。当在自定义MyApplication的构造方法调用Context的getPackageName()时,实际是在调用mBase.getPackageName()。但是,Application方法的执行顺序:构造方法、attachBaseContext方法、onCreate方法。attachBaseContext(Context base) 是被系统调用的,为mBase赋值为ContextImpl类型的context。在自定义MyApplication的构造方法中调用Context的getPackageName()时,attachBaseContext(Context base) 还未被系统调用,因此mBase为Null,出现空指针。
对代码进行如下修改:
public class MyApplication extends Application {
private static final String TAG = "MyApplication_Log";
public MyApplication(){
}
@Override
public void onCreate() {
super.onCreate();
String packageName = getPackageName();
Log.i(TAG, "MyApplication: packageName is " + packageName);
}
}
重新安装运行应用,就不会出现空指针异常了,日志打印如下:
2020-09-06 17:49:57.525 30086-30086/com.example.myapp1 I/MyApplication_Log: MyApplication: packageName is com.example.myapp1
此外,需要注意的是,Application是系统创建的,且已经是单例的,我们不要直接去new,直接new出的Application是不具备context功能的,例如:
public class MyApplication extends Application {
private static MyApplication app;
public static MyApplication getInstance() {
if (app == null) {
app = new MyApplication();
}
return app;
}
}
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity_Log";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MyApplication myApplication = MyApplication.getInstance();
String packageName = myApplication.getPackageName();
Log.i(TAG, "MyApplication: packageName is " + packageName);
}
应用安装启动后,报错:
java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.String android.content.Context.getPackageName()' on a null object reference
对代码进行如下修改:
public class MyApplication extends Application {
private static MyApplication app;
public static MyApplication getInstance() {
return app;
}
@Override
public void onCreate() {
super.onCreate();
app = this;
}
}
重新安装运行应用,就不会出现空指针异常了,日志打印如下:
2020-09-06 18:03:28.846 30400-30400/com.example.myapp1 I/MainActivity_Log: MyApplication: packageName is com.example.myapp1
Service
AOSP路径:/frameworks/base/core/java/android/app/Service.java
public abstract class Service extends ContextWrapper
Service继承ContextWrapper,因此也拥有一个Context类型的成员变量mBase,mBase最终会指向一个ContextImpl对象,ContextImpl是Context的具体实现类,所以Service也就拥有了Context提供的所有功能。
Activity
AOSP路径:/frameworks/base/core/java/android/app/Activity.java
// ContextThemeWrapper于主题相关
public class ContextThemeWrapper extends ContextWrapper
// Activity不同于Application和Service,它是有页面的,与主题相关的,所以它继承ContextThemeWrapper
public class Activity extends ContextThemeWrapper
Activity间接继承ContextWrapper,因此也拥有一个Context类型的成员变量mBase,mBase最终会指向一个ContextImpl对象,ContextImpl是Context的具体实现类,所以Activity也就拥有了Context提供的所有功能。
Context作用域
ContextImpl是Context的具体实现类,提供了操作Activity、Service、广播、文件、数据库、权限校验、获取系统服务等方法;ContextWrapper内部维护了一个Context类型的成员变量mBase,mBase最终会指向一个ContextImpl对象,ContextWrapper的方法其内部依赖mBase;Application、Service、Activity直接或间接继承ContextWrapper,所以它们都拥有Context提供的所有功能。因此,绝大多数场景下,Application、Service、Activity三种类型的Context可以通用。不过有几种场景比较特殊,比如:启动Activity、弹出Dialog,Android不允许Activity或Dialog凭空出现的,一个Activit或Dialog启动必须要建立在另一个Activity的基础之上,因此在这种场景下,只能使用Activity类型的Context,否则将会出错。
Context作用域 | Application | Service | Activity |
---|---|---|---|
Show a Dialog | No | NO | YES |
Start an Activity | 不推荐 | 不推荐 | YES |
Layout Inflation | 不推荐 | 不推荐 | YES |
Start a Service | YES | YES | YES |
Send a Broadcast | YES | YES | YES |
Register Broadcast Receiver | YES | YES | YES |
Laod Resource values | YES | YES | YES |
当以Application或Service类型的context来启动Activity时,会报错:android.util.AndroidRuntimeException: Calling startActivity from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want? 这是因为非Activity类型的Context没有任务栈,所以待启动的Activity就找不到任务栈了。解决方法是为待启动的Activity指定FLAG_ACTIVITY_NEW_TASK标记位,为它创建一个新的任务栈。但这种方式可能存在安全问题,所以Android并不推荐。
当以Application或Service类型的context来执行Layout Inflate时,会使用系统默认的主题样式,可能导致你自定义的主题样式不被使用。所以Android也不推荐以Application或Service类型的context来执行Layout Inflate。
网友评论