2025-03-24

藍牙文件的傳輸是藍牙中的一個很重要的應用,本文就來分析一下這個流程中涉及的一些UI操作。

1、 藍牙圖標的出現

我們在發送文件的時候第一步就是點擊分享按鈕後跳出一個如圖1所示的對話框。這裡就可以選擇我們通過什麼進行分享。

a)長按分享 b)action bar分享

圖1分享界面示意圖

以長按分享為例,這段ui產生實現的代碼如下:(以選擇一個圖片為例,代碼位置:packages/apps/Gallery/src/com/android/camera/MenuHelper.java)

// Called when "Share" is clicked.
    private static boolean onImageShareClicked(MenuInvoker onInvoke,
            final Activity activity) {
        onInvoke.run(new MenuCallback() {
            public void run(Uri u, IImage image) {
                if (image == null) return;

                Intent intent = new Intent();
                intent.setAction(Intent.ACTION_SEND);
                String mimeType = image.getMimeType();
                intent.setType(mimeType);
                intent.putExtra(Intent.EXTRA_STREAM, u);
                boolean isImage = ImageManager.isImage(image);
                try {
//這裡就是列出所有支持ACTION_SEND action的app
                    activity.startActivity(Intent.createChooser(intent,
                            activity.getText(isImage
                            ? R.string.sendImage
                            : R.string.sendVideo)));
                } catch (android.content.ActivityNotFoundException ex) {
                    Toast.makeText(activity, isImage
                            ? R.string.no_way_to_share_image
                            : R.string.no_way_to_share_video,
                            Toast.LENGTH_SHORT).show();
                }
            }
        });

這段代碼最關鍵的地方在於

activity.startActivity(Intent.createChooser(intent,
                            activity.getText(isImage
                            ? R.string.sendImage
                            : R.string.sendVideo)));

它會顯示所有支援ACTION_SEND的action的app。顯然藍牙是其中一個,當然gmail,短信等等也都是支援這個action的。我們是如何知道藍牙是支持這個action的呢,他是在Androidmanifest.xml中可以看出:(代碼位置:packages/apps/Bluetooth/
AndroidManifest.xml)

                
                
                
            

大傢以後若是想讓自己的app也顯示在這個share的列表中,就可以類似的加入瞭。當然Android源碼中還有別的一些原生應用也有這個action,比如Gmail(Email)等等,大傢也可以去對應的源碼位置看一看。當然actionbar其實也是類似的,大傢可以去看一下Gallery2中的代碼實現

所以總得來說,整個流程如下圖2所示:

圖2 Android中分享實現示意圖

2、 藍牙掃描界面的出現

在選擇藍牙圖標之後,就會啟動包含這個action的對應的activity。我們來再看一下上文提到的在bluetooth的AndroidManifest.xml中ACTION_SEND對應的activity:

            
                
                
                
            

清楚地看到對應的activity是BluetoothOppLauncherActivity。所以在分享中點擊藍牙圖標後啟動的就是這個activity。該activity的onCreate函數分析如下:(代碼位置:packages/apps/Bluetooth/src/com/android/bluetooth/opp/ BluetoothOppLauncherActivity.java)

public void onCreate(Bundle savedInstanceState) {
……
If (action.equals(Intent.ACTION_SEND) || action.equals(Intent.ACTION_SEND_MULTIPLE)) {
//得到傳入的uri和type等信息
……
} else if (action.equals(Intent.ACTION_SEND_MULTIPLE)) {
//這個action是批量傳輸的時候使用的,這裡也提一下,和上面是一樣的
……
}
if (!isBluetoothAllowed()) {
//檢查藍牙是否被允許,也就是是否是在飛行模式等等,若是不被允許,進行一些錯誤的處理
……
}
//看藍牙是否打開
    if (!BluetoothOppManager.getInstance(this).isEnabled()) {
                if (V) Log.v(TAG, "Prepare Enable BT!! ");
		//若是藍牙沒有打開,則先啟動BluetoothOppBtEnableActivity這個activity
                Intent in = new Intent(this, BluetoothOppBtEnableActivity.class);
                in.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                this.startActivity(in);
            } else {
		//若是藍牙已經打開,則啟動BluetoothDevicePicker這個activity
               if (V) Log.v(TAG, "BT already enabled!! ");
                Intent in1 = new Intent(BluetoothDevicePicker.ACTION_LAUNCH);
                in1.setFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
                in1.putExtra(BluetoothDevicePicker.EXTRA_NEED_AUTH, false);
                in1.putExtra(BluetoothDevicePicker.EXTRA_FILTER_TYPE,
                        BluetoothDevicePicker.FILTER_TYPE_TRANSFER);
                in1.putExtra(BluetoothDevicePicker.EXTRA_LAUNCH_PACKAGE,
                        Constants.THIS_PACKAGE_NAME);
                in1.putExtra(BluetoothDevicePicker.EXTRA_LAUNCH_CLASS,
                        BluetoothOppReceiver.class.getName());

                this.startActivity(in1);
            }
……
        finish();
    }
}

所以,總得來說在選擇藍牙之後到藍牙的掃描設備界面出來之前的操作還是比較清楚的。它做瞭幾個方面的工作:

1) 得到要分析文件的uri,type等信息。

2) 檢查藍牙是否可以使用(包括是否處於飛行模式等)

3) 檢查藍牙是否已經打開,若未打開則先打開,否則直接去調用我們想看到的掃描設備的界面。

下面我們從兩個角度來看接下來的操作,一個是藍牙開始未打開的情況,一個是藍牙開始已經打開的情況。

2.1
藍牙未打開

上文我們提到,藍牙未打開的時候啟動的activity是BluetoothOppBtEnableActivity,它仍然位於packages/apps/Bluetooth/src/com/android/bluetooth/opp/這個目錄下。

protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        if (SystemProperties.get("ro.btwifi.coexist", "true").equals("false")) {
            mWifiManager = (WifiManager) this.getSystemService(Context.WIFI_SERVICE);
            mSupportBtWifiCoexist = false;
        }
        // Set up the "dialog"
        final AlertController.AlertParams p = mAlertParams;
        p.mIconId = android.R.drawable.ic_dialog_alert;
        p.mTitle = getString(R.string.bt_enable_title);
        p.mView = createView();
        p.mPositiveButtonText = getString(R.string.bt_enable_ok);
        p.mPositiveButtonListener = this;
        p.mNegativeButtonText = getString(R.string.bt_enable_cancel);
        p.mNegativeButtonListener = this;
        setupAlert();
    }

所以,很簡單就是跳出一個對話框詢問是否打開藍牙。這個對話框有兩個按鈕,打開或者取消,如圖3所示。

圖3是否打開藍牙的對話框

毫無疑問,下面就是對“打開”和“取消”兩個按鈕的處理瞭。

public void onClick(DialogInterface dialog, int which) {
        switch (which) {
			//打開按鈕的處理
            case DialogInterface.BUTTON_POSITIVE:
			……
			//打開藍牙
			mOppManager.enableBluetooth();
			//設置sending的flage為true,這個flage很重要是用來表示是否正在發送文件的,我們會在bt打開之後根據這個標志位來決定是否顯示掃描設備的界面,後面會遇到的
mOppManager.mSendingFlag = true;
			//進入藍牙正在打開的ui
			Intent in = new Intent(this, BluetoothOppBtEnablingActivity.class);
                in.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                this.startActivity(in);

                finish();
           break;
			//取消按鈕的處理
            case DialogInterface.BUTTON_NEGATIVE:
                finish();
                break;
……
}

從上面代碼可以看出,若是點擊“取消”按鈕則直接finish,若是點擊“打開”按鈕則會先去enbale藍牙(異步),同時顯示一個正在打開的ui。該ui是通過BluetoothOppBtEnablingActivity這個activity來實現的。藍牙打開的流程在之前的博客中已經分析過瞭,請參見https://blog.csdn.net/u011960402/article/details/12745113以及https://blog.csdn.net/u011960402/article/details/12831653等博文。

由上面可知在點擊瞭“打開”按鈕之後,就會進入到正在打開的界面,該界面是由BluetoothOppBtEnablingActivity來實現的。這個activity的實現代碼如下,代碼位置仍然位於packages/apps/Bluetooth/src/com/android/bluetooth/opp/這個目錄下。

protected void onCreate(Bundle savedInstanceState) {
	……
	//註冊BluetoothAdapter.ACTION_STATE_CHANGED的receiver,用於監聽bt的打開。
        IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
        registerReceiver(mBluetoothReceiver, filter);
	//啟動一個正在打開的UI界面
        final AlertController.AlertParams p = mAlertParams;
        p.mIconId = android.R.drawable.ic_dialog_info;
        p.mTitle = getString(R.string.enabling_progress_title);
        p.mView = createView();
        setupAlert();
	//啟動一個超時的定時器,用於在bt長時間打不開的時候退出正在打開的界面。這個超時時間是20s。	 mTimeoutHandler.sendMessageDelayed(mTimeoutHandler.obtainMessage(BT_ENABLING_TIMEOUT),
                BT_ENABLING_TIMEOUT_VALUE);
}

正在打開的UI界面如圖4所示:

圖4藍牙正在打開的界面示意圖

我們知道藍牙打開後會發送STATE_CHANGE的broadcast,這裡是mBluetoothReceiver來監聽這個action的,收到STATE_ON的action之後就直接把正在打開的這個UI拿finish掉同時remove timeout即可。簡單代碼如下

  case BluetoothAdapter.STATE_ON:
                        mTimeoutHandler.removeMessages(BT_ENABLING_TIMEOUT);
                        finish();

這裡可能您會有疑問,怎麼直接finish就沒有瞭,設備掃描的界面在哪裡實現的,哈哈,不要急,這裡他是通過另外一個receiver來實現的。它就是BluetoothOppReceiver,這個receiver是在opp的apk中靜態註冊的。它的處理代碼如下:

 public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
            if (BluetoothAdapter.STATE_ON == intent.getIntExtra(
BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR)) {
synchronized (this) {
		//這裡會檢查是不是要發送文件,這個標志位就是我們上面在打開bt的時候設置的
                    if (BluetoothOppManager.getInstance(context).mSendingFlag) {
// reset the flags
                        BluetoothOppManager.getInstance(context).mSendingFlag = false;
//啟動devicepicker的action_launch
                        Intent in1 = new Intent(BluetoothDevicePicker.ACTION_LAUNCH);
                        in1.putExtra(BluetoothDevicePicker.EXTRA_NEED_AUTH, false);
                        in1.putExtra(BluetoothDevicePicker.EXTRA_FILTER_TYPE,
                                BluetoothDevicePicker.FILTER_TYPE_TRANSFER);
                        in1.putExtra(BluetoothDevicePicker.EXTRA_LAUNCH_PACKAGE,
                                Constants.THIS_PACKAGE_NAME);
                        in1.putExtra(BluetoothDevicePicker.EXTRA_LAUNCH_CLASS,
                                BluetoothOppReceiver.class.getName());

                        in1.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                        context.startActivity(in1);
……
}

這裡就和藍牙打開的時候的操作是一樣的。所以在藍牙未打開的時候所做的操作是:

1) 打開藍牙

2) 置發送文件的標志位

3) 啟動正在打開藍牙的UI

4) 收到STATE_ON的action後關閉正在打開藍牙的UI

5) 收到STATE_ON的action後,若是發送文件的標志位被置位,先清空,然後啟動devicepicker的action,緊接著和藍牙打開的操作是一樣的。見2.2的分析。

2.2藍牙已經打開

從上面分析我們可以知道,藍牙已經打開的情況下,就是啟動BluetoothDevicePicker的activity。這個activity在packages/apps/Settings/src/com/android/settings/bluetooth目錄下,代碼也很簡單:

 protected void onCreate(Bundle savedInstanceState) {
        if(Settings.UNIVERSEUI_SUPPORT){
            this.setTheme(R.style.Theme_Holo_DialogWhenLarge_new_ui);
        }
        super.onCreate(savedInstanceState);
	//主要就是加載這個layout
        setContentView(R.layout.bluetooth_device_picker);
    }

我們看一下layout的主要內容。目錄packages/apps/Settings/res/內容如下:


    

其實還是很明確的,就是加載com.android.settings.bluetooth.DevicePickerFragment這個瞭,這個Fragment是在packages/apps/Settings/src/com/android/settings/bluetooth目錄下的DevicePickerFragment.java文件中實現的。

 public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
		//設置標題
        getActivity().setTitle(getString(R.string.device_picker));
		//有rotation就不開始掃描瞭
        mStartScanOnResume = (savedInstanceState == null);  // don't start scan after rotation
    }
    public void onResume() {
        super.onResume();
	//顯示之前cached的一些設備
        addCachedDevices();
	//開始掃描
        if (mStartScanOnResume) {
            mLocalAdapter.startScanning(true);
            mStartScanOnResume = false;
        }
    }

至此,我們就可以看到藍牙的掃描設備的界面瞭。如下圖5所示。

圖5藍牙掃描設備的界面

這個過程的流程我們可以總結為下圖6所示:

圖6 ACTION_SEND之後的流程示意圖

至此藍牙傳輸文件過程中開始涉及的UI方面的操作就已經全部分析和總結完畢瞭,下面就是選擇設備後的涉及到藍牙的操作瞭。欲知後事如何,請聽下回分解。

若您有任何問題,請直接留言提出,我會及時回答的。

若您感覺該文章對您有幫助,請按“頂”,哈哈~~

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *