体系结构与设计模式-适配器模式在Android开发中的应用

我们之前在Java语言的学习中会用到设计模式中的适配器模式,但是在Android开发中具体什么地方需要应用适配器模式呢?在查阅了一些资料后整理出以下内容:


适配器模式(Adapter模式)

定义

将一个类的接口转换成客户希望的另外一个接口。
适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

使用场景

主要解决在软件系统中,常常要将一些”现存的对象”放到新的环境中,而新环境要求的接口是现对象不能满足的。
何时使用:
1、系统需要使用现有的类,而此类的接口不符合系统的需要。
2、想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作,这些源类不一定有一致的接口。
3、通过接口转换,将一个类插入另一个类系中。

简单实例

要求机器人可以模拟各种动物行为,在机器人中定义了一系列方法,如机器人叫喊方法cry()、机器人移动方法move()等。如果希望在不修改已有代码的基础上使得机器人能够像狗一样汪汪叫,像狗一样快跑,或者像鸟一样叽叽叫,像鸟一样快快飞,使用适配器模式进行系统设计。现修改实例仿生机器人,使得机器人可以像鸟一样叫,并像狗一样的跑,请绘制类图并编程实现。

UML类图

体系结构与设计模式-适配器模式在Android开发中的应用

实现方式

//Bird类
public class Bird
{
    public void tweedle()
    {
        System.out.println("像鸟一样叫!");
    }

    public void fly()
    {
        System.out.println("像鸟一样飞!");
    }
}
//BirdAdapter类
public class BirdAdapter extends Bird implements Robot
{
    public void cry()
    {
        System.out.print("机器人模仿:");
        super.tweedle();
    }

    public void move()
    {
        System.out.print("机器人模仿:");
        super.fly();
    }
}
//Dog类
public class Dog
{
    public void wang()
    {
        System.out.println("像狗一样叫!");
    }

    public void run()
    {
        System.out.println("像狗一样跑!");
    }
}
//DogAdapter类
public class DogAdapter extends Dog implements Robot
{
    public void cry()
    {
        System.out.print("机器人模仿:");
        super.wang();
    }

    public void move()
    {
        System.out.print("机器人模仿:");
        super.run();
    }
}
//DBAdapter类
public class DBAdapter implements Robot{

    private DogAdapter dogAdapter;
    private BirdAdapter birdAdapter;

    public DBAdapter() {
        dogAdapter = new DogAdapter();
        birdAdapter = new BirdAdapter();
    }

    public void cry() {
        // TODO Auto-generated method stub
        dogAdapter.move();
    }

    public void move() {
        // TODO Auto-generated method stub
        birdAdapter.cry();
    }
}
//Robot接口
public interface Robot
{
    public void cry();
    public void move();
}

//XMLUtil类
import javax.xml.parsers.*;
import org.w3c.dom.*;
import org.xml.sax.SAXException;
import java.io.*;
public class XMLUtil
{
//该方法用于从XML配置文件中提取具体类类名,并返回一个实例对象
    public static Object getBean()
    {
        try
        {
            //创建文档对象
            DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance();
            DocumentBuilder builder = dFactory.newDocumentBuilder();
            Document doc;                           
            doc = builder.parse(new File("src/config.xml")); 

            //获取包含类名的文本节点
            NodeList nl = doc.getElementsByTagName("className");
            Node classNode=nl.item(0).getFirstChild();
            String cName=classNode.getNodeValue();

            //通过类名生成实例对象并将其返回
            Class c=Class.forName(cName);
            Object obj=c.newInstance();
            return obj;
           } 

            catch(Exception e)
            {
                e.printStackTrace();
                return null;
            }

        }
}
//Client类
public class Client
{
    public static void main(String args[])
    {
        Robot robot=(Robot)XMLUtil.getBean();
        robot.cry();
        robot.move();
    }
}

运行结果

体系结构与设计模式-适配器模式在Android开发中的应用


Android开发中的实例

最常见的ListView、GridView、RecyclerView等的Adapter

实现

3大角色
1、目标角色,也就是所期待得到的接口。
2、需要适配的接口
3、适配器角色,是适配器模式的核心。适配器将源接口转换成目标接口

实现的要点

适配器模式分2种,类适配器模式和对象适配器模式。2种模式的区别在于实现适配的方法不同,类适配器模式通过继承需要适配的接口,而对象适配器模式则是通过组合的形式实现接口兼容的效果。因而对象适配器模式更加灵活也使用得更多,我们这里主要就介绍对象适配器模式。

我们在使用ListView时,每一项的布局和数据都不一样,但是最后输出都可以看作是一个View,这就对应了上面的适配器模式应用场景的第三条:需要一个统一的输出接口,而输入端的接口不可预知。下面我们来看看ListView中的适配器模式。
首先我们来看看一般我们的Adapter类的结构

    class Adapter extends BaseAdapter {
        private List<String> mDatas;

public Adapter(List<String> datas) {
        mDatas = datas;
    }

   @Override
    public int getCount() {
        return mDatas.size();
    }

   @Override
    public long getItemId(int position) { return position; }

   @Override
    public Object getItem(int position) { return mDatas.get(position);}

   @Override
    public View getView(int position, View convertView, ViewGroup parent) {

   if (convertView == null) {
            //初始化View
        }
   //初始化数据

   return convertView;
    }
}

可以看出Adapter里面的接口主要是getCount()返回子View的数量,以及getView()返回我们填充好数据的View,ListView则通过这些接口来执行具体的布局、缓存等工作。下面我们来简单看看ListView的实现。
首先这些getCount()等接口都在一个接口类Adapter里

public interface Adapter {
    //省略其他的接口
    int getCount(); 
    Object getItem(int position);
    long getItemId(int position);
    View getView(int position, View convertView, ViewGroup parent);
    //省略其他的接口
}

中间加了一个过渡的接口ListAdapter

public interface ListAdapter extends Adapter {
    //接口省略
}

我们在编写我们自己的Adapter时都会继承一个BaseAdapter,我们来看看BaseAdapter

public abstract class BaseAdapter implements ListAdapter, SpinnerAdapter {
    //BaseAdapter里面实现了ListAdapter的接口以及部分Adapter中的接口
    //而像getCount()以及getView()这些接口则需要我们自己去实现
}

ListView的父类AbsListView中有ListAdapter接口,通过这个接口来调用getCount()等方法获取View的数量等

public abstract class AbsListView extends AdapterView<ListAdapter> implements TextWatcher,
        ViewTreeObserver.OnGlobalLayoutListener, Filter.FilterListener,
        ViewTreeObserver.OnTouchModeChangeListener,
        RemoteViewsAdapter.RemoteAdapterConnectionCallback {

    /**
     * The adapter containing the data to be displayed by this view
     */
    ListAdapter mAdapter;

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();

        final ViewTreeObserver treeObserver = getViewTreeObserver();
        treeObserver.addOnTouchModeChangeListener(this);
        if (mTextFilterEnabled && mPopup != null && !mGlobalLayoutListenerAddedFilter) {
            treeObserver.addOnGlobalLayoutListener(this);
        }

        if (mAdapter != null && mDataSetObserver == null) {
            mDataSetObserver = new AdapterDataSetObserver();
            mAdapter.registerDataSetObserver(mDataSetObserver);

            // Data may have changed while we were detached. Refresh.
            mDataChanged = true;
            mOldItemCount = mItemCount;

            //通过getCount()获取View元素的个数
            mItemCount = mAdapter.getCount();
        }
    }
}

从上面我们可以看出,AbsListView是一个抽象类,它里面封装了一些固定的逻辑,如Adapter模式的应用逻辑、布局的复用逻辑和布局子元素逻辑等。而具体的实现则是在子类ListView中。下面我们来看看ListView中是怎么处理每一个子元素View的。

@Override
protected void layoutChildren() {

    //省略其他代码
    case LAYOUT_FORCE_BOTTOM:
        sel = fillUp(mItemCount - 1, childrenBottom);
        adjustViewsUpOrDown();
        break;
    case LAYOUT_FORCE_TOP:
        mFirstPosition = 0;
        sel = fillFromTop(childrenTop);
        adjustViewsUpOrDown();
        break;

    //省略其他代码
}
在ListView中会覆写AbsListView中的layoutChildren()函数,在layoutChildren()中会根据不同的情况进行布局,比如从上到下或者是从下往上。下面我们看看具体的布局方法fillUp方法。
private View fillUp(int pos, int nextBottom) {
    //省略其他代码

    while (nextBottom > end && pos >= 0) {
        // is this the selected item?
        boolean selected = pos == mSelectedPosition;
        View child = makeAndAddView(pos, nextBottom, false, mListPadding.left, selected);
        nextBottom = child.getTop() - mDividerHeight;
        if (selected) {
            selectedView = child;
        }
        pos--;
    }

    mFirstPosition = pos + 1;
    setVisibleRangeHint(mFirstPosition, mFirstPosition + getChildCount() - 1);
    return selectedView;
}

这里我们看到fillUp方法里面又会通过makeAndAddView()方法来获取View,下面我们来看看makeAndAddView()方法的实现

private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
            boolean selected) {
    if (!mDataChanged) {
        // Try to use an existing view for this position.
        final View activeView = mRecycler.getActiveView(position);
        if (activeView != null) {
            // Found it. We're reusing an existing child, so it just needs
            // to be positioned like a scrap view.
            setupChild(activeView, position, y, flow, childrenLeft, selected, true);
            return activeView;
        }
    }

    // Make a new view for this position, or convert an unused view if
    // possible.
    final View child = obtainView(position, mIsScrap);

    // This needs to be positioned and measured.
    setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);

    return child;
}

不知道大家看到这里想到了什么?
makeAndAddView()方法里面就出现了缓存机制了,这是提升ListView加载效率的关键方法。我们看到,在获取子View时会先从缓存里面找,也就是会从mRecycler中找,mRecycler是AbsListView中的一个用于缓存的RecycleBin类,来,我们看看缓存的实现

class RecycleBin {
    private View[] mActiveViews = new View[0];

    /**
     * Get the view corresponding to the specified position. The view will be removed from
     * mActiveViews if it is found.
     *
     * @param position The position to look up in mActiveViews
     * @return The view if it is found, null otherwise
     */
    View getActiveView(int position) {
        int index = position - mFirstActivePosition;
        final View[] activeViews = mActiveViews;
        if (index >=0 && index < activeViews.length) {
            final View match = activeViews[index];
            activeViews[index] = null;
            return match;
        }
        return null;
    }
}

由上可见,缓存的View保存在一个View数组里面,然后我们来看看如果没有找到缓存的View,ListView是怎么获取子View的,也就是上面的obtainView()方法。需要注意的是obtainView()方法是在AbsListView里面。

View obtainView(int position, boolean[] outMetadata) {

    //省略其他代码

    final View scrapView = mRecycler.getScrapView(position);
    final View child = mAdapter.getView(position, scrapView, this);
    if (scrapView != null) {
        if (child != scrapView) {
            // Failed to re-bind the data, return scrap to the heap.
            mRecycler.addScrapView(scrapView, position);
        } else if (child.isTemporarilyDetached()) {
            outMetadata[0] = true;

            // Finish the temporary detach started in addScrapView().
            child.dispatchFinishTemporaryDetach();
        }
    }

    //省略其他代码

    return child;
}

可以看到没有缓存的View直接就是从我们编写的Adapter的getView()方法里面获取。
以上我们简单看了ListView中适配器模式的应用,从中我们可以看出ListView通过引入Adapter适配器类把那些多变的布局和数据交给用户处理,然后通过适配器中的接口获取需要的数据来完成自己的功能,从而达到了很好的灵活性。这里面最重要的接口莫过于getView()接口了,该接口返回一个View对象,而千变万化的UI视图都是View的子类,通过这样一种处理就将子View的变化隔离了,保证了AbsListView类族的高度可定制化。
当然这里的Adapter并不是经典的适配器模式,却是对象适配器模式的优秀示例,有兴趣的小伙伴可以好好研究一下。需要注意的是文中的源码是Android7.1的,不同的版本可能稍有变化。

参考资料:
《疯狂Android讲义》
《设计模式》(刘伟主编 清华大学出版社)
《Android源码设计模式解析与实战》
本文部分来源于简书作者xxq2dream链接:https://www.jianshu.com/p/b4ae4fe4bcae

作者:tommy_shensanhan
原文链接:

;