Android框架设计模式——Adapter Method
Posted David-Kuper
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android框架设计模式——Adapter Method相关的知识,希望对你有一定的参考价值。
一、适配器模式介绍
适配器在平常在生活中是经常会用到的,特别是电子产品。像手机、电脑、家用电器都会用到适配器来转换电压的大小,以提供合适的电压。适配器就是把原来不符合要求的电压、电路信号转换成合适的电压和信号。简单来说,它就是一个接口转换器。
什么是适配器模式?
定义:
适配器模式把一个类的接口变换成客户端所期待的另一种接口,从而使得原本因接口不匹配(接口名、返回参数、输入参数等)而无法一起工作的两个类能够在一起工作。
分类:
在软件程序设计模式中,适配器模式分为两种:类适配器、对象适配器。
类适配器
概念:通过实现目标Target接口以及继承Adaptee(需要被适配的类)来实现接口转换。把Adaptee的接口转换成Target需要的接口。
UML图
对象适配器
概念:与类适配器也一样的目的:把Adaptee的接口转换成Target需要的接口。但是不同的是,对象适配器是使用代理关系链接到Adaptee类,即将Adaptee作为Adapter中的成员,由Adapter作为代理来实现Adaptee的功能。
UML图
适配器应用于什么场景?
1.系统需要使用现有的类,而此类的接口不符合系统的需要,即接口不兼容(最普通的适配器);
2.想要建立一个可以重复使用的类,用于将一些彼此之间关联不大的一些类,包括一些可能在将来引进的类一起工作(Binder接口链接Activity与Service,Activity和Service两者本就可以独立运行,通过Binder接口,将Service端的服务适配成Activity端能够识别的transact()方法)。
3.需要一个统一的输出接口,而输入端的类型不可预知(BaseAdapter就是将ListView、GridView等控件与多变的自定义View结合使用的适配器,输入端类型为自定义多变的类型,而输出端得到的总是View类型)
二、Android框架中的适配器模式应用
一般来说,现在都倾向于使用对象适配器,因为对象适配器能够将被适配对象的方法隐藏,而如果使用类适配器的话,由于继承的缘故,使得适配器也继承了被适配对象的方法,这样会暴露被适配对象。因此,android中也是使用的对象适配器,通过代理的方法来实现适配。
ListView+BaseAdapter+自定义View
ListView与BaseAdapter的结合是适配器使用情景的第三种情况。即:需要一个统一的输出接口,而输入端的类型不可预知。
自定义View千变万化,不同View接口又各异,因此通过适配器来提供统一的输出接口,能够使得ListView达到以【不变应万变的效果】。
下面的UML图,反应了一般的应用中Activity、ListView、BaseAdapter、自定义View之间的联系。这里的观察者模型只是我自己为了简化而使得BaseAdapter直接实现Observable(通知者接口),ListView实现Observer接口。实际上BaseAdapter与ListView还有更加深层次的继承关系,而且观察者模型是对象观察者模型(即观察者和通知者是作为类成员,通过代理实现的),而不是基于接口实现的观察者模型。
通俗UML图:
关键代码分析:
在BaseAdapter中,最重要的方法就是getView()。它是链接ListView容器和其中的ItemView的桥梁,getView(),是一个统一的接口,它固定返回的是View类型的参数,因此无论是哪种类型的自定义视图,由于它们都是View的子类,因此ListView都能够识别。这就是BaseAdapter中的适配方法,输出是不变的,而输入可以是变化的。
//getView()方法将自定义View进行适配,对各个子View进行装配,最
//后将其装入一个统一的View之中,返回给ListView。
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder = null;
if (convertView == null){
holder = new ViewHolder();
convertView = View.inflate(getBaseContext(),R.layout.activity_audiocable,null);
holder.mImageCover = convertView.findViewById(R.id.img_cover);
holder.mTextTitle = convertView.findViewById(R.id.txt_title);
convertView.setTag(holder);
}
holder = (ViewHolder) convertView.getTag();
holder.mTextTitle.setText((String) getItem(position).getTitle());
holder.mImageCover.setImageResource((String) getItem(position).getCoverRes());
return convertView;
}
class ViewHolder{
TextView mTextTitle;
ImageView mImageCover;
}
Activity+Binder+MediaPlayer
Activity+Binder+Mediaplayer中,Binder充当适配器。这是适配器使用的第二种情况:想要建立一个可以重复使用的类,用于将一些彼此之间关联不大的一些类,包括一些可能在将来引进的类一起工作。
我们启动服务的过程中,Binder就是一个适配器,它提供了transact()方法给框架调用,onTransact()给应用类别实现,而后面的onTransact()方法就是适配方法,通过onTransact方法与不同的后台服务实现对接(多媒体控制、任务下载)。
通俗UML图:
关键代码分析:
我们来看一看适配方法onTransact(),顺便提一下,如果从框架的角度来看,则onTransact()方法是一个hook方法。onTransact()方法的任务就是负责与多媒体、以及后台任务进行对接。在onTransact()方法中就调用多媒体的控制方法(任务控制方法),从而为前端提供服务。
注意:Activity只知道通过Binder调用transact()方法,其余的都是通过onTransact()进行适配。
public class mp3Player_Adapter extends Binder{
private MediaPlayer mPlayer = null;
private Context ctx;
public mp3Player_Adapter(Context cx){ ctx= cx; }
//通过onTransact()方法完成调用play()和stop(),对MP3进行播放和
//停止的控制。
@Override public boolean onTransact(int code, Parcel data, Parcel reply, int flags)throws android.os.RemoteException {
reply.writeString(data.readString()+ " mp3");
if(code == 1)
this.play();
else if(code == 2)
this.stop();
return true;
}
//play()、stop()都是不能被客户端识别的方法,需要通过onTransact()来进行适配
public void play(){
if(mPlayer != null)
return;
mPlayer = MediaPlayer.create(ctx, R.raw.test_cbr);
try {
mPlayer.start();
} catch (Exception e) {
Log.e("StartPlay", "error: " + e.getMessage(), e);
}
}
public void stop(){
if (mPlayer != null) {
mPlayer.stop();
mPlayer = null;
}
}
}
public class ac01 extends Activity implements OnClickListener {
private final int WC =
LinearLayout.LayoutParams.WRAP_CONTENT;
private final int FP = LinearLayout.LayoutParams.FILL_PARENT;
private Button btn, btn2, btn3;
public TextView tv;
private IBinder ib = null;
public void onCreate(Bundle icicle) {
//......初始化
startService(in); //启动服务
//绑定服务
bindService(in, mConnection, Context.BIND_AUTO_CREATE); }
//服务连接,用于通知Activity服务连接的情况,同时返回IBinder接口
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder ibinder) {
ib = ibinder;
}
public void onServiceDisconnected(ComponentName className) {}
};
public void onClick(View v) {
switch (v.getId()) {
case 101:
Parcel pc = Parcel.obtain();
Parcel pc_reply = Parcel.obtain();
pc.writeString("playing");
try {
//调用适配器方法操作MP3播放
ib.transact(1, pc, pc_reply, 0);
tv.setText(pc_reply.readString());
} catch (Exception e) {
e.printStackTrace();
}
break;
case 102:
pc = Parcel.obtain();
pc_reply = Parcel.obtain();
pc.writeString("stop");
try {
//调用适配器方法操作MP3停止
ib.transact(2, pc, pc_reply, 0);
tv.setText(pc_reply.readString());
} catch (Exception e) {
e.printStackTrace();
}
break;
case 103:
finish();
break;
}
}}
三、适配器模式与其他模式的配合
在安卓的组件使用过程中,每一个组件都不止是使用一种设计模式而已,它们都是结合了许多设计模式而形成的。单独的设计模式是无法设计出实用性、拓展性很好的组件。上面所举的两个Android适配器模式的应用也是包含了许多其他设计模式的,只是我们从不同的角度分析而已。下面是我对上面两个例子进行的分析,因为没有分析的很深(源代码的继承链和关系很复杂,只分析表面的几层),就列出一些模式的组合,当然还有其他的模式,这里我深究不来,也暂时不愿深究。
适配器+观察者+模板+策略+组合 = BaseAdapter+ListView+自定义View
整体UML图
模式分析(不同的视角决定)
适配器模式
从兼容的角度来看,那么BaseAdapter中的四个方法(getItemId()、getItem()、getView()、getCount())都是适配器方法,他们链接了ListView和ListView镶嵌的那些不确定的多变的自定义View对象。ListView的装配和初始化只识别这四个方法,适配器将装入的View对象按照这四个方法分别进行适配,输出符合ListView要求的接口和参数。观察者模式
从数据更新的角度来看,那么BaseAdapter与ListView之间存在着通知者/观察者的联系。BaseAdapter是一个通知者,而ListView是一个观察者。BaseAdapter中存放着ListView的数据集合,每当数据集合有更新的时候,就会调用notifyDataChanged()方法,通知ListView进行更新。同时,在ListView更新完毕之后,也可以调用相应的方法反馈给BaseAdapter。模板模式
从框架变与不变的分离的角度上来看,BaseAdapter、ListView的继承链上都存在着模板模式。即他们都有框架父类,然而框架父类定义了【不变】的部分,然后应用程序(程序员继承的类别)部分实现【变化】的部分。策略模式
单从变化的分离来看,那么BaseAdapter中还存在着策略模式的应用。getView()即是一个策略方法,对于不同的界面getView()会有不同的装配策略,但是他们的结果和参数都一样,它承担着变化的部分。我们实现不同的界面就会继承BaseAdapter实现不同的getView(),正好就是策略模式的体现。组合模式
从自定义View本身的布局来看,自定义View是一个由不同布局和控件组合而形成的,它是组合模式的经典呈现,将系统提供给我们的(或者我们自己实现的一个View)组合成一个新的绚丽的视图。从这个角度上来看,它便存在着组合模式的应用。
适配器+观察者+模板 = Service + Activity + 自定义服务
整体UML图
模式分析(不同的视角决定)
适配器模式
从适配器模式的角度分析,Binder就是一个适配器类别,它通过一个transact()接口对多媒体(MP3、MP4)、下载任务等不同的对象进行适配,客户端只需要调用transact(),接口的不兼容问题已经由Binder类别进行了适配。观察者模式
从Activity与Service通信的角度来看,Service与Activity还存在着通知者/观察者关系。Service是通知者,Activity是观察者。这点可以通过绑定服务体现,我们调用了bindService()后,当服务绑定了之后,Service会调用onBind方法返回一个IBinder接口,然后通过调用ServiceConnection中的onServiceConnect()方法返回一个IBinder接口给Activity客户端。完成Activity与Service的通信。模板模式
从框架中的变与不变分离的角度来看,Service和Activity中各自都存在着模板模式。Service中transact()是模板方法,而onTransact()是hook(卡隼)方法;Activity中callApplicationOnCreate是模板方法,而onCreate()是hook(卡隼)方法。我们程序实现的部分就是卡隼方法,而程序框架部分就是模板方法。工厂模式
从对象的产生角度来看,Service与Activity中也各自都存在着工厂模式的应用。对于Activity来说,onCreate()就是一个工厂方法,它创建了系统运行需要的对象;对于Service来说,onCreate()也是一个工厂方法,它产生了Binder对象,然后再返回给客户端Activity。
四、总结
适配器模式是一种在系统已经存在且比较大的情况下,突然增加某些模块之间的联系,而原有的接口又不符合的情况下才使用的。在Android中,适配器模式的使用是每个Android工程师都再熟悉不过的了,不过在使用适配器的过程中,我们需要明白适配器模式与其他模式混合使用的情况,同时也需要明白适配器存在的原因,什么时候需要用适配器,什么时候不需要使用适配器。而且,同样的一个适配器模式的应用中,可能会存在其他不同的设计模式思想,设计模式之间并不是独立的,而是相互渗透相互依赖的。
以上是关于Android框架设计模式——Adapter Method的主要内容,如果未能解决你的问题,请参考以下文章
Android之线程安全的单例模式,Adapter注意事项之引用传值
android开发之路08(ListView&Adapter)
android中Adapter有啥作用?常见的Adapter都有哪些?