有贊微商城POS機的模塊化過程演進

背景

有贊微商城接入了三個廠家的 POS 機罐栈,分別是旺 POS咕缎、商米、拉卡拉料扰,很久之前都是單獨維護這三個 POS 機的代碼分支凭豪,每次發(fā)版就需要把主線分支合并代碼到相應(yīng)的 POS 機的分支,這使得POS機的發(fā)版流程異常繁瑣晒杈,而且很容易讓 POS 機的版本內(nèi)容跟微商城手機 App 的版本不一致嫂伞。
除此之外,每個 POS 機的接入方式和使用方法都有很大的區(qū)別拯钻,使得對應(yīng)的業(yè)務(wù)代碼就很混亂帖努。此時恰逢微商城項目要搞模塊化,正好借機統(tǒng)一所有 POS 機的代碼粪般,讓所有 POS 機的使用在業(yè)務(wù)中是有統(tǒng)一接口去調(diào)用拼余,而且把所有代碼都合并到主分支,統(tǒng)一 POS 機和手機 App 的發(fā)版亩歹。

合并代碼

首先第一件事情是合并所有 POS 機分支的代碼進入主分支匙监,這個過程很艱苦,只能一點點抽取相關(guān)代碼小作,然后放到主分支里亭姥。剛才在背景中已經(jīng)提過正在搞模塊化,那自然是要把相應(yīng)的廠家 POS 機的 lib 包和定制的需求的代碼放到一個 module 里的顾稀。在這里我創(chuàng)建了三個 module达罗,分別 pos_iboxpay,pos_sunmi础拨,pos_wei氮块。參考下圖,因為涉及具體業(yè)務(wù)诡宗,此處不公開代碼滔蝉。

pos.png

統(tǒng)一調(diào)用接口

POS機定制需求放到相應(yīng)module里以后,那就是如何解決在業(yè)務(wù)中有統(tǒng)一的接口去調(diào)用POS機的功能的問題了塔沃。在業(yè)務(wù)中蝠引,需要使用POS機的打印和掃碼功能,那么就對應(yīng)創(chuàng)建了 POSPrinterBuilder蛀柴,POSBase 和 ScannerBuilder螃概,ScannerBase析蝴。其中POSPrinterBuilder 是在業(yè)務(wù)中使用的徒溪,POSBase 是讓pos_iboxpay厕九,pos_sunmi爬骤,pos_wei 這三個 module 里都創(chuàng)建的 POSPrinter 這個類去繼承的。
備注:因為要統(tǒng)一接口調(diào)用词疼,那么在每個 module 里創(chuàng)建的 POSPrinter.java 的路徑是需要保持一致的懒叛。
下面請看代碼:

public class POSPrinterBuilder {

    private static final String POS_PRINTER_CLASS_NAME = "com.qima.kdt.business.pos.POSPrinter";

    private Context mContext;
    private TradesItem mTradeItem;
    private POSPrintListener mPrintListener;
    private POSPrinterInitListener mPrintInitListener;
    private POSPrinterDestroyListener mPrintDestroyListener;

    public POSPrinterBuilder(Context context, POSPrinterInitListener listener) {
        mContext = context;
        mPrintInitListener = listener;
    }

    public POSPrinterBuilder setInitListener(POSPrinterInitListener listener){
        mPrintInitListener = listener;
        return this;
    };

    public POSPrinterBuilder destroy(POSPrinterDestroyListener listener){
        mPrintDestroyListener = listener;
        return this;
    };

    public POSPrinterBuilder setTradeItem(TradesItem tradeItem) {
        mTradeItem = tradeItem;
        return this;
    }

    public POSPrinterBuilder setPrintListener(POSPrintListener printListener) {
        mPrintListener = printListener;
        return this;
    }
    public POSBase build() {
        try {
            Constructor c = Class.forName(POS_PRINTER_CLASS_NAME).getConstructor(Context.class, POSPrinterInitListener.class);
            POSBase printer = (POSBase) c.newInstance(mContext, mPrintInitListener);
            if (mPrintInitListener != null) {
                printer.setInitListener(mPrintInitListener);
            }
            if (mPrintDestroyListener != null) {
                printer.destroy(mPrintDestroyListener);
            }
            if (mTradeItem == null){
                return printer;
            }
            printer.setPOSPrintListener(mPrintListener);
            printer.setTradesListItemEntity(mTradeItem);
            return printer;
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }

        return new POSBase(mContext, mPrintInitListener) {
            @Override
            public void print() {

            }
        };
    }
}
public abstract class POSBase {

    protected POSPrinterInitListener mPrintInitListener;
    protected POSPrinterDestroyListener mPrintDestroyListener;
    protected TradesItem mTradeItem;
    protected POSPrintListener mPrintListener;
    protected Context mContext;

    public POSBase(final Context context, POSPrinterInitListener listener) {
        mContext = context;
        mPrintInitListener = listener;
    }

    public void setInitListener(POSPrinterInitListener listener){
        mPrintInitListener = listener;
    };

    public void destroy(POSPrinterDestroyListener listener){
        mPrintDestroyListener = listener;
    };

    public void setTradesListItemEntity(TradesItem tradeItem) {
        mTradeItem = tradeItem;
    }

    public void setPOSPrintListener(POSPrintListener printListener) {
        mPrintListener = printListener;
    }

    public Context getContext() {
        return mContext;
    }

    public abstract void print();
}

以 pos_sunmi 中的 POSPrinter 為例:

  1. 創(chuàng)建 POSPrinter.java勇蝙,繼承 POSBase.java
  2. 把具體實現(xiàn)內(nèi)容寫在 print() 函數(shù)
  3. 業(yè)務(wù)代碼中只要創(chuàng)建 POSPrinterBuilder,調(diào)用 print() 函數(shù)即可

備注:加“@Keep”综液, 是因為要通過反射方式獲取POSPrinter款慨,要避免打包時被混淆

@Keep
public class POSPrinter extends POSBase {

    public static final String FLAVOR = "sunmi";

    private V1Printer mV1Printer;
    private ICallback mICallback;

    public POSPrinter(Context context, POSPrinterInitListener listener) {
        super(context, listener);
        init();
    }

    private void init() {

    //此處是POS機初始化具體代碼

    }

    @Override
    @WorkerThread
    public void print() {
        if (null != mPrintListener) {
            mPrintListener.onPrintStart();
        }
        new Thread(new Runnable() {
            @Override
            public void run() {
                printOrder();
            }
        }).start();
    }


    /**
     * 訂單打印
     */
    private void printOrder() {

    //此處是具體業(yè)務(wù)實現(xiàn)代碼

    }

}

new POSPrinterBuilder(TestActivity.this, null)
.setTradeItem(mItemEntity)
.setPrintListener(newPOSPrintListenerImp(TestActivity.this))
.build()
.print();

多個FLAVOR問題的解決

由于已經(jīng)把 POS 機的業(yè)務(wù)代碼和手機App的代碼合并成一套,首先想到的是用 BuildConfig.FLAVOR 字段去區(qū)分業(yè)務(wù)邏輯谬莹,之前寫過模塊化項目中如何做檩奠,參考鏈接 http://www.reibang.com/p/e86a3b71a88a/ ,但是這個使用方法如果很多業(yè)務(wù)模塊都要使用附帽,會讓 app 中的 build.gradle 和每個 module 下的 build.gradle 文件配置比較麻煩埠戳。 為此,這里使用反射去解決這個問題士葫。
方案如下:

  1. 在 app 中的 build.gradle 文件中配置 flavor
productFlavors {

  iboxpay {
      buildConfigField "boolean", "POS", "true"
      buildConfigField "String", "DEVICE_TYPE", "\"android-iboxpay\""
  }

  sunmi {
      buildConfigField "boolean", "POS", "true"
      buildConfigField "String", "DEVICE_TYPE", "\"android-sunmi\""
      applicationId "${sunmiId}"
  }

  weipos {
      buildConfigField "boolean", "POS", "true"
      buildConfigField "String", "DEVICE_TYPE", "\"android-weipos\""
  }
}

dependencies {
  sunmiCompile project(':pos_sunmi')
  iboxpayCompile project(':pos_iboxpay')
  weiposCompile project(':pos_wei')
}

2.在每個對應(yīng)的 POS 機的 module 的 POSPrinter 類里定義一個名字為 FLAVOR 的 public 的 static 的string乞而,例如 pos_sunmi 這個 module 里是
public static final String FLAVOR = "sunmi";

3.在 application create 的時候,通過反射方式去讀取對應(yīng)編譯的 POS 機 module 的 POSPrinter 的 static 類型的字符串 "FLAVOR"

public static String FLAVOR = "full";
public static boolean POS = false;
public static void searchPosPrinter(){
    try {
        Class clazz =  Class.forName(POS_PRINTER_CLASS_NAME);
        Field flavorField  = clazz.getField("FLAVOR");
        FLAVOR = (String)flavorField.get(null);
        POS = true;
    } catch (NoSuchFieldException e) {
        e.printStackTrace();
    } catch (ClassNotFoundException e) {
        POS = false;
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        POS = false;
        e.printStackTrace();
    }
}

4.在業(yè)務(wù)代碼中直接使用 FLAVOR 和 POS 的值慢显,不使用 BuildConfig 的值
5.可以開始編譯一個 POS 機的 apk 了,例如 ./gradlew clean assembleSunmiRelease,則只會加載 pos_sunmi 這個 module。

總結(jié)

  1. 同一個項目欠啤,代碼盡量不要散落在不同分支荚藻,一定要保證主分支的代碼的完整性
  2. 當(dāng)具體業(yè)務(wù)具有很多相似操作的時候,盡量封裝一下實現(xiàn)面向接口編程洁段,這會減輕以后業(yè)務(wù)拓展的工作量
  3. 合理使用反射应狱,不要談“反射”色變
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市祠丝,隨后出現(xiàn)的幾起案子疾呻,更是在濱河造成了極大的恐慌,老刑警劉巖写半,帶你破解...
    沈念sama閱讀 222,378評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件岸蜗,死亡現(xiàn)場離奇詭異,居然都是意外死亡叠蝇,警方通過查閱死者的電腦和手機璃岳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,970評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來悔捶,“玉大人铃慷,你說我怎么就攤上這事⊥筛茫” “怎么了犁柜?”我有些...
    開封第一講書人閱讀 168,983評論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長堂淡。 經(jīng)常有香客問我馋缅,道長扒腕,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,938評論 1 299
  • 正文 為了忘掉前任股囊,我火速辦了婚禮袜匿,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘稚疹。我一直安慰自己居灯,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,955評論 6 398
  • 文/花漫 我一把揭開白布内狗。 她就那樣靜靜地躺著怪嫌,像睡著了一般。 火紅的嫁衣襯著肌膚如雪柳沙。 梳的紋絲不亂的頭發(fā)上岩灭,一...
    開封第一講書人閱讀 52,549評論 1 312
  • 那天,我揣著相機與錄音赂鲤,去河邊找鬼噪径。 笑死,一個胖子當(dāng)著我的面吹牛数初,可吹牛的內(nèi)容都是我干的找爱。 我是一名探鬼主播,決...
    沈念sama閱讀 41,063評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼泡孩,長吁一口氣:“原來是場噩夢啊……” “哼车摄!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起仑鸥,我...
    開封第一講書人閱讀 39,991評論 0 277
  • 序言:老撾萬榮一對情侶失蹤吮播,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后眼俊,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體意狠,經(jīng)...
    沈念sama閱讀 46,522評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,604評論 3 342
  • 正文 我和宋清朗相戀三年泵琳,在試婚紗的時候發(fā)現(xiàn)自己被綠了摄职。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,742評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡获列,死狀恐怖谷市,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情击孩,我是刑警寧澤迫悠,帶...
    沈念sama閱讀 36,413評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站巩梢,受9級特大地震影響创泄,放射性物質(zhì)發(fā)生泄漏艺玲。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,094評論 3 335
  • 文/蒙蒙 一鞠抑、第九天 我趴在偏房一處隱蔽的房頂上張望饭聚。 院中可真熱鬧,春花似錦搁拙、人聲如沸秒梳。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,572評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至盐茎,卻和暖如春兴垦,著一層夾襖步出監(jiān)牢的瞬間字柠,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,671評論 1 274
  • 我被黑心中介騙來泰國打工窑业, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留扶关,地道東北人。 一個月前我還...
    沈念sama閱讀 49,159評論 3 378
  • 正文 我出身青樓数冬,卻偏偏與公主長得像搀庶,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子哥倔,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,747評論 2 361

推薦閱讀更多精彩內(nèi)容