Context 都没弄明白,还怎么做 Android 开发?,靠着这份面试题跟答案
###一个应用程序有几个 Context
其实这个问题本身并没有什么意义,关键还是在于对 Context 的理解,从上面的关系图我们已经可以得出答案了,在应用程序中 Context 的具体实现子类就是:Activity,Service,Application。那么Context数量=Activity数量+Service数量+1
。当然如果你足够细心,可能会有疑问:我们常说四大组件,这里怎么只有 Activity,Service 持有 Context,那 Broadcast Receiver,Content Provider 呢?Broadcast Receiver,Content Provider 并不是 Context 的子类,他们所持有的 Context 都是其他地方传过去的,所以并不计入 Context 总数。上面的关系图也从另外一个侧面告诉我们 Context 类在整个 Android 系统中的地位是多么的崇高,因为很显然 Activity,Service,Application 都是其子类,其地位和作用不言而喻。
Context 能干什么
Context 到底可以实现哪些功能呢?这个就实在是太多了,弹出 Toast、启动 Activity、启动 Service、发送广播、操作数据库等等都需要用到 Context。
TextView tv = new TextView(getContext());
ListAdapter adapter = new SimpleCursorAdapter(getApplicationContext(), ...);
AudioManager am = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);getApplicationContext().getSharedPreferences(name, mode);
getApplicationContext().getContentResolver().query(uri, ...);
getContext().getResources().getDisplayMetrics().widthPixels * 5 / 8;
getContext().startActivity(intent);
getContext().startService(intent);
getContext().sendBroadcast(intent);
###Context 作用域
虽然 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 继承自 ContextWrapper,很显然 ContextThemeWrapper 在 ContextWrapper 的基础上又做了一些操作使得 Activity 变得更强大,这里我就不再贴源码给大家分析了,有兴趣的童鞋可以自己查查源码。上图中的 YES 和 NO 我也不再做过多的解释了,这里我说一下上图中 Application 和 Service 所不推荐的两种使用情况。
1:如果我们用 ApplicationContext 去启动一个 LaunchMode 为 standard 的 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 标记位,这样启动的时候就为它创建一个新的任务栈,而此时 Activity 是以 singleTask 模式启动的。所有这种用 Application 启动 Activity 的方式不推荐使用,Service 同 Application。
2:在 Application 和 Service 中去 layout inflate 也是合法的,但是会使用系统默认的主题样式,如果你自定义了某些样式可能不会被使用。所以这种方式也不推荐使用。一句话总结:凡是跟 UI 相关的,都应该使用 Activity 做为 Context 来处理;其他的一些操作,Service,Activity,Application 等实例都可以,当然了,注意 Context 引用的持有,防止内存泄漏。
###如何获取 Context
通常我们想要获取 Context 对象,主要有以下四种方法
1:View.getContext,返回当前 View 对象的 Context 对象,通常是当前正在展示的 Activity 对象。2:Activity.getApplicationContext,获取当前 Activity 所在的(应用)进程的 Context 对象,通常我们使用 Context 对象时,要优先考虑这个全局的进程 Context。3:ContextWrapper.getBaseContext():用来获取一个 ContextWrapper 进行装饰之前的 Context,可以使用这个方法,这个方法在实际开发中使用并不多,也不建议使用。4:Activity.this 返回当前的 Activity 实例,如果是 UI 控件需要使用 Activity 作为 Context 对象,但是默认的 Toast 实际上使用 ApplicationContext 也可以。
###getApplication()和 getApplicationContext()
上面说到获取当前 Application 对象用 getApplicationContext,不知道你有没有联想到 getApplication(),这两个方法有什么区别?相信这个问题会难倒不少开发者。
程序是不会骗人的,我们通过上面的代码,打印得出两者的内存地址都是相同的,看来它们是同一个对象。其实这个结果也很好理解,因为前面已经说过了,Application 本身就是一个 Context,所以这里获取 getApplicationContext()得到的结果就是 Application 本身的实例。那么问题来了,既然这两个方法得到的结果都是相同的,那么 Android 为什么要提供两个功能重复的方法呢?实际上这两个方法在作用域上有比较大的区别。getApplication()方法的语义性非常强,一看就知道是用来获取 Application 实例的,但是这个方法只有在 Activity 和 Service 中才能调用的到。那么也许在绝大多数情况下我们都是在 Activity 或者 Service 中使用 Application 的,但是如果在一些其它的场景,比如 BroadcastReceiver 中也想获得 Application 的实例,这时就可以借助 getApplicationContext()方法了。
publicclassMyReceiverextendsBroadcastReceiver{
@OverridepublicvoidonReceive(Contextcontext,Intentintent){Applicationmy
App=(Application)context.getApplicationContext();
}
}
###Context 引起的内存泄露
但 Context 并不能随便乱用,用的不好有可能会引起内存泄露的问题,下面就示例两种错误的引用方式。
错误的单例模式
public class Singleton {private static Singleton instance;private Context mContext;
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 掉,这样就导致了内存泄漏。
View 持有 Activity 引用
public class MainActivity extends Activity {private static Drawable mDrawable;
@Overrideprotected 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);}}
评论