Android源碼中的靜態工廠方法

我們知道工廠模式有三兄弟,通常我們說的工廠模式指的是工廠方法模式,它的應用頻率最高。本篇博客分享的簡單工廠模式是工廠方法模式的“小弟”,確切的來講它不屬於設計模式,而是一種方法。此外,工廠方法模式還有一位“大哥”——抽象工廠模式。

今天我們來分享一下簡單工廠模式的一些情況,以及它在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自動調用自己的無參構造函數,導致數據丟失。

發佈了40 篇原創文章 · 獲贊 143 · 訪問量 21萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章