简介

近距离设备间数据传输(Nearby Connection):无需连接Internet,通过蓝牙、WIFI等方式,发现并建立与其他设备的直接通信通道,支持无缝的近距离互动,例如本地多人游戏、协作白板、多屏游戏和离线文件传输等。开发者可以针对不同开发场景,自由的选择通信数据类型。近距离设备间数据传输服务支持短文本、流数据和文件数据传输。 通过示例工程,您可以体验到:

您将建立什么

在本篇codelab中,您将创建出一款,使用华为近距离通信服务功能的应用程序,可以通过Nearby Connection方便、快捷的让两台智能手机建立连接,进而完成两台手机的数据通信。

您将会学到什么

硬件要求

软件要求

集成HMS Core能力,需要完成以下准备工作

具体操作,请按照《HMS Core集成准备》中详细说明来完成。

登录华为开发者联盟的AGC页面,选择"我的应用",点击创建的应用。

点击"开发"–>"概览",点击图标,选择"API管理",进入服务管理菜单,打开"Nearby Service"开关。

登录华为开发者联盟网站,进入"管理中心"。

在"API和服务"-> "凭证"中,在创建的应用下,打开API密钥的"编辑"操作。

"应用限制"区域中,选择"Android应用",将创建的应用包名及生成的SHA256证书指纹填入后保存:

添加AGC配置文件

在华为开发者联盟的AGC页面,选择"我的应用",点击创建的应用。选择"开发"–> "概览",点击,选"添加SDK",按照文档指引操作。

添加编译依赖

打开应用级别的build.gradle文件:

在build.gradle文件头添加配置:

apply plugin: 'com.huawei.agconnect'

将编译依赖项添加到"dependencies" 中:

dependencies { implementation 'com.huawei.hms:nearby: {version}' }

如图所示:

配置混淆脚本

打开Android Studio工程的混淆配置文件proguard-rules.pro。

加入混淆配置。

-ignorewarnings -keepattributes *Annotation* -keepattributes Exceptions -keepattributes InnerClasses -keepattributes Signature -keepattributes SourceFile,LineNumberTable -keep class com.hianalytics.android.**{*;} -keep class com.huawei.updatesdk.**{*;} -keep class com.huawei.hms.**{*;}

如果开发者使用了AndResGuard,需要在混淆配置文件中加入AndResGuard白名单。

"R.string.hms*", "R.string.connect_server_fail_prompt_toast", "R.string.getting_message_fail_prompt_toast", "R.string.no_available_network_prompt_toast", "R.string.third_app_*", "R.string.upsdk_*", "R.layout.hms*", "R.layout.upsdk_*", "R.drawable.upsdk*", "R.color.upsdk*", "R.dimen.upsdk*", "R.style.upsdk*" "R.string.agc*"

同步工程

点击File->Sync Project with Gradle Files,开始同步。

如下图出现synced successfully即代表同步成功。

在上一小节中,您已经成功集成了Nearby Service SDK。Nearby Service SDK主要分为广播扫描模块,建立连接模块以及数据传输模块。
本小节您将尝试编写一个Demo,学会如何使用Nearby Service SDK的主要接口。您只需要按照下文的每一步提示,就可以边学边做,一起构建出这个Demo。

源码下载

将示例代码提取到src目录

将所有java文件从代码目录复制到您的项目中。

将所有资源文件从res目录复制到您的项目中。

修改AndroidManifest.xml

首先,您需要修改应用的权限。修改app/src/main/目录下面的AndroidManifest.xml文件,在文件中添加以下六种权限。自Android 6.0版本开始,系统对高危权限引入了权限申请机制。所以在后续章节还有对高危权限的动态申请步骤。

代码如下:

<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" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

