Android疑难 —隐式Intent匹配原则、Android Studio Clean and Rerun、LayoutInflater、Context

Android疑难

       —隐式Intent匹配原则、Android Studio Clean and Rerun、LayoutInflater、Context

1、隐式Intent匹配原则

    1Intent中只能包含一个Action,可以包含多个Category;

2、清单文件中一个Activity可配置多个IntentFilter, 一个IntentFilter可包含多个

Action,多个Category,但是只能包含一个data;

3、Intent若含有Action,IntentFilter中包含多个Action,只要Intent中的Action与IntentFilter中的任一一个Action匹配,则算匹配成功;

4、Intent中若含有category,则这些category必须属于intentFilter中的category的子集,不管intentFilter中配置了多少category,Intent只需携带其子集数量的category即可匹配。

PS:IntentFilter中必须包含:Android:name="android.intent.category.DEFAULT"/>因为使用隐式意图的Intent中会添加默认的Category,即上图中的category值,如IntentFilter中不添加上图的Category,则匹配不成功,会爆出android.content.ActivityNotFoundException: No Activity found to handle Intent { act=xxxxxxxx }异常,但是我其实不太明白,为什么入口Activity中不需要这个默认的Category,希望有知道的能提点一下。

 

5、若清单文件中一个Activity配置了多个IntentFilter,Intent只需与其中一个IntentFilter中的内容相匹配即可,但不可跨越多个IntentFilter去匹配。

 

6、Data包含Scheme,Host,Port,Path和Type五部分,Intent中Data和Type是分开设置的,且会按照设置顺序后设置Type或Data会覆盖前设置的Data或type,如果要让Intent中Data和Type共同存在需要使用setDataAndType(Uri data,String type);的API同时设置。Intent中的Data的5个组成部分必须是IntentFilter中5个部分的自己,才算匹配。

2、Android Studio的Clean and Rerun

     新版的 Android Studio 引入了 instant run 功能,也就是俗称的热补丁技术,但是热补丁尚有缺陷有时候会出现莫名其妙的失败和错误,这种情况下直接点击 Run 标签下的 Clean and Rerun 就能重新 rebuild 项目。你退出 Android Studio 然后重新进入 Android Studio 实际上也是一个 Rebuild 过程。

当然也可以直接禁用 Instant Run 功能,在 Setting -> BuildExecution and Deployment -> Instant Run 中取消第一个勾就行

   可能造成的错误:Execution failed for task ':app:buildInfoDebugLoader'. > Exception while doing past iteration backup

3、LayoutInflater

     LayoutInflater实例:

LayoutInflater layoutInflater = LayoutInflater.from(Context context);

            这里的Context一般是Activity实例。

     inflate方法:

         LayoutInflaterinflate方法两个参数和三个参数的区别如下

 

 

inflate方法从大范围来看,分两种,三个参数的构造方法和两个参数的构造方法。在这两类中又有细分,OK,那我们就把各种情况都来演示一遍。 

1.三个参数的inflate方法

方法头如下:

[java] view plain copy print?

1. public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot)  


好,这里主要分为三种情况,分别来看

1.1 root不为null,attachToRoot为true

root不为null,attachToRoot为true时,表示将resource指定的布局添加到root中,添加的过程中resource所指定的的布局的根节点的各个属性都是有效的。比如下面一个案例,我的Activity的布局如下:

[java] view plain copy print?

1. "1.0" encoding="utf-8"?>  

2. "http://schemas.android.com/apk/res/android"  

3.     xmlns:tools="http://schemas.android.com/tools"  

4.     android:layout_width="match_parent"  

5.     android:layout_height="match_parent"  

6.     android:orientation="vertical"  

7.     android:id="@+id/ll"  

8.     tools:context="org.sang.layoutinflater.MainActivity">  

9.   


我还有一个布局linearlayout.xml如下:

[java] view plain copy print?

1. "1.0" encoding="utf-8"?>  

2. "http://schemas.android.com/apk/res/android"  

3.     android:id="@+id/ll"  

4.     android:layout_width="200dp"  

5.     android:layout_height="200dp"  

6.     android:background="@color/colorPrimary"  

7.     android:gravity="center"  

8.     android:orientation="vertical">  

9.   

10.     

11.         android:layout_width="wrap_content"  

12.         android:layout_height="wrap_content" />  

13.   


我现在想把这个linearlayout.xml布局文件添加到我的activity的布局中,那么我可以这么做:

[java] view plain copy print?

1. @Override  

2. protected void onCreate(Bundle savedInstanceState) {  

3.     super.onCreate(savedInstanceState);  

4.     setContentView(R.layout.activity_main);  

5.     LinearLayout ll = (LinearLayout) findViewById(R.id.ll);  

6.     LayoutInflater inflater = LayoutInflater.from(this);  

7.     inflater.inflate(R.layout.linearlayout, ll,true);  

8. }  

小伙伴们注意到,这里我都没写将inflate出来的View添加到ll中的代码,但是linearlayout布局文件就已经添加进来了,这就是因为我第三个参数设置为了true,表示将第一个参数所指定的布局添加到第二个参数的View中。最终显示效果如下:

 

如果我作死多写这么一行代码,如下:

[java] view plain copy print?

1. protected void onCreate(Bundle savedInstanceState) {  

2.     super.onCreate(savedInstanceState);  

3.     setContentView(R.layout.activity_main);  

4.     LinearLayout ll = (LinearLayout) findViewById(R.id.ll);  

5.     LayoutInflater inflater = LayoutInflater.from(this);  

6.     View view = inflater.inflate(R.layout.linearlayout, ll, true);  

7.     ll.addView(view);  

8. }  


