单例模式进阶之Android中单例模式的应用场景

Posted Android进阶之剑

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了单例模式进阶之Android中单例模式的应用场景相关的知识,希望对你有一定的参考价值。

android单例模式源码分析



上篇中介绍了单例模式与序列化和反序列化之间的问题,本篇继续在这个问题基础上进行讨论,同时对Andtoid中的Intent的源码进行简单分析以及通过分析安卓源码中哪里应用到了单例设计模式;最后给出一种使用单例模式来管理Activity方式的案例。【注:因笔者水平有限,如果内容有误望读者朋友给出指正单例模式进阶(二)之Android中单例模式的应用场景




1、Android通过序列化方式来传递数据


这里要给出一个简单的Demo:

定义一个订单类,并序列化该类,待会就需要把这个类从一个Activity传递到另一个Activity:

public class OrderBean implements Serializable{

    private static final long serialVersionUID = -5809782578272943999L;

    private String name;
    private String id;
    private double price;

    public OrderBean(String name, String id, double price) {
        this.name = name;
        this.id = id;
        this.price = price;
    }

    @Override
    public String toString() {
        return "OrderBean{" +
                "name='" + name + '\'' +
                ", id='" + id + '\'' +
                ", price=" + price +
                '}';
    }
}

然后写一个简单的序列化工具类:

public class SerializeUtils {

    /**
     * 获取SD卡路径
     * @return
     */
    public static File getSDCardFile(){
        File file = new File(Environment.getExternalStorageDirectory(),"Serializable");
        if(!file.exists()){
            file.mkdir();
        }
        return file;
    }

