Android Context完全解析与各种获取Context方法

2023-11-05

Context类型

我们知道,Android应用都是使用Java语言来编写的,那么大家可以思考一下,一个Android程序和一个Java程序,他们最大的区别在哪里?划分界限又是什么呢?其实简单点分析,Android程序不像Java程序一样,随便创建一个类,写个main()方法就能跑了,而是要有一个完整的Android工程环境,在这个环境下,我们有像Activity、Service、BroadcastReceiver等系统组件,而这些组件并不是像一个普通的Java对象new一下就能创建实例的了,而是要有它们各自的上下文环境,也就是我们这里讨论的Context。可以这样讲,Context是维持Android程序中各组件能够正常工作的一个核心功能类。

下面我们来看一下Context的继承结构:

Context的继承结构还是稍微有点复杂的,可以看到,直系子类有两个,一个是ContextWrapper,一个是ContextImpl。那么从名字上就可以看出,ContextWrapper是上下文功能的封装类,而ContextImpl则是上下文功能的实现类。而ContextWrapper又有三个直接的子类,ContextThemeWrapper、Service和Application。其中,ContextThemeWrapper是一个带主题的封装类,而它有一个直接子类就是Activity。

那么在这里我们至少看到了几个所比较熟悉的面孔,Activity、Service、还有Application。由此,其实我们就已经可以得出结论了,Context一共有三种类型,分别是Application、Activity和Service。这三个类虽然分别各种承担着不同的作用,但它们都属于Context的一种,而它们具体Context的功能则是由ContextImpl类去实现的。

那么Context到底可以实现哪些功能呢?这个就实在是太多了,弹出Toast、启动Activity、启动Service、发送广播、操作数据库等等等等都需要用到Context。由于Context的具体能力是由ContextImpl类去实现的,因此在绝大多数场景下,Activity、Service和Application这三种类型的Context都是可以通用的。不过有几种场景比较特殊,比如启动Activity,还有弹出Dialog。出于安全原因的考虑,Android是不允许Activity或Dialog凭空出现的,一个Activity的启动必须要建立在另一个Activity的基础之上,也就是以此形成的返回栈。而Dialog则必须在一个Activity上面弹出(除非是System Alert类型的Dialog),因此在这种场景下,我们只能使用Activity类型的Context,否则将会出错。

Context的应用场景

 

大家注意看到有一些NO上添加了一些数字,其实这些从能力上来说是YES,但是为什么说是NO呢?下面一个一个解释:

数字1:启动Activity在这些类中是可以的,但是需要创建一个新的task。一般情况不推荐。

数字2:在这些类中去layout inflate是合法的,但是会使用系统默认的主题样式,如果你自定义了某些样式可能不会被使用。

数字3:在receiver为null时允许,在4.2或以上的版本中,用于获取黏性广播的当前值。(可以无视)

注:ContentProvider、BroadcastReceiver之所以在上述表格中,是因为在其内部方法中都有一个context用于使用。

 

好了,这里我们看下表格,重点看Activity和Application,可以看到,和UI相关的方法基本都不建议或者不可使用Application,并且,前三个操作基本不可能在Application中出现。实际上,只要把握住一点,凡是跟UI相关的,都应该使用Activity做为Context来处理;其他的一些操作,Service,Activity,Application等实例都可以,当然了,注意Context引用的持有,防止内存泄漏。

 

Context数量

那么一个应用程序中到底有多少个Context呢?其实根据上面的Context类型我们就已经可以得出答案了。Context一共有Application、Activity和Service三种类型,因此一个应用程序中Context数量的计算公式就可以这样写:

[plain] view plain copy

 

  1. Context数量 = Activity数量 + Service数量 + 1  

上面的1代表着Application的数量,因为一个应用程序中可以有多个Activity和多个Service,但是只能有一个Application。

Android获取各种Context

1. getApplicationContext() :

这个函数返回的这个Application的上下文,所以是与app挂钩的,所以在整个生命周期里面都是不变的,这个好理解,但是使用的时候要注意,该context是和引用的生命周期一致的,所以和activity生命周期挂钩的任务不要使用该context,比如网络访问,防止内存泄露

2. getBasecontext():

stackoverflow上面写的是,这个函数不应该被使用,用Context代替,而Context是与activity相关连,所以当activity死亡后可能会被destroyed,我举个我自己写的例子

