专栏名称: 安卓开发精选
伯乐在线旗下账号,分享安卓应用相关内容,包括:安卓应用开发、设计和动态等。
目录
相关文章推荐
郭霖  ·  Android音视频基础能力之音频路由 ·  3 天前  
鸿洋  ·  系统Apk、普通Apk、core ... ·  2 天前  
鸿洋  ·  一波深入的Android 性能优化 ·  3 天前  
郭霖  ·  activityGuard:Android ... ·  4 天前  
51好读  ›  专栏  ›  安卓开发精选

Android 源码中的静态工厂方法

安卓开发精选  · 公众号  · android  · 2016-10-05 10:16

正文

(点击上方公众号,可快速关注)


来源:伯乐在线专栏作者 - PleaseCallMeCoder

链接:http://android.jobbole.com/84838/

点击 → 了解如何加入专栏作者


我们知道工厂模式有三兄弟,通常我们说的工厂模式指的是工厂方法模式,它的应用频率最高。本篇博客分享的简单工厂模式是工厂方法模式的“小弟”,确切的来讲它不属于设计模式,而是一种方法。此外,工厂方法模式还有一位“大哥”——抽象工厂模式。


今天我们来分享一下简单工厂模式的一些情况,以及它在Android源码中的应用。


简单工厂模式


定义


简单工厂模式是类的创建模式,又叫做静态工厂方法(Static Factory Method)模式。简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例。


结构



简单工厂模式所涉及到的角色:


  • Product(抽象产品角色):产品的通用接口,定义产品的行为。

  • ConcreteProduct(具体产品角色):具体产品类,实现了Product接口。

  • Creator(工厂角色):工厂类,通过静态工厂方法factoryMethord来创建对象。


实现


抽象产品角色


abstract class Product {  

    //所有产品类的公共业务方法  

    public void methodSame() {  

        //公共方法的实现  

    }  

 

    //声明抽象业务方法  

    public abstract void methodDiff();  

}


具体产品角色


class ConcreteProduct extends Product {  

    //实现业务方法  

    public void methodDiff() {  

        //业务方法的实现  

    }  

}


工厂角色


class Creator {  

    //静态工厂方法  

    public static Product getProduct(String arg) {  

        Product product = null;  

        if (arg.equalsIgnoreCase("A")) {  

            product = new ConcreteProductA();  

            //初始化设置product  

        }  

        else if (arg.equalsIgnoreCase("B")) {  

            product = new ConcreteProductB();  

            //初始化设置product  

        }  

        return product;  

    }  

}


使用场景


在以下情况下可以考虑使用简单工厂模式:


  1. 工厂类负责创建的对象比较少,由于创建的对象较少,不会造成工厂方法中的业务逻辑太过复杂。

  2. 客户端只知道传入工厂类的参数,对于如何创建对象并不关心。


优点


  • 工厂类包含必要的判断逻辑,可以决定在什么时候创建哪一个产品类的实例,客户端可以免除直接创建产品对象的职责,而仅仅“消费”产品,简单工厂模式实现了对象创建和使用的分离。

  • 客户端无须知道所创建的具体产品类的类名,只需要知道具体产品类所对应的参数即可,对于一些复杂的类名,通过简单工厂模式可以在一定程度减少使用者的记忆量。


缺点


  • 系统扩展困难,一旦添加新产品就不得不修改工厂逻辑,在产品类型较多时,有可能造成工厂逻辑过于复杂,不利于系统的扩展和维护。

  • 简单工厂模式由于使用了静态工厂方法,造成工厂角色无法形成基于继承的等级结构。


Android中简单工厂模式的应用


在Android中我们了解的使用到了简单工厂方法的地方有Bitmap对象的获取、Fragment创建等。接下来我们分开看一下。


Bitmap源码分析


首先来说我们是不能通过new方法来创建Bitmap对象的,因为Bitmap类的构造函数是私有的,只能是通过JNI实例化。


接下来我们随便找个入口开始看,比如:


Bitmap bmp = BitmapFactory.decodeFile(String pathName);


我们把源码中的调用关系找出来,如下


public static Bitmap decodeFile(String pathName) {

    return decodeFile(pathName, null);

}

 

public static Bitmap decodeFile(String pathName, Options opts) {

    Bitmap bm = null;

    InputStream stream = null;

    try {

        stream = new FileInputStream(pathName);

        bm = decodeStream(stream, null, opts);

    } catch (Exception e) {

        /*  do nothing.

            If the exception happened on open, bm will be null.

        */

        Log.e("BitmapFactory", "Unable to decode stream: " + e);

    } finally {

        if (stream != null) {

            try {

                stream.close();

            } catch (IOException e) {

                // do nothing here

            }

        }

    }

    return bm;

}

 

public static Bitmap decodeStream(InputStream is, Rect outPadding, Options opts) {

    // we don't throw in this case, thus allowing the caller to only check

    // the cache, and not force the image to be decoded.

    if (is == null) {

        return null;

    }

 

    Bitmap bm = null;

 

    Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "decodeBitmap");

    try {

        if (is instanceof AssetManager.AssetInputStream) {

            final long asset = ((AssetManager.AssetInputStream) is).getNativeAsset();

            bm = nativeDecodeAsset(asset, outPadding, opts);

        } else {

            bm = decodeStreamInternal(is, outPadding, opts);

        }

 

        if (bm == null && opts != null && opts.inBitmap != null) {

            throw new IllegalArgumentException("Problem decoding into existing bitmap");

        }

 

        setDensityFromOptions(bm, opts);

    } finally {

        Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);

    }

 

    return bm;

}

 

private static native Bitmap nativeDecodeStream(InputStream is, byte[] storage,

        Rect padding, Options opts);

 

/**

* Set the newly decoded bitmap's density based on the Options.

*/

private static void setDensityFromOptions(Bitmap outputBitmap, Options opts) {

    if (outputBitmap == null || opts == null) return;

 

    final int density = opts.inDensity;

    if (density != 0) {

        outputBitmap.setDensity(density);

        final int targetDensity = opts.inTargetDensity;

        if (targetDensity == 0 || density == targetDensity || density == opts.inScreenDensity) {

            return;

        }

 

        byte[] np = outputBitmap.getNinePatchChunk();

        final boolean isNinePatch = np != null && NinePatch.isNinePatchChunk(np);

        if (opts.inScaled || isNinePatch) {

            outputBitmap.setDensity(targetDensity);

        }

    } else if (opts.inBitmap != null) {

        // bitmap was reused, ensure density is reset

        outputBitmap.setDensity(Bitmap.getDefaultDensity());

    }

}


我们来分析一下调用过程,可以看到decodeFile(String pathName)调用的是decodeFile(String pathName, Options opts),在两个参数的decodeFile方法中又去调用了decodeStream(InputStream is, Rect outPadding, Options opts)方法,然后最终调用nativeDecodeAsset或者nativeDecodeStream来构建Bitmap对象,这两个都是native方法(Android中使用Skia库来解析图像 )。再经过setDensityFromOptions方法的一些设置解码密度之类的操作,返回我们要的Bitmap对象。


/**

* Creates Bitmap objects from various sources, including files, streams, and byte-arrays.

*/


看下BitmapFactory的注释我们可以看到,这个工厂支持从不同的资源创建Bitmap对象,包括files, streams, 和byte-arrays,但是调用关系都大同小异。


Fragment创建


有时候,为了简化简单工厂模式,我们可以将抽象产品类和工厂类合并,将静态工厂方法移至抽象产品类中。Fragment的创建使用简单工厂方法没有抽象产品类,所以工厂类放到了实现产品类中。


在AndroidStudio中输入newInstance会自动补全Fragment的简单工厂方法。


public static TasksFragment newInstance() {

 

    Bundle args = new Bundle();

 

    TasksFragment fragment = new TasksFragment();

    fragment.setArguments(args);

    return fragment;

}


使用静态工厂方法,将外部传入的参数可以通过Fragment.setArgument保存在它自己身上,这样我们可以在Fragment.onCreate(…)调用的时候将这些参数取出来。


这样写有什么好处呢?


  • 避免了在创建Fragment的时候无法在类外部知道所需参数的问题。

  • Fragment推荐使用setArguments来传递参数,避免在横竖屏切换的时候Fragment自动调用自己的无参构造函数,导致数据丢失。


专栏作者简介( 点击 → 加入专栏作者 


PleaseCallMeCoder:我是 PleaseCallMeCoder,一个小小的90后程序员。热衷于移动开发,喜欢研究新技术,奔跑在成为大神的路上。

打赏支持作者写出更多好文章,谢谢!



 关注「安卓开发精选

看更多精选安卓技术文章
↓↓↓