我试图让我的设备同时做广告和发现,有点像Varun使用Nearby Connections API v2描述的here。
问题是我能够在切换开关时开始做广告,但startDiscovery()
方法总是返回失败。此外,onResult()
回调中收到的状态有null
条消息。这是我的代码:
public class HomeActivity extends AppCompatActivity implements HomeMvp.View{
private HomePresenter presenter;
private HomeVH viewHolder;
private HomeActionBarVH actionBarVH;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.home_activity);
viewHolder = new HomeVH(findViewById(android.R.id.content));
setUpViews();
presenter = new HomePresenter(this);
presenter.onViewCreate();
}
@Override
protected void onStart() {
super.onStart();
presenter.startGoogleApiClient();
}
private void setUpActionBar() {
ActionBar actionBar = getSupportActionBar();
if (actionBar == null) {
return;
}
actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
actionBar.setDisplayShowCustomEnabled(true);
actionBar.setCustomView(R.layout.home_action_bar);
actionBarVH = new HomeActionBarVH(actionBar.getCustomView());
actionBarVH.startSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (isChecked) {
showToastShort(getResources().getString(R.string.toast_starting_services));
presenter.startAdvertising();
presenter.startDiscovery();
} else {
showToastShort(getResources().getString(R.string.toast_stopping_services));
presenter.stopAdvertising();
presenter.stopDiscovery();
}
}
});
actionBarVH.startSwitch.setEnabled(false);
actionBarVH.shareButton.setOnClickListener(getShareButtonClickListener());
}
private View.OnClickListener getShareButtonClickListener() {
return new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent filePickerIntent = new Intent(Intent.ACTION_GET_CONTENT);
filePickerIntent.setType("*/*");
startActivityForResult(filePickerIntent, Constants.PICK_FILE);
}
};
}
private void setUpViews() {
setUpActionBar();
HomeRvAdapter adapter = new HomeRvAdapter(getCuratedDeviceList());
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
viewHolder.deviceListRv.setLayoutManager(linearLayoutManager);
viewHolder.deviceListRv.setAdapter(adapter);
viewHolder.googleApiStatus.setText(getResources().getString(R.string.msg_please_wait));
}
private ArrayList<BaseRvData> getCuratedDeviceList() {
ArrayList<BaseRvData> deviceList = new ArrayList<>();
deviceList.add(new EmptyListData());
return deviceList;
}
@Override
protected void onStop() {
super.onStop();
presenter.stopGoogleApiClient();
}
@Override
protected void onDestroy() {
presenter.onViewDestroy();
super.onDestroy();
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == Constants.PICK_FILE && resultCode == RESULT_OK) {
if (data.getData() != null) {
String filePath = data.getData().getPath();
presenter.onNewFileSelected(filePath);
showToastShort(filePath);
}
}
}
@Override
public void onInitialDataLoaded() {
}
@Override
public void onGoogleApiConnected() {
viewHolder.googleApiStatus.setText(getResources().getString(R.string.msg_ready));
actionBarVH.startSwitch.setEnabled(true);
}
@Override
public void onGoogleApiConnectionFailed() {
viewHolder.googleApiStatus.setText(getResources().getString(R.string.msg_please_wait));
actionBarVH.startSwitch.setEnabled(false);
}
@Override
public void onStartedAdvertising() {
viewHolder.advertiseStatus.setText(R.string.msg_advert_service_started);
}
@Override
public void onAdvertisingFailed() {
viewHolder.advertiseStatus.setText(R.string.msg_advert_service_failed);
}
@Override
public void onAdvertisingStopped() {
viewHolder.advertiseStatus.setText(R.string.msg_advert_service_stopped);
}
@Override
public void onDiscoveryStarted() {
viewHolder.discoveryStatus.setText(R.string.msg_discovery_started);
}
@Override
public void onDiscoveryFailure() {
viewHolder.discoveryStatus.setText(R.string.msg_discovery_failed);
}
@Override
public void onDiscoveryStopped() {
viewHolder.discoveryStatus.setText(R.string.msg_discovery_stopped);
}
@Override
public void onConnectionInitiated(String msg) {
showToastShort(msg);
}
@Override
public void onNewConnection(String msg) {
showToastShort(msg);
}
@Override
public void onDisconnected(String msg) {
showToastShort(msg);
}
@Override
public void showToastShort(String msg) {
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
}
}
class HomePresenter implements GoogleApiClient.ConnectionCallbacks,
GoogleApiClient.OnConnectionFailedListener, HomeMvp.Presenter {
private boolean isGoogleApiConnected;
private HomeMvp.View activityInterface;
private GoogleApiClient googleApiClient;
private EndpointDiscoveryCallback endpointDiscoveryCallback = new EndpointDiscoveryCallback() {
@Override
public void onEndpointFound(String endpointId,
DiscoveredEndpointInfo discoveredEndpointInfo) {
// An endpoint was found!
// Connect to the end point
// TODO make a dialog to decide whether to connect ??
Nearby.Connections.requestConnection(googleApiClient, "listener", endpointId,
connectionLifecycleCallback)
.setResultCallback(new ResultCallback<Status>() {
@Override
public void onResult(@NonNull Status status) {
if (status.isSuccess()) {
// Successfully requested for a connection
} else {
// failed to request the connection
// TODO view should show a msg here
}
}
});
}
@Override
public void onEndpointLost(String endpointId) {
// A previously discovered endpoint has gone away.
// TODO update view here along with cleanup
}
};
private final ConnectionLifecycleCallback connectionLifecycleCallback =
new ConnectionLifecycleCallback() {
@Override
public void onConnectionInitiated(String endpointId, ConnectionInfo connectionInfo) {
onEndpointConnectionInitiated(endpointId, connectionInfo);
}
@Override
public void onConnectionResult(String endpointId, ConnectionResolution connectionResolution) {
switch (connectionResolution.getStatus().getStatusCode()) {
case ConnectionsStatusCodes.STATUS_OK:
onNewEndpointConnection(endpointId, connectionResolution);
break;
case ConnectionsStatusCodes.STATUS_CONNECTION_REJECTED:
onNewEndpointConnectionReject(endpointId, connectionResolution);
break;
}
}
@Override
public void onDisconnected(String endpointId) {
onEndpointDisconnected(endpointId);
}
};
private void acceptConnection(String endpointId, ConnectionInfo connectionInfo) {
Nearby.Connections.acceptConnection(googleApiClient, endpointId, null);
}
HomePresenter(HomeMvp.View activityInterface) {
this.activityInterface = activityInterface;
}
@Override
public void onViewCreate() {
isGoogleApiConnected = false;
googleApiClient = new GoogleApiClient.Builder((Context) activityInterface)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(Nearby.CONNECTIONS_API).build();
activityInterface.onInitialDataLoaded();
}
@Override
public void onViewDestroy() {
stopAdvertising();
stopDiscovery();
}
@Override
public void onConnected(@Nullable Bundle bundle) {
isGoogleApiConnected = true;
activityInterface.onGoogleApiConnected();
}
@Override
public void onConnectionSuspended(int i) {
}
@Override
public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {
isGoogleApiConnected = false;
activityInterface.onGoogleApiConnectionFailed();
}
/**
* Sets the device to advertising mode. It will broadcast to other devices in discovery mode.
* Either {@link #onAdvertisingStarted()} or {@link #onAdvertisingFailed(Status)} will be called once
* we've found out if we successfully entered this mode.
*/
@Override
public void startAdvertising() {
if (isGoogleApiConnected && googleApiClient != null) {
Logger.d(HomeConstants.TAG, HomeConstants.STARTING_ADVERTISING);
Nearby.Connections.startAdvertising(
googleApiClient, "NickName", Constants.NEARBY_API_SERVICE_ID,
connectionLifecycleCallback, new AdvertisingOptions(Constants.CLUSTER_STRATEGY))
.setResultCallback(new ResultCallback<Connections.StartAdvertisingResult>() {
@Override
public void onResult(@NonNull Connections.StartAdvertisingResult startAdvertisingResult) {
if (startAdvertisingResult.getStatus().isSuccess()) {
onAdvertisingStarted();
} else {
onAdvertisingFailed(startAdvertisingResult.getStatus());
}
}
});
}
}
@Override
public void stopAdvertising() {
if (isGoogleApiConnected && googleApiClient != null) {
Logger.d(HomeConstants.TAG, HomeConstants.STOPPING_ADVERTISING);
Nearby.Connections.stopAdvertising(googleApiClient);
activityInterface.onAdvertisingStopped();
}
}
/**
* Sets the device to discovery mode. It will now listen for devices in advertising mode. Either
* {@link #onDiscoveryStarted()} ()} or {@link #onDiscoveryFailed(Status)} ()} will be called once we've
* found out if we successfully entered this mode.
*/
@Override
public void startDiscovery() {
if (isGoogleApiConnected && googleApiClient != null && endpointDiscoveryCallback != null) {
Logger.d(HomeConstants.TAG, HomeConstants.STARTING_DISCOVERY);
Nearby.Connections.startDiscovery(googleApiClient, Constants.NEARBY_API_SERVICE_ID,
endpointDiscoveryCallback, new DiscoveryOptions(Constants.CLUSTER_STRATEGY))
.setResultCallback(new ResultCallback<Status>() {
@Override
public void onResult(@NonNull Status status) {
if (status.isSuccess()) {
onDiscoveryStarted();
} else {
onDiscoveryFailed(status);
}
}
});
}
}
@Override
public void stopDiscovery() {
if (isGoogleApiConnected && googleApiClient != null) {
Logger.d(HomeConstants.TAG, HomeConstants.STOPPING_DISCOVERY);
Nearby.Connections.stopDiscovery(googleApiClient);
activityInterface.onDiscoveryStopped();
}
}
@Override
public void onNewFileSelected(String filePath) {
// add the new file to our list of files
}
@Override
public void startGoogleApiClient() {
if (googleApiClient != null && !googleApiClient.isConnected()) {
googleApiClient.connect();
Logger.d(HomeConstants.TAG, HomeConstants.GOOGLE_API_CONNECTED);
} else {
Logger.d(HomeConstants.TAG, HomeConstants.GOOGLE_API_NULL_OR_CONNECTED);
}
}
@Override
public void stopGoogleApiClient() {
if (googleApiClient != null && googleApiClient.isConnected()) {
googleApiClient.disconnect();
}
}
// ======================= private methods ================================
private void onAdvertisingStarted() {
Logger.d(HomeConstants.TAG, HomeConstants.NEARBY_RESULT_ADVERT_SUCCESS);
activityInterface.onStartedAdvertising();
}
private void onAdvertisingFailed(Status status) {
if (status.isCanceled()) {
Logger.d(HomeConstants.TAG, HomeConstants.NEARBY_RESULT_ADVERT_CANCELLED);
} else if (status.isInterrupted()) {
Logger.d(HomeConstants.TAG, HomeConstants.NEARBY_RESULT_ADVERT_INTERRUPTED);
}
activityInterface.onAdvertisingFailed();
}
private void onDiscoveryStarted() {
Logger.d(HomeConstants.TAG, HomeConstants.NEARBY_RESULT_DISCOVERY_SUCCESS);
activityInterface.onDiscoveryStarted();
}
private void onDiscoveryFailed(Status status) {
Logger.d(HomeConstants.TAG, HomeConstants.NEARBY_RESULT_DISCOVERY_FAILURE);
Logger.d(HomeConstants.TAG, status.getStatusMessage());
activityInterface.onDiscoveryFailure();
}
private void onEndpointConnectionInitiated(String endpointId, ConnectionInfo connectionInfo) {
activityInterface.onConnectionInitiated("Connection initiated " + endpointId);
// TODO auto accept connection here
acceptConnection(endpointId, connectionInfo);
}
private void onNewEndpointConnection(String endpointId, ConnectionResolution connectionResolution) {
// connected now transfer data
activityInterface.onNewConnection("Connected with " + endpointId);
Logger.d(HomeConstants.TAG,
HomeConstants.NEARBY_NEW_CONNECTION + endpointId);
}
private void onNewEndpointConnectionReject(String endpointId, ConnectionResolution connectionResolution) {
Logger.d(HomeConstants.TAG, HomeConstants.NEARBY_CONNECTION_REJECTED + endpointId);
}
private void onEndpointDisconnected(String endpointId) {
activityInterface.onDisconnected("Disconnected " + endpointId);
}
}
我无法理解为什么行为是这样的。此外,每当我存在活动时,我都会收到错误:
Unable to destroy activity {com.forkit.arka.rapidsync/com.forkit.arka.rapidsync.home.HomeActivity}: java.lang.IllegalStateException: GoogleApiClient is not connected yet.
以下是我的明确权限:
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
无法启动发现问题似乎已得到解决。问题是ACCESS_COARSE_LOCATION是一个危险的权限,并且仅在清单中使用它是不够的。我们必须明确询问用户。