public Dialog displayDialog(int choice) { 

    switch(choice){ 

    case 0

      AlertDialog aDialog = new AlertDialog.Builder(this

      .setIcon(R.drawable.ic_launcher) 

      .setTitle("Hello World"

      .setPositiveButton("OK", new DialogInterface.OnClickListener() { 

   

      @Override 

      public void onClick(DialogInterface arg0, int arg1) { 

        Toast.makeText(getBaseContext(), "OK clicked", Toast.LENGTH_SHORT).show(); 

      } 

    });

  }

}

  

这个例子中的getBaseContext()就不能被this代替,因为上面的this返回的是这个activity的context,而在这个onClick函数中如果使用this的话,则返回的是这个AlertDialog的context,所以要使用的是当前activity名.this 去使用,比如当前activity为 TestActivity,那么在里面就是用TestActivity.this即可

 3. getApplication():

getApplication只能被Activity和Services使用,虽然在现在的Android的实现中,getApplication和getApplicationContext返回一样的对象,但也不能保证这两个函数一样(例如在特殊的提供者来说),所以如果你想得到你在Manifest文件里面注册的App class,你不要去调用getApplicationContext,以为你可能得不到你所要的app实例(你显然有测试框架的经验)。。。。

翻译完成,一目了然(哪里翻译错误,请指出,水B一只),原文:

getApplication() is available to Activity and Services only. Although in current Android Activity and Service implementations, getApplication() and getApplicationContext() return the same object, there is no guarantee that this will always be the case (for example, in a specific vendor implementation). So if you want the Application class you registered in the Manifest, you should never call getApplicationContext() and cast it to your application, because it may not be the application instance (which you obviously experienced with the test framework).

4. getParent() :

返回activity的上下文,如果这个子视图的话,换句话说,就是当在子视图里面调用的话就返回一个带有子视图的activity对象,一目了然。。。

5.getActivity():

在fragment中使用,返回该fragment所依附的activity上下文

6.this

记住Activity,Service类,Application类是继承自Context类的,所以在有的时候需要上下文,只需要使用this关键字即可,但是有的时候再线程里面,this关键字的意义就改变了,但这个时候如果需要上下文,则需要使用 类名.this,这样就可以了

这里有点注意的:

做项目时遇见的,提一下吧,动态注册广播,在调用registerBroadcast函数的时候,需要传入一个上下文和broadcastReceiver,查看源码可以知道,存储的时候context是作为一个key的作用,所以使用同一个context来注册同一个广播,onreceive只会调用一次,但是如果使用不同的context,则会调用多次,虽然不调用unregisterBroadcast有时也没事,不会报错,但是一定不要忘记取消注销

后续:为了简化context的使用方法,现在有这么一种方法,就是在Application类里面维护一个弱引用:

1

2

3

4

/** 用来保存当前该Application的context */ 

private static Context instance; 

/** 用来保存最新打开页面的context */ 

private volatile static WeakReference<Context> instanceRef = null;

再写一个方法,

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

public static Context getInstance(){ 

        if (instanceRef == null || instanceRef.get() == null){ 

            synchronized (RootApplication.class) { 

                if (instanceRef == null || instanceRef.get() == null) { 

                    Context context = ActivityManager.getInstance().getActivity(); 

                    if (context != null

                        instanceRef = new WeakReference<>(context); 

                    else 

                        instanceRef = new WeakReference<>(instance); 

                        L.w("请确保RootActivity调用setInstanceRef方法"); 

                    

                

            

        

        return instanceRef.get(); 

    

  

最后在应用的Activity基类中(这个应该有的吧)加上两个语句:

1

2

3

4

public void onCreate(Bundle savedInstanceState) { 

    super.onCreate(savedInstanceState); 

    RootApplication.setInstanceRef(this); 

1

2

3

4

5

protected void onResume() { 

    super.onResume(); 

    //也要在onresume函数里面进行设置,保证弱引用一直引用当前的可见页面 

    RootApplication.setInstanceRef(this); 

  

这样每次调用application的getInstance()方法一定能够返回一个context,而且是当前唯一可见activity的context,其他地方就可以直接使用了,不用到处传递context,再此处统一维护即可,

Context泄露:Handler&内部类

复制代码

复制代码

1 public class SampleActivity extends Activity {
2 
3   private final Handler mLeakyHandler = new Handler() {
4     @Override
5     public void handleMessage(Message msg) {
6       // ... 
7     }
8   }
9 }

复制代码

复制代码

如果没有仔细观察,上面的代码可能导致严重的内存泄露。Android Lint会给出下面的警告:

In Android, Handler classes should be static or leaks might occur.

但是到底是泄漏,如何发生的?让我们确定问题的根源,先写下我们所知道的
1、当一个Android应用程序第一次启动时,Android框架为应用程序的主线程创建一个Looper对象。一个Looper实现了一个简单的消息队列,在一个循环中处理Message对象。所有主要的应用程序框架事件(如活动生命周期方法调用,单击按钮,等等)都包含在Message对象,它被添加到Looper的消息队列然后一个个被处理。主线程的Looper在应用程序的整个生命周期中存在。
2、当一个Handle在主线程被实例化,它就被关联到Looper的消息队列。被发送到消息队列的消息会持有一个Handler的引用,以便Android框架可以在Looper最终处理这个消息的时候,调用Handler#handleMessage(Message)
3、在Java中,非静态的内部类和匿名类会隐式地持有一个他们外部类的引用。静态内部类则不会。

那么,到底是内存泄漏?好像很难懂,让我们以下面的代码作为一个例子

复制代码

复制代码

1 public class SampleActivity extends Activity {
 2  
 3   private final Handler mLeakyHandler = new Handler() {
 4     @Override
 5     public void handleMessage(Message msg) {
 6       // ...
 7     }
 8   }
 9  
10   @Override
11   protected void onCreate(Bundle savedInstanceState) {
12     super.onCreate(savedInstanceState);
13  
14     // 延时10分钟发送一个消息
15     mLeakyHandler.postDelayed(new Runnable() {
16       @Override
17       public void run() { }
18     }, 60 * 10 * 1000);
19  
20     // 返回前一个Activity
21     finish();
22   }
23 }

复制代码

复制代码

当这个Activity被finished后,延时发送的消息会继续在主线程的消息队列中存活10分钟,直到他们被处理。这个消息持有这个Activity的Handler引用,这个Handler有隐式地持有他的外部类(在这个例子中是SampleActivity)。直到消息被处理前,这个引用都不会被释放。因此Activity不会被垃圾回收机制回收,泄露他所持有的应用程序资源。注意,第15行的匿名Runnable类也一样。匿名类的非静态实例持有一个隐式的外部类引用,因此context将被泄露。

为了解决这个问题,Handler的子类应该定义在一个新文件中或使用静态内部类。静态内部类不会隐式持有外部类的引用。所以不会导致它的Activity泄露。如果你需要在Handle内部调用外部Activity的方法,那么让Handler持有一个Activity的弱引用(WeakReference)以便你不会意外导致context泄露。为了解决我们实例化匿名Runnable类可能导致的内存泄露,我们将用一个静态变量来引用他(因为匿名类的静态实例不会隐式持有他们外部类的引用)。

复制代码

复制代码

1 public class SampleActivity extends Activity {
 2     /**
 3     * 匿名类的静态实例不会隐式持有他们外部类的引用
 4     */
 5     private static final Runnable sRunnable = new Runnable() {
 6             @Override
 7             public void run() {
 8             }
 9         };
10 
11     private final MyHandler mHandler = new MyHandler(this);
12 
13     @Override
14     protected void onCreate(Bundle savedInstanceState) {
15         super.onCreate(savedInstanceState);
16 
17         // 延时10分钟发送一个消息.
18         mHandler.postDelayed(sRunnable, 60 * 10 * 1000);
19 
20         // 返回前一个Activity
21         finish();
22     }
23 
24     /**
25     * 静态内部类的实例不会隐式持有他们外部类的引用。
26     */
27     private static class MyHandler extends Handler {
28         private final WeakReference<SampleActivity> mActivity;
29 
30         public MyHandler(SampleActivity activity) {
31             mActivity = new WeakReference<SampleActivity>(activity);
32         }
33 
34         @Override
35         public void handleMessage(Message msg) {
36             SampleActivity activity = mActivity.get();
37 
38             if (activity != null) {
39                 // ...
40             }
41         }
42     }
43 }

复制代码

复制代码

静态和非静态内部类的区别是比较难懂的,但每一个Android开发人员都应该了解。开发中不能碰的雷区是什么?不在一个Activity中使用非静态内部类, 以防它的生命周期比Activity长。相反,尽量使用持有Activity弱引用的静态内部类。

Application Context的设计

基本上每一个应用程序都会有一个自己的Application,并让它继承自系统的Application类,然后在自己的Application类中去封装一些通用的操作。其实这并不是Google所推荐的一种做法,因为这样我们只是把Application当成了一个通用工具类来使用的,而实际上使用一个简单的单例类也可以实现同样的功能。但是根据我的观察,有太多的项目都是这样使用Application的。当然这种做法也并没有什么副作用,只是说明还是有不少人对于Application理解的还有些欠缺。那么这里我们先来对Application的设计进行分析,讲一些大家所不知道的细节,然后再看一下平时使用Application的问题。

首先新建一个MyApplication并让它继承自Application,然后在AndroidManifest.xml文件中对MyApplication进行指定,如下所示:

[html] view plain copy

 

  1. <application  
  2.     android:name=".MyApplication"  
  3.     android:allowBackup="true"  
  4.     android:icon="@drawable/ic_launcher"  
  5.     android:label="@string/app_name"  
  6.     android:theme="@style/AppTheme" >  
  7.     ......  
  8. </application>  

指定完成后,当我们的程序启动时Android系统就会创建一个MyApplication的实例,如果这里不指定的话就会默认创建一个Application的实例。

 

前面提到过,现在很多的Application都是被当作通用工具类来使用的,那么既然作为一个通用工具类,我们要怎样才能获取到它的实例呢?如下所示:

[java] view plain copy

 

  1. public class MainActivity extends Activity {  
  2.       
  3.     @Override  
  4.     protected void onCreate(Bundle savedInstanceState) {  
  5.         super.onCreate(savedInstanceState);  
  6.         setContentView(R.layout.activity_main);  
  7.         MyApplication myApp = (MyApplication) getApplication();  
  8.         Log.d("TAG", "getApplication is " + myApp);  
  9.     }  
  10.       
  11. }  

可以看到,代码很简单,只需要调用getApplication()方法就能拿到我们自定义的Application的实例了,打印结果如下所示:

 

那么除了getApplication()方法,其实还有一个getApplicationContext()方法,这两个方法看上去好像有点关联,那么它们的区别是什么呢?我们将代码修改一下:

[java] view plain copy

 

  1. public class MainActivity extends Activity {  
  2.       
  3.     @Override  
  4.     protected void onCreate(Bundle savedInstanceState) {  
  5.         super.onCreate(savedInstanceState);  
  6.         setContentView(R.layout.activity_main);  
  7.         MyApplication myApp = (MyApplication) getApplication();  
  8.         Log.d("TAG", "getApplication is " + myApp);  
  9.         Context appContext = getApplicationContext();  
  10.         Log.d("TAG", "getApplicationContext is " + appContext);  
  11.     }  
  12.       
  13. }  

同样,我们把getApplicationContext()的结果打印了出来,现在重新运行代码,结果如下图所示:

 

咦?好像打印出的结果是一样的呀,连后面的内存地址都是相同的,看来它们是同一个对象。其实这个结果也很好理解,因为前面已经说过了,Application本身就是一个Context,所以这里获取getApplicationContext()得到的结果就是MyApplication本身的实例。

那么有的朋友可能就会问了,既然这两个方法得到的结果都是相同的,那么Android为什么要提供两个功能重复的方法呢?实际上这两个方法在作用域上有比较大的区别。getApplication()方法的语义性非常强,一看就知道是用来获取Application实例的,但是这个方法只有在Activity和Service中才能调用的到。那么也许在绝大多数情况下我们都是在Activity或者Service中使用Application的,但是如果在一些其它的场景,比如BroadcastReceiver中也想获得Application的实例,这时就可以借助getApplicationContext()方法了,如下所示:

[java] view plain copy

 

  1. public class MyReceiver extends BroadcastReceiver {  
  2.   
  3.     @Override  
  4.     public void onReceive(Context context, Intent intent) {  
  5.         MyApplication myApp = (MyApplication) context.getApplicationContext();  
  6.         Log.d("TAG", "myApp is " + myApp);  
  7.     }  
  8.   
  9. }  

也就是说,getApplicationContext()方法的作用域会更广一些,任何一个Context的实例,只要调用getApplicationContext()方法都可以拿到我们的Application对象。

那么更加细心的朋友会发现,除了这两个方法之外,其实还有一个getBaseContext()方法,这个baseContext又是什么东西呢?我们还是通过打印的方式来验证一下:

哦?这次得到的是不同的对象了,getBaseContext()方法得到的是一个ContextImpl对象。这个ContextImpl是不是感觉有点似曾相识?回去看一下Context的继承结构图吧,ContextImpl正是上下文功能的实现类。也就是说像Application、Activity这样的类其实并不会去具体实现Context的功能,而仅仅是做了一层接口封装而已,Context的具体功能都是由ContextImpl类去完成的。那么这样的设计到底是怎么实现的呢?我们还是来看一下源码吧。因为Application、Activity、Service都是直接或间接继承自ContextWrapper的,我们就直接看ContextWrapper的源码,如下所示:

[java] view plain copy

 

  1. /** 
  2.  * Proxying implementation of Context that simply delegates all of its calls to 
  3.  * another Context.  Can be subclassed to modify behavior without changing 
  4.  * the original Context. 
  5.  */  
  6. public class ContextWrapper extends Context {  
  7.     Context mBase;  
  8.       
  9.     /** 
  10.      * Set the base context for this ContextWrapper.  All calls will then be 
  11.      * delegated to the base context.  Throws 
  12.      * IllegalStateException if a base context has already been set. 
  13.      *  
  14.      * @param base The new base context for this wrapper. 
  15.      */  
  16.     protected void attachBaseContext(Context base) {  
  17.         if (mBase != null) {  
  18.             throw new IllegalStateException("Base context already set");  
  19.         }  
  20.         mBase = base;  
  21.     }  
  22.   
  23.     /** 
  24.      * @return the base context as set by the constructor or setBaseContext 
  25.      */  
  26.     public Context getBaseContext() {  
  27.         return mBase;  
  28.     }  
  29.   
  30.     @Override  
  31.     public AssetManager getAssets() {  
  32.         return mBase.getAssets();  
  33.     }  
  34.   
  35.     @Override  
  36.     public Resources getResources() {  
  37.         return mBase.getResources();  
  38.     }  
  39.   
  40.     @Override  
  41.     public ContentResolver getContentResolver() {  
  42.         return mBase.getContentResolver();  
  43.     }  
  44.   
  45.     @Override  
  46.     public Looper getMainLooper() {  
  47.         return mBase.getMainLooper();  
  48.     }  
  49.       
  50.     @Override  
  51.     public Context getApplicationContext() {  
  52.         return mBase.getApplicationContext();  
  53.     }  
  54.   
  55.     @Override  
  56.     public String getPackageName() {  
  57.         return mBase.getPackageName();  
  58.     }  
  59.   
  60.     @Override  
  61.     public void startActivity(Intent intent) {  
  62.         mBase.startActivity(intent);  
  63.     }  
  64.       
  65.     @Override  
  66.     public void sendBroadcast(Intent intent) {  
  67.         mBase.sendBroadcast(intent);  
  68.     }  
  69.   
  70.     @Override  
  71.     public Intent registerReceiver(  
  72.         BroadcastReceiver receiver, IntentFilter filter) {  
  73.         return mBase.registerReceiver(receiver, filter);  
  74.     }  
  75.   
  76.     @Override  
  77.     public void unregisterReceiver(BroadcastReceiver receiver) {  
  78.         mBase.unregisterReceiver(receiver);  
  79.     }  
  80.   
  81.     @Override  
  82.     public ComponentName startService(Intent service) {  
  83.         return mBase.startService(service);  
  84.     }  
  85.   
  86.     @Override  
  87.     public boolean stopService(Intent name) {  
  88.         return mBase.stopService(name);  
  89.     }  
  90.   
  91.     @Override  
  92.     public boolean bindService(Intent service, ServiceConnection conn,  
  93.             int flags) {  
  94.         return mBase.bindService(service, conn, flags);  
  95.     }  
  96.   
  97.     @Override  
  98.     public void unbindService(ServiceConnection conn) {  
  99.         mBase.unbindService(conn);  
  100.     }  
  101.   
  102.     @Override  
  103.     public Object getSystemService(String name) {  
  104.         return mBase.getSystemService(name);  
  105.     }  
  106.   
  107.     ......  
  108. }  

由于ContextWrapper中的方法还是非常多的,我就进行了一些筛选,只贴出来了部分方法。那么上面的这些方法相信大家都是非常熟悉的,getResources()、getPackageName()、getSystemService()等等都是我们经常要用到的方法。那么所有这些方法的实现又是什么样的呢?其实所有ContextWrapper中方法的实现都非常统一,就是调用了mBase对象中对应当前方法名的方法。

 

那么这个mBase对象又是什么呢?我们来看第16行的attachBaseContext()方法,这个方法中传入了一个base参数,并把这个参数赋值给了mBase对象。而attachBaseContext()方法其实是由系统来调用的,它会把ContextImpl对象作为参数传递到attachBaseContext()方法当中,从而赋值给mBase对象,之后ContextWrapper中的所有方法其实都是通过这种委托的机制交由ContextImpl去具体实现的,所以说ContextImpl是上下文功能的实现类是非常准确的。

那么另外再看一下我们刚刚打印的getBaseContext()方法,在第26行。这个方法只有一行代码,就是返回了mBase对象而已,而mBase对象其实就是ContextImpl对象,因此刚才的打印结果也得到了印证。

使用Application的问题

虽说Application的用法确实非常简单,但是我们平时的开发工作当中也着实存在着不少Application误用的场景,那么今天就来看一看有哪些比较容易犯错的地方是我们应该注意的。

Application是Context的其中一种类型,那么是否就意味着,只要是Application的实例,就能随时使用Context的各种方法呢?我们来做个实验试一下就知道了:

[java] view plain copy

 

  1. public class MyApplication extends Application {  
  2.       
  3.     public MyApplication() {  
  4.         String packageName = getPackageName();  
  5.         Log.d("TAG", "package name is " + packageName);  
  6.     }  
  7.       
  8. }  

这是一个非常简单的自定义Application,我们在MyApplication的构造方法当中获取了当前应用程序的包名,并打印出来。获取包名使用了getPackageName()方法,这个方法就是由Context提供的。那么上面的代码能正常运行吗?跑一下就知道了,你将会看到如下所示的结果:

 

应用程序一启动就立刻崩溃了,报的是一个空指针异常。看起来好像挺简单的一段代码,怎么就会成空指针了呢?但是如果你尝试把代码改成下面的写法,就会发现一切正常了:

[java] view plain copy

 

  1. public class MyApplication extends Application {  
  2.       
  3.     @Override  
  4.     public void onCreate() {  
  5.         super.onCreate();  
  6.         String packageName = getPackageName();  
  7.         Log.d("TAG", "package name is " + packageName);  
  8.     }  
  9.       
  10. }  

运行结果如下所示:

 

在构造方法中调用Context的方法就会崩溃,在onCreate()方法中调用Context的方法就一切正常,那么这两个方法之间到底发生了什么事情呢?我们重新回顾一下ContextWrapper类的源码,ContextWrapper中有一个attachBaseContext()方法,这个方法会将传入的一个Context参数赋值给mBase对象,之后mBase对象就有值了。而我们又知道,所有Context的方法都是调用这个mBase对象的同名方法,那么也就是说如果在mBase对象还没赋值的情况下就去调用Context中的任何一个方法时,就会出现空指针异常,上面的代码就是这种情况。Application中方法的执行顺序如下图所示:

Application中在onCreate()方法里去初始化各种全局的变量数据是一种比较推荐的做法,但是如果你想把初始化的时间点提前到极致,也可以去重写attachBaseContext()方法,如下所示:

[java] view plain copy

 

  1. public class MyApplication extends Application {  
  2.       
  3.     @Override  
  4.     protected void attachBaseContext(Context base) {  
  5.         // 在这里调用Context的方法会崩溃  
  6.         super.attachBaseContext(base);  
  7.         // 在这里可以正常调用Context的方法  
  8.     }  
  9.       
  10. }  

以上是我们平时在使用Application时需要注意的一个点,下面再来介绍另外一种非常普遍的Application误用情况。

 

其实Android官方并不太推荐我们使用自定义的Application,基本上只有需要做一些全局初始化的时候可能才需要用到自定义Application,官方文档描述如下:

但是就我的观察而言,现在自定义Application的使用情况基本上可以达到100%了,也就是我们平时自己写测试demo的时候可能不会使用,正式的项目几乎全部都会使用自定义Application。可是使用归使用,有不少项目对自定义Application的用法并不到位,正如官方文档中所表述的一样,多数项目只是把自定义Application当成了一个通用工具类,而这个功能并不需要借助Application来实现,使用单例可能是一种更加标准的方式。

不过自定义Application也并没有什么副作用,它和单例模式二选一都可以实现同样的功能,但是我见过有一些项目,会把自定义Application和单例模式混合到一起使用,这就让人大跌眼镜了。一个非常典型的例子如下所示:

[java] view plain copy

 

  1. public class MyApplication extends Application {  
  2.       
  3.     private static MyApplication app;  
  4.       
  5.     public static MyApplication getInstance() {  
  6.         if (app == null) {  
  7.             app = new MyApplication();  
  8.         }  
  9.         return app;  
  10.     }  
  11.       
  12. }  

就像单例模式一样,这里提供了一个getInstance()方法,用于获取MyApplication的实例,有了这个实例之后,就可以调用MyApplication中的各种工具方法了。

 

但是这种写法对吗?这种写法是大错特错!因为我们知道Application是属于系统组件,系统组件的实例是要由系统来去创建的,如果这里我们自己去new一个MyApplication的实例,它就只是一个普通的Java对象而已,而不具备任何Context的能力。有很多人向我反馈使用 LitePal 时发生了空指针错误其实都是由于这个原因,因为你提供给LitePal的只是一个普通的Java对象,它无法通过这个对象来进行Context操作。

那么如果真的想要提供一个获取MyApplication实例的方法,比较标准的写法又是什么样的呢?其实这里我们只需谨记一点,Application全局只有一个,它本身就已经是单例了,无需再用单例模式去为它做多重实例保护了,代码如下所示:

[java] view plain copy

 

  1. public class MyApplication extends Application {  
  2.       
  3.     private static MyApplication app;  
  4.       
  5.     public static MyApplication getInstance() {  
  6.         return app;  
  7.     }  
  8.       
  9.     @Override  
  10.     public void onCreate() {  
  11.         super.onCreate();  
  12.         app = this;  
  13.     }  
  14.       
  15. }  

getInstance()方法可以照常提供,但是里面不要做任何逻辑判断,直接返回app对象就可以了,而app对象又是什么呢?在onCreate()方法中我们将app对象赋值成this,this就是当前Application的实例,那么app也就是当前Application的实例了。

附Android根据上下文对象Context找到对应的Activity:

    @Nullable
    public static Activity findActivity(Context context) {
        if (context instanceof Activity) {
            return (Activity) context;
        }
        if (context instanceof ContextWrapper) {
            ContextWrapper wrapper = (ContextWrapper) context;
            return findActivity(wrapper.getBaseContext());
        } else {
            return null;
        }
    }
 

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Android Context完全解析与各种获取Context方法 的相关文章

  • 慢~再听我讲一遍Activity的启动流程

    前言 activity启动的流程分为两部分 一是在activity中通过startActivity Intent intent 方法启动一个Activity 二是我们在桌面通过点击应用图标启动一个App然后显示Activity 第二种方式相
  • 征服面试官:Handler 原理篇 掌握这篇面试题汇总,吊打面试官!

    1 Handler 实现机制 Handler 机制有几个核心类 Handler Looper Message MessageQueue Handler 机制是一个典型的生产者消费者模式 多个生产者 一个消费者 该模式是处理线程安全的一个经典
  • 解决 if you already hava 64-bit JDK installed,define a JAVA_HOME variable in Computer>System....

    启动Android studio 弹窗 提示如图 一般是修改studio64 exe vmoptions文件出差 例如我的studio64 exe vmoptions位于C Users 24359 AppData Roaming Googl
  • ubuntu 20.04.4编译 继续尝试编译Android 12,13

    之前使用虚拟机编译过Android10 现在开始记录编译12 上次忘记给镜像了这次补上镜像ubuntu 20 04 4 desktop amd64 链接 https pan baidu com s 1REJ2cIJyqupLRQjN9SW0
  • 通过一个链接打开本地app,或者去下载app

    以前支付宝发短信给你手机 然后你点开这个链接的时候会发现它自动的打开了支付宝软件 这个是如何做到的呢 我认为实现的原理就是 这个链接通过浏览器打开 然后自动执行js方法 如果本地有支付宝软件 就去打开这个方法 当然支付宝app也要做对应的处
  • 04 ImageView中图片保存到文件

    最近做的一个小App中的一个功能 把ImageView中的图片保存为一个 jpg文件 如果设备上有SDCard 图片会被保存到SD卡上 如果没有则保存在设备的存储空间中 这里主要包含了两个要点 一是 Android文件保存时文件夹的创建 二
  • 想拿 20k 无压力?Android开发必读的一篇文章!

    程序员分很多种类和等级 如果要提高达到20k的概率 有两个条件如果满足的话 则很容易达到 1 一线城市 北上广深杭 2 互联网行业 如果你非得抬杠 我要在三线城市 做外包要赚20k的话 很难 我自己也做不到 所以 如果你要做程序员的话 必须
  • Android studio 多渠道开发App以及打包方法

    Android studio 多渠道开发App及打包方法 大家好 技术小白又来总结经验了 开发过程中我们的App首先需要在开发环境下开发 开发完之后需要打包到测试环境提供给测试人员开发 测试人员测试通过后需要打包到正式环境进行上线 这些过程
  • onInterceptTouchEvent和onTouchEvent调用关系详解

    一 onTouch onTouch是View中OnTouchListener接口中的方法 处理View及其子类被touch是的事件处理 当然 前提是touch时间能够传递到指定的view Q1 为什么会传递不到呢 Java代码 Interf
  • Android Context完全解析与各种获取Context方法

    Context类型 我们知道 Android应用都是使用Java语言来编写的 那么大家可以思考一下 一个Android程序和一个Java程序 他们最大的区别在哪里 划分界限又是什么呢 其实简单点分析 Android程序不像Java程序一样
  • 自学Android开发 AES加密

    目录 一 问题 二 解决源码 一 问题 在Java 后端发来的AES加密数据时 发现Android APP不能解密 而且Android 端 加密同样的数据返回的结果居然不一致 所以我在网上查询后 发现在SecureRandom在产生安全随机
  • android实现ios形式的字体,实现各种自定义的字体

    这段时间整个人都是处在一种颓废的状态 不想做任何事情 每天就是行尸般的上班 下班 然后在家玩游戏 状态是差的要死 脑子感觉就是一片混沌态 也不知道要写些啥了 真的是太可怕 为了防止延续 决定先写篇基础的东西压压惊吧 这样的状态真的不想再有了
  • Android 学习笔记

    目录 一 Android入门 1 Android 概述 2 Android Studio 3 创建模拟器 4 使用外部模拟器 5 第一个app 二 app开发基础 1 开发语言 2 app工程目录结构 3 文本控件TextView 1 设置
  • android面试题大全 android面试题总结

    android面试题大全 android面试题总结 1 Android系统的架构 Android系统架构之应用程序 Android会同一系列核心应用程序包一起发布 该应用程序包包括email客户端 SMS短消息程序 日历 地图 浏览器 联系
  • Eclipse ADT中的logcat不显示解决方法

    1 在Eclipse界面中找到DDMS 然后找到device选项卡 在这个选项卡中选择reset adb 如果不行尝试方法2 2 不用关闭eclipse和模拟器 在Android SDK的tools目录下有个 ddms bat 批处理文件
  • RxPermissions简单使用

    RxPermissions简单使用 描述 随着社会的发展人们也开始重视对隐私的保护 谷歌也在Android6 0 sdk 23 增加了动态权限申请来保护广大用户的隐私 使我们开发者实现起来会很繁琐 代码量也会增多 但是对于程序员来说永远都是
  • Android中 @id 与 @+id 区别

    Android 中的组件需要用一个int 类型的值来表示 这个值也就是组件标签中的id 属性值 id 属性只能接受资源类型的值 也就是必须以 开头的值 例如 id abc id xyz等 如果在 后面使用 表示当修改完某个布局文件并保存后
  • Android 使用高德SDK编写周边搜索定位

    转载请注明 前言 使用高德SDK实现定位及周边的搜索界面 先看效果图 效果图看这 传不上 使用到了高德以下sdk com amap api 3dmap latest integration com amap api search lates
  • Android---SpringBoot实现前后端数据交互

    Android SpringBoot实现前后端数据交互 星光不问赶路人 时间不负有心人 这篇是针对android传数据到后台springboot 使用Xutils框架 使用Xutils框架 关于xutils的使用这是老师的博客大家可以看看
  • kotlin 之于 java 的差异

    一 变量声名 赋值和访问 kotlin 变量是空安全的 一般要求声明时就赋值 声名变量有两个关键字var和val 其中val相当于 java 的中的final 变量类型可显式给出 也可隐式推断 变量也可分可空类型和非空类型 以下给出几种声名

随机推荐

  • Windows 系统中安装 MySQL 5.6 zip 步骤并配置 root 密码

    说明 最早我安装 MySQL 还是使用安装版的进行安装 但是作为一名程序员 MySQL 公司既然提供了 zip 版本的安装 我们当然要使用这种绿色的安装方式 MySQL 5 6 版本和 5 7 版本的安装步骤有很大不同 本文记录了 5 6
  • ultraiso制作u盘启动盘教程图文详解纯净-U盘启动教程

    制作u盘启动盘用软碟通ultraiso轻松制作纯净windows7 u盘装系统 网友们除了知道的u大师u盘启动盘制作工具 u启动 u深度 老毛桃 大白菜u盘启动盘制作工具外 还有量产 fbinstTool 我这再介绍一种u盘启动盘的制作方式
  • maven子依赖版本覆盖父依赖

    比如父依赖定义了jaskson version为2 13 3 在
  • 01-Java基础-变量

    一 变量介绍 变量就是向操作系统申请内存来存储值 也就是说 当创建变量的时候 需要在内存中申请空间 内存管理系统根据变量的类型为变量分配存储空间 分配的空间只能用来储存该类型数据 简单理解 类似数学中的 设 x 1 在程序中就表示声明了一个
  • 移动级处理芯片岁末盘点

    时间过得真快 不知不觉间又到了年关 这就说明一年一度做盘点汇总的时候也要到了 作为即将踏入这个科技行业快有三个年头的媒体人 笔者也这在这段时间内跟随新兴的移动互联网市场一起成长着 同时也看尽了这三年来行业里无情的变迁 感叹身在同一个行业里厂
  • 层次分析法(多准则决策方法)

    这是介于定量分析与定性分析的一种方法 运用层次分析法建模 大体上可按下面四个步骤进行 建立递阶层次结构模型 构造出各层次中的所有判断矩阵 层次单排序及一致性检验 层次总排序及一致性检验 建立递阶层次结构模型 每一层次中各元素所支配的元素一般
  • 笔试题13:采用UDP协议,编写一个简单发送字符串的程序(源码)

    UDP协议是一种无须建立连接的网络通信协议 采用Java来编写 一般有以下几个步骤 包括接收端和发送端 1 创建数据Socket 指定一个端口号 2 对于接收消息的一端来说 提供一个byte数组进行数据的存储 而对于发送消息一端 除此之外还
  • keras 对于大数据的训练,无法一次性载入内存,使用迭代器

    说明 我是在keras的官方demo上进行修改https github com fchollet keras blob master examples imdb cnn py 1 几点说明 从文件中读入数据 会降低GPU的使用率 如果能够直
  • C++【侯捷】——— 成员模板、模板特化、模板模板参数

    C 侯捷 成员模板 模板特化 模板模板参数 成员模板 模板特化 偏特化 模版模板参数 自己写的程序最好用namespace包起来 不要直接使用全局的using namespace 尤其是代码量比较多的大程序 成员模板 类模板在使用的时候 后
  • Vim的NerdTree插件

    在vundle插件管理的方式 直接在 vimrc中的Plugin段落中加入 Plugin scrooloose nerdtree 然后重启Vim并输入PluginInstall 即可完成安装 然后输入 NERDTreeToggle即可打开文
  • vue-property-decorator的简单介绍,一看就会

    参考 https github com kaorun343 vue property decorator 怎么使vue支持ts写法呢 我们需要用到vue property decorator 这个组件完全依赖于vue class compo
  • Layui学习笔记,隔壁都馋哭了

    添加一个Tab 切换用户管理 删除商品管理 删除所有tab 简单风格的Tab 网站设置 用户管理 权限分配 商品管理 订单管理 高度默认自适应 也可以随意固宽 2 Tab进行了响应式处理 所以无需担心数量多少 内容2 内容3 内容4 内容5
  • 【Unity Shader】unity海边波浪效果的实现

    效果图如下 GIF因为为了把图压小所以删掉了一些帧导致后面速度突然很快 实际效果并不是这样 PS 对于移动端 参考该文章 http www lsngo net 2018 03 22 unity seawave vertexcolor 之前在
  • react(56)——在项目中使用开发者工具redux-devtools-extension

    1 谷歌安装插件 需要访问外网 在谷歌商店中下载 也可以自己格外找资源 2 项目中添加redux devtools extension库 在终端运行cnpm add redux devtools extension即可 3 在store j
  • 使用cookie和session实现登录(简单原理解析)

    cookie 浏览器在客户端电脑硬盘中开辟的一块空间 主要用来存储服务端数据 比如sessionId cookie中的数据是以域名的形式进行区分的 cookie中的数据是有过期时间的超过时间会被浏览器自动删除 cookie中的数据可以随着请
  • 2023网络安全面试题(附答案)+面经

    前言 随着国家政策的扶持 网络安全行业也越来越为大众所熟知 相应的想要进入到网络安全行业的人也越来越多 为了拿到心仪的Offer之外 除了学好网络安全知识以外 还要应对好企业的面试 所以在这里我归纳总结了一些网络安全方面的常见面试题 希望能
  • 读磁盘概述

    磁盘结构 磁道C 磁头H 扇区S 一个磁盘有很多个盘面 上面是其中一个盘面 每个盘面对应一个磁头 磁盘的最小单元是扇区 通过CHS可以定位到一个确定的扇区 每个扇区一般是512个字节 CHS寻道方式 设置好寄存器的值 然后一个int 13就
  • [笔记]MySQL 删除重复数据

    通过内连接 INNER JOIN 方式删除重复数据 场景复现 CREATE TABLE user id bigint 11 AUTO INCREMENT name varchar 64 PRIMARY KEY id engine InnoD
  • Google guava之SortedMultiset简介说明

    转自 Google guava之SortedMultiset简介说明 下文笔者讲述guava中SortedMultiset集合的简介说明 如下所示 guava之SortedMultiset集合简介 SortedMultiset集合 可用于按
  • Android Context完全解析与各种获取Context方法

    Context类型 我们知道 Android应用都是使用Java语言来编写的 那么大家可以思考一下 一个Android程序和一个Java程序 他们最大的区别在哪里 划分界限又是什么呢 其实简单点分析 Android程序不像Java程序一样