GPS代碼學習 --- 設置菜單

一、GPS在設置中的代碼。

        節前看了一小部分GPS設置部分代碼,對應手機菜單中“設置\位置信息訪問權限”,參考代碼LocationSettings.java, AgpsEpoSettings.java, CustomSwitchPreference.java。
該設置菜單使用SettingsPreferenceFragment(繼承自preferenceFragment)實現,設置菜單在onResume中通過createPreferenceHierarchy加載佈局,並對部分控件設置監聽。
onPreferenceTreeClick實現菜單的點擊響應,onPreferenceChange響應之前設置的CheckboxPreference控件(允許訪問我的位置信息)的勾選處理。

在GPS衛星定位這個選擇開關菜單的處理上,MTK使用自定義的CustomSwitchPreference來實現開關,和android framework中定義的SwitchPreference類似。
差別在SwitchPreference類中註冊了一個CompoundButton的OnCheckedChangeListener,在該監聽中會調用一個callChangeListener的API。

調用該API和不調用API有何差別?從callChangeListener函數實現來看,

    protected boolean callChangeListener(Object newValue) {
        return mOnChangeListener == null ? true : mOnChangeListener.onPreferenceChange(this, newValue);
    }

原來它會調用mOnChangeListener.onPreferenceChange,也就是爲什麼我們之前需要在onResume中註冊對CheckboxPreference的監聽了,
mLocationAccess.setOnPreferenceChangeListener(this);
         那如果我們想要讓MTK自己設計的CustomSwitchPreference控件也能像mLocationAccess一樣在點擊後響應onPreferenceChange,那我們就必須先增加監聽,
         mGps.setOnPreferenceChangeListener(this);
然後,關鍵要在CustomSwitchPreference的監聽CompoundButton的Listener中實現onCheckedChange,

private class Listener implements CompoundButton.OnCheckedChangeListener {
    		@Override
   		 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
        			Settings.Secure.setLocationProviderEnabled(buttonView.getContext().getContentResolver(),
                			LocationManager.GPS_PROVIDER, isChecked);
        			setChecked(isChecked);
        			// Joseph fixed.
        			if (isChecked) {
            			boolean value = callChangeListener(isChecked);
            			Log.d("CustomSwitchPreference", "setChecked: value = " + value);
        			}
        			// Joseph fixed end.
        			return;
    		}
}

然後,就可以在LocationSettings.java的onPreferenceChange函數中增加對控件Gps的狀態改變處理,例如:

    @Override
    public boolean onPreferenceChange(Preference pref, Object newValue) {
        ......
        else if (pref.getKey().equals(KEY_LOCATION_GPS)) { // Joseph fixed.
            if (getAgpsEnable()) {
                showDialog(CONFIRM_AGPS_DIALOG_ID);
            }
        } // Joseph fixed end.
        ......
    }

二、GPS在widget中的代碼。
       先說說widget上的。代碼可參考SettingsAppWidgetProvider.java, 和一般widget的實現一樣,該類繼承自AppWidgetProvider。說白了就是一個BroadcastReceiver,呵呵~
如何實現一個widget就不在這裏展開了。在SettingsAppWidgetProvider中實現了wifi,BT,GPS,Sync,Brightness這幾個按鈕的狀態Tracker,這幾個按鈕大致的響應動作是類似的,所以有了一個abstract class StateTracker,這個模式讓我想到了設計模式中的State模式。


        StateTracker中有虛函數getContainerId(), getButtonId(), getIndicatorId(), getButtonImageId()等,用於更換顯示對應的圖標按鈕等。在onReceive()中會收到兩類消息,一類是LocationManager.PROVIDERS_CHANGED_ACTION,一類是intent.hasCategory(Intent.CATEGORY_ALTERNATIVE)。前者處理來自設置菜單功能開關消息,後者處理待機widget點擊button的事件。以GPS按鈕的點擊事件處理來講,在SettingsAppWidgetProvider的onUpdate()中調用了buildUpdate,代碼如下:

static RemoteViews buildUpdate(Context context) {
    ......
        if (FeatureOption.MTK_GPS_SUPPORT) {
        views.setOnClickPendingIntent(R.id.btn_gps,
                getLaunchPendingIntent(context, BUTTON_GPS));
        } else {
            views.setViewVisibility(R.id.btn_gps, View.GONE);
        }
    ......
}

        Views是RemoteViews對象,android官網介紹setOnClickPendingIntent()如下:
Equivalent to calling setOnClickListener(android.view.View.OnClickListener) to launch the provided PendingIntent.等價於調用setOnClickListener進入對應的PendingIntent。也就是說點擊btn_gps,會發送intent給SettingsAppWidgetProvider,傳入的參數有ButtonID:BUTTON_GPS,category是Intent.CATEGORY_ALTERNATIVE。代碼如下:

private static PendingIntent getLaunchPendingIntent(Context context,
            int buttonId) {
        Intent launchIntent = new Intent();
        launchIntent.setClass(context, SettingsAppWidgetProvider.class);
        launchIntent.addCategory(Intent.CATEGORY_ALTERNATIVE);
        launchIntent.setData(Uri.parse("custom:" + buttonId));
        PendingIntent pi = PendingIntent.getBroadcast(context, 0 /* no requestCode */,
                launchIntent, 0 /* no flags */);
        Log.d(TAG, "PendingIntent , buttonId = " + buttonId + " pi = " + pi);
        return pi;
}

        所以點擊按鈕BUTTON_GPS後,SettingsAppWidgetProvider會收到消息並響應toggleState。

    @Override
    public void onReceive(Context context, Intent intent) {
        super.onReceive(context, intent);
        ......
        else if (intent.hasCategory(Intent.CATEGORY_ALTERNATIVE)) {
            Uri data = intent.getData();
            int buttonId = Integer.parseInt(data.getSchemeSpecificPart());
            Log.d(TAG, "onReceive , toggleState");
            ......
            } else if (FeatureOption.MTK_GPS_SUPPORT && (buttonId == BUTTON_GPS)){
                Log.d(TAG, "onReceive , sGpsState.toggleState");
                sGpsState.toggleState(context);
            }
            ......
        }
    ......
    }

        toggleState在判斷當前按鈕未在轉換過程中,會調用對應GpsStateTracker的requestStateChange()。在其中運用了AsyncTask來處理。簡單介紹下AsyncTask:
AsyncTask is designed to be a helper class around Thread and Handler and does not constitute a generic threading framework. AsyncTasks should ideally be used for short operations (a few seconds at the most.) If you need to keep threads running for long periods of time, it is highly recommended you use the various APIs provided by the java.util.concurrent pacakge such as Executor, ThreadPoolExecutor and FutureTask.
AsyncTask設計爲一個圍繞Thread和Handler的輔助類,用於簡短的操作。它會執行以下4個步驟:
1、onPreExecute(), 在task執行前被UI線程調用,往往用來設置task,比如顯示進度條。
2、doInBackground(Params...),在onPreExecute()完成執行後,馬上會被後臺線程調用。該步驟用來完成費時的後臺計算,計算結果返回並傳遞給最後一個步驟。這些值會在UI線程打印在onProgressUpdate步驟。
3、onProgressUpdate(Progress...),該方法用來在後臺數據在計算過程中顯示進度,執行時間不定,在UI線程中由publishProgress調用。
4、onPostExecute(Result),在後臺計算完成後,由UI線程調用,參數爲後臺計算的返回值。
在GPS按鈕點擊後,在doInBackground()中設置狀態後,在onPostExecute中得到設置的狀態,並調用updateWidget(context)來刷新widget。


三、GPS在SystemUI中的代碼。
        再講systemUI中的GPS按鈕。對應代碼在QuickSettingsConnectionModel.java,QuickSettings.java。與在SettingsAppWidgetProvider.java中實現類似,GPS,wifi,BT,AirlineMode,mobileState等也都繼承自StateTracker(frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/util/StateTracker.java)。同樣在QucikSetting中調用setupQuickSettings(),成員變量mQuickSettingsConnectionModel調用buildIconViews,在其中設置mGpsStateTracker以及對應view的onClickListener。在點擊view後,觸發mGpsStateTracker的toggleState,類似在widget中的處理,在toggleState調用requestStateChange後用了AsyncTask實現狀態的更新。

        @Override
        public void requestStateChange(final Context context, final boolean desiredState) {
            final ContentResolver resolver = context.getContentResolver();
            new AsyncTask<Void, Void, Boolean>() {
                @Override
                protected Boolean doInBackground(Void... args) {
                    Settings.Secure.setLocationProviderEnabled(resolver, LocationManager.GPS_PROVIDER, desiredState);
                    return desiredState;
                }
                @Override
                protected void onPostExecute(Boolean result) {
                    setCurrentState(context, result ? STATE_ENABLED : STATE_DISABLED);
                    setImageViewResources(context);
                }
            }.execute();
        }

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章