溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

JJEvent 一個(gè)可靠的Android端數(shù)據(jù)埋點(diǎn)SDK

發(fā)布時(shí)間:2020-07-12 21:25:18 來(lái)源:網(wǎng)絡(luò) 閱讀:223 作者:wx5bc013d4e741f 欄目:移動(dòng)開(kāi)發(fā)

  • 本文是原理介紹
  • 這里是如何使用傳送門
  • 這里是源碼地址

V1.0.0功能列表 是否支持
接口自定義 支持
緩存策略 支持
外部cookie注入 支持
推送周期設(shè)定 支持
強(qiáng)制推送 支持
自定義埋點(diǎn)事件 支持
獨(dú)立運(yùn)行 支持
多線程寫(xiě)入 支持
后臺(tái)線程服務(wù) 支持

注:代碼已經(jīng)經(jīng)過(guò)線上項(xiàng)目驗(yàn)證, 橫向Google統(tǒng)計(jì)對(duì)比,統(tǒng)計(jì)數(shù)據(jù)無(wú)丟失,性能穩(wěn)定.

JJEvent 一個(gè)可靠的Android端數(shù)據(jù)埋點(diǎn)SDK

項(xiàng)目背景

統(tǒng)計(jì)數(shù)據(jù) 是BI做大數(shù)據(jù),智能推薦,千人千面,機(jī)器學(xué)習(xí)的 數(shù)據(jù)源和依據(jù).
在這個(gè)app都是千人千面,智能推薦,ab流量測(cè)試的時(shí)代, 一個(gè)可以根據(jù)BI部門的需求, 可以自有定制的 數(shù)據(jù)統(tǒng)計(jì)上報(bào), 就顯得非常重要.

目前, 市面上 做統(tǒng)計(jì)的第三方平臺(tái)有很多, 比如最出名的Google的GTM統(tǒng)計(jì),友盟統(tǒng)計(jì)等等.

但是 這些統(tǒng)計(jì), 第一點(diǎn),就是上傳的頻率,比較固定, 難以滿足要求不同的頻次需求. 第二點(diǎn),需要統(tǒng)計(jì)到的字段和規(guī)則都是死板的,無(wú)法定制.

目前GitHub上, 沒(méi)有一個(gè) 自定義的 統(tǒng)計(jì)SDK 思路和源碼.

我想,在這里分享下,我的思路和代碼.

這里有幾個(gè)要點(diǎn)

  • 統(tǒng)計(jì)分類:統(tǒng)計(jì)分為屏幕值,事件兩種,后續(xù)可能擴(kuò)展.
  • 統(tǒng)計(jì)規(guī)則: 支持簡(jiǎn)單Google統(tǒng)計(jì)方式,支持自定義字段.
  • 推送方式:每?jī)煞昼娚蟼鞯?a title="服務(wù)器" target="_blank" href="http://www.kemok4.com/">服務(wù)器,
  • 作為sdk,可以單獨(dú)集成,獨(dú)立運(yùn)行.

這是一個(gè)什么樣的統(tǒng)計(jì)SDK?

做統(tǒng)計(jì)SDK的方式有這兩種

1.用AOP的處理方式, 在方法內(nèi),插入統(tǒng)計(jì)代碼. 這種方式雖然在.java文件里 沒(méi)有代碼侵入,但是可定制行不高,只適合簡(jiǎn)單的 統(tǒng)計(jì)需求.

2.用普通的方法樣式,使用GTM.event(xxx)方式,代碼侵入極高, 但是可以實(shí)現(xiàn)高度自定義.

現(xiàn)階段, 我會(huì)采用第二種方式,為了數(shù)據(jù)的精確要求,采用侵入式.

后續(xù), 我會(huì)繼續(xù)思考,更好的實(shí)現(xiàn)方式. 也請(qǐng)大家一起分享自己的思路.

因?yàn)榻y(tǒng)計(jì)規(guī)則業(yè)務(wù)定制性很強(qiáng),無(wú)法對(duì)傳送數(shù)據(jù)進(jìn)行統(tǒng)一的抽象管理, 該項(xiàng)目就不單獨(dú)發(fā)布到j(luò)center,
如果需要,可以參考源碼思路, 自己修改源碼,修改數(shù)據(jù)載體,實(shí)現(xiàn)需求即可.


