流量管理工作總結(jié)

流量管理的位置在com.oneplus.security.network下面


流量管理目錄結(jié)構(gòu)圖

包含

  • calibrate 校準(zhǔn)功能包


    calibrate部分代碼結(jié)構(gòu)圖
    • AutoCalibrateBootReceiver
<receiver android:name="com.oneplus.security.network.calibrate.AutoCalibrateBootReceiver"  
    android:exported="true"
    android:enabled="true" >
        <intent-filter>
            <action android:name="android.intent.action.BOOT_COMPLETED" />
            <action android:name="android.intent.action.DATE_CHANGED"/>
        </intent-filter>
</receiver>

接收日期變更和開機(jī)的廣播來自行流量自動校驗

        String action = intent.getAction();
        if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
            Log.d(TAG, "set up auto calibrate function according to preference config state");
            Intent changeDateIntent = new Intent(context, AutoCalibrateService.class);
            changeDateIntent.putExtra(AutoCalibrateUtil.KEY_AUTO_CALIBRATE_SERVICE_TASK,
                    AutoCalibrateUtil.INDEX_CONFIG_AUTO_CALIBRATE_TASK);
            context.startService(changeDateIntent);
        } else if (Intent.ACTION_DATE_CHANGED.equals(action)) {
            Intent changeDateIntent = new Intent(context, AutoCalibrateService.class);
            changeDateIntent.putExtra(AutoCalibrateUtil.KEY_AUTO_CALIBRATE_SERVICE_TASK,
                    AutoCalibrateUtil.INDEX_CHECK_CALIBRATE_DATE_TASK);
            context.startService(changeDateIntent);
        }
  • AutoCalibrateService
    這個文件是自動流量校準(zhǔn)的主服務(wù)
@Override
    public int onStartCommand (Intent intent, int flags, int startId) {
        Log.i(TAG, "called on start command ");
        if (null != intent) {
            int taskIndex = intent.getIntExtra(AutoCalibrateUtil.KEY_AUTO_CALIBRATE_SERVICE_TASK,
                    AutoCalibrateUtil.INDEX_INVALID_TASK);
            mCurrentSlotId = mSimcardDataModel.getCurrentTrafficRunningSlotId();
            if (AutoCalibrateUtil.INDEX_START_AUTO_CALIBRATE_TASK == taskIndex) {
                //啟動一個異步任務(wù)來調(diào)用歐朋sdk的短信查詢接口,在回調(diào)中對本地保存的流量數(shù)據(jù)及上一次校準(zhǔn)信息做處理
            } else if (AutoCalibrateUtil.INDEX_CHECK_CALIBRATE_DATE_TASK == taskIndex) {
                // 響應(yīng)日期變化并且更新與月結(jié)日相關(guān)的功能邏輯
            } else if (AutoCalibrateUtil.INDEX_CONFIG_AUTO_CALIBRATE_TASK == taskIndex) {
                // 發(fā)生手機(jī)重啟時,檢查是否已經(jīng)配置了流量自動校準(zhǔn)服務(wù),并相應(yīng)選擇恢復(fù)或者退出
                stopSelf();
            } else {
                stopSelf();
            }
        } else {
            stopSelf();
        }
        return super.onStartCommand(intent, flags, startId);
    }
  • AutoCalibrateUtil
    這個文件是為了保存和獲取雙卡場景下流量自動校準(zhǔn)配置信息所做的工具類,數(shù)據(jù)存儲使用SharedPreference
    public static final int INDEX_CHECK_CALIBRATE_DATE_TASK = 0;//接收到日期變更后觸發(fā),任務(wù)發(fā)送給AutoCalibrateService處理
    public static final int INDEX_START_AUTO_CALIBRATE_TASK = 1;//在設(shè)置中使用,打開自動校準(zhǔn)開關(guān)后生效
    public static final int INDEX_CONFIG_AUTO_CALIBRATE_TASK = 2;//接收到開機(jī)廣播觸發(fā),用于重啟因手機(jī)掉電導(dǎo)致的Alarm丟失