这个时候再运行,系统会抛如下异常:

[java] view plain copy print?

1. java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.  


原因就是因为当第三个参数为true时,会自动将第一个参数所指定的View添加到第二个参数所指定的View中。

1.2 root不为null,attachToRoot为false

如果root不为null,而attachToRoot为false的话,表示不将第一个参数所指定的View添加到root中,那么这个时候有的小伙伴可能就有疑问了,既然不添加到root中,那我还写这么多干嘛?我第二个参数直接给null不就可以了?其实不然,这里涉及到另外一个问题:我们在开发的过程中给控件所指定的layout_width和layout_height到底是什么意思?该属性的表示一个控件在容器中的大小,就是说这个控件必须在容器中,这个属性才有意义,否则无意义。这就意味着如果我直接将linearlayout加载进来而不给它指定一个父布局,则inflate布局的根节点的layout_width和layout_height属性将会失效(因为这个时候linearlayout将不处于任何容器中,那么它的根节点的宽高自然会失效)。如果我想让linearlayout的根节点有效,又不想让其处于某一个容器中,那我就可以设置root不为null,而attachToRoot为false。这样,指定root的目的也就很明确了,即root会协助linearlayout的根节点生成布局参数,只有这一个作用。OK,还是上面的布局文件,如果我想将之添加到activity的布局中又该如何呢?

[java] view plain copy print?

1. protected void onCreate(Bundle savedInstanceState) {  

2.     super.onCreate(savedInstanceState);  

3.     setContentView(R.layout.activity_main);  

4.     LinearLayout ll = (LinearLayout) findViewById(R.id.ll);  

5.     LayoutInflater inflater = LayoutInflater.from(this);  

6.     View view = inflater.inflate(R.layout.linearlayout, ll, false);  

7.     ll.addView(view);  

8. }  


大家注意,这个时候我需要手动的将inflate加载进来的view添加到ll容器中,因为inflate的最后一个参数false表示不将linealayout添加到ll中。显示效果和上文一样,不再贴图。

1.3 root为null

root为null时,不论attachToRoot为true还是为false,显示效果都是一样的。当root为null表示我不需要将第一个参数所指定的布局添加到任何容器中,同时也表示没有任何容器来来协助第一个参数所指定布局的根节点生成布局参数。我还是使用上文提到的linearlayout,我们来看下面一段代码:

[java] view plain copy print?

1. protected void onCreate(Bundle savedInstanceState) {  

2.         super.onCreate(savedInstanceState);  

3.         setContentView(R.layout.activity_main);  

4.         LinearLayout ll = (LinearLayout) findViewById(R.id.ll);  

5.         LayoutInflater inflater = LayoutInflater.from(this);  

6.         View view = inflater.inflate(R.layout.linearlayout, nullfalse);  

7.         ll.addView(view);  

8.     }  


当第二个参数为null,第三个参数为false时(即使为true显示效果也是一样的,这里以false为例),由于在inflate方法中没有将linearlayout添加到某一个容器中,所以我需要手动添加,另外由于linearlayout并没有处于某一个容器中,所以它的根节点的宽高属性会失效,显示效果如下:

 

小伙伴们注意,这个时候不管我给linearlayout的根节点的宽高设置什么,都是没有效果的,它都是包裹button,如果我修改button,则button会立即有变化,因为button是处于某一个容器中的。

2.两个参数的inflate方法

两个参数的inflate方法就很简单了,我们来稍微看一点点源码:

[java] view plain copy print?

1. public View inflate(XmlPullParser parser, @Nullable ViewGroup root) {  

2.         return inflate(parser, root, root != null);  

3.     }  


这是两个参数的inflate方法,大家注意两个参数实际上最终也是调用了三个参数。

两个参数的inflate方法分为如下两种情况:

1.root为null,等同于1.3所述情况。

2.root不为null,等同于1.1所述情况。

3.为什么Activity布局的根节点的宽高属性会生效?

inflate方法我们已经说完了,小伙伴们可能有另外一个疑问,那为什么Activity布局的根节点的宽高属性会生效?其实原因很简单,大部分情况下我们一个Activity页面由两部分组成(Android的版本号和应用主题会影响到Activity页面组成,这里以常见页面为例),我们的页面中有一个顶级View叫做DecorView,DecorView中包含一个竖直方向的LinearLayout,LinearLayout由两部分组成,第一部分是标题栏,第二部分是内容栏,内容栏是一个FrameLayout,我们在Activity中调用setContentView就是将View添加到这个FrameLayout中,所以给大家一种错觉仿佛Activity的根布局很特殊,其实不然。

 

OK,以上就是对LayoutInflater中inflate方法的一个简单介绍,希望能够帮助到还没弄懂这个的小伙伴。

 

以上。

4、Context

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数量

 

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

[plain] view plain copy

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

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

 

Application Context的设计

 

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

 

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

[html] view plain copy

1.   

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.   

指定完成后,当我们的程序启动时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的实例了。

 

 

 

补充:

OnClick(View v) 方法中v即为所点击的View

 

 

使用getContext获取的是当前对象所在的Context

关于书上P117 getView方法中的getContext

         P123 onCreateXxx方法中的parent.getContext

         p131 onCreateXxx方法中的v.getContext

 

 第一个能直接使用getContextthis.getContext是因为Adapter已经在Activity中实例化,所以可以直接获得Context。而第二第三不能直接getContext是因为她们出现在onCreate方法中,此时Adapter未完全实例化,即没有所在的Context,所以不能直接使用getContext方法。转而使用传入的一些参数,比如parentv(view)

 

你可能感兴趣的:(Android疑难 —隐式Intent匹配原则、Android Studio Clean and Rerun、LayoutInflater、Context)