您的位置:首页 > 移动开发 > Android开发

Android Beam文件分享失败问题单处理

2016-11-23 10:42 381 查看
     昨天收到测试同事提的一个问题单,一个大文件,使用Android Beam分享失败。自己拿到之后,先进行了下测试,真是,找了个Nexus原生机对比一下,怪了,原生机是成功的。那就说明修改原生的问题哪里出错了,好了,开始干活吧。

     再进行验证,先把现象搞清楚,首先找测试同事要到他测试的原视频,大小是843M,用这个反复测了多次,一直失败。好了,自己随便录制一段,再进行Beam分享,咦,好奇怪啊,我录制的视频分享成功!!不对,还不能确定,再试,拿测试同事的视频试一下,还是失败,难道是文件的问题?对比文件看了一下,哦,测试同事提供的文件命名中有中文字符。好,那我把自己录的名字改成中文的,再测试一下,也失败,OK了,看来不是文件差别引起的,可能跟命名当中带有的中文字符有关了。这就是一点眉目,再继续试,现在的分享是从文件管理器中进去的,录制的视频也可以直接从图库中找到,那么相同的文件,从图库中进去执行Beam分享,我靠,神奇了,从图库中进去执行分享,同一个文件,而且命名也有中文字符,是成功的!哇,有收获,不错,进一步明确了差异。现在的初步结论应该是容易确定了,如果从图库中进入,那么无论要分享的文件命名当中是否包含中文,都可以成功,而如果从文件管理器进入,执行分享,那么此时的文件名如果有中文,则必现失败。

     有了补步的现象,接下来,我们就要对这个现象进一步分析了,差别是在哪里呢?首先来看一下NFC beam分享功能,beam的属于系统提供的功能,所以它的代码位于packages包下面,因为beam功能是对应有分享和接收两侧的,所以这里会对应有send和receive,当我们选中一个文件,点击beam分享时,就会执行BeamManager类的startBeamSend方法,大家从BeamManager类的方法当中也可以看到,既有发送,又有接收,整个类的代码如下:

/**
* Manager for starting and stopping Beam transfers. Prevents more than one transfer from
* happening at a time.
*/
public class BeamManager implements Handler.Callback {
private static final String TAG = "BeamManager";
private static final boolean DBG = false;

private static final String ACTION_WHITELIST_DEVICE =
"android.btopp.intent.action.WHITELIST_DEVICE";
public static final int MSG_BEAM_COMPLETE = 0;

private final Object mLock;

private boolean mBeamInProgress;
private final Handler mCallback;

private NfcService mNfcService;

private static final class Singleton {
public static final BeamManager INSTANCE = new BeamManager();
}

private BeamManager() {
mLock = new Object();
mBeamInProgress = false;
mCallback = new Handler(Looper.getMainLooper(), this);
mNfcService = NfcService.getInstance();
}

public static BeamManager getInstance() {
return Singleton.INSTANCE;
}

public boolean isBeamInProgress() {
synchronized (mLock) {
return mBeamInProgress;
}
}

public boolean startBeamReceive(Context context,
HandoverDataParser.BluetoothHandoverData handoverData) {
synchronized (mLock) {
if (mBeamInProgress) {
return false;
} else {
mBeamInProgress = true;
}
}

BeamTransferRecord transferRecord =
BeamTransferRecord.forBluetoothDevice(
handoverData.device, handoverData.carrierActivating, null);

Intent receiveIntent = new Intent(context.getApplicationContext(),
BeamReceiveService.class);
receiveIntent.putExtra(BeamReceiveService.EXTRA_BEAM_TRANSFER_RECORD, transferRecord);
receiveIntent.putExtra(BeamReceiveService.EXTRA_BEAM_COMPLETE_CALLBACK,
new Messenger(mCallback));
whitelistOppDevice(context, handoverData.device);
context.startServiceAsUser(receiveIntent, UserHandle.CURRENT);
return true;
}

public boolean startBeamSend(Context context,
HandoverDataParser.BluetoothHandoverData outgoingHandoverData,
Uri[] uris, UserHandle userHandle) {
synchronized (mLock) {
if (mBeamInProgress) {
return false;
} else {
mBeamInProgress = true;
}
}

BeamTransferRecord transferRecord = BeamTransferRecord.forBluetoothDevice(
outgoingHandoverData.device, outgoingHandoverData.carrierActivating,
uris);
Intent sendIntent = new Intent(context.getApplicationContext(),
BeamSendService.class);
sendIntent.putExtra(BeamSendService.EXTRA_BEAM_TRANSFER_RECORD, transferRecord);
sendIntent.putExtra(BeamSendService.EXTRA_BEAM_COMPLETE_CALLBACK,
new Messenger(mCallback));
context.startServiceAsUser(sendIntent, userHandle);
return true;
}

@Override
public boolean handleMessage(Message msg) {
if (msg.what == MSG_BEAM_COMPLETE) {
synchronized (mLock) {
mBeamInProgress = false;
}

boolean success = msg.arg1 == 1;
if (success) {
mNfcService.playSound(NfcService.SOUND_END);
}
return true;
}
return false;
}

void whitelistOppDevice(Context context, BluetoothDevice device) {
if (DBG) Log.d(TAG, "Whitelisting " + device + " for BT OPP");
Intent intent = new Intent(ACTION_WHITELIST_DEVICE);
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
context.sendBroadcastAsUser(intent, UserHandle.CURRENT);
}

}

     可以从startBeamSend方法当中看到,后边的发送事件是通过BeamSendService来完成的,这里还请大家注意,方法参数Uri[] uris正是保存了我们要分享的目标文件的uri,数据都是在这里的。好了,通过调用context.startServiceAsUser(sendIntent, userHandle)就把BeamSendService启动起来了。那我们再来看一下BeamSendService类的代码。