JJEvent設(shè)計(jì)初衷為:一個(gè)統(tǒng)計(jì)SDK, 可以單獨(dú)發(fā)布到倉(cāng)庫(kù),單獨(dú)被項(xiàng)目依賴而不產(chǎn)生沖突,擁有自己的數(shù)據(jù)存儲(chǔ),網(wǎng)絡(luò)請(qǐng)求.


1.上傳規(guī)則

這些都是可以自定義的,修改源碼即可

  • 固定周期進(jìn)行上傳: 比如每2分鐘,進(jìn)行一次數(shù)據(jù)上傳.數(shù)據(jù)為 觸發(fā)推送的時(shí)間節(jié)點(diǎn) 之前的數(shù)據(jù).用于大部分統(tǒng)計(jì).

  • 固定條數(shù)進(jìn)行上傳: 比如每100條,進(jìn)行一次數(shù)據(jù)上傳.數(shù)據(jù)為 觸發(fā) 觸發(fā)100條推送開(kāi)始 之前的數(shù)據(jù).用于大部分統(tǒng)計(jì).

  • 實(shí)時(shí)上傳:每次點(diǎn)擊就進(jìn)行push操作.數(shù)據(jù)為 觸發(fā)推送的時(shí)間節(jié)點(diǎn) 之前的數(shù)據(jù).用于特定統(tǒng)計(jì).
2.統(tǒng)計(jì)分類

這里, 可以根據(jù)BI的業(yè)務(wù)需求而定, 大家可以在此基礎(chǔ)上修改.

1.PV(PageView) 屏幕事件
  • sn(screen) 屏幕名稱 遵循舊策略(Android/好價(jià)/好價(jià)詳情頁(yè)/title).
  • ltp 屏幕加載方式 下拉刷新=1、翻頁(yè)=2、標(biāo)簽切換=3、局部彈屏4、篩選刷新=5.
  • ecp 自定義事件 ,json map存儲(chǔ).
2.Event 點(diǎn)擊事件
  • ec(event category) 事件類別
  • ea(event action) 事件操作
  • el(event lable) 事件標(biāo)簽
  • ecp 自定義事件 ,json map存儲(chǔ).
3.expose曝光 事件
  • url 曝光url
  • ecp 自定義事件 ,json map存儲(chǔ).
4. 其他事件

支持自定義擴(kuò)展

SDK抽象過(guò)程

面向?qū)ο笳Z(yǔ)言的特點(diǎn): 就是要面向?qū)ο缶幊?面向接口編程.當(dāng)你在抽象的過(guò)程中,只關(guān)注某個(gè)對(duì)象是什么,然后他擁有什么屬性,什么功能即可.不需要考慮其中的實(shí)現(xiàn).這也就是Java乃至面向?qū)ο笳Z(yǔ)言,為啥這么多類的原因,這其中有單一職責(zé)原則,接口分隔原則.

模塊之間的依賴,應(yīng)該最大程度的依賴抽象.

      要想完整的把整個(gè)過(guò)程抽象清楚,需要對(duì)整個(gè)流程有個(gè)最大的認(rèn)知.
判斷邏輯,技術(shù)選型

思考:肯定會(huì)想到這些東西,只不過(guò)想到的過(guò)程可能不同,而且每個(gè)設(shè)計(jì)者,想法都不會(huì)一樣,實(shí)現(xiàn)過(guò)程也不一樣.

首先需要一個(gè)配置類Constant ,對(duì)常量,開(kāi)關(guān)進(jìn)行管理.

一個(gè)sdk有事件統(tǒng)計(jì),那么必須要有一個(gè)Event類來(lái)進(jìn)行屏幕值,事件兩種統(tǒng)計(jì)動(dòng)作.

統(tǒng)計(jì)事件發(fā)生后, 需要一個(gè)持久化過(guò)程DbHelper,即需要一個(gè)數(shù)據(jù)庫(kù)支持存取.

如何推送呢? 需要建立一個(gè)后臺(tái)服務(wù)JJService,對(duì)數(shù)據(jù)進(jìn)行推送.

用什么推送呢?肯定需要網(wǎng)絡(luò)啊, 需要一個(gè)網(wǎng)絡(luò)模塊NetHelper從數(shù)據(jù)庫(kù)中拿數(shù)據(jù),進(jìn)行推送.

推送的是什么呢? 需要建一個(gè)任務(wù)Task,讓task承載推送的過(guò)程.

