流量管理工作總結

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


流量管理目錄結構圖

包含

  • calibrate 校準功能包


    calibrate部分代碼結構圖
    • 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>

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

        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
    這個文件是自動流量校準的主服務
@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) {
                //啟動一個異步任務來調用歐朋sdk的短信查詢接口,在回調中對本地保存的流量數據及上一次校準信息做處理
            } else if (AutoCalibrateUtil.INDEX_CHECK_CALIBRATE_DATE_TASK == taskIndex) {
                // 響應日期變化并且更新與月結日相關的功能邏輯
            } else if (AutoCalibrateUtil.INDEX_CONFIG_AUTO_CALIBRATE_TASK == taskIndex) {
                // 發生手機重啟時,檢查是否已經配置了流量自動校準服務,并相應選擇恢復或者退出
                stopSelf();
            } else {
                stopSelf();
            }
        } else {
            stopSelf();
        }
        return super.onStartCommand(intent, flags, startId);
    }
  • AutoCalibrateUtil
    這個文件是為了保存和獲取雙卡場景下流量自動校準配置信息所做的工具類,數據存儲使用SharedPreference
    public static final int INDEX_CHECK_CALIBRATE_DATE_TASK = 0;//接收到日期變更后觸發,任務發送給AutoCalibrateService處理
    public static final int INDEX_START_AUTO_CALIBRATE_TASK = 1;//在設置中使用,打開自動校準開關后生效
    public static final int INDEX_CONFIG_AUTO_CALIBRATE_TASK = 2;//接收到開機廣播觸發,用于重啟因手機掉電導致的Alarm丟失
// 以下為針對雙卡場景定義的用于保存自動校準相關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 運營商信息管理包
    用于運營商相關的流量信息讀取、配置和管理


    operator包文件結構
    • OperatorModelInterface的定義和使用


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

* OperatorPackageUsageUpdater 運營商流量使用情況更新接口


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

* OperatorAccountDayUpdater 運營商月結日更新接口


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

本包中定義的兩個本地月結日管理相關類

* NativeOperatorDataModel 僅使用建立在原生流量統計基礎上的流量模型進行流量數據管理
    @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數據使用情況的幾個方法

    @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 空模式對應的流量模型,無sim卡時得到一個空模型,關鍵的接口實現直接返回無效值,可以參考調用端代碼

@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 歐朋流量服務對應的流量模型,對于移動、聯通、電信三大運營商生效  

調用歐朋sdk服務,進行短信校準回調注冊,PackageQueryServiceAgent是一個抽象了歐朋sdk接口調用的可實例化的類

@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);
    }

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

@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);
                }
            });
        }
    }

短信校準結果的回調處理

@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接口的實現方式

@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;
    }
  • 本包中其他回調接口的定義
  • OperatorDataModelFactory工廠類
    根據sim卡是否為歐朋sdk支持的運營商來決定生成哪一個流量模型
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 設置界面及功能管理包
    settings文件結構圖
    • 界面相關類
      CarrierConfigFragment, CarrierConfigSettingsActivity, 用于使用歐朋sdk時進行運營商信息配置
      TrafficMonthlyUsageConfigActivity, 用于配置月已用流量
      TrafficTotalAndClosingDayConfigAcitivity, 用于配置月總流量和月結日
      TrafficUsageSettingsActivity 用于展示整個設置界面,包含了一個PreferenceFragment內部類TrafficUsageSettingsFragment
    • 設置數據模型類
      • OpertorPrefModel
    abstract String getProvinceAndBrandPrefKey();
    abstract String getTrafficTotalAndClosingDayPrefKey();
    abstract String getTrafficMonthlyUsagePrefKey();
OpertorPrefModel作為抽象類定義上述三個抽象方法,實現放在具體類中,用于獲取雙卡工作時需要拿到的Preference key

OperatorPrefModel中對SimPreferenceValueUpdater的處理

相關類截圖

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


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


    simcard包結構圖
    • SimcardDataModelInterface及其實現類SimcardDataModel
      可以說有一些多余,因為這個接口只有一個具體實現,應該是一個過度設計的問題
      這個類主要負責獲取sim卡的相關信息,使用接口定義,看的更加清楚吧,避免陷入實現細節
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";
        // 監聽sim卡狀態變化的廣播回調,插拔卡是一種觸發場景
        void onSimStateChanged (String simState);
        // 需求中需要更新運營商,但是單純依賴sim卡廣播回調,不能獲取到運營商碼,且運營商碼的變更不會有sim卡狀態廣播發出,
        // 所以自定義了一個運營商碼變更事件
        void onSimOperatorCodeChanged(int slotId, String simValue);
    }

SimStateListener的調用端代碼,定義在SimcardDataModel中,動態注冊一個監聽sim卡狀態的廣播,在廣播回調中被處理

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在項目中的實現類
  • smsquery 短信查詢功能管理包
    • SmsQueryStateManager 單例實現,用于整個流量模塊獲取和更新當前短信查詢的狀態
      查詢同時對卡1和卡2的狀態做跟蹤,可能出現的值及初始化方式都定義在下面代碼中
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;

實現中發現,完全只靠內存中對象做記錄的話,會出現SmsQueryStateManager因為各種原因對象被重建導致短信查詢裝填出錯,一直提示查詢中的狀況,所以存儲設計為使用SharedPreference進行值的長時間保存

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退出時,也需要強制做一次數據重置,后續迭代更改了應用架構的話,要注意這一點

@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);
    }
短信解析結果分析類結構
  • statics 流量使用信息統計包
    這個包是做第一個版本的流量詳情統計和流量權限開關時實現的,目前因需求調整不會上線,需要對無用代碼做清理,因為涉及到數據庫操作,需要避免不必要的影響
  • trafficalarm 流量告警功能管理包
    目前的基本思路是在app中注冊監聽發生數據行為的廣播,收到廣播后根據當前是否在使用數據流量而啟動流量使用狀況分析的服務,在后臺定時的查詢流量使用狀況,觸發了異常條件后進行對話框彈出提醒
    該服務直接繼承于Service類,后續修改時要注意添加新分支后需要主動調用stop Service的相關方法
    流量告警功能結構圖
    • 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時進行必要的資源初始化操作
@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時,調用OperatorModelInterface上的異步查詢流量使用情況的方法

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

在流量總量和月用量的回調時,進行是否需要發出流量使用警告的邏輯判斷,跟據當前的slotId,對應slotId上面的流量警告配置狀況做決定,如果需要進行彈框提醒,則發消息給主線程的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回調,用于真正啟動警告窗口

@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;
    }

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

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 基于原生流量統計的原生流量信息獲取包
public interface TrafficDataModelInterface {
    long getDataUsageWithinSpecificTime (int slotId, long start, long end);
    long getDailyUsageBySlotId (int slotId, long monthlyUsage, int accountDay);//不再需要日已用提醒,所以該接口實際已無用了
    void clearTrafficData();
}
原生流量信息統計模型
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

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