// 以下為針對雙卡場景定義的用于保存自動校準(zhǔn)相關(guān)SharedPreference的key 
private static final String KEY_AUTO_CALIBRATE_SWITCH_SIM_ONE = "key_auto_calibrate_switch_sim_one";
    private static final String KEY_AUTO_CALIBRATE_SWITCH_SIM_TWO = "key_auto_calibrate_switch_sim_two";

    private static final String KEY_AUTO_CALIBRATE_TIME_INTERVAL_INDEX_SIM_ONE =
            "key_auto_calibrate_time_interval_index_sim_one";
    private static final String KEY_AUTO_CALIBRATE_TIME_INTERVAL_INDEX_SIM_TWO =
            "key_auto_calibrate_time_interval_index_sim_two";

    private static final String KEY_AUTO_CALIBRATE_TIME_INTERVAL_SIM_ONE =
            "key_auto_calibrate_time_interval_sim_one";
    private static final String KEY_AUTO_CALIBRATE_TIME_INTERVAL_SIM_TWO =
            "key_auto_calibrate_time_interval_sim_two";
  • operator 運(yùn)營商信息管理包
    用于運(yùn)營商相關(guān)的流量信息讀取、配置和管理


    operator包文件結(jié)構(gòu)
    • OperatorModelInterface的定義和使用


      OperatorModelInterface結(jié)構(gòu)圖
      • AbstractOperatorDataModel 抽象流量數(shù)據(jù)模型,定義了具體類里需要使用的回調(diào)集合類并且實現(xiàn)了注冊和釋放對應(yīng)接口的方法
    protected List<OperatorProvinceUpdater> mProvinceUpdaterList;
    protected List<OperatorBrandUpdater> mBrandUpdaterList;
    protected List<OperatorPackageUsageUpdater> mOperatorQueryResultUpdaterList;
    protected List<OperatorAccountDayUpdater> mOperatorAccountDayUpdaterList;
  * OperatorProvinceUpdater 運(yùn)營商省份更新接口![根據(jù)sim卡id更新省份信息,僅用于歐朋sdk中生效](http://upload-images.jianshu.io/upload_images/1437965-7d43d324d630bf58.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
  * OperatorBrandUpdater 運(yùn)營商品牌更新接口
運(yùn)營商品牌更新接口,比如“全球通”,“動感地帶”等,僅用于歐朋sdk

* OperatorPackageUsageUpdater 運(yùn)營商流量使用情況更新接口


早起定義了1,2,4三個接口,后期使用接口3直接替代1,2但聲明尚未刪除

* OperatorAccountDayUpdater 運(yùn)營商月結(jié)日更新接口


根據(jù)sim卡卡槽更新月結(jié)日,早期有綁定歐朋sdk,后面迭代過程中,歐朋不再支持月結(jié)日變更,全部切換到本地管理的月結(jié)日邏輯中

本包中定義的兩個本地月結(jié)日管理相關(guān)類

* NativeOperatorDataModel 僅使用建立在原生流量統(tǒng)計基礎(chǔ)上的流量模型進(jìn)行流量數(shù)據(jù)管理
    @Override
    public void requesetPkgMonthlyUsageAndTotalInByte (final int slotId) {
        checkThreadPoolExistence();
        mThreadPool.execute(new Runnable() {
            @Override
            public void run () {
                long lastCalibrateTime = AutoCalibrateUtil.getLastCalibrateTime(mContext.get(),
                        slotId);
                long used = NativeOperatorDataManager.getPkgUsedMonthlyInByte(mContext.get(), slotId);
                final long start = (used == OperaConst.PKG_USAGE_INVALID_VALUE)
                        ? TimeUtils.getTrafficStaticsMonthlyStartTime(getAccountDay(slotId))
                        : lastCalibrateTime;
                final long end = TimeUtils.getTrafficStaticsMonthlyEndTime();
                long extra = mTrafficDataModel.getDataUsageWithinSpecificTime
                        (slotId, start, end);
                boolean isLastCalibrateTimeLongAgo = ((System.currentTimeMillis() -
                        lastCalibrateTime) > 1000 * 60 * 1);
                notifyMonthlyUsageAndTotalChanged(slotId, getPkgTotalInByte(slotId),
                        isLastCalibrateTimeLongAgo ? used + extra : used);
            }
        });
    }

get數(shù)據(jù)使用情況的幾個方法

    @Override
    public long getPkgTotalInByte (int slotId) {
        return NativeOperatorDataManager.getPkgTotalInByte(mContext.get(), slotId);
    }
    @Override
    public int getAccountDay (int slotId) {
        return NativeOperatorDataManager.getAccountDay(mContext.get(), slotId);
    }
    @Override
    public long getPkgUsedMonthlyInByte (int slotId) {
        return NativeOperatorDataManager.getPkgUsedMonthlyInByte(mContext.get(), slotId);
    }

NativeOperatorDataManager —— 一個用于保存本地流量值的SharedPreference管理類
* InvalidOperatorDataModel 空模式對應(yīng)的流量模型,無sim卡時得到一個空模型,關(guān)鍵的接口實現(xiàn)直接返回?zé)o效值,可以參考調(diào)用端代碼

@Override
    public long getPkgTotalInByte (int slotId) {
        return OperaConst.PKG_USAGE_INVALID_VALUE;
    }
    @Override
    public int getAccountDay (int slotId) {
        return OperaConst.ACCOUNT_DAY_INVALID_VALUE;
    }
    @Override
    public long getPkgUsedMonthlyInByte (int slotId) {
        return OperaConst.PKG_USAGE_INVALID_VALUE;
    }
* OperaOperatorDataModel 歐朋流量服務(wù)對應(yīng)的流量模型,對于移動、聯(lián)通、電信三大運(yùn)營商生效  

調(diào)用歐朋sdk服務(wù),進(jìn)行短信校準(zhǔn)回調(diào)注冊,PackageQueryServiceAgent是一個抽象了歐朋sdk接口調(diào)用的可實例化的類