如何將模塊進(jìn)行連接,統(tǒng)一管理?

SDK整體架構(gòu)

1.統(tǒng)計(jì)客戶端SDK架構(gòu)圖

JJEvent 一個(gè)可靠的Android端數(shù)據(jù)埋點(diǎn)SDK

2.服務(wù)端數(shù)據(jù)收集采用的是
  • openresty實(shí)現(xiàn)客戶端日志上報(bào)接口
  • flume實(shí)現(xiàn)日志采集發(fā)送kafka
  • 最終落地到硬盤(pán)
3. 大數(shù)據(jù)端

經(jīng)過(guò)抓取數(shù)據(jù)庫(kù)數(shù)據(jù)快照 ,進(jìn)行數(shù)據(jù)清洗,然后提供給機(jī)器學(xué)習(xí),或者千人千面.

模塊建設(shè)

這里如果有興趣,請(qǐng)配合源代碼.

1.JJEventManager管理模塊

首先,sdk的生命周期是整個(gè)application的周期,所以我讓sdk 持有application 上下文,不會(huì)存在內(nèi)存泄漏.所以,我考慮將全局上下文放在這里管理.當(dāng)其他位置需要的時(shí)候到JJEventManager .getContext() 取值.

作為管理類,需要擁有控制sdk完整生命周期的功能.即init(),cancelPush(),destroy()等方法.讓各個(gè)模塊的生命周期在這里管理.

然后考慮到,讓用戶可以動(dòng)態(tài)配置各種參數(shù),比如周期,是否是debug模式,主動(dòng)推送周期等等.所以在內(nèi)部使用buider模式,進(jìn)行動(dòng)態(tài)構(gòu)建.

        JJEventManager.Builder builder =new JJEventManager.Builder(this);
        builder.setHostCookie("s test=cookie String;")//cookie
                .setDebug(false)//是否是debug
                .setSidPeriodMinutes(15)//sid改變周期
                .setPushLimitMinutes(0.10)//多少分鐘 push一次
                .setPushLimitNum(100)//多少條 就主動(dòng)進(jìn)行push
                .start();//開(kāi)始
    }

2.Event動(dòng)作模塊

動(dòng)作類,統(tǒng)計(jì)只有兩個(gè)動(dòng)作,即兩個(gè)方法screen (),event(),以及一些重載方法.

因?yàn)槭枪_(kāi)類,所以要做到簡(jiǎn)潔,注釋要到位..(導(dǎo)入項(xiàng)目中的jar包,沒(méi)有Java document..因?yàn)閐oc生成在本地..云端沒(méi)有)

由于是數(shù)據(jù)入口類,所有堅(jiān)決不能存在崩潰的情況發(fā)生.
所以在相應(yīng)的地方加上了try catch處理.

/**
 * 統(tǒng)計(jì)入口
 * Created by chenchangjun on 18/2/8.
 */
public final class JJEvent {
    /**
     * pageview 屏幕值
     * @param sn  screen 屏幕值,例`Android/主頁(yè)/推薦`
     * @param ltp 屏幕加載方式
     */
    public static void screen(String sn, LTPType ltp) {
        screen(sn, ltp, null);
    }
   /**
     * pageview 屏幕值
    * @param sn  screen 屏幕值,例`Android/主頁(yè)/推薦`
     * @param ltp 屏幕加載方式
     * @param ecp event custom Parameters 自定義參數(shù)Map<key,value>
     */

    public static void screen(String sn, LTPType ltp, Map ecp) {

         try {
                  ScreenTask screenTask =new ScreenTask(sn,ltp,ecp);
                  JJPoolExecutor.getInstance().execute(new FutureTask<Object>(screenTask,null));
              } catch (Exception e) {
                  e.printStackTrace();
                  ELogger.logWrite(EConstant.TAG, "expose " + e.getMessage());

              }

    }

將處理細(xì)節(jié)交給其他類處理,這里我用了一個(gè) Event包裝類EventDecorator來(lái)做EventBean中統(tǒng)一的數(shù)據(jù)緩存,參數(shù)值處理.遵循單一職責(zé)原則.

注意:

在修改數(shù)據(jù)體EventBean來(lái)滿足業(yè)務(wù)需求時(shí), 請(qǐng)?jiān)?code>EventDecorator的相關(guān)方法中進(jìn)行修改.

3.DBHelper模塊