修改MainActivity类

  1. 从Nearby Service SDK中导入以下类

    import com.huawei.hms.nearby.Nearby; import com.huawei.hms.nearby.StatusCode; import com.huawei.hms.nearby.discovery.BroadcastOption; import com.huawei.hms.nearby.discovery.ConnectCallback; import com.huawei.hms.nearby.discovery.ConnectInfo; import com.huawei.hms.nearby.discovery.ConnectResult; import com.huawei.hms.nearby.discovery.DiscoveryEngine; import com.huawei.hms.nearby.discovery.Policy; import com.huawei.hms.nearby.discovery.ScanEndpointCallback; import com.huawei.hms.nearby.discovery.ScanEndpointInfo; import com.huawei.hms.nearby.discovery.ScanOption; import com.huawei.hms.nearby.transfer.Data; import com.huawei.hms.nearby.transfer.DataCallback; import com.huawei.hms.nearby.transfer.TransferEngine; import com.huawei.hms.nearby.transfer.TransferStateUpdate;
  2. 从系统库中导入以下类

    import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import android.Manifest; import android.annotation.SuppressLint; import android.content.Context; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.os.RemoteException; import android.text.TextUtils; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.ListView; import android.widget.Toast; import com.huawei.hms.nearbyconnectiondemo.utils.ToastUtil; import com.huawei.hms.nearbyconnectiondemo.utils.permission.PermissionHelper; import com.huawei.hms.nearbyconnectiondemo.utils.permission.PermissionInterface; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List;
  3. 在MainActivity中添加以下代码

    private static final int TIMEOUT_MILLISECONDS = 10000; private static final String TAG = "Nearby Connection Demo"; private TransferEngine mTransferEngine = null; private DiscoveryEngine mDiscoveryEngine = null; private PermissionHelper mPermissionHelper; private EditText myNameEt; private EditText friendNameEt; private EditText msgEt; private ListView messageListView; private List<MessageBean> msgList; private ChatAdapter adapter; private Button sendBtn; private Button connectBtn; private int connectTaskResult; private String myNameStr; private String friendNameStr; private String myServiceId; private String mEndpointId; private String msgStr;
  4. 添加连接超时处理函数

    /** * Handle timeout function */ @SuppressLint("HandlerLeak") private Handler handler = new Handler() { @Override public void handleMessage(@NonNull Message msg) { handler.removeMessages(0); if (connectTaskResult != StatusCode.STATUS_SUCCESS) { ToastUtil.showShortToastTop("Connection timeout, make sure your friend is ready and try again."); if (myNameStr.compareTo(friendNameStr) > 0) { mDiscoveryEngine.stopScan(); } else { mDiscoveryEngine.stopBroadcasting(); } myNameEt.setEnabled(true); friendNameEt.setEnabled(true); connectBtn.setEnabled(true); } } };
  5. 在onCreate函数中添加以下代码

    @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); requestPermissions(); initView(); sendBtn.setEnabled(false); msgEt.setEnabled(false); } private void initView() { myNameEt = findViewById(R.id.et_my_name); friendNameEt = findViewById(R.id.et_friend_name); msgEt = findViewById(R.id.et_msg); connectBtn = findViewById(R.id.btn_connect); sendBtn = findViewById(R.id.btn_send); connectBtn.setOnClickListener(this); sendBtn.setOnClickListener(this); messageListView = findViewById(R.id.lv_chat); msgList = new ArrayList<>(); adapter = new ChatAdapter(this, msgList); messageListView.setAdapter(adapter); connectTaskResult = StatusCode.STATUS_ENDPOINT_UNKNOWN; }
  6. 添加权限申请函数

    private void requestPermissions() { mPermissionHelper = new PermissionHelper(this, this); mPermissionHelper.requestPermissions(); } @Override public int getPermissionsRequestCode() { return 10086; } @Override public String[] getPermissions() { return new String[]{Manifest.permission.BLUETOOTH, Manifest.permission.BLUETOOTH_ADMIN, Manifest.permission.ACCESS_WIFI_STATE, Manifest.permission.CHANGE_WIFI_STATE, Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION}; } @Override public void requestPermissionsSuccess() { } @Override public void requestPermissionsFail() { Toast.makeText(this, R.string.error_missing_permissions, Toast.LENGTH_LONG).show(); finish(); } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { if (mPermissionHelper.requestPermissionsResult(requestCode, permissions, grantResults)) { return; } super.onRequestPermissionsResult(requestCode, permissions, grantResults); }
  7. 添加扫描、广播函数

    public void doStartBroadcast(View view) throws RemoteException { BroadcastOption.Builder advBuilder = new BroadcastOption.Builder(); advBuilder.setPolicy(Policy.POLICY_STAR); mDiscoveryEngine.startBroadcasting(myNameStr, myServiceId, mConnCb, advBuilder.build()); } public void doStartScan(View view) throws RemoteException { ScanOption.Builder discBuilder = new ScanOption.Builder(); discBuilder.setPolicy(Policy.POLICY_STAR); mDiscoveryEngine.startScan(myServiceId, mDiscCb, discBuilder.build()); }
  8. 添加数据收发函数

    /** * Send message function */ private void sendMessage() { msgStr = msgEt.getText().toString(); Data data = Data.fromBytes(msgStr.getBytes(Charset.defaultCharset())); Log.d(TAG, "myEndpointId " + mEndpointId); mTransferEngine.sendData(mEndpointId, data); MessageBean item = new MessageBean(); item.setMyName(myNameStr); item.setFriendName(friendNameStr); item.setMsg(msgStr); item.setSend(true); msgList.add(item); adapter.notifyDataSetChanged(); msgEt.setText(""); messageListView.setSelection(messageListView.getBottom()); } /** * Receive message function */ private void receiveMessage(Data data) { msgStr = new String(data.asBytes()); MessageBean item = new MessageBean(); item.setMyName(myNameStr); item.setFriendName(friendNameStr); item.setMsg(msgStr); item.setSend(false); msgList.add(item); adapter.notifyDataSetChanged(); messageListView.setSelection(messageListView.getBottom()); }
  9. 添加扫描回调函数

    private ScanEndpointCallback mDiscCb = new ScanEndpointCallback() { @Override public void onFound(String endpointId, ScanEndpointInfo discoveryEndpointInfo) { mEndpointId = endpointId; mDiscoveryEngine.requestConnect(myNameStr, mEndpointId, mConnCb); } @Override public void onLost(String endpointId) { Log.d(TAG, "Nearby Connection Demo app: Lost endpoint: " + endpointId); } };
  10. 添加连接回调函数

    private ConnectCallback mConnCb = new ConnectCallback() { @Override public void onEstablish(String endpointId, ConnectInfo connectionInfo) { mTransferEngine = Nearby.getTransferEngine(getApplicationContext()); mEndpointId = endpointId; mDiscoveryEngine.acceptConnect(endpointId, mDataCb); ToastUtil.showShortToastTop("Let's chat!"); sendBtn.setEnabled(true); msgEt.setEnabled(true); connectBtn.setEnabled(false); connectTaskResult = StatusCode.STATUS_SUCCESS; if (myNameStr.compareTo(friendNameStr) > 0) { mDiscoveryEngine.stopScan(); } else { mDiscoveryEngine.stopBroadcasting(); } } @Override public void onResult(String endpointId, ConnectResult resolution) { mEndpointId = endpointId; } @Override public void onDisconnected(String endpointId) { ToastUtil.showShortToastTop("Disconnect."); connectTaskResult = StatusCode.STATUS_NOT_CONNECTED; sendBtn.setEnabled(false); connectBtn.setEnabled(true); msgEt.setEnabled(false); myNameEt.setEnabled(true); friendNameEt.setEnabled(true); } };
  11. 在onDisconnected函数中,设置了界面按钮状态。

  12. 添加数据回调函数

    private DataCallback mDataCb = new DataCallback() { @Override public void onReceived(String string, Data data) { receiveMessage(data); } @Override public void onTransferUpdate(String string, TransferStateUpdate update) { } };
  13. 添加按钮点击函数

    @Override public void onClick(View view) { switch (view.getId()) { case R.id.btn_connect: if (checkName()) { return; } connect(view); handler.sendEmptyMessageDelayed(0, TIMEOUT_MILLISECONDS); break; case R.id.btn_send: if (checkMessage()) { return; } sendMessage(); break; default: break; } } private void connect(View view) { ToastUtil.showShortToastTop("Connecting to your friend."); connectBtn.setEnabled(false); myNameEt.setEnabled(false); friendNameEt.setEnabled(false); Context context = getApplicationContext(); mDiscoveryEngine = Nearby.getDiscoveryEngine(context); try { if (myNameStr.compareTo(friendNameStr) > 0) { doStartScan(view); } else { doStartBroadcast(view); } } catch (RemoteException e) { Log.e(TAG, "remote exception." + e.getMessage()); } }
  14. 添加输入检查函数

    /** * Check input message */ private boolean checkMessage() { if (TextUtils.isEmpty(msgEt.getText())) { ToastUtil.showShortToastTop("Please input data you want to send."); return true; } return false; } /** * Check input name */ private boolean checkName() { if (TextUtils.isEmpty(myNameEt.getText())) { ToastUtil.showShortToastTop("Please input your name."); return true; } if (TextUtils.isEmpty(friendNameEt.getText())) { ToastUtil.showShortToastTop("Please input your friend's name."); return true; } if (TextUtils.equals(myNameEt.getText().toString(), friendNameEt.getText().toString())) { ToastUtil.showShortToastTop("Please input two different names."); return true; } friendNameStr = friendNameEt.getText().toString(); myNameStr = myNameEt.getText().toString(); getServiceId(); return false; }
  15. 添加ServiceId生成函数

    private void getServiceId() { if (myNameStr.compareTo(friendNameStr) > 0) { myServiceId = myNameStr + friendNameStr; } else { myServiceId = friendNameStr + myNameStr; } }
  16. 编译、加载、调试

    当您完成上述业务代码的补全之后,就可以进行编译。

完成编译后,生成apk,安装到两台智能手机上,并运行。

当您完成了上述步骤时,让我们在两台手机上运行这个应用吧!

运行界面介绍

此处展示了智能手机上运行示例应用的截图。
应用共包含两个按钮,他们的功能分别是:
BEGIN TO CHAT!:两部手机分别启动扫描和发现,并自动建立连接;
SEND:向对端手机发送聊天数据。
另外界面中自上至下一共有三个文本框,他们分别是:
"My name"框:您在聊天时展示的名字;
"Friend's name"框:您想要聊天的朋友;
Data框:您想要发送的聊天信息。

运行步骤

两部手机的操作步骤相同,请您按照图上的指示操作。

  1. 输入您的名字;
  2. 输入您朋友的名字;
  3. 点击BEGIN TO CHAT! 按钮;
  4. 编辑您的消息,并向您的朋友发送(点击SEND按钮)它。

聊天展示

两部手机输入了正确的聊天名字,在建立连接之后就可以相互发送聊天数据。

干得好,你已经成功完成了Codelab并学到了:

现在,马上把您自己的创意用HMS Core Nearby Service SDK来实现吧!

您可以阅读下面链接,了解更多相关的信息。
相关文档

源码下载

已复制代码