@Override
    public void addQueryResultListener (final int slotId) {
        if (null != mPackageQueryServiceAgent && mPackageQueryServiceAgent.isServiceBound()) {
            addQueryResultListenrToAgentService(slotId);
        } else {
            if (null != mPackageQueryServiceAgent) {
                mPackageQueryServiceAgent.addConnectionListener(new PackageQueryServiceAgent.ServiceConnectionListener() {
                    @Override
                    public void onConnected (IPackageQueryService service) {
                        Log.d(TAG, "recall add sms query result listener after service bound");
                        addQueryResultListenrToAgentService(slotId);
                    }
                    @Override
                    public void onDisconnected () {

                    }
                });
            } else {
                Log.e(TAG, "package service agent is null, impossible to fetch query result.");
            }
        }
    }
    private void addQueryResultListenrToAgentService (int slotId) {
        mPackageQueryServiceAgent.addQueryResultListener(slotId, this);
    }

獲取流量信息的請求方法
該方法直接使用本對象初始化時創(chuàng)建的線程池進(jìn)行異步操作,所以回調(diào)不在主線程中,更新數(shù)據(jù)時需要注意

@Override
    public void requesetPkgMonthlyUsageAndTotalInByte (final int slotId) {
        if (null != mPackageQueryServiceAgent && mPackageQueryServiceAgent.isServiceBound()) {
            startQueryPackageTotalAndMonthlyUsage(slotId);
        } else {
            if (null != mPackageQueryServiceAgent) {
                mPackageQueryServiceAgent.addConnectionListener(new PackageQueryServiceAgent.ServiceConnectionListener() {
                    @Override
                    public void onConnected (IPackageQueryService service) {
                        Log.d(TAG, "recall start query after service bound");
                        startQueryPackageTotalAndMonthlyUsage(slotId);
                    }

                    @Override
                    public void onDisconnected () {

                    }
                });
            } else {
                Log.e(TAG, "package service agent is null, impossible to fetch query result.");
            }
        }
    }
    private void startQueryPackageTotalAndMonthlyUsage (final int slotId) {
        if (null != mThreadPool && !mThreadPool.isShutdown()) {
            mThreadPool.execute(new Runnable() {
                @Override
                public void run () {
                    int total = mPackageQueryServiceAgent.getPkgTotalInKb(slotId);
                    int used = mPackageQueryServiceAgent.getPkgUsedInKb(slotId);
                    long extra = 0L;
                    long lastCalibrateTime = AutoCalibrateUtil.getLastCalibrateTime(mContext,
                            slotId);
                    if (lastCalibrateTime != AutoCalibrateUtil.DEFAULT_INVALID_LAST_CALIBRATE_DAY) {
                        long currentTime = System.currentTimeMillis();
                        // if last calibrate time is more than 20 minutes ago, we would like to double check
                        // monthly usage rather than just show monthly usage result obtained from sms.
                        boolean isLastCalibrateTimeLongAgo = (currentTime - lastCalibrateTime) >
                                (1 * 60 * 1000);
                        if (isLastCalibrateTimeLongAgo) {
                            // add used value.
                            final long start = (used == OperaConst.PKG_USAGE_INVALID_VALUE)
                                    ? TimeUtils.getTrafficStaticsMonthlyStartTime(getAccountDay(slotId))
                                    : AutoCalibrateUtil.getLastCalibrateTime(mContext, slotId);
                            final long end = TimeUtils.getTrafficStaticsMonthlyEndTime();
                            extra = mNativeTrafficDataModel.getDataUsageWithinSpecificTime
                                    (slotId, start, end);
                            Log.i(TAG, "last calibrate time is 20 minutes long ago, we add " +
                                    "calibrated result with native saved value from last " +
                                    "calibration till now value is " + extra);
                        }
                    }
                    notifyMonthlyUsageAndTotalChanged(
                            slotId,
                            (total == OperaConst.PKG_USAGE_INVALID_VALUE)
                                    ? OperaConst.PKG_USAGE_INVALID_VALUE
                                    : total * LONG_BYTE_FACTOR,
                            (used == OperaConst.PKG_USAGE_INVALID_VALUE)
                                    ? OperaConst.PKG_USAGE_INVALID_VALUE
                                    : used * LONG_BYTE_FACTOR + extra);
                }
            });
        }
    }

短信校準(zhǔn)結(jié)果的回調(diào)處理

@Override
    public void onQueryResult (JSONObject result) {
        Log.d(TAG, "onQueryResult");
        SmsQueryResult queryResult = SmsQueryResultUtils.analyzeAndSaveSmsQueryResult(mContext, result,
                mSmsQueryStateManager);
        if (queryResult.isQuerySuccessed) {
            requesetPkgMonthlyUsageAndTotalInByte(queryResult.queriedSlotId);
        }
        for (SmsQueryResultListener listener : mOperatorSmsQueryResultListenerList) {
            listener.onSmsQueryResultAnalyzed(queryResult.queriedSlotId, queryResult.queriedErrorCode);
        }
    }