剛開(kāi)始想用模板方法繼承來(lái)做,將CRUD的實(shí)現(xiàn)放在宿主中,

但是, 由于用戶不太清楚sdk內(nèi)部實(shí)現(xiàn)邏輯,用戶維護(hù)sdk的成本太高.所以,我就重新裁剪了開(kāi)源的XUtils中的dbUtils,然后修改類名,作為db服務(wù).

4.ThreadPool模塊

為了減少UI線程的壓力, 有必要將數(shù)據(jù)操作放到子線程中. 考慮到數(shù)據(jù)量時(shí)大時(shí)小, 所以需要自定義一個(gè)線程池,來(lái)管理線程和縣城任務(wù).

這里, 最主要的就是 控制好線程的對(duì)共享變量的訪問(wèn)鎖.保證線程的原子性和可見(jiàn)性.

將所有Event任務(wù),作為一個(gè)Runable,放到阻塞隊(duì)列中,讓線程池隊(duì)列執(zhí)行.注意設(shè)置runable超時(shí)時(shí)間,異常處理.盡量保證數(shù)據(jù)錄入成功.

要注意的是, Event任務(wù) 執(zhí)行有快有慢, 所以,最終保存到數(shù)據(jù)庫(kù)的時(shí)候, 并不是按照隊(duì)列的順序.

4.1 如何保證線程安全?

對(duì)于變量
比如int eventNum=1;
線程在執(zhí)行過(guò)程中, 會(huì)將主內(nèi)存區(qū)的變量,拷貝到線程內(nèi)存中, 當(dāng)修改完a后,再將a的值返回到主內(nèi)存中.這個(gè)時(shí)候,如果兩個(gè)線程同時(shí)修改該變量,第三個(gè)線程在訪問(wèn)的時(shí)候,很有可能a的值還沒(méi)有改變.這個(gè)時(shí)候就會(huì)讓a的改變不可見(jiàn).所以,可以用線程安全變量AtomicInteger,或者原子性變量volatile,讓他們咋發(fā)生改變的時(shí)候,立刻通知主內(nèi)存中的變量.

對(duì)于方法
為了保證線程間訪問(wèn)方法互斥, 用synchronized對(duì)線程訪問(wèn)方法,進(jìn)行同步.保證線程順序執(zhí)行.即要將所有共通操作,放到一個(gè)加載器方法中,用synchronized同步.

另外,避免線程濫用,性能浪費(fèi), 要仔細(xì)考量voliate,synchronized等字段的頻次.

詳情處理可見(jiàn)EventDecorator.java中的 變量處理.

4.2 sqlite數(shù)據(jù)庫(kù)是否 線程安全?

目前, 統(tǒng)計(jì)sdk狀態(tài)是

  • 多個(gè)線程同時(shí)執(zhí)行數(shù)據(jù)庫(kù)操作,

  • Timer擁有自己的單線程 執(zhí)行數(shù)據(jù)庫(kù)讀取.

要保證數(shù)據(jù)庫(kù)使用的安全,一般可以采用如下幾種模式

SQLite 采用單線程模型,用專門的線程/隊(duì)列(同時(shí)只能有一個(gè)任務(wù)執(zhí)行訪問(wèn)) 進(jìn)行訪問(wèn)
SQLite 采用多線程模型,每個(gè)線程都使用各自的數(shù)據(jù)庫(kù)連接 (即 sqlite3 *)
SQLite 采用串行模型,所有線程都共用同一個(gè)數(shù)據(jù)庫(kù)連接。

在本SDK中,采用串行模式,在初始化過(guò)程中,SQLiteDatabase靜態(tài)單例, 來(lái)保證線程安全.

項(xiàng)目經(jīng)過(guò)測(cè)試部門,和線上檢驗(yàn),線程間訪問(wèn)正確,數(shù)據(jù)統(tǒng)計(jì)正確.

5.NetHelper模塊

首先,net請(qǐng)求,我裁剪的是volley.

NetHelper應(yīng)該采用的是靜態(tài)或者單例,采用單例的原因是,他的生命周期和application同級(jí).功能應(yīng)該是 接受數(shù)據(jù),然后推送數(shù)據(jù),最后暴露告知結(jié)果.封裝里面的請(qǐng)求轉(zhuǎn)發(fā)邏輯.

