Android源码适配器模式---Activity类结构

其实适配器模式在Android源码中非常多,而从整体的源码角度上来看Activity的结构就是一种适配器模式。从这个角度上面看Activity,对Activity和应用层框架会有更加深入的理解。

适配器模式

意图

将一个接口转换为用户需要的另外一个接口,适配器模式使得原本由于接口不兼容不能一起工作的那些类可以一起工作。

UML图

适配器模式有两种模式,UML分别如下:

http://xxxzhi.github.io/images/adapter1.png

http://xxxzhi.github.io/images/adapter2.png

第一种是直接继承已经有的接口适配目标接口,而第二种是引用已有的接口适配目标接口。

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
interface Target{
void request();
}
class Adaptee{
public void specialRequest(){
System.out.println("special from adaptee");
}
}
class Adapter extends Adaptee implements Target{
public void request(){
//do something to implements request
specialRequest();
}
}
public static final void main(String args[]){
Target target = new Adapter();
target.request();
}

上面是第一种适配器模式的简单代码示例,通过继承已有的类来适配,另外一种组合的方式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
interface Target{
void request();
}
class Adaptee{
public void specialRequest(){
System.out.println("special from adaptee");
}
}
class Adapter implements Target{
private Adaptee adaptee ;
public void Adapter(Adaptee adaptee){
this.adaptee = adaptee;
}
public void request(){
//do something to implements request
adaptee.specialRequest();
}
}
public static final void main(String args[]){
Target target = new Adapter(new Adaptee());
target.request();
}

两种方式类适配器和对象适配器。

Activity与适配器模式

Activity是Android的核心组件,它是负责应用UI的组件,可以说是Android四大组件中最重要,使用最多,最复杂的组建。它的源码也相当地庞大。从适配器的角度上来看,Activity适配了多个接口,先看一下它的类结构图:
此处输入图片的描述

将Activity看成是适配器模式初看可能会有点牵强。但是ContextThemeWrapper是表示主题的环境类,Context可以翻译为应用环境,但是对于需要显示UI的一个应用组建除了应用环境外,还需要适应其他的内容信息,比如Window,比如KeyEvent等等。

拿窗口系统举例。Android中有Window管理系统,但是窗口系统需要与的Window.Callback接口,但是现在是有了Context,组建需要Window.Callback接口,这样创建Activity(这个是Adapter)实现Window.Callback接口,并且继承ContextThemeWrapper,将ContextWrapper与Window.Callback协作,让Context与Window一起工作。Window.Callback只是Activity适配的其中一个接口,下面分别介绍类结构的每一个部分。

ContextThemeWrapper

这是一个包含主题的Context装饰器,本身ContextWrapper是一个装饰器模式,在Android中,四大组建都是ContextWrapper的子类,四大组建都需要应用环境。关于这部分可以看我这篇文章Android源码装饰模式—ContextWrapper。需要理解的是Context是一个应用环境类型,Context包含了各种跟应用环境相关的信息,可以用来与应用系统打交道的。

Window.Callback, Window.OnWindowDismissedCallback

Window.Callback 这个接口包含了很多接口函数,上面的UML图中只包含了部分接口,全部的接口类可见下面的Outline截图:

此处输入图片的描述

这个接口是窗口的回调接口,主要分为屏幕事件触发,按键事件触发,Panel相关的View创建与Prepare,Menu的回调,Window的变化回调,SearchRequest的回调,以及ActionMode的回调。

Window.OnWindowDismissedCallback是一个hide类,是无法通过API调用的,是当窗口消失(Window系统移除)的时候的回调接口。Activity的实现也很简单,直接finish掉自己。

1
2
3
4
5
6
7
8
/**
* Called when the main window associated with the activity has been dismissed.
* @hide
*/
@Override
public void onWindowDismissed() {
finish();
}

Callback,OnWindowDismissedCallback是Window与Activity交互的回调接口。

初始部分代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//Activity.java
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor) {
attachBaseContext(context);
mFragments.attachHost(null /*parent*/);
mWindow = new PhoneWindow(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
...

但实际上一个Window并不是只和一个Activity关联,而是一个Window和一个Callback关联,Activity也是Context,Android中Dialog里面也包含了Window,Dialog也实现了Callback接口。一个应用环境中(Context)可能包含多个Window,也就会有多个Callback,只是Activity这种应用环境本身就实现了Callback接口。

KeyEvent.Callback

对应着Key事件的回调接口,当按下按键的时候,会回调该接口。主要是为了适配输入系统。

ComponentCallbacks2

它是ComponentCallbacks的子接口,CompoentCallbacks包含下面两个接口:

1
2
3
void onConfigurationChanged(Configuration newConfig);
void onLowMemory();

ComponentCallbacks2新增了onTrimMemory接口。
ComponentCallbacks是专门为Android组件使用的回调接口,Android组件都会实现该接口(目前变成了ConponentCallbacks2),当配置信息变化,内存变化的时候,这些接口会被调用。调用这些接口的是ActivityThread(消息循环中,收到变化消息时),ViewRootImpl(在Window有变化的时候,ViewRootImpl负责与WindowManagerService通信)等。该接口是为了适配系统信息管理部分。

这里有两个跟内存相关的接口,这其实是为帮应用应对Android内存满负荷,提醒应用程序做一些释放内存处理,如果占用内存过大,应用将会更容易被杀死。具体可以看LowMemoryKiller的介绍。

OnCreateContextMenuListener

Android上下文菜单: 当给一个View注册了上下文菜单后,对这个View长按2秒,会弹出一个浮动的菜单。OnCreateContextMenuListener 它只有一个接口函数:

1
2
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
}

当View的Context menu被创建的时候,该接口的会被调用,用于获取Menu(作为实现改接口的Activity来讲,是设置Menu)。在Activity中,与这个接口函数对应的函数是onContextItemSelected,而该函数是继承自Window.Callback接口的onMenuItemSelected函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public boolean onMenuItemSelected(int featureId, MenuItem item) {
CharSequence titleCondensed = item.getTitleCondensed();
switch (featureId) {
case Window.FEATURE_OPTIONS_PANEL:
if(titleCondensed != null) {
EventLog.writeEvent(50000, 0, titleCondensed.toString());
}
if (onOptionsItemSelected(item)) {
return true;
}
...
case Window.FEATURE_CONTEXT_MENU:
if(titleCondensed != null) {
EventLog.writeEvent(50000, 1, titleCondensed.toString());
}
if (onContextItemSelected(item)) { //这里
return true;
}
return mFragments.dispatchContextItemSelected(item);
default:
return false;
}
}

我们平时监听普通的Menu的函数onOptionsItemSelected也是由onMenuItemSelected调用的。

另外一边View中显示ContextMenu的函数是showContextMenu:

1
2
3
public boolean showContextMenu() {
return getParent().showContextMenuForChild(this);
}

ViewGroup的showContextMenuForChild为:

1
2
3
public boolean showContextMenuForChild(View originalView) {
return mParent != null && mParent.showContextMenuForChild(originalView);
}

getParent()最终会到DecorView,DecorView中创建了ContextMenu。然后调用View的createContextMenu方法,最终使用mOnCreateContextMenuListener获取Menu:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public void createContextMenu(ContextMenu menu) {
ContextMenuInfo menuInfo = getContextMenuInfo();
// Sets the current menu info so all items added to menu will have
// my extra info set.
((MenuBuilder)menu).setCurrentMenuInfo(menuInfo);
onCreateContextMenu(menu);
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnCreateContextMenuListener != null) {
li.mOnCreateContextMenuListener.onCreateContextMenu(menu, this, menuInfo);
}
// Clear the extra information so subsequent items that aren't mine don't
// have my extra info.
((MenuBuilder)menu).setCurrentMenuInfo(null);
if (mParent != null) {
mParent.createContextMenu(menu);
}
}

DecorView在PhoneWindow中,Menu其实会由Window统一管理,响应Item的点击事件的接口是一致的(Window.Callback.onMenuItemSelected),另外ContextMenu实际上显示出来的就是一个Dialog。但由于ContextMenu是跟View对应的,所以有了OnCreateContextMenuListener接口,它是用于当View需要创建ContextMenu的时候,方便指定ContextMenu的内容。

LayoutInflater.Factory2

这个接口只有一个接口函数:

1
public View onCreateView(View parent, String name, Context context, AttributeSet attrs);

它继承自Factory:

1
2
3
4
5
6
7
8
9
10
11
12
13
public interface Factory {
/**
* Hook you can supply that is called when inflating from a LayoutInflater.
* You can use this to customize the tag names available in your XML
* layout files.
*
* <p>
* Note that it is good practice to prefix these custom names with your
* package (i.e., com.coolcompany.apps) to avoid conflicts with system
* names.
*/
public View onCreateView(String name, Context context, AttributeSet attrs);
}

用于跟LayoutInflater系统交互,为了适配LayoutInflater系统。实现改接口,可以在Inflater的时候,解析XML中自定义的Tag。该接口为LayoutInflater调用,而LayoutInflater的实现为PhoneLayoutInflater。对于Window和LayoutInflater结构可以看这篇Android源码抽象工厂—IPolicy

除了Activity外,Application和Service都实现了ComponnentCallbacks接口,继承了ContextWrapper,其实都可以用类适配器模式看待。

设计思考

本身应用组件都应该是一种应用环境(Context),但是又需要满足Window等系统的回调需求,我们平时可能直接单独实现Window.Callback接口,但是将Activity实现Window.Callback接口,那么Activity会更加具有整体性,不过设计意图在这里思考过多感觉有点太牵强。

总结

从Android应用层源码整理来看,Activity的类结构完全可以看成是一种适配器模式,在基于应用环境(Context)的情况下,去满足LayoutInflater系统(LayoutInflater.Factory2),Window系统(Window.Callback,Window.OnWindowDismissedCallback),输入系统(KeyEvent.Callback)的接口需求,另外ComponnentCallbacks更是ActivityThread和ViewRootImpl需要的接口。通过适配器模式来看Activity,对于Activity,对于Activity与其他部分的交互,对于应用层框架会有更好的理解。另外再有装饰模式看Context,对于整个应用层结构会更清晰。