android中的设计模式

整理自 https://github.com/simple-android-framework-exchange/android_design_patterns_analysis

一、单例模式

1. 模式介绍

模式的定义

确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。

模式的使用场景

确保某个类有且只有一个对象的场景,例如创建一个对象需要消耗的资源过多,如要访问 IO 和数据库等资源。

2. UML类图

url

角色介绍

  • Client : 高层客户端。
  • Singleton : 单例类。

3. 模式的简单实现

简单实现的介绍

单例模式是设计模式中最简单的,只有一个单例类,没有其他的层次结构与抽象。该模式需要确保该类只能生成一个对象,通常是该类需要消耗太多的资源或者没有没有多个实例的理由。例如一个公司只有一个CEO、一台电脑通常只有一个显示器等。下面我们以公司里的CEO为例来简单演示一下,一个公司可以有几个VP,无数个员工,但是CEO只有一个,请看下面示例。

实现源码

package com.dp.example.singleton;
/**
 * 人的基类
 * @author mrsimple
 *
 */
public abstract class Person {
    public abstract void talk() ;
}

// 普通员工
public class Staff extends Person {

    @Override
    public void talk() {

    }

}

// 副总裁
public class VP extends Person {

    @Override
    public void talk() {

    }
}

// CEO, 单例模式
public class CEO extends Person {

    private static final CEO mCeo = new CEO();

    private CEO() {
    }

    public static CEO getCeo() {
        return mCeo;
    }

    @Override
    public void talk() {
        System.out.println("CEO发表讲话");
    }

}

// 公司类
import java.util.ArrayList;
import java.util.List;

public class Company {
    private List<Person> allPersons = new ArrayList<Person>();

    public void addStaff(Person per) {
        allPersons.add(per);
    }

    public void showAllStaffs() {
        for (Person per : allPersons) {
            System.out.println("Obj : " + per.toString());
        }
    }
}

// test
public class Test {
    public static void main(String[] args) {
        Company cp = new Company() ;
        Person ceo1 = CEO.getCeo() ;
        Person ceo2 = CEO.getCeo() ;
        cp.addStaff(ceo1);
        cp.addStaff(ceo2);

        Person vp1 = new VP() ;
        Person vp2 = new VP() ;

        Person staff1 = new Staff() ;
        Person staff2 = new Staff() ;
        Person staff3 = new Staff() ;

        cp.addStaff(vp1);
        cp.addStaff(vp2);
        cp.addStaff(staff1);
        cp.addStaff(staff2);
        cp.addStaff(staff3);

        cp.showAllStaffs();
    }
}

输出结果如下 : result

可以看到, CEO两次输出的CEO对象的文字描述都是一样的,而VP、Staff类的对象都是不同的。即CEO是唯一实例,而其他类型都是不同的实例。这个实现的核心在于将CEO类的构造方法私有化,使得外部程序不能通过构造函数来构造CEO对象,而CEO类通过一个静态方法返回一个唯一的对象。

单例模式的其他实现

package com.dp.example.singleton;

public class Singleton {
    private static Singleton mInstance = null;

    private Singleton() {

    }

    public void doSomething() {
        System.out.println("do sth.");
    }

    /**
     * 方式二、double-check, 避免并发时创建了多个实例, 该方式不能完全避免并发带来的破坏.
     * 
     * @return
     */
    public static Singleton getInstance() {
        if (mInstance == null) {
            synchronized (Singleton.class) {
                if (mInstance == null) {
                    mInstance = new Singleton();
                }
            }
        }
        return mInstance;
    }

    /**
     * 方式三 : 在第一次加载SingletonHolder时初始化一次mOnlyInstance对象, 保证唯一性, 也延迟了单例的实例化,
     * 如果该单例比较耗资源可以使用这种模式.
     * 
     * @return
     */
    public static Singleton getInstanceFromHolder() {
        return SingletonHolder.mOnlyInstance;
    }

    /**
     * 静态内部类
     * 
     * @author mrsimple
     *
     */
    private static class SingletonHolder {
        private static final Singleton mOnlyInstance = new Singleton();
    }

    /**
     *  方式四 : 枚举单例, 线程安全
     * @author mrsimple
     *
     */
    enum SingletonEnum {
        INSTANCE;
        public void doSomething() {
            System.out.println("do sth.");
        }
    }

    /**
     * 方式五 : 注册到容器, 根据key获取对象.一般都会有多种相同属性类型的对象会注册到一个map中
     * instance容器
     */
    private static Map<string singleton=""> objMap = new HashMap<string singleton="">();
    /**
     * 注册对象到map中
     * @param key
     * @param instance
     */
    public static void registerService(String key, Singleton instance) {
        if (!objMap.containsKey(key) ) {
            objMap.put(key, instance) ;
        }
    }

    /**
     * 根据key获取对象
     * @param key
     * @return
     */
    public static Singleton getService(String key) {
        return objMap.get(key) ;
    }

}

不管以哪种形式实现单例模式,它们的核心原理都是将构造函数私有化,并且通过静态方法获取一个唯一的实例,在这个获取的过程中你必须保证线程安全、反序列化导致重新生成实例对象等问题,该模式简单,但使用率较高。

Android源码中的模式实现

在Android系统中,我们经常会通过Context获取系统级别的服务,比如WindowsManagerService, ActivityManagerService等,更常用的是一个叫LayoutInflater的类。这些服务会在合适的时候以单例的形式注册在系统中,在我们需要的时候就通过Context的getSystemService(String name)获取。我们以LayoutInflater为例来说明, 平时我们使用LayoutInflater较为常见的地方是在ListView的getView方法中。

@Override
public View getView(int position, View convertView, ViewGroup parent)   
    View itemView = null;
    if (convertView == null) {
        itemView = LayoutInflater.from(mContext).inflate(mLayoutId, null);
        // 其他代码
    } else {
        itemView = convertView;
    }
    // 获取Holder
    // 初始化每项的数据
    return itemView;
}

通常我们使用LayoutInflater.from(Context)来获取LayoutInflater服务, 下面我们看看LayoutInflater.from(Context)的实现。

    /**
     * Obtains the LayoutInflater from the given context.
     */
    public static LayoutInflater from(Context context) {
        LayoutInflater LayoutInflater =
                (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        if (LayoutInflater == null) {
            throw new AssertionError("LayoutInflater not found.");
        }
        return LayoutInflater;
    }

可以看到from(Context)函数内部调用的是Context类的getSystemService(String key)方法,我们跟踪到Context类看到, 该类是抽象类。

public abstract class Context {
    // 省略
}

使用的getView中使用的Context对象的具体实现类是什么呢 ?其实在Application,Activity, Service,中都会存在一个Context对象,即Context的总个数为Activity个数 + Service个数 + 1。而ListView通常都是显示在Activity中,那么我们就以Activity中的Context来分析。

我们知道,一个Activity的入口是ActivityThread的main函数。在该main函数中创建一个新的ActivityThread对象,并且启动消息循环(UI线程),创建新的Activity、新的Context对象,然后将该Context对象传递给Activity。下面我们看看ActivityThread源码。

    public static void main(String[] args) {
        SamplingProfilerIntegration.start();

        // CloseGuard defaults to true and can be quite spammy.  We
        // disable it here, but selectively enable it later (via
        // StrictMode) on debug builds, but using DropBox, not logs.
        CloseGuard.setEnabled(false);

        Environment.initForCurrentUser();

        // Set the reporter for event logging in libcore
        EventLogger.setReporter(new EventLoggingReporter());
        Process.setArgV0("<pre-initialized>");
        // 主线程消息循环
        Looper.prepareMainLooper();
        // 创建ActivityThread对象
        ActivityThread thread = new ActivityThread();
        thread.attach(false);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        AsyncTask.init();

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

    private void attach(boolean system) {
        sThreadLocal.set(this);
        mSystemThread = system;
        if (!system) {
            ViewRootImpl.addFirstDrawHandler(new Runnable() {
                public void run() {
                    ensureJitEnabled();
                }
            });
            android.ddm.DdmHandleAppName.setAppName("<pre-initialized>",
                                                    UserHandle.myUserId());
            RuntimeInit.setApplicationObject(mAppThread.asBinder());
            IActivityManager mgr = ActivityManagerNative.getDefault();
            try {
                mgr.attachApplication(mAppThread);
            } catch (RemoteException ex) {
                // Ignore
            }
        } else {
               // 省略
        }
}

在main方法中,我们创建一个ActivityThread对象后,调用了其attach函数,并且参数为false. 在attach函数中, 参数为false的情况下, 会通过Binder机制与ActivityManagerService通信,并且最终调用handleLaunchActivity函数 ( 具体分析请参考老罗的博客 : Activity的启动流程),我们看看该函数的实现 。

    private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        // 代码省略
        Activity a = performLaunchActivity(r, customIntent);
        // 代码省略
    }

     private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        // System.out.println("##### [" + System.currentTimeMillis() + "] ActivityThread.performLaunchActivity(" + r + ")");
        // 代码省略
        Activity activity = null;
        try {
            java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
            activity = mInstrumentation.newActivity(         // 1 : 创建Activity
                    cl, component.getClassName(), r.intent);
         // 代码省略
        } catch (Exception e) {
         // 省略
        }

        try {
            Application app = r.packageInfo.makeApplication(false, mInstrumentation);

            if (activity != null) {
                Context appContext = createBaseContextForActivity(r, activity); // 2 : 获取Context对象
                CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
                Configuration config = new Configuration(mCompatConfiguration);
                // 3: 将appContext等对象attach到activity中
                activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config);

                // 代码省略
                // 4 : 调用Activity的onCreate方法
                mInstrumentation.callActivityOnCreate(activity, r.state);
                // 代码省略
        } catch (SuperNotCalledException e) {
            throw e;
        } catch (Exception e) {
            // 代码省略
        }

        return activity;
    }


    private Context createBaseContextForActivity(ActivityClientRecord r,
            final Activity activity) {
        // 5 : 创建Context对象, 可以看到实现类是ContextImpl
        ContextImpl appContext = new ContextImpl();           appContext.init(r.packageInfo, r.token, this);
        appContext.setOuterContext(activity);

        // 代码省略
        return baseContext;
    }

通过上面1~5的代码分析可以知道, Context的实现类为ComtextImpl类。我们继续跟踪到ContextImpl类。

class ContextImpl extends Context {

    // 代码省略
    /**
     * Override this class when the system service constructor needs a
     * ContextImpl.  Else, use StaticServiceFetcher below.
     */
     static class ServiceFetcher {
        int mContextCacheIndex = -1;

        /**
         * Main entrypoint; only override if you don't need caching.
         */
        public Object getService(ContextImpl ctx) {
            ArrayList<Object> cache = ctx.mServiceCache;
            Object service;
            synchronized (cache) {
                if (cache.size() == 0) {
                    for (int i = 0; i < sNextPerContextServiceCacheIndex; i++) {
                        cache.add(null);
                    }
                } else {
                    service = cache.get(mContextCacheIndex);
                    if (service != null) {
                        return service;
                    }
                }
                service = createService(ctx);
                cache.set(mContextCacheIndex, service);
                return service;
            }
        }

        /**
         * Override this to create a new per-Context instance of the
         * service.  getService() will handle locking and caching.
         */
        public Object createService(ContextImpl ctx) {
            throw new RuntimeException("Not implemented");
        }
    }

    // 1 : service容器
    private static final HashMap<String, ServiceFetcher> SYSTEM_SERVICE_MAP =
            new HashMap<String, ServiceFetcher>();

    private static int sNextPerContextServiceCacheIndex = 0;
    // 2: 注册服务器
    private static void registerService(String serviceName, ServiceFetcher fetcher) {
        if (!(fetcher instanceof StaticServiceFetcher)) {
            fetcher.mContextCacheIndex = sNextPerContextServiceCacheIndex++;
        }
        SYSTEM_SERVICE_MAP.put(serviceName, fetcher);
    }


    // 3: 静态语句块, 第一次加载该类时执行 ( 只执行一次, 保证实例的唯一性. )
    static {
        //  代码省略
        // 注册Activity Servicer
        registerService(ACTIVITY_SERVICE, new ServiceFetcher() {
                public Object createService(ContextImpl ctx) {
                    return new ActivityManager(ctx.getOuterContext(), ctx.mMainThread.getHandler());
                }});

        // 注册LayoutInflater service
        registerService(LAYOUT_INFLATER_SERVICE, new ServiceFetcher() {
                public Object createService(ContextImpl ctx) {
                    return PolicyManager.makeNewLayoutInflater(ctx.getOuterContext());
                }});
        // 代码省略
    }

    // 4: 根据key获取对应的服务, 
    @Override
    public Object getSystemService(String name) {
        // 根据name来获取服务
        ServiceFetcher fetcher = SYSTEM_SERVICE_MAP.get(name);
        return fetcher == null ? null : fetcher.getService(this);
    }

    // 代码省略
}

从ContextImpl类的部分代码中可以看到,在虚拟机第一次加载该类时会注册各种服务,其中就包含了LayoutInflater Service, 将这些服务以键值对的形式存储在一个HashMap中,用户使用时只需要根据key来获取到对应的服务,从而达到单例的效果。这种模式就是上文中提到的“单例模式的实现方式5”。系统核心服务以单例形式存在,减少了资源消耗。

4. 杂谈

优点与缺点

优点
  • 由于单例模式在内存中只有一个实例,减少了内存开支,特别是一个对象需要频繁地创建、销毁时,而且创建或销毁时性能又无法优化,单例模式的优势就非常明显。
  • 由于单例模式只生成一个实例,所以减少了系统的性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后用永久驻留内存的方式来解决;
  • 单例模式可以避免对资源的多重占用,例如一个写文件动作,由于只有一个实例存在内存中,避免对同一个资源文件的同时写操作。
  • 单例模式可以在系统设置全局的访问点,优化和共享资源访问,例如可以设计一个单例类,负责所有数据表的映射处理。
缺点
  • 单例模式一般没有接口,扩展很困难,若要扩展,除了修改代码基本上没有第二种途径可以实现。


Android设计模式源码解析之适配器(Adapter)模式

本文为 Android 设计模式源码解析 中 适配器模式 分析
Android系统版本: 2.3
分析者:Mr.Simple,分析状态:完成,校对者:Mr.Simple,校对状态:完成

1. 模式介绍

模式的定义

适配器模式把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。

使用场景

  用电源接口做例子,笔记本电脑的电源一般都是接受5V的电压,但是我们生活中的电线电压一般都是220V的输出。这个时候就出现了不匹配的状况,在软件开发中我们称之为接口不兼容,此时就需要适配器来进行一个接口转换。在软件开发中有一句话正好体现了这点:任何问题都可以加一个中间层来解决。这个层我们可以理解为这里的Adapter层,通过这层来进行一个接口转换就达到了兼容的目的。

2. UML类图

     适配器模式也分两种,即类适配器模式、对象适配器模式,结构图如图1、图2。   

图1图2

   如图所示,类适配器是通过实现Target接口以及继承Adaptee类来实现接口转换,而对象适配器模式则是通过实现Target接口和代理Adaptee的某个方法来实现。结构上略有不同。

角色介绍  

  • 目标(Target)角色:这就是所期待得到的接口。注意:由于这里讨论的是类适配器模式,因此目标不可以是类。
  • 源(Adapee)角色:现在需要适配的接口。
  • 适配器(Adaper)角色:适配器类是本模式的核心。适配器把源接口转换成目标接口。显然,这一角色不可以是接口,而必须是具体类。

3. 模式的简单实现

在上述电源接口这个示例中,5V电压就是Target接口,220v电压就是Adaptee类,而将电压从220V转换到5V就是Adapter。

类适配器模式

/**
 * Target角色
 */
public interface FiveVolt {
    public int getVolt5();
}

/**
 * Adaptee角色,需要被转换的对象
 */
public class Volt220 {
    public int getVolt220() {
        return 220;
    }
}

// adapter角色
public class ClassAdapter extends Volt220 implements FiveVolt {

    @Override
    public int getVolt5() {
        return 5;
    }

}

  Target角色给出了需要的目标接口,而Adaptee类则是需要被转换的对象。Adapter则是将Volt220转换成Target的接口。对应的是Target的目标是要获取5V的输出电压,而Adaptee即正常输出电压是220V,此时我们就需要电源适配器类将220V的电压转换为5V电压,解决接口不兼容的问题。

public class Test {
    public static void main(String[] args) {
        ClassAdapter adapter = new ClassAdapter();
        System.out.println("输出电压 : " + adapter.getVolt5());
    }
}

 

对象适配器模式

  与类的适配器模式一样,对象的适配器模式把被适配的类的API转换成为目标类的API,与类的适配器模式不同的是,对象的适配器模式不是使用继承关系连接到Adaptee类,而是使用代理关系连接到Adaptee类。
     从图2可以看出,Adaptee类 ( Volt220 ) 并没有getVolt5()方法,而客户端则期待这个方法。为使客户端能够使用Adaptee类,需要提供一个包装类Adapter。这个包装类包装了一个Adaptee的实例,从而此包装类能够把Adaptee的API与Target类的API衔接起来。Adapter与Adaptee是委派关系,这决定了适配器模式是对象的。

示例代码如下 :

/**
 * Target角色
 */
public interface FiveVolt {
    public int getVolt5();
}

/**
 * Adaptee角色,需要被转换的对象
 */
public class Volt220 {
    public int getVolt220() {
        return 220;
    }
}

// 对象适配器模式
public class ObjectAdapter implements FiveVolt {

    Volt220 mVolt220;

    public ObjectAdapter(Volt220 adaptee) {
        mVolt220 = adaptee;
    }

    public int getVolt220() {
        return mVolt220.getVolt220();
    }

    @Override
    public int getVolt5() {
        return 5;
    }

}

注意,这里为了节省代码,我们并没有遵循一些面向对象的基本原则。

使用示例 :

public class Test {
    public static void main(String[] args) {
        ObjectAdapter adapter = new ObjectAdapter(new Volt220());
        System.out.println("输出电压 : " + adapter.getVolt5());
    }
}

类适配器和对象适配器的权衡

  *  类适配器使用对象继承的方式,是静态的定义方式;而对象适配器使用对象组合的方式,是动态组合的方式。

  *  对于类适配器,由于适配器直接继承了Adaptee,使得适配器不能和Adaptee的子类一起工作,因为继承是静态的关系,当适配器继承了Adaptee后,就不可能再去处理Adaptee的子类了。对于对象适配器,一个适配器可以把多种不同的源适配到同一个目标。换言之,同一个适配器可以把源类和它的子类都适配到目标接口。因为对象适配器采用的是对象组合的关系,只要对象类型正确,是不是子类都无所谓。

  *  对于类适配器,适配器可以重定义Adaptee的部分行为,相当于子类覆盖父类的部分实现方法。对于对象适配器,要重定义Adaptee的行为比较困难,这种情况下,需要定义Adaptee的子类来实现重定义,然后让适配器组合子类。虽然重定义Adaptee的行为比较困难,但是想要增加一些新的行为则方便的很,而且新增加的行为可同时适用于所有的源。

  *  对于类适配器,仅仅引入了一个对象,并不需要额外的引用来间接得到Adaptee。对于对象适配器,需要额外的引用来间接得到Adaptee。

  建议尽量使用对象适配器的实现方式,多用合成/聚合、少用继承。当然,具体问题具体分析,根据需要来选用实现方式,最适合的才是最好的。   

Android ListView中的Adapter模式

在开发过程中,ListView的Adapter是我们最为常见的类型之一。一般的用法大致如下:

// 代码省略
 ListView myListView = (ListView)findViewById(listview_id);
 // 设置适配器
 myListView.setAdapter(new MyAdapter(context, myDatas));

// 适配器
public class MyAdapter extends BaseAdapter{

        private LayoutInflater mInflater;
        List<String> mDatas ; 

        public MyAdapter(Context context, List<String> datas){
            this.mInflater = LayoutInflater.from(context);
            mDatas = datas ;
        }
        @Override
        public int getCount() {
            return mDatas.size();
        }

        @Override
        public String getItem(int pos) {
            return mDatas.get(pos);
        }

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

        // 解析、设置、缓存convertView以及相关内容
        @Override
        public View getView(int position, View convertView, ViewGroup parent) { 
            ViewHolder holder = null;
            // Item View的复用
            if (convertView == null) {
                holder = new ViewHolder();  
                convertView = mInflater.inflate(R.layout.my_listview_item, null);
                // 获取title
                holder.title = (TextView)convertView.findViewById(R.id.title);
                convertView.setTag(holder);
            } else {
                holder = (ViewHolder)convertView.getTag();
            }
            holder.title.setText(mDatas.get(position));
            return convertView;
        }

    }

这看起来似乎还挺麻烦的,看到这里我们不禁要问,ListView为什么要使用Adapter模式呢?
我们知道,作为最重要的View,ListView需要能够显示各式各样的视图,每个人需要的显示效果各不相同,显示的数据类型、数量等也千变万化。那么如何隔离这种变化尤为重要。

Android的做法是增加一个Adapter层来应对变化,将ListView需要的接口抽象到Adapter对象中,这样只要用户实现了Adapter的接口,ListView就可以按照用户设定的显示效果、数量、数据来显示特定的Item View。
通过代理数据集来告知ListView数据的个数( getCount函数 )以及每个数据的类型( getItem函数 ),最重要的是要解决Item View的输出。Item View千变万化,但终究它都是View类型,Adapter统一将Item View输出为View ( getView函数 ),这样就很好的应对了Item View的可变性。

那么ListView是如何通过Adapter模式 ( 不止Adapter模式 )来运作的呢 ?我们一起来看一看。
ListView继承自AbsListView,Adapter定义在AbsListView中,我们看一看这个类。

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

    ListAdapter mAdapter ;

    // 关联到Window时调用的函数
    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        // 代码省略
        // 给适配器注册一个观察者,该模式下一篇介绍。
        if (mAdapter != null && mDataSetObserver == null) {
            mDataSetObserver = new AdapterDataSetObserver();
            mAdapter.registerDataSetObserver(mDataSetObserver);

            // Data may have changed while we were detached. Refresh.
            mDataChanged = true;
            mOldItemCount = mItemCount
            // 获取Item的数量,调用的是mAdapter的getCount方法
            mItemCount = mAdapter.getCount();
        }
        mIsAttached = true;
    }

  /**
     * 子类需要覆写layoutChildren()函数来布局child view,也就是Item View
     */
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        mInLayout = true;
        if (changed) {
            int childCount = getChildCount();
            for (int i = 0; i < childCount; i++) {
                getChildAt(i).forceLayout();
            }
            mRecycler.markChildrenDirty();
        }

        if (mFastScroller != null && mItemCount != mOldItemCount) {
            mFastScroller.onItemCountChanged(mOldItemCount, mItemCount);
        }
        // 布局Child View
        layoutChildren();
        mInLayout = false;

        mOverscrollMax = (b - t) / OVERSCROLL_LIMIT_DIVISOR;
    }

    // 获取一个Item View
    View obtainView(int position, boolean[] isScrap) {
        isScrap[0] = false;
        View scrapView;
        // 从缓存的Item View中获取,ListView的复用机制就在这里
        scrapView = mRecycler.getScrapView(position);

        View child;
        if (scrapView != null) {
            // 代码省略
            child = mAdapter.getView(position, scrapView, this);
            // 代码省略
        } else {
            child = mAdapter.getView(position, null, this);
            // 代码省略
        }

        return child;
    }
    }

AbsListView定义了集合视图的框架,比如Adapter模式的应用、复用Item View的逻辑、布局Item View的逻辑等。子类只需要覆写特定的方法即可实现集合视图的功能,例如ListView。

ListView中的相关方法。

    @Override
    protected void layoutChildren() {
        // 代码省略

        try {
            super.layoutChildren();
            invalidate();
            // 代码省略
            // 根据布局模式来布局Item View
            switch (mLayoutMode) {
            case LAYOUT_SET_SELECTION:
                if (newSel != null) {
                    sel = fillFromSelection(newSel.getTop(), childrenTop, childrenBottom);
                } else {
                    sel = fillFromMiddle(childrenTop, childrenBottom);
                }
                break;
            case LAYOUT_SYNC:
                sel = fillSpecific(mSyncPosition, mSpecificTop);
                break;
            case LAYOUT_FORCE_BOTTOM:
                sel = fillUp(mItemCount - 1, childrenBottom);
                adjustViewsUpOrDown();
                break;
            case LAYOUT_FORCE_TOP:
                mFirstPosition = 0;
                sel = fillFromTop(childrenTop);
                adjustViewsUpOrDown();
                break;
            case LAYOUT_SPECIFIC:
                sel = fillSpecific(reconcileSelectedPosition(), mSpecificTop);
                break;
            case LAYOUT_MOVE_SELECTION:
                sel = moveSelection(oldSel, newSel, delta, childrenTop, childrenBottom);
                break;
            default:
                // 代码省略
                break;
            }

    }

    // 从上到下填充Item View  [ 只是其中一种填充方式 ]
    private View fillDown(int pos, int nextTop) {
        View selectedView = null;

        int end = (mBottom - mTop);
        if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
            end -= mListPadding.bottom;
        }

        while (nextTop < end && pos < mItemCount) {
            // is this the selected item?
            boolean selected = pos == mSelectedPosition;
            View child = makeAndAddView(pos, nextTop, true, mListPadding.left, selected);

            nextTop = child.getBottom() + mDividerHeight;
            if (selected) {
                selectedView = child;
            }
            pos++;
        }

        return selectedView;
    }

    // 添加Item View
    private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
            boolean selected) {
        View child;

        // 代码省略 
        // Make a new view for this position, or convert an unused view if possible
        child = obtainView(position, mIsScrap);

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

        return child;
    }

ListView覆写了AbsListView中的layoutChilden函数,在该函数中根据布局模式来布局Item View。Item View的个数、样式都通过Adapter对应的方法来获取,获取个数、Item View之后,将这些Item View布局到ListView对应的坐标上,再加上Item View的复用机制,整个ListView就基本运转起来了。

当然这里的Adapter并不是经典的适配器模式,但是却是对象适配器模式的优秀示例,也很好的体现了面向对象的一些基本原则。这里的Target角色和Adapter角色融合在一起,Adapter中的方法就是目标方法;而Adaptee角色就是ListView的数据集与Item View,Adapter代理数据集,从而获取到数据集的个数、元素。

通过增加Adapter一层来将Item View的操作抽象起来,ListView等集合视图通过Adapter对象获得Item的个数、数据元素、Item View等,从而达到适配各种数据、各种Item视图的效果。因为Item View和数据类型千变万化,Android的架构师们将这些变化的部分交给用户来处理,通过getCount、getItem、getView等几个方法抽象出来,也就是将Item View的构造过程交给用户来处理,灵活地运用了适配器模式,达到了无限适配、拥抱变化的目的。

杂谈

优点

  • 更好的复用性
      系统需要使用现有的类,而此类的接口不符合系统的需要。那么通过适配器模式就可以让这些功能得到更好的复用。

  • 更好的扩展性
      在实现适配器功能的时候,可以调用自己开发的功能,从而自然地扩展系统的功能。

缺点

  • 过多的使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是A接口,其实内部被适配成了B接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。



Android设计模式源码解析之观察者模式

本文为 Android 设计模式源码解析 中 观察者模式 分析
Android系统版本: 2.3
分析者:Mr.Simple,分析状态:未完成,校对者:Mr.Simple,校对状态:未开始

1. 模式介绍

模式的定义

定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新。

模式的使用场景

  • 关联行为场景。需要注意的是,关联行为是可拆分的,而不是“组合”关系;
  • 事件多级触发场景;
  • 跨系统的消息交换场景,如消息队列、事件总线的处理机制。

2. UML类图

uml

角色介绍

  • 抽象主题 (Subject) 角色
    抽象主题角色把所有观察者对象的引用保存在一个聚集(比如ArrayList对象)里,每个主题都可以有任意数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对象,抽象主题角色又叫做抽象被观察者(Observable)角色。

  • 具体主题 (ConcreteSubject) 角色
    将有关状态存入具体观察者对象;在具体主题的内部状态改变时,给所有登记过的观察者发出通知。具体主题角色又叫做具体被观察者(Concrete Observable)角色。

  • 抽象观察者 (Observer) 角色
    为所有的具体观察者定义一个接口,在得到主题的通知时更新自己,这个接口叫做更新接口。

  • 具体观察者 (ConcreteObserver) 角色
    存储与主题的状态自恰的状态。具体观察者角色实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态 像协调。如果需要,具体观察者角色可以保持一个指向具体主题对象的引用。

3. 模式的简单实现

简单实现的介绍

AndroidWeekly是一个每周都会发布关于Android新技术、开源库、招聘信息等内容的网站,在这里我们可以看到最新的技术,最牛X的工程师,经常逛逛这类网站不仅能够开阔我们的眼界,也能让我们接触到最前言的科技信息。这其实就是一个RSS系统,用户订阅Android Weekly的文章,每当有更新的时候将新的内容推送给订阅用户。这不就是观察者模式吗?观察者模式的另一个名字叫做发布-订阅模式,下图就是我订阅AndroidWeekly之后他们发来的确认邮件。下面让我们来简单模拟一下AndroidWeekly的发布过程吧!

android-weekly

实现源码

/**
 * 程序员是观察者
 * 
 * @author mrsimple
 */
public class Coder implements Observer {
    public String name ;

    public Coder(String aName) {
        name = aName ;
    }

    @Override
    public void update(Observable o, Object arg) {
        System.out.println( "Hi, " +  name + ", AndroidWeekly更新啦, 内容 : " + arg);
    }

    @Override
    public String toString() {
        return "码农 : " + name;
    }

}


/**
 * AndroidWeekly这个网站是被观察者,它有更新所有的观察者 (这里是程序员) 都会接到相应的通知.
 * 
 * @author mrsimple
 */
public class AndroidWeekly extends Observable {

    public void postNewPublication(String content) {
        // 标识状态或者内容发生改变
        setChanged();
        // 通知所有观察者
        notifyObservers(content);
    }
}

// 测试代码
public class Test {
    public static void main(String[] args) {
        // 被观察的角色
        AndroidWeekly androidWeekly = new AndroidWeekly();
        // 观察者
        Coder mrsimple = new Coder("mr.simple");
        Coder coder1 = new Coder("coder-1");
        Coder coder2 = new Coder("coder-2");
        Coder coder3 = new Coder("coder-3");

        // 将观察者注册到可观察对象的观察者列表中
        androidWeekly.addObserver(mrsimple);
        androidWeekly.addObserver(coder1);
        androidWeekly.addObserver(coder2);
        androidWeekly.addObserver(coder3);

        // 发布消息
        androidWeekly.postNewPublication("新的一期AndroidWeekly来啦!");
    }

}

输出结果:

Hi, coder-3, AndroidWeekly更新啦, 内容 : 新的一期AndroidWeekly来啦!
Hi, coder-2, AndroidWeekly更新啦, 内容 : 新的一期AndroidWeekly来啦!
Hi, coder-1, AndroidWeekly更新啦, 内容 : 新的一期AndroidWeekly来啦!
Hi, mr.simple, AndroidWeekly更新啦, 内容 : 新的一期AndroidWeekly来啦!

可以看到所有订阅了AndroidWeekly的用户都受到了更新消息,一对多的订阅-发布系统这么简单就完成了。

这里Observer是抽象的观察者角色,Coder扮演的是具体观察者的角色;Observable对应的是抽象主题角色,AndroidWeekly则是具体的主题角色。Coder是具体的观察者,他们订阅了AndroidWeekly这个具体的可观察对象,当AndroidWeekly有更新时,会遍历所有观察者 ( 这里是Coder码农 ),然后给这些观察者发布一个更新的消息,即调用Coder中的update方法,这样就达到了1对多的通知功能。Observer和Observable都已经内置在jdk中,可见观察者模式在java中的重要性。

Android源码中的模式实现

ListView是Android中最重要的控件,没有之一。而ListView最重要的一个点就是Adapter,在Android设计模式源码解析之适配器(Adapter)模式中我们分析了Adapter模式,在我们往ListView添加数据后,我们都会调用一个方法: notifyDataSetChanged(), 这是为什么呢? 今天我们就来揭开它的神秘面纱。

第一步我们就跟进这个方法notifyDataSetChanged方法,这个方法定义在BaseAdapter中,代码如下:

public abstract class BaseAdapter implements ListAdapter, SpinnerAdapter {
    // 数据集观察者
    private final DataSetObservable mDataSetObservable = new DataSetObservable();

    // 代码省略

    public void registerDataSetObserver(DataSetObserver observer) {
        mDataSetObservable.registerObserver(observer);
    }

    public void unregisterDataSetObserver(DataSetObserver observer) {
        mDataSetObservable.unregisterObserver(observer);
    }

    /**
     * Notifies the attached observers that the underlying data has been changed
     * and any View reflecting the data set should refresh itself.
     * 当数据集用变化时通知所有观察者
     */
    public void notifyDataSetChanged() {
        mDataSetObservable.notifyChanged();
    }
}

我们一看BaseAdapter上述代码,大体有了这么一个了解,原来BaseAdapter是一个观察者模式!
那么BaseAdapter是如何运作的? 这些观察者又是什么呢?我们一步一步来分析。

我们先跟到mDataSetObservable.notifyChanged()函数中看看。

/**
 * A specialization of Observable for DataSetObserver that provides methods for
 * invoking the various callback methods of DataSetObserver.
 */
public class DataSetObservable extends Observable<DataSetObserver> {
    /**
     * Invokes onChanged on each observer. Called when the data set being observed has
     * changed, and which when read contains the new state of the data.
     */
    public void notifyChanged() {
        synchronized(mObservers) {
            // 调用所有观察者的onChanged方式
            for (int i = mObservers.size() - 1; i >= 0; i--) {
                mObservers.get(i).onChanged();
            }
        }
    }
    // 代码省略
}

恩,代码很简单,就是在mDataSetObservable.notifyChanged()中遍历所有观察者,并且调用它们的onChanged方法。

那么这些观察者是从哪里来的呢?首先ListView通过setAdapter方法来设置Adapter,我们看看相关代码。

    @Override
    public void setAdapter(ListAdapter adapter) {
        // 如果已经有了一个adapter,那么先注销该Adapter对应的观察者
        if (mAdapter != null && mDataSetObserver != null) {
            mAdapter.unregisterDataSetObserver(mDataSetObserver);
        }

        // 代码省略

        super.setAdapter(adapter);

        if (mAdapter != null) {
            mAreAllItemsSelectable = mAdapter.areAllItemsEnabled();
            mOldItemCount = mItemCount;
            // 获取数据的数量
            mItemCount = mAdapter.getCount();
            checkFocus();
            // 注意这里 : 创建一个一个数据集观察者
            mDataSetObserver = new AdapterDataSetObserver();
            // 将这个观察者注册到Adapter中,实际上是注册到DataSetObservable中
            mAdapter.registerDataSetObserver(mDataSetObserver);

            // 代码省略
        } else {
            // 代码省略
        }

        requestLayout();
    }

可以看到在设置Adapter时会构建一个AdapterDataSetObserver,这不就是我们上面所说的观察者么,最后将这个观察者注册到adapter中,这样我们的被观察者、观察者都有了。一般来说我们的数据集会放到Adapter中,例如 :

public abstract class UserAdapter extends BaseAdapter {
    // 数据集
    protected List<String> mDataSet = new LinkedList<String>();
    protected Context mContext = null;

    public CommonAdapter(Context context, List<String> dataSet) {
        this.mDataSet = dataSet;
        this.mContext = context;
    }
}

这个时候可能你就有点晕了? AdapterDataSetObserver是什么?它是如何运作的?那么我就先来看看AdapterDataSetObserver吧。

AdapterDataSetObserver定义在ListView的父类AbsListView中,代码如下 :

    class AdapterDataSetObserver extends AdapterView<ListAdapter>.AdapterDataSetObserver {
        @Override
        public void onChanged() {
            super.onChanged();
            if (mFastScroller != null) {
                mFastScroller.onSectionsChanged();
            }
        }

        @Override
        public void onInvalidated() {
            super.onInvalidated();
            if (mFastScroller != null) {
                mFastScroller.onSectionsChanged();
            }
        }
    }

它由继承自AbsListView的父类AdapterView的AdapterDataSetObserver, 代码如下 :

    class AdapterDataSetObserver extends DataSetObserver {

        private Parcelable mInstanceState = null;
        // 上文有说道,调用Adapter的notifyDataSetChanged的时候会调用所有观察者的onChanged方法,核心实现就在这里
        @Override
        public void onChanged() {
            mDataChanged = true;
            mOldItemCount = mItemCount;
            // 获取Adapter中数据的数量
            mItemCount = getAdapter().getCount();

            // Detect the case where a cursor that was previously invalidated has
            // been repopulated with new data.
            if (AdapterView.this.getAdapter().hasStableIds() && mInstanceState != null
                    && mOldItemCount == 0 && mItemCount > 0) {
                AdapterView.this.onRestoreInstanceState(mInstanceState);
                mInstanceState = null;
            } else {
                rememberSyncState();
            }
            checkFocus();
            // 重新布局ListView、GridView等AdapterView组件
            requestLayout();
        }

        // 代码省略

        public void clearSavedState() {
            mInstanceState = null;
        }
    }

到这里我们就知道了,当ListView的数据发生变化时,调用Adapter的notifyDataSetChanged函数,这个函数又会调用DataSetObservable的notifyChanged函数,这个函数会调用所有观察者 (AdapterDataSetObserver) 的onChanged方法。这就是一个观察者模式!

最后我们再捋一捋,AdapterView中有一个内部类AdapterDataSetObserver,在ListView设置Adapter时会构建一个AdapterDataSetObserver,并且注册到Adapter中,这个就是一个观察者。而Adapter中包含一个数据集可观察者DataSetObservable,在数据数量发生变更时开发者手动调用AdapternotifyDataSetChanged,而notifyDataSetChanged实际上会调用DataSetObservable的notifyChanged函数,该函数会遍历所有观察者的onChanged函数。在AdapterDataSetObserver的onChanged函数中会获取Adapter中数据集的新数量,然后调用ListView的requestLayout()方法重新进行布局,更新用户界面。

img1img1
图1图2

4. 杂谈

ListView主要运用了Adapter和观察者模式使得可扩展性、灵活性非常强,而耦合度却很低,这是我认为设计模式在Android源码中优秀运用的典范。那么为什么Android架构师们会这么设计ListView,它们如何达到低耦合、高灵活性呢?这个留给大家思考吧,如果有时间我再分享我的看法。

优点

  • 观察者和被观察者之间是抽象耦合

缺点

  • 观察者模式需要考虑一下开发效率和运行效率问题,一个被观察者,多个观察者,开发和调试就会比较复杂,而且在 Java 中消息的通知默认是顺序执行,一个观察者卡壳,会影响整体的执行效率。在这种情况下,一般考虑采用异步的方式。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值