public class BeamSendService extends Service implements BeamTransferManager.Callback {
private static String TAG = "BeamSendService";
private static boolean DBG = true;

public static String EXTRA_BEAM_TRANSFER_RECORD
= "com.android.nfc.beam.EXTRA_BEAM_TRANSFER_RECORD";
public static final String EXTRA_BEAM_COMPLETE_CALLBACK
= "com.android.nfc.beam.TRANSFER_COMPLETE_CALLBACK";

private BeamTransferManager mTransferManager;
private BeamStatusReceiver mBeamStatusReceiver;
private boolean mBluetoothEnabledByNfc;
private Messenger mCompleteCallback;
private int mStartId;

private final BluetoothAdapter mBluetoothAdapter;
private final BroadcastReceiver mBluetoothStateReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
handleBluetoothStateChanged(intent);
}
}
};

public BeamSendService() {
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
}

@Override
public void onCreate() {
super.onCreate();

// register BT state receiver
IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
registerReceiver(mBluetoothStateReceiver, filter);
}

@Override
public void onDestroy() {
super.onDestroy();

if (mBeamStatusReceiver != null) {
unregisterReceiver(mBeamStatusReceiver);
}
unregisterReceiver(mBluetoothStateReceiver);
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
mStartId = startId;

BeamTransferRecord transferRecord;
if (intent == null ||
(transferRecord = intent.getParcelableExtra(EXTRA_BEAM_TRANSFER_RECORD)) == null) {
if (DBG) Log.e(TAG, "No transfer record provided. Stopping.");
stopSelf(startId);
return START_NOT_STICKY;
}

mCompleteCallback = intent.getParcelableExtra(EXTRA_BEAM_COMPLETE_CALLBACK);

if (doTransfer(transferRecord)) {
if (DBG) Log.i(TAG, "Starting outgoing Beam transfer");
return START_STICKY;
} else {
invokeCompleteCallback(false);
stopSelf(startId);
return START_NOT_STICKY;
}
}

boolean doTransfer(BeamTransferRecord transferRecord) {
if (createBeamTransferManager(transferRecord)) {
// register Beam status receiver
mBeamStatusReceiver = new BeamStatusReceiver(this, mTransferManager);
registerReceiver(mBeamStatusReceiver, mBeamStatusReceiver.getIntentFilter(),
BeamStatusReceiver.BEAM_STATUS_PERMISSION, new Handler());

if (transferRecord.dataLinkType == BeamTransferRecord.DATA_LINK_TYPE_BLUETOOTH) {
if (mBluetoothAdapter.isEnabled()) {
// Start the transfer
mTransferManager.start();
} else {
if (!mBluetoothAdapter.enableNoAutoConnect()) {
Log.e(TAG, "Error enabling Bluetooth.");
mTransferManager = null;
return false;
}
mBluetoothEnabledByNfc = true;
if (DBG) Log.d(TAG, "Queueing out transfer "
+ Integer.toString(transferRecord.id));
}
}
return true;
}

return false;
}

boolean createBeamTransferManager(BeamTransferRecord transferRecord) {
if (mTransferManager != null) {
return false;
}

if (transferRecord.dataLinkType != BeamTransferRecord.DATA_LINK_TYPE_BLUETOOTH) {
// only support BT
return false;
}

mTransferManager = new BeamTransferManager(this, this, transferRecord, false);
mTransferManager.updateNotification();
return true;
}

private void handleBluetoothStateChanged(Intent intent) {
int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
BluetoothAdapter.ERROR);
if (state == BluetoothAdapter.STATE_ON) {
if (mTransferManager != null &&
mTransferManager.mDataLinkType == BeamTransferRecord.DATA_LINK_TYPE_BLUETOOTH) {
mTransferManager.start();
}
} else if (state == BluetoothAdapter.STATE_OFF) {
mBluetoothEnabledByNfc = false;
}
}

private void invokeCompleteCallback(boolean success) {
if (mCompleteCallback != null) {
try {
Message msg = Message.obtain(null, BeamManager.MSG_BEAM_COMPLETE);
msg.arg1 = success ? 1 : 0;
mCompleteCallback.send(msg);
} catch (RemoteException e) {
Log.e(TAG, "failed to invoke Beam complete callback", e);
}
}
}

@Override
public void onTransferComplete(BeamTransferManager transfer, boolean success) {
// Play success sound
if (!success) {
if (DBG) Log.d(TAG, "Transfer failed, final state: " +
Integer.toString(transfer.mState));
}

if (mBluetoothEnabledByNfc) {
mBluetoothEnabledByNfc = false;
mBluetoothAdapter.disable();
}

invokeCompleteCallback(success);
stopSelf(mStartId);
}

@Override
public IBinder onBind(Intent intent) {
return null;
}
}

     在BeamSendService类的onCreate方法当中就是简单的注册了一个mBluetoothStateReceiver广播,从这里我们也可以清晰的看到,使用beam功能来进行文件分享,其实最底层的还是使用的蓝牙的功能。mBluetoothStateReceiver广播就是用来监听蓝牙状态变化的。如果广播打开了,并且mTransferManagerr对象不为空,就调用它的start()方法开始传输。或者当我们的service启动完成之后,系统层也会回调onStartCommand方法,这里也会调用mTransferManager.start()开始传输,两种情况都是同样的目的。

     为了方便我们分析,我把传输成功的部分log日志贴出来,方便大家查看,如果大家需要完整的日志,可以在这里下载,我提供的日志也是在framework当中加了自己的一些日志打印的:

     beam传输成功和失败两种情况的完整日志

Line 8667: 11-18 20:09:20.526 D/ActivityManager( 1218): ACT-attachApplication pid 7715 to thread android.app.ApplicationThreadProxy@dccee4b
Line 9112: 11-18 20:09:21.011 D/BeamSendService( 7715): Queueing out transfer 0
Line 9113: 11-18 20:09:21.011 I/BeamSendService( 7715): Starting outgoing Beam transfer
Line 11242: 11-18 20:09:23.462 V/IntentResolver( 1218): Matching against filter BroadcastFilter{3dadbc3 u0 ReceiverList{3775b72 7715 com.android.nfc:beam/1027/u0 remote:ce2607d}}
Line 11245: 11-18 20:09:23.462 V/IntentResolver( 1218): oneResult is not null, BroadcastFilter{3dadbc3 u0 ReceiverList{3775b72 7715 com.android.nfc:beam/1027/u0 remote:ce2607d}}
Line 11247: 11-18 20:09:23.462 V/ActivityManager( 1218): if case, ordered = false, NR = 7, registeredReceivers = [BroadcastFilter{2edf13d u0 ReceiverList{bdfd294 1218 system/1000/u0 local:70796e7}}, BroadcastFilter{6568293
u0 ReceiverList{cb11b82 4063 com.android.systemui/10033/u0 remote:f89d6cd}}, BroadcastFilter{c4af99f u0 ReceiverList{f18173e 4652 com.quicinc.wbcserviceapp/1000/u0 remote:e1cbbf9}}, BroadcastFilter{d63a15c u0 ReceiverList{d61c3cf 4063 com.android.systemui/10033/u0
remote:55fec2e}}, BroadcastFilter{27d8145 u0 ReceiverList{e82fdbc 1218 system/1000/u0 local:9ed55af}}, BroadcastFilter{a929949 u0 ReceiverList{ec52350 4563 com.qti.dpmserviceapp/1000/u0 remote:c790513}}, BroadcastFilter{3dadbc3 u0 ReceiverList{3775b72 7715
com.android.nfc:beam/1027/u0 remote:ce2607d}}], resultTo = null
Line 11288: 11-18 20:09:23.479 V/BroadcastQueue( 1218): Delivering non-ordered on [foreground] to registered BroadcastFilter{3dadbc3 u0 ReceiverList{3775b72 7715 com.android.nfc:beam/1027/u0 remote:ce2607d}}: BroadcastRecord{fffd539
u-1 android.bluetooth.adapter.action.STATE_CHANGED, isShadow:false}
Line 11295: 11-18 20:09:23.487 I/BroadcastQueue( 1218): Delivering to BroadcastFilter{3dadbc3 u0 ReceiverList{3775b72 7715 com.android.nfc:beam/1027/u0 remote:ce2607d}} : BroadcastRecord{fffd539 u-1 android.bluetooth.adapter.action.STATE_CHANGED,
isShadow:false}
Line 11929: 11-18 20:09:24.403 V/IntentResolver( 1218): Matching against filter BroadcastFilter{3dadbc3 u0 ReceiverList{3775b72 7715 com.android.nfc:beam/1027/u0 remote:ce2607d}}
Line 11932: 11-18 20:09:24.403 V/IntentResolver( 1218): oneResult is not null, BroadcastFilter{3dadbc3 u0 ReceiverList{3775b72 7715 com.android.nfc:beam/1027/u0 remote:ce2607d}}
Line 11943: 11-18 20:09:24.409 V/ActivityManager( 1218): if case, ordered = false, NR = 9, registeredReceivers = [BroadcastFilter{2edf13d u0 ReceiverList{bdfd294 1218 system/1000/u0 local:70796e7}}, BroadcastFilter{6568293
u0 ReceiverList{cb11b82 4063 com.android.systemui/10033/u0 remote:f89d6cd}}, BroadcastFilter{c4af99f u0 ReceiverList{f18173e 4652 com.quicinc.wbcserviceapp/1000/u0 remote:e1cbbf9}}, BroadcastFilter{d63a15c u0 ReceiverList{d61c3cf 4063 com.android.systemui/10033/u0
remote:55fec2e}}, BroadcastFilter{27d8145 u0 ReceiverList{e82fdbc 1218 system/1000/u0 local:9ed55af}}, BroadcastFilter{a929949 u0 ReceiverList{ec52350 4563 com.qti.dpmserviceapp/1000/u0 remote:c790513}}, BroadcastFilter{3dadbc3 u0 ReceiverList{3775b72 7715
com.android.nfc:beam/1027/u0 remote:ce2607d}}, BroadcastFilter{3fc6419 u0 ReceiverList{2ff1760 7732 com.android.bluetooth/1002/u0 remote:9f0ff63}}, BroadcastFilter{96936b7 u0 ReceiverList{47339b6 7732 com.android.bluetooth/1002/u0 remote:c6d7d51}}], resultTo
= null
Line 11982: 11-18 20:09:24.463 V/BroadcastQueue( 1218): Delivering non-ordered on [foreground] to registered BroadcastFilter{3dadbc3 u0 ReceiverList{3775b72 7715 com.android.nfc:beam/1027/u0 remote:ce2607d}}: BroadcastRecord{a1b7c66
u-1 android.bluetooth.adapter.action.STATE_CHANGED, isShadow:false}
Line 11984: 11-18 20:09:24.463 I/BroadcastQueue( 1218): Delivering to BroadcastFilter{3dadbc3 u0 ReceiverList{3775b72 7715 com.android.nfc:beam/1027/u0 remote:ce2607d}} : BroadcastRecord{a1b7c66 u-1 android.bluetooth.adapter.action.STATE_CHANGED,
isShadow:false}
Line 13084: 11-18 20:09:29.582 D/BluetoothOppHandover( 7715): Handing off outging transfer to BT, userid = 0
Line 14370: 11-18 20:09:34.555 V/IntentResolver( 1218): Matching against filter BroadcastFilter{7422235 u0 ReceiverList{d42cc6c 7715 com.android.nfc:beam/1027/u0 remote:b85711f}}
Line 14373: 11-18 20:09:34.555 V/IntentResolver( 1218): oneResult is not null, BroadcastFilter{7422235 u0 ReceiverList{d42cc6c 7715 com.android.nfc:beam/1027/u0 remote:b85711f}}
Line 14375: 11-18 20:09:34.555 V/ActivityManager( 1218): if case, ordered = false, NR = 1, registeredReceivers = [BroadcastFilter{7422235 u0 ReceiverList{d42cc6c 7715 com.android.nfc:beam/1027/u0 remote:b85711f}}], resultTo
= null
Line 14385: 11-18 20:09:34.573 V/BroadcastQueue( 1218): Delivering non-ordered on [background] to registered BroadcastFilter{7422235 u0 ReceiverList{d42cc6c 7715 com.android.nfc:beam/1027/u0 remote:b85711f}}: BroadcastRecord{8f1472e
u0 android.nfc.handover.intent.action.TRANSFER_PROGRESS, isShadow:false}
Line 14386: 11-18 20:09:34.574 I/BroadcastQueue( 1218): Delivering to BroadcastFilter{7422235 u0 ReceiverList{d42cc6c 7715 com.android.nfc:beam/1027/u0 remote:b85711f}} : BroadcastRecord{8f1472e u0 android.nfc.handover.intent.action.TRANSFER_PROGRESS,
isShadow:false}
Line 14391: 11-18 20:09:34.595 I/BeamStatusReceiver( 7715): receive broadcat, action = android.nfc.handover.intent.action.TRANSFER_PROGRESS
Line 14580: 11-18 20:09:35.124 V/IntentResolver( 1218): Matching against filter BroadcastFilter{7422235 u0 ReceiverList{d42cc6c 7715 com.android.nfc:beam/1027/u0 remote:b85711f}}
Line 14584: 11-18 20:09:35.124 V/IntentResolver( 1218): oneResult is not null, BroadcastFilter{7422235 u0 ReceiverList{d42cc6c 7715 com.android.nfc:beam/1027/u0 remote:b85711f}}
Line 14587: 11-18 20:09:35.124 V/ActivityManager( 1218): if case, ordered = false, NR = 1, registeredReceivers = [BroadcastFilter{7422235 u0 ReceiverList{d42cc6c 7715 com.android.nfc:beam/1027/u0 remote:b85711f}}], resultTo
= null
Line 26291: 11-18 20:09:55.935 V/BroadcastQueue( 1218): Delivering non-ordered on [background] to registered BroadcastFilter{7422235 u0 ReceiverList{d42cc6c 7715 com.android.nfc:beam/1027/u0 remote:b85711f}}: BroadcastRecord{f683203
u0 android.nfc.handover.intent.action.TRANSFER_DONE, isShadow:false}
Line 26292: 11-18 20:09:55.935 I/BroadcastQueue( 1218): Delivering to BroadcastFilter{7422235 u0 ReceiverList{d42cc6c 7715 com.android.nfc:beam/1027/u0 remote:b85711f}} : BroadcastRecord{f683203 u0 android.nfc.handover.intent.action.TRANSFER_DONE,
isShadow:false}
Line 26305: 11-18 20:09:55.960 I/BeamStatusReceiver( 7715): receive broadcat, action = android.nfc.handover.intent.action.TRANSFER_DONE
Line 26309: 11-18 20:09:55.965 D/BeamTransferManager( 7715): Transfer success, uri file:///%E4%B8%8D%E6%96%AD%E6%B5%8B%E8%AF%95.jpg mimeType image/jpeg
Line 26456: 11-18 20:09:56.072 V/IntentResolver( 1218): Matching against filter BroadcastFilter{3dadbc3 u0 ReceiverList{3775b72 7715 com.android.nfc:beam/1027/u0 remote:ce2607d}}

     mTransferManager对象是一个BeamTransferManager实体,它的start方法实现如下:

public void start() {
if (mStartTime > 0) {
// already started
return;
}

mStartTime = System.currentTimeMillis();

if (!mIncoming) {
if (mDataLinkType == BeamTransferRecord.DATA_LINK_TYPE_BLUETOOTH) {
new BluetoothOppHandover(mContext, mRemoteDevice, mUris, mRemoteActivating).start();
}
}
}

     它当中又调用了BluetoothOppHandover类的start方法:

public class BluetoothOppHandover implements Handler.Callback {
static final String TAG = "BluetoothOppHandover";
static final boolean DBG = true;

static final int STATE_INIT = 0;
static final int STATE_TURNING_ON = 1;
static final int STATE_WAITING = 2; // Need to wait for remote side turning on BT
static final int STATE_COMPLETE = 3;

static final int MSG_START_SEND = 0;

static final int REMOTE_BT_ENABLE_DELAY_MS = 5000;

static final String ACTION_HANDOVER_SEND =
"android.nfc.handover.intent.action.HANDOVER_SEND";

static final String ACTION_HANDOVER_SEND_MULTIPLE =
"android.nfc.handover.intent.action.HANDOVER_SEND_MULTIPLE";

final Context mContext;
final BluetoothDevice mDevice;

final ArrayList mUris;
final boolean mRemoteActivating;
final Handler mHandler;
final Long mCreateTime;

int mState;

public BluetoothOppHandover(Context context, BluetoothDevice device, ArrayList uris,
boolean remoteActivating) {
mContext = context;
mDevice = device;
mUris = uris;
mRemoteActivating = remoteActivating;
mCreateTime = SystemClock.elapsedRealtime();

mHandler = new Handler(context.getMainLooper(), this);
mState = STATE_INIT;
}

/**
* Main entry point. This method is usually called after construction,
* to begin the BT sequence. Must be called on Main thread.
*/
public void start() {
if (mRemoteActivating) {
Long timeElapsed = SystemClock.elapsedRealtime() - mCreateTime;
if (timeElapsed < REMOTE_BT_ENABLE_DELAY_MS) {
mHandler.sendEmptyMessageDelayed(MSG_START_SEND,
REMOTE_BT_ENABLE_DELAY_MS - timeElapsed);
} else {
// Already waited long enough for BT to come up
// - start send.
sendIntent();
}
} else {
// Remote BT enabled already, start send immediately
sendIntent();
}
}

void complete() {
mState = STATE_COMPLETE;
}
void sendIntent() {
Intent intent = new Intent();
intent.setPackage("com.android.bluetooth");
String mimeType = MimeTypeUtil.getMimeTypeForUri(mContext, mUris.get(0));
intent.setType(mimeType);
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
for (Uri uri : mUris) {
// TODO we need to transfer our permission grant from NFC
// to the Bluetooth process. This works, but we don't have
// a good framework API for revoking permission yet.
try {
mContext.grantUriPermission("com.android.bluetooth", uri,
Intent.FLAG_GRANT_READ_URI_PERMISSION);
} catch (SecurityException e) {
Log.e(TAG, "Failed to transfer permission to Bluetooth process.");
}
}
if (mUris.size() == 1) {
intent.setAction(ACTION_HANDOVER_SEND);
intent.putExtra(Intent.EXTRA_STREAM, mUris.get(0));
} else {
intent.setAction(ACTION_HANDOVER_SEND_MULTIPLE);
intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, mUris);
}
if (DBG) Log.d(TAG, "Handing off outging transfer to BT");
intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
mContext.sendBroadcast(intent);

complete();
}

@Override
public boolean handleMessage(Message msg) {
if (msg.what == MSG_START_SEND) {
sendIntent();
return true;
}
return false;
}
}

     这里我们可以看到,就是向蓝牙进程发送了一个广播,广播参数当中带有我们要分享的目标文件的uri,并且要给蓝牙进程赋予权限,否则蓝牙进程无法读取该数据。那么当蓝牙进程收到广播后,就开始文件传输,传输过程中会不断的发送ACTION_TRANSFER_PROGRESS广播,BeamStatusReceiver广播接收到蓝牙模块的ACTION_TRANSFER_PROGRESS广播之后,就调用BeamTransferManager类的函数对界面显示进行处理,当文件传输完成后,蓝牙模块最后发一个HANDOVER_TRANSFER_STATUS_SUCCESS广播,然后原分享的beam进程就知道文件分享完成了。整个的过程大概就是这样的。

     说完大概的beam传输过程之后,还有一点要特别说一下,在BeamTransferManager的构造方法当中,会调用mHandler.sendEmptyMessageDelayed(MSG_TRANSFER_TIMEOUT, ALIVE_CHECK_MS)来在20秒之后检测一下当前的连接状态,这个就和我们http连接当中的心跳包差不多,每隔20秒就会检测一次,如果20秒后MSG_TRANSFER_TIMEOUT还在,那就说明超时了,则取消本次传输,它的更新是在updateStateAndNotification方法当中:

void updateStateAndNotification(int newState) {
this.mState = newState;
this.mLastUpdate = SystemClock.elapsedRealtime();

mHandler.removeMessages(MSG_TRANSFER_TIMEOUT);
if (isRunning()) {
// Update timeout timer if we're still running
mHandler.sendEmptyMessageDelayed(MSG_TRANSFER_TIMEOUT, ALIVE_CHECK_MS);
}

updateNotification();

if ((mState == STATE_SUCCESS || mState == STATE_FAILED || mState == STATE_CANCELLED)
&& !mCalledBack) {
mCalledBack = true;
// Notify that we're done with this transfer
mCallback.onTransferComplete(this, mState == STATE_SUCCESS);
}
}

     可以看到,在这个方法当中,每次进来,先把MSG_TRANSFER_TIMEOUT事件移除,判断当前state正常,然后以当前时间点起始,又添加一个新的MSG_TRANSFER_TIMEOUT消息,如此往复循环,handleMessage当中不断的处理队列中的消息,如果有一次执行这个方法时,MSG_TRANSFER_TIMEOUT事件在队列当中,那就说明超时了。

     好了,那么在理解了beam文件分享的过程之后,我们来对比看一下正确传输和错误传输的日志,原进程在调用了BluetoothOppHandover类的sendIntent()发送了ACTION_HANDOVER_SEND广播之后,蓝牙模块始终没有响应的日志,那就是说广播根本没发到蓝牙进程当中,这是为什么呢?看来我们要跟踪一下系统广播分发的逻辑了。

     在BluetoothOppHandover类中调用mContext.sendBroadcast(intent)之后,经过ContextImpl的中转,那么我们的广播就到了AMS类的broadcastIntent方法当中了,这个方法是调用broadcastIntentLocked来执行广播分发的,broadcastIntentLocked方法是AMS广播分发的核心逻辑,大家可以看一下这个方法的代码,一个方法就610行,里边的逻辑非常复杂,各种细节性的处理,大家如果在研究,可以自己研究一下。需要给大家说一下,最终要分发一个广播,都会先构建一个BroadcastRecord对象,然后调用queue.enqueueOrderedBroadcastLocked(r)将广播入队,再调用queue.scheduleBroadcastsLocked()开始处理队列中的广播,不管中间的逻辑如何复杂,这三步都是相同的。

     好了,回到我们的问题单当中。我在这个方法当中加了一些日志,图示如下:





     在我们这个问题单的场景当中,广播分发非常重要的就是第一张图中的19182行的collectReceiverComponents方法,这里会将我们应用侧注册的所有的广播目标过滤出来,后续分发就是针对这个过滤后得到的列表中的目标进行分发的,而大家可以对比日志看一下,过滤后得到的size为0,就是说没有找到匹配的目标,难怪蓝牙模块收不到广播,原来这里出问题了。

     找到了进一步的目标之后,那我们就要跟踪collectReceiverComponents方法了,按照正常的逻辑,过滤完成,应该是要有一个广播分发的目标的。我在collectReceiverComponents方法当中的各关节点也加了一些日志来查找问题原因,这里就把我添加日志后的方法代码贴出来,方便大家对比查看:

private List collectReceiverComponents(Intent intent, String resolvedType,
int callingUid, int[] users) {
List receivers = null;
Slog.w(TAG, "collect receivers, intent = " + intent + ", resolvedType = " + resolvedType + ", callingUid = " + callingUid
+ ", users = " + users + ", user size = " + users.length);
try {
HashSet singleUserReceivers = null;
boolean scannedFirstReceivers = false;
for (int user : users) {
// Skip users that have Shell restrictions
if (callingUid == Process.SHELL_UID
&& getUserManagerLocked().hasUserRestriction(
UserManager.DISALLOW_DEBUGGING_FEATURES, user)) {
continue;
}
List newReceivers = AppGlobals.getPackageManager()
.queryIntentReceivers(intent, resolvedType, STOCK_PM_FLAGS, user);
Slog.w(TAG, "query intent receivers, newReceivers size = " + (newReceivers == null ? 0 : newReceivers.size()));
if (user != UserHandle.USER_OWNER && newReceivers != null) {
// If this is not the primary user, we need to check for
// any receivers that should be filtered out.
for (int i=0; i     加了日志之后,我们再对比一下正常的情况和失败情况的日志的不同,非常明显可以看到,在List<ResolveInfo> newReceivers = AppGlobals.getPackageManager().queryIntentReceivers(intent, resolvedType, STOCK_PM_FLAGS, user)这句代码执行完后,newReceivers中的数据就出现差别了,正常情况下,匹配到了一个目标receiver,而异常情况没有任何目标,好了,那我们继续往下跟踪。AppGlobals.getPackageManager()得到的就是ActivityThread当中的PMS的binder对象,那么我们就进一步看一下PackageManagerService类的queryIntentReceivers方法:


@Override
public List queryIntentReceivers(Intent intent, String resolvedType, int flags,
int userId) {
if (!sUserManager.exists(userId)) return Collections.emptyList();
ComponentName comp = intent.getComponent();
if (comp == null) {
if (intent.getSelector() != null) {
intent = intent.getSelector();
comp = intent.getComponent();
}
}
if (comp != null) {
List list = new ArrayList(1);
ActivityInfo ai = getReceiverInfo(comp, flags, userId);
if (ai != null) {
ResolveInfo ri = new ResolveInfo();
ri.activityInfo = ai;
list.add(ri);
}
return list;
}

// reader
synchronized (mPackages) {
String pkgName = intent.getPackage();
if (pkgName == null) {
return mReceivers.queryIntent(intent, resolvedType, flags, userId);
}
final PackageParser.Package pkg = mPackages.get(pkgName);
if (pkg != null) {
return mReceivers.queryIntentForPackage(intent, resolvedType, flags, pkg.receivers,
userId);
}
return null;
}
}

     在我们当前的场景中,获取到的comp为空,就会进入synchronized (mPackages)同步代码块,pkgName就是我们广播要分发的目标蓝牙进程了,它的包中为com.android.bluetooth,而调用final PackageParser.Package pkg = mPackages.get(pkgName)不为空,那么我们要匹配的目标就要通过mReceivers.queryIntentForPackage方法来完成了,mReceivers是PackageManagerService的内部类ActivityIntentResolver对象,那我们就来看一下它的queryIntentForPackage方法的实现:

public List queryIntentForPackage(Intent intent, String resolvedType,
int flags, ArrayList packageActivities, int userId) {
Log.v(TAG, "userid exist, " + (!sUserManager.exists(userId)));
if (!sUserManager.exists(userId)) return null;
if (packageActivities == null) {
return null;
}
mFlags = flags;
final boolean defaultOnly = (flags&PackageManager.MATCH_DEFAULT_ONLY) != 0;
final int N = packageActivities.size();
ArrayList listCut =
new ArrayList(N);

ArrayList intentFilters;
for (int i = 0; i < N; ++i) {
intentFilters = packageActivities.get(i).intents;
if (intentFilters != null && intentFilters.size() > 0) {
PackageParser.ActivityIntentInfo[] array =
new PackageParser.ActivityIntentInfo[intentFilters.size()];
intentFilters.toArray(array);
listCut.add(array);
}
}
return super.queryIntentFromList(intent, resolvedType, defaultOnly, listCut, userId);
}

     从我们加的日志打印中明显可以看出来,这里方法中的参数packageActivities值是一个List集合,里边收集了蓝牙进程注册的receivers,接下来的任务就是要从这个列表当中匹配出我们的目标receiver,具体的匹配工作是调用super.queryIntentFromList(intent, resolvedType, defaultOnly, listCut, userId)来完成的,它的实现在父类IntentResolver当中,我们就来看一下它的实现:

11-18 20:10:37.086 V/PackageManager( 1218): mpackages get = Package{54c276a com.android.bluetooth}, pkg.receivers = [Activity{ef3ff3b com.android.bluetooth/.opp.BluetoothOppReceiver}, Activity{849d658 com.android.bluetooth/.opp.BluetoothOppHandoverReceiver},
Activity{6cf43b1 com.android.bluetooth/.pbap.BluetoothPbapReceiver}]

public List queryIntentFromList(Intent intent, String resolvedType,
boolean defaultOnly, ArrayList listCut, int userId) {
ArrayList resultList = new ArrayList();

final boolean debug = localLOGV ||
((intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) != 0);

//[+LEUI] [REQ][LEUI-8957] [xulei] add
//for support double app
String LETV_CLONE = ".LetvClone";
boolean isRemove = false;
if(intent.hasCategory(LETV_CLONE)){
intent.removeCategory(LETV_CLONE);
isRemove = true;
}
//[+LEUI]end

FastImmutableArraySet categories = getFastIntentCategories(intent);
final String scheme = intent.getScheme();
int N = listCut.size();
for (int i = 0; i < N; ++i) {
buildResolveList(intent, categories, debug, defaultOnly,
resolvedType, scheme, listCut.get(i), resultList, userId);
}
sortResults(resultList);

//[+LEUI] [REQ][LEUI-8957] [xulei] add
//for support double app
if(isRemove){
intent.addCategory(LETV_CLONE);
}
//[+LEUI]end
Slog.v(TAG, N + ", this is result, " + resultList.size());
return resultList;
}

     在这个方法的最后,我们加了日志,从失败的场景日志中可以明显看到,返回的resultList的size为0,表示没有匹配到。数据匹配是在buildResolveList方法中完成的,那我们再进去看一下它的实现:

private void buildResolveList(Intent intent, FastImmutableArraySet categories,
boolean debug, boolean defaultOnly,
String resolvedType, String scheme, F[] src, List dest, int userId) {
final String action = intent.getAction();
final Uri data = intent.getData();
final String packageName = intent.getPackage();

final boolean excludingStopped = intent.isExcludingStopped();

final Printer logPrinter;
final PrintWriter logPrintWriter;
if (debug) {
logPrinter = new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM);
logPrintWriter = new FastPrintWriter(logPrinter);
} else {
logPrinter = null;
logPrintWriter = null;
}
final int N = src != null ? src.length : 0;
boolean hasNonDefaults = false;
int i;
F filter;
for (i=0; i= 0) {
if (true) Slog.v(TAG, "  Filter matched!  match=0x" +
Integer.toHexString(match) + " hasDefault="
+ filter.hasCategory(Intent.CATEGORY_DEFAULT));
if (!defaultOnly || filter.hasCategory(Intent.CATEGORY_DEFAULT)) {
final R oneResult = newResult(filter, match, userId);
if (oneResult != null) {
Slog.v(TAG, "oneResult is not null, " + oneResult);
dest.add(oneResult);
if (debug) {
dumpFilter(logPrintWriter, "    ", filter);
logPrintWriter.flush();
filter.dump(logPrinter, "    ");
}
}
} else {
hasNonDefaults = true;
}
} else {
if (true) {
String reason;
switch (match) {
case IntentFilter.NO_MATCH_ACTION: reason = "action"; break;
case IntentFilter.NO_MATCH_CATEGORY: reason = "category"; break;
case IntentFilter.NO_MATCH_DATA: reason = "data"; break;
case IntentFilter.NO_MATCH_TYPE: reason = "type"; break;
default: reason = "unknown reason"; break;
}
Slog.v(TAG, "  Filter did not match: " + reason);
}
}
}

if (hasNonDefaults) {
if (dest.size() == 0) {
Slog.w(TAG, "resolveIntent failed: found match, but none with CATEGORY_DEFAULT");
} else if (dest.size() > 1) {
Slog.w(TAG, "resolveIntent: multiple matches, only some with CATEGORY_DEFAULT");
}
}
}

     上面的调用都是收集数据,在这里就是具体的对每个数据进行匹配了,最重要的逻辑就是for循环当中的match = filter.match(action, resolvedType, scheme, data, categories, TAG)这句代码,每个filter是一个IntentFilter对象,匹配完成后,返回值match表示的匹配结果,如果匹配成功,则match大于0,否则小于0,小于0的时候,会根据match的值把匹配失败的原因打印出来。我们对比一下成功和失败的日志,就可以非常清楚的看到,不管成功和失败的两种场景,方法调用进来的数据都是相同的,成功场景中for循环进行到第三次时候匹配成功了,也就是找到了我们的广播要分发的目标,而失败场景中第三个广播目标BluetoothOppHandoverReceiver因为type不匹配而失败了。




     那好,我们就来看一下match方法当中为什么会出现type失败的,进入IntentFilter类的match方法,注意这里是带有六个参数match方法,因为这个类中match方法重载的有好几个,这里要分清楚。

public final int match(String action, String type, String scheme,
Uri data, Set categories, String logTag) {
if (action != null && !matchAction(action)) {
if (false) Log.v(
logTag, "No matching action " + action + " for " + this);
return NO_MATCH_ACTION;
}

int dataMatch = matchData(type, scheme, data);
if (dataMatch < 0) {
if (false) {
if (dataMatch == NO_MATCH_TYPE) {
Log.v(logTag, "No matching type " + type
+ " for " + this);
}
if (dataMatch == NO_MATCH_DATA) {
Log.v(logTag, "No matching scheme/path " + data
+ " for " + this);
}
}
return dataMatch;
}

String categoryMismatch = matchCategories(categories);
if (categoryMismatch != null) {
if (false) {
Log.v(logTag, "No matching category " + categoryMismatch + " for " + this);
}
return NO_MATCH_CATEGORY;
}

// It would be nice to treat container activities as more
// important than ones that can be embedded, but this is not the way...
if (false) {
if (categories != null) {
dataMatch -= mCategories.size() - categories.size();
}
}

return dataMatch;
}

     从代码的逻辑当中就可以看出来,返回的NO_MATCH_TYPE肯定是在调用matchData当中完成的,再进入matchData方法看一下它的实现:

public final int matchData(String type, String scheme, Uri data) {
Log.v("intentfilter", "type = " + type + ", scheme = " + scheme + ", data = " + data);
final ArrayList types = mDataTypes;
final ArrayList schemes = mDataSchemes;

int match = MATCH_CATEGORY_EMPTY;

if (types == null && schemes == null) {
return ((type == null && data == null)
? (MATCH_CATEGORY_EMPTY+MATCH_ADJUSTMENT_NORMAL) : NO_MATCH_DATA);
}

if (schemes != null) {
if (schemes.contains(scheme != null ? scheme : "")) {
match = MATCH_CATEGORY_SCHEME;
} else {
return NO_MATCH_DATA;
}

final ArrayList schemeSpecificParts = mDataSchemeSpecificParts;
if (schemeSpecificParts != null && data != null) {
match = hasDataSchemeSpecificPart(data.getSchemeSpecificPart())
? MATCH_CATEGORY_SCHEME_SPECIFIC_PART : NO_MATCH_DATA;
}
if (match != MATCH_CATEGORY_SCHEME_SPECIFIC_PART) {
// If there isn't any matching ssp, we need to match an authority.
final ArrayList authorities = mDataAuthorities;
if (authorities != null) {
int authMatch = matchDataAuthority(data);
if (authMatch >= 0) {
final ArrayList paths = mDataPaths;
if (paths == null) {
match = authMatch;
} else if (hasDataPath(data.getPath())) {
match = MATCH_CATEGORY_PATH;
} else {
return NO_MATCH_DATA;
}
} else {
return NO_MATCH_DATA;
}
}
}
// If neither an ssp nor an authority matched, we're done.
if (match == NO_MATCH_DATA) {
return NO_MATCH_DATA;
}
} else {
// Special case: match either an Intent with no data URI,
// or with a scheme: URI.  This is to give a convenience for
// the common case where you want to deal with data in a
// content provider, which is done by type, and we don't want
// to force everyone to say they handle content: or file: URIs.
if (scheme != null && !"".equals(scheme)
&& !"content".equals(scheme)
&& !"file".equals(scheme)) {
return NO_MATCH_DATA;
}
}
Log.v("intentfilter", "types = " + types);
if (types != null) {
if (findMimeType(type)) {
match = MATCH_CATEGORY_TYPE;
} else {
Log.v("intentfilter", "no match type.");
return NO_MATCH_TYPE;
}
} else {
// If no MIME types are specified, then we will only match against
// an Intent that does not have a MIME type.
if (type != null) {
return NO_MATCH_TYPE;
}
}

return match + MATCH_ADJUSTMENT_NORMAL;
}

     在这个方法当中加的日志可以看到,types不为空,继续在执行if判断时,findMimeType(type)返回false,最终就返回NO_MATCH_TYPE了,继续跟踪看一下findMimeType为什么会返回false。

private final boolean findMimeType(String type) {
final ArrayList t = mDataTypes;
Log.v("intentfilter", "findmimetype " + type + " for " + this + ", mDataTypes = " + mDataTypes);
if (type == null) {
return false;
}

if (t.contains(type)) {
return true;
}

// Deal with an Intent wanting to match every type in the IntentFilter.
final int typeLength = type.length();
if (typeLength == 3 && type.equals("*/*")) {
Log.v("intentfilter", "findmimetype, typeLength = " + typeLength);
return !t.isEmpty();
}

// Deal with this IntentFilter wanting to match every Intent type.
if (mHasPartialTypes && t.contains("*")) {
return true;
}

final int slashpos = type.indexOf('/');
Log.v("intentfilter", "findmimetype, slashpos = " + slashpos);
if (slashpos > 0) {
if (mHasPartialTypes && t.contains(type.substring(0, slashpos))) {
return true;
}
if (typeLength == slashpos+2 && type.charAt(slashpos+1) == '*') {
// Need to look through all types for one that matches
// our base...
final int numTypes = t.size();
for (int i = 0; i < numTypes; i++) {
final String v = t.get(i);
if (type.regionMatches(0, v, 0, slashpos+1)) {
return true;
}
}
}
}

return false;
}

     在这个方法的入口这里加了一句日志,先把进来的数据打印出来,可以看到我们传进来的type为空,直接就返回false了。真是辛苦啊,一层一层跟踪,终于找到源头了,但是问题还没完,为什么这个type会为空呢?又要往回找。一直往上追,这个type参数的根源就是我们开始发广播时在ContextImpl类中通过String resolvedType = intent.resolveTypeIfNeeded(getContentResolver())获取到的,那么继续看一下Intent类的resolveTypeIfNeeded方法:

public String resolveTypeIfNeeded(ContentResolver resolver) {
Log.w("Intent", "resolve type, resolver = " + resolver + ", " + mComponent + ", " + mType);
if (mComponent != null) {
return mType;
}
return resolveType(resolver);
}

     在我们当前的场景中mComponent为空,那么继续调用resolveType去查找type:

public String resolveType(ContentResolver resolver) {
Log.w("Intent", "resolve type, mData = " + mData);
if (mType != null) {
return mType;
}
if (mData != null) {
if ("content".equals(mData.getScheme())) {
return resolver.getType(mData);
}
}
return null;
}

     在我们这个场景中mType和mData都为空了,所以返回的type就是空的了,那么继续找应用层,为什么intent的mType和mData都会是空的呢?看一下BluetoothOppHandover类的sendIntent()方法,在封装广播数据时,调用了一下intent.setType(mimeType),加了日志验证后,果然传入的mimeType就是空的,哇。终于找回来了,好兴奋,这个问题快水落石出了!进入MimeTypeUtil类的getMimeTypeForUri方法看一下:

public static String getMimeTypeForUri(Context context, Uri uri) {
if (uri.getScheme() == null) return null;

if (uri.getScheme().equals(ContentResolver.SCHEME_CONTENT)) {
ContentResolver cr = context.getContentResolver();
return cr.getType(uri);
} else if (uri.getScheme().equals(ContentResolver.SCHEME_FILE)) {
String extension = MimeTypeMap.getFileExtensionFromUrl(uri.getPath()).toLowerCase();
if (extension != null) {
return MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
} else {
return null;
}
} else {
Log.d(TAG, "Could not determine mime type for Uri " + uri);
return null;
}
}

     这里很明显,会根据我们传进来的Uri分别进行匹配,当我们从文件管理器中执行分享时,那么是执行的(uri.getScheme().equals(ContentResolver.SCHEME_FILE))分支,extension在文件名带中文的时候,返回的是一个空串,我们来看一下MimeTypeMap.getFileExtensionFromUrl(uri.getPath())方法的实现:

public static String getFileExtensionFromUrl(String url) {
if (!TextUtils.isEmpty(url)) {
int fragment = url.lastIndexOf('#');
if (fragment > 0) {
url = url.substring(0, fragment);
}

int query = url.lastIndexOf('?');
if (query > 0) {
url = url.substring(0, query);
}

int filenamePos = url.lastIndexOf('/');
String filename =
0 <= filenamePos ? url.substring(filenamePos + 1) : url;

// if the filename contains special characters, we don't
// consider it valid for our matching purposes:
if (!filename.isEmpty() &&
Pattern.matches("[a-zA-Z_0-9\\.\\-\\(\\)\\%]+", filename)) {
int dotPos = filename.lastIndexOf('.');
if (0 <= dotPos) {
return filename.substring(dotPos + 1);
}
}
}

return "";
}

     这里就是因为我们传入的uri带有中文特殊字符,导致Pattern.matches("[a-zA-Z_0-9\\.\\-\\(\\)\\%]+", filename)正则表达式匹配失败,所以extension就是一个空字符串了。如果我们从图库进程执行分享,那么传入的uri就是contenturi,它是用index位置标识文件的,那么与文件名根本就没有关系了;而如果我们从文件管理器进程执行分享,那么它传过来的uri就是fileuri,这就跟文件名有关系了。这也就是为什么我们从文件管理器分享带中文的文件会失败的原因了。

     终于可以松一口气了,查找这问题,真是杀的七进七出,从应用层查找到AMS,再从AMS查到PMS,继续从PMS查到IntentFilter,然后又回过头来在应用层最终找到问题原因,真是没少花时间啊。好了,找到根本原因了,我们就把NFC模块的MimeTypeUtil类的getMimeTypeForUri方法修改一下,不论是contenturi还是fileuri,都要能成功匹配到目标文件的mime类型,这样才能成功传输文件。

     从这个问题单的处理过程当中,虽然花了很长时间,但是我们从中学到了不少东西,深入了解到了广播分发的过程,了解了intentfilter匹配的过程,当然这里的了解还是非常浅的,因为我们根本没有深究每个方法的具体逻辑,只是大概走了一下流程,同学们如果有兴趣,可以自己深入研究一下。

     好了,这节课也就完了,谢谢大家,下课!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息