各個get接口的實現(xiàn)方式

@Override
    public long getPkgTotalInByte (int slotId) {
        int total = mPackageQueryServiceAgent.getPkgTotalInKb(slotId);
        return (total == OperaConst.PKG_USAGE_INVALID_VALUE)
                ? OperaConst.PKG_USAGE_INVALID_VALUE
                : total * LONG_BYTE_FACTOR;
    }
    @Override
    public int getAccountDay (int slotId) {
        //int accountday = mPackageQueryServiceAgent.getAccountDay(slotId);
        int accountday = AccountDayLocalCache.getAccountDay(mContext, slotId);
        return accountday;
    }
    @Override
    public long getPkgUsedMonthlyInByte (int slotId) {
        int used = mPackageQueryServiceAgent.getPkgUsedInKb(slotId);
        return (used == OperaConst.PKG_USAGE_INVALID_VALUE)
                ? OperaConst.PKG_USAGE_INVALID_VALUE
                : used * LONG_BYTE_FACTOR;
    }
  • 本包中其他回調(diào)接口的定義
  • OperatorDataModelFactory工廠類
    根據(jù)sim卡是否為歐朋sdk支持的運(yùn)營商來決定生成哪一個流量模型
public class OperatorDataModelFactory {
    private static final String TAG = "OperatorDataModelFactory";
    public static OperatorModelInterface getOperatorDataModel (Context context, int slotId,
                                                               boolean isSdkSupported) {
        OperatorModelInterface mOperatorDataModel;
        if (isSdkSupported) {
            Log.d(TAG, "build opera operator model.");
            mOperatorDataModel = OperaOperatorDataModel.getInstance(context);
        } else {
            // use native operator data model.
            Log.d(TAG, "build native operator model.");
            mOperatorDataModel = NativeOperatorDataModel.getInstance(context);
        }
        return mOperatorDataModel;
    }
}
  • settings 設(shè)置界面及功能管理包
    settings文件結(jié)構(gòu)圖
    • 界面相關(guān)類
      CarrierConfigFragment, CarrierConfigSettingsActivity, 用于使用歐朋sdk時進(jìn)行運(yùn)營商信息配置
      TrafficMonthlyUsageConfigActivity, 用于配置月已用流量
      TrafficTotalAndClosingDayConfigAcitivity, 用于配置月總流量和月結(jié)日
      TrafficUsageSettingsActivity 用于展示整個設(shè)置界面,包含了一個PreferenceFragment內(nèi)部類TrafficUsageSettingsFragment
    • 設(shè)置數(shù)據(jù)模型類
      • OpertorPrefModel
    abstract String getProvinceAndBrandPrefKey();
    abstract String getTrafficTotalAndClosingDayPrefKey();
    abstract String getTrafficMonthlyUsagePrefKey();
OpertorPrefModel作為抽象類定義上述三個抽象方法,實現(xiàn)放在具體類中,用于獲取雙卡工作時需要拿到的Preference key

OperatorPrefModel中對SimPreferenceValueUpdater的處理

相關(guān)類截圖

* SimPreferenceValueUpdater
用于統(tǒng)一處理設(shè)置需要顯示的值如省份、品牌、流量值等變更的接口,接收OperatorPrefModel為參數(shù),使用其中定義的抽象方法動態(tài)獲取到具體的OperatorPrefModel實現(xiàn),并進(jìn)行數(shù)據(jù)更新


使用到該接口的界面類
public interface SimPreferenceValueUpdater {
        void updateProvinceAndBrand (OperatorPrefModel model);
        void updateAccountDay(OperatorPrefModel model);
        void updateTrafficTotal(OperatorPrefModel model);
        void updateTrafficMonthlyUsed(OperatorPrefModel model);
        void updateSmsQueryResult(int errorCode);
}
  • simcard 手機(jī)sim卡信息管理包


    simcard包結(jié)構(gòu)圖
    • SimcardDataModelInterface及其實現(xiàn)類SimcardDataModel
      可以說有一些多余,因為這個接口只有一個具體實現(xiàn),應(yīng)該是一個過度設(shè)計的問題
      這個類主要負(fù)責(zé)獲取sim卡的相關(guān)信息,使用接口定義,看的更加清楚吧,避免陷入實現(xiàn)細(xì)節(jié)