NetHelper網(wǎng)絡(luò)模塊,應(yīng)該有一個(gè)請(qǐng)求隊(duì)列(避免請(qǐng)求數(shù)據(jù)錯(cuò)亂),,還應(yīng)該提供針對(duì)不同EventType進(jìn)行不同處理請(qǐng)求的方法,然后還需要一個(gè)統(tǒng)一的網(wǎng)絡(luò)請(qǐng)求監(jiān)聽(tīng).

為了保證 推送不出現(xiàn)數(shù)據(jù)錯(cuò)亂,應(yīng)該在上一次網(wǎng)絡(luò)訪問(wèn)沒(méi)有結(jié)束前,不能繼續(xù)訪問(wèn)的鎖,用鎖isLoading來(lái)控制.

將 請(qǐng)求分發(fā)邏輯,是否正在請(qǐng)求,以及監(jiān)聽(tīng)完全封裝在里面.對(duì)外只暴露OnNetResponseListener.

按照上述邏輯,調(diào)用方式是這樣的.簡(jiǎn)單實(shí)用.


        ENetHelper.create(JJEventManager.getContext(), new OnNetResponseListener() {
            @Override
            public void onPushSuccess() {
                //5*請(qǐng)求成功,返回值正確, 刪除`cut_point_date`之前的數(shù)據(jù)
                EDBHelper.deleteEventListByDate(cut_point_date);
            }

            @Override
            public void onPushEorr(int errorCode) {
                //.請(qǐng)求成功,返回值錯(cuò)誤,根據(jù)接口返回值,進(jìn)行處理.
            }

            @Override
            public void onPushFailed() {
                //請(qǐng)求失敗;不做處理.

            }
        }).sendEvent(EConstant.EVENT_TYPE_DEFAULT, list);

6. EPushTask模塊

Push的邏輯比較復(fù)雜,所以更需要這個(gè)類,專門來(lái)做push任務(wù).

6.1 如何保證 數(shù)據(jù) 推送不會(huì)出現(xiàn)重復(fù)推送,或者缺少數(shù)據(jù)?

請(qǐng)看如下push的邏輯.
JJEvent 一個(gè)可靠的Android端數(shù)據(jù)埋點(diǎn)SDK

  經(jīng)過(guò)測(cè)試部和線上數(shù)據(jù)驗(yàn)證, 數(shù)據(jù)量統(tǒng)計(jì)無(wú)誤,沒(méi)有重復(fù)數(shù)據(jù),沒(méi)有遺漏數(shù)據(jù).

7.EPushService模塊

這應(yīng)該是一個(gè)后臺(tái)服務(wù)模塊. 功能應(yīng)該有 開(kāi)啟服務(wù),周期推送,主動(dòng)推送,停止推送.

需不需要用一個(gè)不會(huì)被殺死的后臺(tái)服務(wù)?

答案是不需要,

1.從用戶體驗(yàn)上講,一個(gè)系統(tǒng)殺不死的服務(wù),是一個(gè)用戶體驗(yàn)極差的處理方式.有些手機(jī) 甚至?xí)崾?該app正在后臺(tái)運(yùn)行.

2.從sdk必要屬性上講, 統(tǒng)計(jì)sdk,只有app在前臺(tái)的時(shí)候,才會(huì)有事件統(tǒng)計(jì).所以推送服務(wù)沒(méi)有必要一直存在.

3.當(dāng)系統(tǒng)內(nèi)存不足的時(shí)候, 會(huì)把后臺(tái)推送線程殺死. 但是殺死的僅僅是周期推送 ,數(shù)據(jù)記錄并不會(huì)停止. 等待滿足條件 (100條記錄),就會(huì)主動(dòng)推送.

所以,結(jié)論是 推送服務(wù),僅僅需要在用戶可見(jiàn)的情況下,進(jìn)行即可. 線程是否被殺死,影響的僅僅是推送到服務(wù)器是否及時(shí).

經(jīng)過(guò)考量, 采用Timer+TimerTask的方式,進(jìn)行周期推送服務(wù).因?yàn)?雖然Timer不保證任務(wù)執(zhí)行的十分精確。 但是Timer類的線程安全的。

而且TimerTask是在子線程中,不會(huì)push服務(wù)不會(huì)阻塞主線程.

sdk整體框架調(diào)整

1.訪問(wèn)權(quán)限

sdk 對(duì)外暴露類和方法,要盡可能少.只暴露用戶可操作的方法.隱藏其他細(xì)節(jié).
所以在這個(gè)sdk中,用戶只需要知道 設(shè)置必要參數(shù),開(kāi)啟,添加統(tǒng)計(jì)即可,其他無(wú)需了解.

所以,我對(duì)訪問(wèn)權(quán)限進(jìn)行了處理,只公開(kāi)以下類,以及相應(yīng)方法.

  • JJEventManager 事件管理

    • JJEventManager.init() 初始化

    • JJEventManager.cancelEventPush()取消推送

    • JJEventManager.destoryEventService()終止所有服務(wù)
  • JJEvent 統(tǒng)計(jì)入口

    • JJEvent.event(String ec, String ea, String el) 事件

    • JJEvent.screen(String sn, LTPType ltp)屏幕值
3.sdk唯一性

為了保證sdk命名唯一性,采用所有必要模塊加前綴E代表Event的處理方式,
避免出現(xiàn)在業(yè)務(wù)層 查看調(diào)用出處的時(shí)候,造成誤解.比如

JJEvent 一個(gè)可靠的Android端數(shù)據(jù)埋點(diǎn)SDK

后期,在我們做自己的業(yè)務(wù)線的時(shí)候,大家也可以采用這種方法.

2.sdk生成,版本管理,混淆打包

自己在gradle中寫(xiě)了一個(gè)打包腳本,讓打包的過(guò)程,自動(dòng)化.詳情見(jiàn)源碼.

task release_jj_analytics_lib_aar(group:"JJPackaged",type: Copy) {
    delete('build/myaar')
    from( 'build/outputs/aar')
    into( 'build/mylibs')
    include('analytics_lib-release.aar')
    rename('analytics_lib-release.aar', 'jj-analytics-lib-v' + rootProject.ext.versionName +'-release'+ '.aar')
}
release_jj_analytics_lib_aar.dependsOn("build")

JJEvent 一個(gè)可靠的Android端數(shù)據(jù)埋點(diǎn)SDK

當(dāng)然, 也可以將sdk放到Nexus Maven倉(cāng)庫(kù),或者公司私有倉(cāng)庫(kù),進(jìn)行api依賴.

2.3 sdk需不需要混淆?

這個(gè)問(wèn)題我考慮了很久, sdk給自己用,用的著混淆嘛? 混淆會(huì)不會(huì)讓同事們可讀性變差,想到最后,發(fā)現(xiàn)app上線前,也需要打包混淆.如果我在app的progurd.rules中,添加各種規(guī)則,那么sdk用起來(lái)很繁瑣.

so~ , 我在 jar 包打包前,進(jìn)行了必要混淆,keep了兩個(gè)公開(kāi)類.

現(xiàn)在,在任何app如果想使用sdk, 那么只需要 app的progurd.rules中添加兩句混淆規(guī)則即可.

-dontwarn com.ccj.client.android.analyticlib.**
-keep class com.ccj.client.android.analytics.**{*;}

總結(jié)思考

  1. 在本sdk中,
    由于所有動(dòng)作的生命周期,是全局周期,所以,選擇了sdk持有applicatin上下文進(jìn)行操作.
    對(duì)于需要上下文的地方,直接用持有applicatin,可以考慮
    DBHelper中方法是靜態(tài)的,由于依賴于其中Java靜態(tài)方法,不能被靜態(tài)實(shí)現(xiàn)..,所以依賴的實(shí)現(xiàn).后期可以采用單例進(jìn)行處理.

  2. 無(wú)從下手的感覺(jué)...無(wú)從下手的感覺(jué)的根本原因就是你沒(méi)有下手去做..寫(xiě)寫(xiě),畫(huà)畫(huà),慢慢就會(huì)了然于胸.

后期優(yōu)化

為了操作方便,直接讓EDBHelper,ENetHelper直接作為靜態(tài)類...

后期可以用單例取代.在管理類JJEventManager中,統(tǒng)一初始化.這樣,就可以 依賴抽象.比如持有DBDao.saveEvent(),而不是用實(shí)現(xiàn)類EDBHelper.saveEvent().就避免了后期牽一發(fā)而動(dòng)全身的問(wèn)題.

About Me

===

CSDN:http://blog.csdn.net/ccj659/article/

簡(jiǎn)書(shū):http://www.jianshu.com/u/94423b4ef5cf

github: https//github.com/ccj659/

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI