蓝牙文件的传输是蓝牙中的一个很重要的应用,本文就来分析一下这个流程中涉及的一些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)
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="*/*" />
</intent-filter>
大家以后若是想让自己的app也显示在这个share的列表中,就可以类似的加入了。当然Android源码中还有别的一些原生应用也有这个action,比如Gmail(Email)等等,大家也可以去对应的源码位置看一看。当然actionbar其实也是类似的,大家可以去看一下Gallery2中的代码实现
所以总得来说,整个流程如下图2所示:
图2 Android中分享实现示意图
2、 蓝牙扫描界面的出现
在选择蓝牙图标之后,就会启动包含这个action的对应的activity。我们来再看一下上文提到的在bluetooth的AndroidManifest.xml中ACTION_SEND对应的activity:
<activity android:name=".opp.BluetoothOppLauncherActivity"
android:process="@string/process"
android:theme="@android:style/Theme.Holo.Dialog" android:label="@string/bt_share_picker_label">
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="*/*" />
</intent-filter>
清楚地看到对应的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来实现的。蓝牙打开的流程在之前的博客中已经分析过了,请参见http://blog.csdn.net/u011960402/article/details/12745113以及http://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)) { //启动BluetoothOppService这个service context.startService(new Intent(context, BluetoothOppService.class)); 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后,启动BluetoothOppService的service,若是发送文件的标志位被置位,先清空,然后启动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/内容如下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <fragment android:id="@+id/bluetooth_device_picker_fragment" android:name="com.android.settings.bluetooth.DevicePickerFragment" android:layout_width="match_parent" android:layout_height="0dip" android:layout_weight="1" /> </LinearLayout>
其实还是很明确的,就是加载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方面的操作就已经全部分析和总结完毕了,下面就是选择设备后的涉及到蓝牙的操作了。欲知后事如何,请听下回分解。