public interface SimcardDataModelInterface {
        int SIM_CARD_ONE_INDEX = 0;
        int SIM_CARD_TWO_INDEX = 1;
        int INVALID_SIM_SLOT_ID = -1;
        int INVALID_CLOSING_DAY = -1;
        String KEY_SIM_CARD_SLOT = "sim_card_slot";
        String OPERATOR_CODE_CHINA_MOBILE = "46000";
        String OPERATOR_CODE_CHINA_MOBILE_1 = "46002";
        String OPERATOR_CODE_CHINA_UNICOM = "46001";
        String OPERATOR_CODE_CHINA_TELECOM = "46003";
        String DEFAULT_OPERATOR_NUMBER = "";
        int getCurrentUsingSimNumber ();
        int getPhoneCount ();
        int getCurrentTrafficRunningSlotId();
        void registerSimStateListener (SimStateListener listener);
        void removeSimStateListener (SimStateListener listener);
        boolean isSlotSimInserted (int slotId);
        boolean isSlotOperatorSupportedBySdk(int slotId);
        String getSlotOperatorName(int slotId);
        String getSlotOperatorNumber(int slotId);
        int getSubIdBySlotId(int slotId);
        String getIMSIBySlotId(int slotId);
        void setDataEnabled(boolean state);
    }
  • SimStateListener的用途
public interface SimStateListener {
        String SS_ABSENT = "ABSENT";
        String SS_READY = "READY";
        // 監(jiān)聽sim卡狀態(tài)變化的廣播回調(diào),插拔卡是一種觸發(fā)場景
        void onSimStateChanged (String simState);
        // 需求中需要更新運(yùn)營商,但是單純依賴sim卡廣播回調(diào),不能獲取到運(yùn)營商碼,且運(yùn)營商碼的變更不會有sim卡狀態(tài)廣播發(fā)出,
        // 所以自定義了一個運(yùn)營商碼變更事件
        void onSimOperatorCodeChanged(int slotId, String simValue);
    }

SimStateListener的調(diào)用端代碼,定義在SimcardDataModel中,動態(tài)注冊一個監(jiān)聽sim卡狀態(tài)的廣播,在廣播回調(diào)中被處理

private BroadcastReceiver mSimStateChangeReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive (Context context, Intent intent) {
            String action = intent.getAction();
            String state = intent.getStringExtra(SIM_STATE_KEY);
            if (action.equals(TelephonyIntents.ACTION_SIM_STATE_CHANGED)) {
                boolean hasSimStateReallyChanged = false;
                for (int i = 0; i < mPhoneCount; i++) {
                    int originalState = mMSimState[i];
                    mMSimState[i] = TelephonyManager.getDefault().getSimState(i);
                    if (originalState != mMSimState[i]) {
                        hasSimStateReallyChanged = true;
                    }
                    Log.d(TAG, "original state " + originalState + " new state " + mMSimState[i]);
                    parseOperatorCode(i, getOperatorCode(i));
                }
                if (hasSimStateReallyChanged) {
                    notifySimStateChanged(state);
                }
            }
        }
    };
...
private void notifySimStateChanged (String simState) {
        if (SimStateListener.SS_ABSENT.equals(simState) || SimStateListener.SS_READY.equals
                (simState)) {
            for (SimStateListener s : mSimStateListeners) {
                s.onSimStateChanged(simState);
            }
        } else {
            Log.e(TAG, "simState value " + simState + " not supported yet.");
        }
    }
SimStateListener在項目中的實現(xiàn)類
  • smsquery 短信查詢功能管理包
    • SmsQueryStateManager 單例實現(xiàn),用于整個流量模塊獲取和更新當(dāng)前短信查詢的狀態(tài)
      查詢同時對卡1和卡2的狀態(tài)做跟蹤,可能出現(xiàn)的值及初始化方式都定義在下面代碼中
public static final int SMS_QUERY_INITIALIZED = 100;
    public static final int SMS_QUERY_PROCESSING = 101;
    public static final int SMS_QUERY_FINISHED = 102;
    public static final int SMS_QUERY_ERROR = 103;
    private int slotOneSmsQueryState = SMS_QUERY_INITIALIZED;
    private int slotTwoSmsQueryState = SMS_QUERY_INITIALIZED;

實現(xiàn)中發(fā)現(xiàn),完全只靠內(nèi)存中對象做記錄的話,會出現(xiàn)SmsQueryStateManager因為各種原因?qū)ο蟊恢亟▽?dǎo)致短信查詢裝填出錯,一直提示查詢中的狀況,所以存儲設(shè)計為使用SharedPreference進(jìn)行值的長時間保存

public synchronized int getSmsQueryStateBySlotId (int slotId) {
        SharedPreferences sp = mAppContext.getSharedPreferences(KEY_QUERY_STATE_SAVER, Activity
                .MODE_PRIVATE);
        if (SimcardDataModelInterface.SIM_CARD_ONE_INDEX == slotId) {
            slotOneSmsQueryState = getSavedQueryStateOnSlot(sp, KEY_QUERY_STATE_SLOT_ONE_SAVER);
            return slotOneSmsQueryState;
        } else if (SimcardDataModelInterface.SIM_CARD_TWO_INDEX == slotId) {
            slotTwoSmsQueryState = getSavedQueryStateOnSlot(sp, KEY_QUERY_STATE_SLOT_TWO_SAVER);
            return slotTwoSmsQueryState;
        } else {
            Log.e(TAG, "queried with invalid slotId");
            return SMS_QUERY_ERROR;
        }
    }