    /**
     * MethodName: Serialize
     * Description: 序列化
     */
    public static void Serialize(Serializable serializable)  {
        // ObjectOutputStream 对象输出流,将Person对象存储到E盘的Person.txt文件中,完成对Person对象的序列化操作
        ObjectOutputStream oo = null;
        try {
            oo = new ObjectOutputStream(new FileOutputStream(
                    new File(getSDCardFile().getAbsolutePath()+"/"+serializable.getClass().getSimpleName()+".txt")));
            oo.writeObject(serializable);
            System.out.println("Person对象序列化成功!");
            oo.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * MethodName: Deserialize
     * Description: 反序列
     */
    public static <T extends Serializable> T deserialize(Class<T> clazz) {
        ObjectInputStream ois = null;
        try {
            ois = new ObjectInputStream(new FileInputStream(
                    new File(getSDCardFile().getAbsolutePath()+"/"+clazz.getSimpleName()+".txt")));
            Object readObject = ois.readObject();
            System.out.println("Person对象反序列化成功!");
            return (T) readObject;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

}

然后希望从MainActivity跳转到LoginActivity,并且把订单信息通过序列化的方式传递过去:

单例模式进阶(二)之Android中单例模式的应用场景

这里的代码很简单,就不再浪费时间了,看一下LoginActivity中:

单例模式进阶(二)之Android中单例模式的应用场景

打印一行log,如果能打印成功,就表情通过这种方式传递数据是正确的,运行程序看一下log:

单例模式进阶(二)之Android中单例模式的应用场景

发现确实把数据传递过去了。

这就是序列化在安卓中的应用。咱们平时传递数据往往是通过Intent来传递的是吧,那么Intent它里面的原理是什么呢?下一节将会对Intent的源码进行简要分析:




2、Intent传递序列化对象源码浅析


有人说Intent传递数据是使用了Parcelable方式,但是在传递序列化对象的时候唯独有一些特殊【因为Intent可以传递多种数据类型】就不仅仅是Parcelable了。

在开始本节内容前先要知道结论:Intent传递序列化对象并不是只是按照Parcelable方式进行传递的,它的底层是通过Serializable+Parcelable方式传递的。


上文也说道,通过Intent传递序列化对象方式。代码如下:

单例模式进阶(二)之Android中单例模式的应用场景

获取数据方:

单例模式进阶(二)之Android中单例模式的应用场景

运行程序发现Log结果是一样的。不禁会问,Intent是如何将数据传递给另一个Activity的呢?单例模式进阶(二)之Android中单例模式的应用场景

通过追踪源码一探究竟:

单例模式进阶(二)之Android中单例模式的应用场景

我们通过Intent传递的数据,在Intent里面封装到了Bundle里面,而Bundle它是实现了Parcelable接口的,也就是说,Bundle对象也是通过序列化的方式进行传递的,要不然我们能从别的Activity拿到相同的Bundle对象副本呢(其实Intent也是这样)。

继续跟进代码,最终走到Bundle的父亲BaseBundle里面:

单例模式进阶(二)之Android中单例模式的应用场景

在这里把序列化好的数据封装到了ArrayMap里面了,Intent的key作为了这里ArrayMap的key,值Bundle作为了map的值。这里可以理解为把数据保存到了内存ArrayMap里面。


上面说了,Bundle对象也是通过序列化的方式进行传递的,而且他是实现了Parcelable接口的。对于Parcelable方式序列化方式在上一篇文章中已经介绍过了,会调用writeToParcel(Parcel dest, int flags)方法把序列化对象的字段写入,看一下这个方法:

单例模式进阶(二)之Android中单例模式的应用场景

它调用父类的writeToParcel(Parcel dest, int flags)方法,在父类该方法里面有对刚刚创建的ArrayMap对象的序列化过程:

单例模式进阶(二)之Android中单例模式的应用场景

它调用的是Parcel的方法,跟进:

单例模式进阶(二)之Android中单例模式的应用场景

这里首先对map进行遍历,因为完成序列化的不是map而是map里面存储的东西(值),因此我们要拿到每一个值然后对其进行写入操作。对map里面的键和值进行写入,主要看一下键的写入方法writeValue(Object v):

单例模式进阶(二)之Android中单例模式的应用场景

这个方法里面有调用了writeSerializable((Serializable) v);方法:

单例模式进阶(二)之Android中单例模式的应用场景

哇!单例模式进阶(二)之Android中单例模式的应用场景终于找到了,这不就是Serializable 方式序列化吗!原来我们每一次通过intent传递的序列化的对象在底层都是通过Serializable 方式进行序列化存储的。然后看一下取的时候。跟进readSerializable(final ClassLoader loader)方法:

单例模式进阶(二)之Android中单例模式的应用场景

它本质上也是通过反射的方式把存储的序列化的对象又读取出来的。

总结:

Intent传递序列化对象做一个总结,首先通过intent.putExtra("OrderBean",mOrderBean);方式把想要序列化的对象存储到Bundle中,在Bundle里面其实是把所有的序列化对象存储到了map集合中了,键和值一一对应的方式。Bundle是Parcelable序列化方式,这样保证了在接收参数端获取到的对象是当前传递数据的Bundle的副本。这样也就保证了接收参数端拿到的map也是同一个map,这样也就验证了同参数可以获取传递对象的正确性。同时,map里面存储的是真正序列化的对象,它通过集合遍历的方式获取每一个对象,并通过Serializable方式进行序列化的,它在序列化是把对象全部写入磁盘;在读取的时候又从磁盘全部取出,放入map里面。这样Bundle通过Parcelable序列化方式+要传递的数据通过Serializable序列化方式把数据辗转到了接收参数端,完成了对序列化对象数据的传递。




3、单例模式在安卓源码中的体现。


该节我们会对安卓中的LayoutInflater生成过程进行源码分析,只要知道LayoutInflater的生成过程,那么大家所使用的WindowManager、ActivityManager、NotificationManager、LocationManager等等系统级别的服务是如何创建的过程就水到渠成了。在开始源码分析之前,我们要知道一些结论:

1、在一个App中,每创建一个Activity,只要这个Activity没有被杀死,那么在这个Activity中获取到的LayoutInflater实例始终都是一个(即单例)。

2、不同的Activity都会维护绑定在一起的LayoutInflater实例(即Activity不同LayoutInflater实例不同,跨进程不保证LayoutInflater单例)。

3、LayoutInflater是抽象类,我们所使用的的不是它而是它的实现类PhoneLayoutInflater。

4、LayoutInflater、WindowManager、ActivityManager等系统级别的服务都是通过容器管理器来实现的单例。

好了,就赶紧看一下具体的产生过程吧!

其实如果要全部追踪的话要从Activity的启动流程开始,鉴于篇幅和个人水平有限原因,这里不去介绍启动流程,只会分析LayoutInflater单例实现过程。


对于结论1和2,我还是想通过一个简单的案例来验证一下:

3.1、验证Activity不同LayoutInflater实例不同,跨进程不保证LayoutInflater单例

为了保证两个Activity,我这里创建一个MainActivioty和LoginActivity:

代码很简单,直接截图如下:

MainActivity代码:

单例模式进阶(二)之Android中单例模式的应用场景

这里放了两个按钮,作用如上。

LoginActivity代码:

单例模式进阶(二)之Android中单例模式的应用场景

这里放了一个按钮,作用如上。

首先点击MainActivity的btn1,然后点击MainActivity的btn2进入LoginActivity,然后再点击LoginActivity的btn1.看log如下:

单例模式进阶(二)之Android中单例模式的应用场景

发现每个Activity中的实例都是一个,而两个Activity中的实例不是同一个,这也就验证了:Activity不同LayoutInflater实例不同,跨进程不保证LayoutInflater单例。同时验证了本质上的对象是LayoutInflater的子类PhoneLayoutInflater类型的实例。


3.2、LayoutInflater单例生成过程源码分析【这块内容可能有点复杂,不想看的朋友可以直接跳到总结部分】

假设目前Activity已经加载成功,系统帮我们把Activity和Content对象都已经初始化完毕了。

当创建一个LayoutInflater对象的时候,我们会按照如下方式:

单例模式进阶(二)之Android中单例模式的应用场景

它底层调用了content的getSystemService()方法。

单例模式进阶(二)之Android中单例模式的应用场景

这个方法里面需要传递一个字符串常量进去:“layout_inflater“,返回值是Object类型的,然后强转为LayoutInflater实例。

单例模式进阶(二)之Android中单例模式的应用场景


然后跟进content的getSystemService()方法:

单例模式进阶(二)之Android中单例模式的应用场景

发现Content是一个抽象类,然后调用的是抽象方法。那么,Content肯定有一个具体的子类,调用的是具体子类的方法了。这里也只是给出结论:其实调用的是ContentImpl的getSystemService()方法。如果非要找到这个原因要去Activity启动流程中去查找了,感兴趣读者可以参考其他资料。

单例模式进阶(二)之Android中单例模式的应用场景

这里又做了周转,调用的是SystemServiceRegistry的getSystemService(this, name);方法,然后他需要两个值,一个是当前的content对象即ContentImpl,和“layout_inflater“这个字符串。


对于SystemServiceRegistry类,这里叫做系统服务注册管理类。它里面实现了对系统级别服务的注册和获取。而他的注册和获取方式总共做了两层嵌套。还是看代码来说吧:

单例模式进阶(二)之Android中单例模式的应用场景

单例模式进阶(二)之Android中单例模式的应用场景

这里就是具体的获取LayoutInflater对象的地方了。是直接在一个HashMap中获取的。看代码应该更清楚,第一步先获取fetcher,然后再通过tetcher调用getService获取具体的LayoutInflater对象,这里总共两层嵌套。                    

即: 第一步从SYSTEM_SERVICE_FETCHERS这个map里面根据键“layout_inflater“这个字符串获取ServiceFether对象,然后通过 ServiceFether再根据传入的上下文获取LayoutInflater对象。【注意上面两个map不属于两层哈,这两个map的存储属于并行关系

--------

这里可能把人绕晕了,在看具体的取出对象之前先来看看是如何把LayoutInflater对象存起来的吧:

SystemServiceRegistry这个类里面有一个静态代码块,专门用来注册各种服务的。部分代码如下:

static {
     //。。。代码省略
        registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
                new CachedServiceFetcher<LayoutInflater>() {
            @Override
            public LayoutInflater createService(ContextImpl ctx) {
                return new PhoneLayoutInflater(ctx.getOuterContext());
            }});
        //。。。代码省略}

这里注册的是LayoutInflater,看具体的注册方法:

单例模式进阶(二)之Android中单例模式的应用场景

接收三个参数,1、serviceName:表示服务的字符串表示名称(比如这里的“layout_inflater“);2、Class类型,如这里的LayoutInflater.class;3、ServiceFetcher一个接口,接口T getService(ContextImpl ctx);是专门用来获取具体的服务的。

然后往下是通过刚才的两个map进行保存工作。

HashMap<Class<?>, String> SYSTEM_SERVICE_NAMES:SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);把类类型作为了键(如这里的LayoutInflater.class),把服务对应的名称作为了值(比如这里的“layout_inflater“

HashMap<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS:SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);,把服务对应的名称作为了键(比如这里的“layout_inflater“),把ServiceFetcher作为了值。

注意这里是在静态代码块中注册的,也就是说SystemServiceRegistry这个类加载的时候,已经完成了注册工作了。


再回到注册的地方:

单例模式进阶(二)之Android中单例模式的应用场景

这里使用了匿名内部类的方式创建一个ServiceFetcher类型的实现类,CachedServiceFetcher是一个实现了ServiceFetcher的抽象类,这里实现createService(ContextImpl ctx)方法进行创建具体的LayoutInflater对象,可以看到本质上创建的是PhoneLayoutInflater对象。

那么这个匿名内部类里面做了什么工作呢?这个需要追踪CachedServiceFetcher抽象类了:

单例模式进阶(二)之Android中单例模式的应用场景

这里的代码很简单,我把每个要点都标注了起来。本质就是:先从缓存中取出LayoutInflater,如果没有的话则去创建LayoutInflater,创建完成后再进行缓存。这里的缓存使用的是数组(注意5.0以前的代码缓存使用的ArrayList<Object>)。你可能会问,系统中有这么多的系统级别的服务,这里怎么让数组与之一一对应的呢?看上面的构造方法,每创建一个系统级别服务进行注册的时候都会调用构造方法进行扩容,这样就保证了数组每个index位置与对应的系统级别的服务一一对应了。这样我们就得出结论:我们要获取的系统级别的服务都已经注册到了ServiceFetcher中了,比如LayoutInflater已经注册到CachedServiceFetcher的实现类中了。


分析到这里,我们再先做一个小总结再往下讲:

系统默认已经在SystemServiceRegistry这个类的 static代码块中注册了系统级别的所有服务。注册过程是通过两层包装:

第一层:

1、HashMap<Class<?>, String> SYSTEM_SERVICE_NAMES:SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);把类类型作为了键(如这里的LayoutInflater.class),把服务对应的名称作为了值(比如这里的“layout_inflater“

2、HashMap<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS:SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);,把服务对应的名称作为了键(比如这里的“layout_inflater“),把ServiceFetcher作为了值。

第二层:

在注册系统级别服务的时候,调用SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);前,会对需要对真正的LayoutInflater实现类对象进行缓存,这里的缓存是通过CachedServiceFetcher的getService(ContextImpl ctx)方法缓存的,它是缓存到了一个Object类型的可变数组里面了。但是,如果这个方法一次没有被调用过,LayoutInflater实现类对象并没有创建也就还没有缓存,应该先去创建再缓存。


继续往下讲:

那么,这个getService(ContextImpl ctx)方法在哪里开始调用呢?是不是已经很熟悉了?上文不就已经提到过了吗!

单例模式进阶(二)之Android中单例模式的应用场景

这样就把前文串联了起来了。

上面的过程可以理解成,先通过CachedServiceFetcher来保存具体的系统级别的服务对象(如这里的LayoutInflater实现类对象),然后再通过map把CachedServiceFetcher实现类对象给保存起来。map集合保证了对象唯一。取出的时候,可以先从map中根据key值(如这里的“layout_inflater“作为了键)取出CachedServiceFetcher实现类对象,然后再通过CachedServiceFetcher实现类对象的getService()方法取出具体的系统级别的服务对象(如这里的LayoutInflater实现类对象)返回给调用方。


最后再做一个总结吧:

在虚拟机第一次加载SystemServiceRegistry类的时候,会注册各种ServiceFatcher,其中就包含了LayoutInflater Service。将这些服务以键值对的形式存储在一个HashMap中,用户使用时只需要根据key来获取到对应的ServiceFatcher,然后通过ServiceFatcher对象的getService函数来获取具体的服务对象。当第一次获取时,会调用ServiceFatcher中的createService函数创建服务对象,然后该对象缓存到一个数组中【5.0以前的代码缓存在ArrayList中】,下次再取出时。直接从缓存中获取避免了对象重复创建,从而达到单例的效果。系统核心服务以单例的形式存在,减少了资源消耗。下一节的Activity管理是简单模仿这种方式。




4、单例模式在安卓中的应用案例——Activity管理。


注:因为个人经验原因,本节内容并不是说就一定能应用在实际项目中,但是为了描述单例模式的应用场景本节内容仅作为一些参考和了解,Activity管理每个公司应该都有自己不同的方式,系统也指定了Api接口ActivityLifecycleCallbacks

假设有如下场景:

场景一:

单例模式进阶(二)之Android中单例模式的应用场景

这个场景很常见。在一个详情页点击一个收藏按钮,如果接口返回值发现此时没有登录,点击收藏后会跳转到登录页面(实际中应该有一个回调,回调里面也会给出成功和失败),而在登录页面又可以点击注册按钮跳转到注册页面。在注册页面对新用户进行注册(注册成功后过程大致是提交数据、保存用户信息、关闭注册页面)。如果没有管理Activity的话,关闭注册页面就会回到登录页面。产品如果说,注册成功后,应该关闭注册页面也要关闭登录页面,退回到详情页面怎么办呢?这就需要在注册页面的回调里面来想办法关闭掉登录页面。

可能会有很多的方式来做,EventBus、任务栈......

那么再加上一种场景:

场景2:

单点登录场景:假设你在设备1上登录,然后设备2又一次登录。那么设备1就会弹窗告知“您在别的设备登录“,这个弹窗应该在Activity的基础上弹出,那么,弹窗应该在哪个Activity中弹出呢?有的应用中是位于栈顶的Activity需要弹窗,但是你如何知道栈顶是哪个Activity呢?因此需要一些办法来获取栈顶Activity。这样上面的方式可能就不太好处理了。


为了能同时满足上面两种情景(还有退出应用等其它功能),接下来就使用单例模式来实现一个Activity管理类解决这种场景。

ActivityManager类很简单,直接上代码了:

public class ActivityManager {

    private Stack<Activity> mActivities = new Stack<>();

    private ActivityManager(){}

    private static class InnerActivityManager{
        private final static ActivityManager sManager = new ActivityManager();
    }

    public static ActivityManager getInstance(){
        return InnerActivityManager.sManager;
    }

    /**
     * 绑定Activity
     * @param activity
     */
    public void attch(Activity activity){
        mActivities.add(activity);
    }

    /**
     * 解绑Activity
     * @param detachActivity
     */
    public void deTach(Activity detachActivity){
        int size = mActivities.size();
        for (int i = 0; i < size; i++) {
            Activity activity = mActivities.get(i);
            if(activity == detachActivity){
                mActivities.remove(i);
                i--;
                size--;
            }
        }
    }

    /**
     * 关闭指定的Activity
     * @param finishActivity
     *      结束哪个Activity的实例
     */
    public void finish(Activity finishActivity){
        int size = mActivities.size();
        for (int i = 0; i < size; i++) {
            Activity activity = mActivities.get(i);
            if(activity == finishActivity){
                mActivities.remove(i);
                activity.finish();
                i--;
                size--;
            }
        }
    }

    /**
     * 关闭指定的Activity
     * @param clazz
     *      结束哪个Activity的Class对象
     */
    public void finish(Class<? extends Activity> clazz){
        int size = mActivities.size();
        for (int i = 0; i < size; i++) {
            Activity activity = mActivities.get(i);
            if(activity.getClass().getCanonicalName() == clazz.getCanonicalName()){
                mActivities.remove(i);
                activity.finish();
                i--;
                size--;
            }
        }
    }

