美文网首页
Android Context浅析

Android Context浅析

作者: XX的太阳 | 来源:发表于2021-03-11 08:04 被阅读0次

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


image.png

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。

相关文章

  • Android Context浅析

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

  • Android Context浅析

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

  • Android技术分享| Context浅析

    类继承图 我们来看下关于 Context 的类继承图,我们通过查看源码得知,Context 是一个抽象类,所以它肯...

  • 浅析 go context

    浅析 go context 用法demo WithCancelfunc WithCancel(parent Con...

  • 基于Android 的Log机制

    基于Android N的Log机制浅析 上篇 基于Android N的Log机制浅析 下篇 android log...

  • Android基础之Context

    Android基础之Context Context 对于Android开发者来说Context应该是非常熟悉的。我...

  • Android构建过程分析

    Android构建过程分析浅析 Android 打包流程

  • Android全局Context

    Android 全局Context 继承Application类 注册全局Context类

  • Go Context浅析

    在日常开发中,我们通常会在函数的第一个参数设置为 ctx context.context,这就是golang 的 ...

  • Lifecycle用法与解析

    Android Architecture Component -- Lifecycle 浅析 Lifecycle ...

网友评论

      本文标题:Android Context浅析

      本文链接:https://www.haomeiwen.com/subject/ydsuqltx.html