public static synchronized void clearQueryState(Context mAppContext) {
        SharedPreferences sp = mAppContext.getSharedPreferences(KEY_QUERY_STATE_SAVER, Activity
                .MODE_PRIVATE);
        SharedPreferences.Editor e = sp.edit();
        e.putInt(KEY_QUERY_STATE_SLOT_ONE_SAVER, SMS_QUERY_INITIALIZED);
        e.putInt(KEY_QUERY_STATE_SLOT_TWO_SAVER, SMS_QUERY_INITIALIZED);
        e.apply();
    }
public synchronized void setSmsQueryStateBySlotId (int slotId, int state) {
        boolean isStateValueInvalid =
                state != SMS_QUERY_PROCESSING
                        && state != SMS_QUERY_FINISHED
                        && state != SMS_QUERY_INITIALIZED;
        if (isStateValueInvalid) {
            Log.e(TAG, "set query state with invalid state value " + state);
            return;
        }
        SharedPreferences sp = mAppContext.getSharedPreferences(KEY_QUERY_STATE_SAVER, Activity.MODE_PRIVATE);
        SharedPreferences.Editor e = sp.edit();
        slotOneSmsQueryState = getSavedQueryStateOnSlot(sp, KEY_QUERY_STATE_SLOT_ONE_SAVER);
        slotTwoSmsQueryState = getSavedQueryStateOnSlot(sp, KEY_QUERY_STATE_SLOT_TWO_SAVER);
        boolean changed = false;
        if (SimcardDataModelInterface.SIM_CARD_ONE_INDEX == slotId) {
            if (slotOneSmsQueryState != state) {
                slotOneSmsQueryState = state;
                changed = true;
                e.putInt(KEY_QUERY_STATE_SLOT_ONE_SAVER, state);
                e.apply();
            }
        } else if (SimcardDataModelInterface.SIM_CARD_TWO_INDEX == slotId) {
            if (slotTwoSmsQueryState != state) {
                slotTwoSmsQueryState = state;
                changed = true;
                e.putInt(KEY_QUERY_STATE_SLOT_TWO_SAVER, state);
                e.apply();
            }
        } else {
            Log.e(TAG, "set query state with invalid slotId");
        }
        Log.d(TAG, "notify smsQueryStateMananger state change " + changed + " on " + slotId);
        if (changed) {
            for (SmsQueryStateUpdater updater : mQueryStateList) {
                updater.onSmsQueryStateValueUpdate(slotId, state);
            }
        } else {
            // force update query finish state in case query is successfully but never change UI
            // state.
            if (slotOneSmsQueryState == SMS_QUERY_FINISHED) {
                for (SmsQueryStateUpdater updater : mQueryStateList) {
                    updater.onSmsQueryStateValueUpdate(SimcardDataModelInterface
                            .SIM_CARD_ONE_INDEX, slotOneSmsQueryState);
                }
            } else if (slotTwoSmsQueryState == SMS_QUERY_FINISHED) {
                for (SmsQueryStateUpdater updater : mQueryStateList) {
                    updater.onSmsQueryStateValueUpdate(SimcardDataModelInterface
                            .SIM_CARD_TWO_INDEX, slotTwoSmsQueryState);
                }
            }
            Log.d(TAG, "state is " + state + "slot 1 " + slotOneSmsQueryState + " slot 2 " +
                    slotTwoSmsQueryState);
        }
    }

在主Activity,SecurityMainActivity退出時,也需要強(qiáng)制做一次數(shù)據(jù)重置,后續(xù)迭代更改了應(yīng)用架構(gòu)的話,要注意這一點

@Override
    protected void onDestroy () {
        super.onDestroy();
        // when finish activity actively, reset sms query state as initialized.
        if (hasRegisteredColorThemeChangeReceiver) {
            unregisterReceiver(mChangeColorThemeReceiver);
            hasRegisteredColorThemeChangeReceiver = false;
        } else {
            // do nothing.
        }
        SmsQueryStateManager sqm = SmsQueryStateManager.getInstance(getApplicationContext());
        sqm.setSmsQueryStateBySlotId(
                SimcardDataModelInterface.SIM_CARD_ONE_INDEX
                , SmsQueryStateManager.SMS_QUERY_INITIALIZED);
        sqm.setSmsQueryStateBySlotId(
                SimcardDataModelInterface.SIM_CARD_TWO_INDEX
                , SmsQueryStateManager.SMS_QUERY_INITIALIZED);
    }
短信解析結(jié)果分析類結(jié)構(gòu)
  • statics 流量使用信息統(tǒng)計包
    這個包是做第一個版本的流量詳情統(tǒng)計和流量權(quán)限開關(guān)時實現(xiàn)的,目前因需求調(diào)整不會上線,需要對無用代碼做清理,因為涉及到數(shù)據(jù)庫操作,需要避免不必要的影響
  • trafficalarm 流量告警功能管理包
    目前的基本思路是在app中注冊監(jiān)聽發(fā)生數(shù)據(jù)行為的廣播,收到廣播后根據(jù)當(dāng)前是否在使用數(shù)據(jù)流量而啟動流量使用狀況分析的服務(wù),在后臺定時的查詢流量使用狀況,觸發(fā)了異常條件后進(jìn)行對話框彈出提醒
    該服務(wù)直接繼承于Service類,后續(xù)修改時要注意添加新分支后需要主動調(diào)用stop Service的相關(guān)方法
    流量告警功能結(jié)構(gòu)圖
    • TrafficUsageAlarmReceiver說明
        <receiver android:name="com.oneplus.security.network.trafficalarm.TrafficUsageAlarmReceiver"
                  android:exported="false"
                  android:enabled="true" >
            <intent-filter>
                <action android:name="android.net.conn.DATA_ACTIVITY_CHANGE"/>
            </intent-filter>
        </receiver>
@Override
    public void onReceive (Context context, Intent intent) {
        if (!FunctionUtils.isNetworkEnabled) {
            return;
        }
        PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
        if (!pm.isInteractive()) {
            Log.i(TAG, "screen is off stop any data usage check process. buy action is " + intent
                    .getAction());
            cancelAnyTrafficUsageService(context);
            return;
        }
        String action = intent.getAction();
        if (ConnectivityManager.ACTION_DATA_ACTIVITY_CHANGE.equals(action)) {
            if (ConnectivityUtil.isMobileConnected(context)) {
                boolean isDataNetworkActive = intent.getBooleanExtra(ConnectivityManager
                        .EXTRA_IS_ACTIVE, false);
                Log.d(TAG, "state is " + isDataNetworkActive);
                if (isDataNetworkActive) {
                    enableTrafficUsageAlertServices(context);
                } else {
                    cancelAnyTrafficUsageService(context);
                }
            } else {
                // is using wifi now, do nothing.
            }
        }
    }
  • TrafficUsageAlarmIntentService說明
    在onCreate時進(jìn)行必要的資源初始化操作
@Override
    public void onCreate () {
        super.onCreate();
        Log.d(TAG, "create usage alarm service");
        mHandler = new Handler(this);
        mSimcardDataModel = SimcardDataModel.getInstance(getApplicationContext());
        mCurrentSlotId = mSimcardDataModel.getCurrentTrafficRunningSlotId();
        if (mCurrentSlotId < 0) {
            Log.e(TAG, "query data alert with invalid slotId.");
        }
        mOperatorDataModel = OperatorDataModelFactory.getOperatorDataModel(getApplicationContext
                (), mCurrentSlotId, mSimcardDataModel.isSlotOperatorSupportedBySdk(mCurrentSlotId));
        mOperatorDataModel.addTrafficUsageUpdater(this);
    }

在onStartCommand時,調(diào)用OperatorModelInterface上的異步查詢流量使用情況的方法

@Override
    public int onStartCommand (Intent intent, int flags, int startId) {
        mOperatorDataModel.requesetPkgMonthlyUsageAndTotalInByte(mCurrentSlotId);
        mServiceStartedAction = intent.getAction();
        return super.onStartCommand(intent, flags, startId);
    }

在流量總量和月用量的回調(diào)時,進(jìn)行是否需要發(fā)出流量使用警告的邏輯判斷,跟據(jù)當(dāng)前的slotId,對應(yīng)slotId上面的流量警告配置狀況做決定,如果需要進(jìn)行彈框提醒,則發(fā)消息給主線程的Handler處理

@Override
    public void onTrafficTotalAndUsedUpdate (long totalByte, long usedbyte, int slotId) {
        if (null == mHandler) {
            stopSelf();
            return;
        }
        Log.d(TAG, "total is " + totalByte + " used is " + usedbyte);
        if (totalByte < 0) {
            Log.d(TAG, "total pkg usage returned is invalid");
            return;
        }
        boolean isPkgRunOut = usedbyte >= totalByte;
        boolean hasLeftLessThanTenPercent = ((totalByte - usedbyte) * 10 - totalByte) < 0;
        if (ACTION_CONFIRM_WHETHER_TO_START_DATA_ALARM.equals(mServiceStartedAction)) {
            final Context c = TrafficUsageAlarmIntentService.this;
            boolean hasLeftLessThanTwentyPercent = (totalByte - usedbyte) * 5 - totalByte < 0;
            // less than 30 MB.
            final long LOW_DATA_LIMIT = 30 * 1024 * 1024L;
            boolean isRemainingDataLessThan30Mb = totalByte - usedbyte < LOW_DATA_LIMIT;
            if (isRemainingDataLessThan30Mb) {
                TrafficUsageAlarmUtils.startTrafficUsageRunningOutService(c);
            } else {
                TrafficUsageAlarmUtils.cancelTrafficUsageRunningOutService(c);
            }
            if (hasLeftLessThanTwentyPercent && !hasLeftLessThanTenPercent) {
                TrafficUsageAlarmUtils.startAlertTenPercentLeftService(c);
            }
            if (isPkgRunOut) {
                TrafficUsageAlarmUtils.cancelAnyTrafficRunningOutCheckService
                        (TrafficUsageAlarmIntentService.this);
            }
        }
        if (isPkgRunOut) {
            // pkg run off.
            if (TrafficUsageAlarmUtils.shouldAlertTrafficRunningOut(TrafficUsageAlarmIntentService
                    .this, mCurrentSlotId)) {
                Message message = mHandler.obtainMessage(MESSAGE_ALERT_PKG_RUN_OUT);
                mHandler.sendMessage(message);
            } else if (TrafficUsageAlarmUtils.shouldAlertTrafficRunningOutAutoClose
                    (TrafficUsageAlarmIntentService.this, mCurrentSlotId)) {
                Message message = mHandler.obtainMessage(MESSAGE_ALERT_PKG_RUN_OUT);
                mHandler.sendMessage(message);
            } else {
                stopSelf();
            }
        } else {
            if (hasLeftLessThanTenPercent) {
                if (TrafficUsageAlarmUtils.shouldAlertLessThanTenPercentPkgLeft
                        (TrafficUsageAlarmIntentService.this, mCurrentSlotId)) {
                    Message message = mHandler.obtainMessage(MESSAGE_ALERT_TEN_PERCENT_LOW);
                    mHandler.sendMessage(message);
                }
            } else {
                stopSelf();
            }
        }
    }