    /**
     * 关闭整个应用,移除所有A窗体vitivity实例
     */
    public void closeApplication(){
        int size = mActivities.size();
        for (int i = 0; i < size; i++) {
            Activity activity = mActivities.get(i);
            mActivities.remove(i);
            activity.finish();
            i--;
            size--;
        }
    }
}

这里通过静态内部类的方式实现单例,也推荐大家使用这种方式,因为这种方式实在找不到什么缺点。

然后通过一个Stack集合来管理整个应用的Activity,给出了绑定、解绑、以及关闭指定Activity的功能(还可以继续增加更多的功能)。这样的话,使用起来很方便了,只需要在基类Activity里面的onCreate()方法中绑定Activity,在onDestary()方法里面解绑Activity即可。这里Demo写在了BaseActivity里面:

public class BaseActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ActivityManager.getInstance().attch(this);
        super.onCreate(savedInstanceState);
    }

    @Override
    protected void onDestroy() {
        ActivityManager.getInstance().deTach(this);
        super.onDestroy();
    }
}

来看一下最初的需求:

1、点击注册按钮,跳转到MainActivity:

    /**
     * 点击按钮,关闭自己同时关闭LoginActivity
     * @param view
     */
    public void btn4(View view){
        ActivityManager.getInstance().finish(this);
        ActivityManager.getInstance().finish(LoginActivity.class);
    }

2、点击关闭按钮,退出整个应用:

/**
 * 点击按钮,关闭整个应用
 * @param view
 */
public void btn2(View view){
    ActivityManager.getInstance().closeApplication();
}

然后运行看一下效果:

单例模式进阶(二)之Android中单例模式的应用场景

这样管理一个Activity就特别的方便了吧。

到此整个单例设计模式相关的知识点就全部讲完了,喜欢的朋友捧个人场或者捧个钱赏哈单例模式进阶(二)之Android中单例模式的应用场景


下一篇:工厂模式进阶。


欢迎关注,欢迎投稿。



以上是关于单例模式进阶之Android中单例模式的应用场景的主要内容,如果未能解决你的问题,请参考以下文章

java中单例模式singleton

Android框架设计模式——Singleton Method

Unity---游戏设计模式之单例模式

Java中单例模式和静态类的区别

单例模式的作用及创建方法

设计模式之单例模式