主線程中的Handler回調(diào),用于真正啟動警告窗口

@Override
    public boolean handleMessage (Message msg) {
        switch (msg.what) {
            case MESSAGE_ALERT_PKG_RUN_OUT:
                boolean isAutoCloseNetworkEnabled = TrafficUsageAlarmUtils
                        .getAutoCloseNetworkWhenDataRunningOut(this, false, mCurrentSlotId);
                if (isAutoCloseNetworkEnabled) {
                    showPkgRunningOutAutoCloseAlertDialog();
                } else {
                    showPkgRunningOutAlertDialog();
                }
                break;
            case MESSAGE_ALERT_TEN_PERCENT_LOW:
                showTenPercentLeftAlertDialog();
                break;
            default:
                stopSelf();
                break;
        }
        return false;
    }

對于對話框提醒的部分,設(shè)計了提醒過之后就不再提醒的功能,以及發(fā)生過流量校準(zhǔn),及手工修改流量后,清空不再提醒標(biāo)志的功能,后續(xù)涉及到相關(guān)邏輯的修改,以及增加新功能時,需要關(guān)注到
目前我想到還沒做的 重新設(shè)置開關(guān)后,是否需要重置 已發(fā)出對話框提醒的標(biāo)志

public static boolean getTenPercentPkgLeftAlert (Context context, boolean defaultValue, int slotId) {
        if (SimcardDataModelInterface.INVALID_SIM_SLOT_ID == slotId) {
            return false;
        }
        return getTrafficUsagePreferenceBooleanValue(context,
                getTenPercentDataLeftConfigKey(slotId), defaultValue);
    }

    public static void setHasAlertedTenPercentLeft (Context context, boolean state, int slotId) {
        if (SimcardDataModelInterface.SIM_CARD_ONE_INDEX == slotId) {
            setTrafficUsagePreferenceBooleanValue(context, KEY_HAS_SIM_1_ALERT_TEN_PERCENT_LEFT, state);
        } else if (SimcardDataModelInterface.SIM_CARD_TWO_INDEX == slotId) {
            setTrafficUsagePreferenceBooleanValue(context, KEY_HAS_SIM_2_ALERT_TEN_PERCENT_LEFT,
                    state);
        } else {
            logOutUsingInvalidSlotId();
        }
    }
  • trafficinfo 基于原生流量統(tǒng)計的原生流量信息獲取包
public interface TrafficDataModelInterface {
    long getDataUsageWithinSpecificTime (int slotId, long start, long end);
    long getDailyUsageBySlotId (int slotId, long monthlyUsage, int accountDay);//不再需要日已用提醒,所以該接口實際已無用了
    void clearTrafficData();
}
原生流量信息統(tǒng)計模型
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 本人初學(xué)Android,最近做了一個實現(xiàn)安卓簡單音樂播放功能的播放器,收獲不少,于是便記錄下來自己的思路與知識總結(jié)...
    落日柳風(fēng)閱讀 19,450評論 2 41
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,554評論 19 139
  • 背景 一年多以前我在知乎上答了有關(guān)LeetCode的問題, 分享了一些自己做題目的經(jīng)驗。 張土汪:刷leetcod...
    土汪閱讀 12,916評論 0 33
  • ¥開啟¥ 【iAPP實現(xiàn)進(jìn)入界面執(zhí)行逐一顯】 〖2017-08-25 15:22:14〗 《//首先開一個線程,因...
    小菜c閱讀 7,328評論 0 17
  • 一、雞湯 appwidget是android中小組件,我們經(jīng)常說的widget其實是指的那些button、text...
    歡樂斗佛閱讀 2,393評論 1 8

友情鏈接更多精彩內(nèi)容