使用Xamarin和Visual Studio开发Android可穿戴设备应用

译文
移动开发 Android
在本文中,我将向您详细介绍使用Xamarin和Visual Studio开发Android可穿戴设备应用程序的完整过程。

搭建开发环境

我们需要做的第一件事情是安装必要的工具。因此,你需要首先安装Visual Studio。如果您使用的是Visual Studio 2010,2012或2013,那么请确保它是一个专业版本或更高级版本,因为Visual Studio的Xamarin扩展并不支持精简版。有关详细信息,请参阅 https://xamarin.com/faq

对我本人来说,使用了Visual Studio 2013 Untimate版本。一旦你已经安装了受支持的 Visual Studio版本,那么请接着下载Visual Studio的Xamarin扩展(下载地址是http://xamarin.com/visual-studio)。(你可以免费试用或申请一个许可证版本)。

接下来的操作,只需按照下载的向导中的说明进行即可,直到安装完全为止。为了正常使用扩展,您可能需要重新启动您的计算机。

现在,当您正常启动Visual Studio,你应该能够在工具菜单下看到如图所示的以下项目。

图1:Android Tools和SDK

为确保您所需要的一切已经正常安装,您可以通过Android SDK Manager来验证。为此,只需打开Tools > Android > Android SDK Manager。这将打开下图所示的窗口:

图2:Android SDK Manager

现在,在确保你已经安装所需要的一切后,接下来你可以开始使用Visual Studio中的Xamarin创建你自己的Android应用程序了。

创建简单的Android可穿戴设备应用

首先,让我们创建一个新的Visual Studio项目,然后从模板中选择Android > Wear App (Android)。你应该能够看到如下图所示的东西。

图3:Visual Studio模板列表

现在,只需单击OK命令让Visual Studio为你生成创建可穿戴应用程序所需的文件。下面的图像显示了默认生成的文件。注意:其中提供了你着手构建可穿戴应用程序的示例代码。

图4:默认的可穿戴应用程序代码

很容易吧!官方文档中提供的有关Xamarin.Android应用程序分析的资料对应的URL是http://developer.xamarin.com/guides/android/getting_started/

运行程序

为了在不需要真实设备的情况下即可运行应用程序,我们首先需要建立一个模拟器程序。您可以通过以下几个步骤创建模拟器程序。

转到命令Tools > Andriod > Andriod Emulator Manager或单击工具栏上的AVD图标。请参阅下图。

图5:Andriod内置模拟器程序

之后,应显示安卓系统设备管理器窗口。在这里,我们可以编辑、启动、 创建、删除或修复虚拟设备。

现在,我们需要单击"Create"按钮。下图中的窗口应打开,允许您选择您想要创建的设备。在本例中,我们只需要创建一个虚拟Android可穿戴设备,用于在模拟器上运行我们的应用程序。

图6:AVD对话框

一旦一切设置好,只需单击OK便可创建虚拟设备。在下图中,你应该能够看到您刚通过AVD管理器创建的设备。

图7:AVD管理器

现在,只需单击命令Start > Launch便可在后台运行模拟程序。注意,系统可能需要一些时间来加载模拟器。模拟程序加载并准备好之后,你应该能够看到您刚创建的虚拟设备的名称显示于开始调试下拉列表中。

图8:新创建的虚拟设备

现在,将您的应用程序设置为启动项目,然后按F5键运行它。下图展示了示例可穿戴应用程序在模拟器程序中的输出结果。

图9:输出

请记住,集成到Visual Studio内部的默认模拟器速度有点慢,仅适用于测试小型应用程序。在实际开发中创建并测试应用程序的话,我会推荐你使用Xamarin安卓播放机(https://developer.xamarin.com/guides/android/deployment,_testing,_and_metrics/debug-on-emulator/)来模拟android应用程序。

就这样!如果您想要参考更多的可穿戴设备应用程序实例,只需访问此链接:http://developer.xamarin.com/samples/android/Android%20Wear/

同步Android可穿戴设备和手持设备间的数据

让我们再努力一点,创建一个支持在您的可穿戴程序和手持设备之间进行通信的可工作的应用程序。这个练习将演示如何同步你的安卓应用程序之间的数据。

创建Android可穿戴设备程序

开始,让我们启动Visual Studio 2013,然后选择命令"File" -> "New" -> "Project..."。在模板中选择 C# > Android并选择Wear App (Android) Project。

命名您的应用程序,然后单击OK让Visual Studio为您生成所需的文件。在本练习中,程序名为"WearDemo"。下面的图像显示了默认示例代码生成的文件,用于帮助您编写可穿戴设备应用程序。

图 10:可穿戴设备应用程序项目

在我们开始修改默认代码之前,我想指出:有两种方法可以实现可穿戴设备和手持设备之间进行通信,分别是DataApi和MessageApi。以下是每个API的简短描述。

DataApi:它所输出的API供组件读取或写入数据项(Items)及相关资源(Assets)。其中,DataItem提供数据存储,支持掌上电脑和手持设备之间自动同步。Assets用于发送例如图像这样的Blob数据。你只需要将Assets关联到DataItems,则系统会自动为你实现其他的处理。有关细节,请阅读这个URL(https://developers.google.com/android/reference/com/google/android/gms/wearable/DataApi)。

MessageApi:它提供的API供组件将消息发送到其他节点。消息通常应该包含小型有效数据。你应使用Assets与 DataApi来存储大数据。有关细节,请阅读这个URL(https://developers.google.com/android/reference/com/google/android/gms/wearable/MessageApi)。

在下面提供的这个特别的演示例程中,我要使用DataApi来发送/同步设备之间的数据。由于DataApi是Google Play Services的一部分,那么我们需要做的第一件事是添加以下命名空间引用:

  1. using Android.Gms.Common.Apis; 
  2.  
  3. using Android.Gms.Wearable; 

Android.Gms.Common.Apis允许我们使用GoogleApiClient,这是集成Google Play Services的主入口点。Android.Gms.Wearable使我们能够使用WearableClass类。下一步是要扩展我们的MainActivity类,即需要继承以下接口:

1. IDataApiDataListener

2. IGoogleApiClientConnectionCallbacks

3. IGoogleApiClientOnConnectionFailedListener

IDataApiDataListener用来接收数据事件。IGoogleApiClientConnectionCallbacks接口提供的回调函数在客户端连接或断开服务时调用。IGoogleApiClientOnConnectionFailedListener接口提供的回调函数在客户端连接到服务失败时调用。

概括一下,将数据发送到手持设备的示例代码归纳如下:

  1. using System; 
  2.  
  3. using Android.Runtime; 
  4.  
  5. using Android.Widget; 
  6.  
  7. using Android.OS; 
  8.  
  9. using Android.Support.Wearable.Views; 
  10.  
  11. using Java.Interop; 
  12.  
  13. using Android.Gms.Common.Apis; 
  14.  
  15. using Android.Gms.Wearable; 
  16.  
  17. using System.Linq; 
  18.  
  19. namespace WearDemo 
  20.  
  21.  
  22. [Activity(Label = "WearDemo", MainLauncher = true, Icon = "@drawable/icon")] 
  23.  
  24. public class MainActivity : Activity,IDataApiDataListener, IGoogleApiClientConnectionCallbacks, IGoogleApiClientOnConnectionFailedListener 
  25.  
  26.  
  27. private IGoogleApiClient _client; 
  28.  
  29. const string _syncPath = "/WearDemo/Data"
  30.  
  31. protected override void OnCreate(Bundle bundle) { 
  32.  
  33. base.OnCreate(bundle); 
  34.  
  35. _client = new GoogleApiClientBuilder(this, this, this) 
  36.  
  37. .AddApi(WearableClass.Api) 
  38.  
  39. .Build(); 
  40.  
  41. // Set our view from the "main" layout resource 
  42.  
  43. SetContentView(Resource.Layout.Main); 
  44.  
  45. var v = FindViewById<WatchViewStub>(Resource.Id.watch_view_stub); 
  46.  
  47. v.LayoutInflated += delegate { 
  48.  
  49. // Get our button from the layout resource, 
  50.  
  51. // and attach an event to it 
  52.  
  53. Button button = FindViewById<Button>(Resource.Id.myButton); 
  54.  
  55. button.Click += delegate { 
  56.  
  57. SendData(); 
  58.  
  59. }; 
  60.  
  61. }; 
  62.  
  63.  
  64. public void SendData() { 
  65.  
  66. try { 
  67.  
  68. var request = PutDataMapRequest.Create(_syncPath); 
  69.  
  70. var map = request.DataMap; 
  71.  
  72. map.PutString("Message""Vinz says Hello from Wearable!"); 
  73.  
  74. map.PutLong("UpdatedAt", DateTime.UtcNow.Ticks); 
  75.  
  76. WearableClass.DataApi.PutDataItem(_client, request.AsPutDataRequest()); 
  77.  
  78.  
  79. finally { 
  80.  
  81. _client.Disconnect(); 
  82.  
  83.  
  84.  
  85. protected override void OnStart() { 
  86.  
  87. base.OnStart(); 
  88.  
  89. _client.Connect(); 
  90.  
  91.  
  92. public void OnConnected(Bundle p0) { 
  93.  
  94. WearableClass.DataApi.AddListener(_client, this); 
  95.  
  96.  
  97. public void OnConnectionSuspended(int reason) { 
  98.  
  99. Android.Util.Log.Error("GMSonnection suspended " + reason); 
  100.  
  101. WearableClass.DataApi.RemoveListener(_client, this); 
  102.  
  103.  
  104. public void OnConnectionFailed(Android.Gms.Common.ConnectionResult result) { 
  105.  
  106. Android.Util.Log.Error("GMSonnection failed " + result.ErrorCode); 
  107.  
  108.  
  109. protected override void OnStop() { 
  110.  
  111. base.OnStop(); 
  112.  
  113. _client.Disconnect(); 
  114.  
  115.  
  116. public void OnDataChanged(DataEventBuffer dataEvents) { 
  117.  
  118. var dataEvent = Enumerable.Range(0, dataEvents.Count
  119.  
  120. .Select(i => dataEvents.Get(i).JavaCast<IDataEvent) 
  121.  
  122. .FirstOrDefault(x => x.Type == DataEvent.TypeChanged && x.DataItem.Uri.Path.Equals(_syncPath)); 
  123.  
  124. if (dataEvent == null
  125.  
  126. return
  127.  
  128. //do stuffs here 
  129.  
  130.  
  131.  

现在来解释一下上面代码中发生的事情。在OnCreate事件中,我们构建了一个Google Play Services客户端,它包括可穿戴设备API。然后,我们在按钮的click事件处理程序中调用SendData()方法实现数据发送。其中,SendData()方法包含发送数据的实际逻辑。具体实现中,我们通过传递数据对象路径(即WearDemo/Data)创建了一个DataMapRequest请求。实际的数据是DataMap,其中包含了一个Message值和一个UpdatedAt值。接收方可以使用路径来确定本文中稍后要介绍的数据来源。

事件

1.OnStart:当活动启动时触发此事件,用于连接到数据层。

2.OnConnected:当数据层的连接成功时触发此事件。

3.OnStop:断开数据层连接时触发此事件,此时活动停止 。你可以使用OnConnectionSuspended和OnConnectionFailed来实现对应的连接回调(例如在本演示中我们记录错误及分离服务)。

4.OnDataChanged:当数据更改时触发此事件。

需要牢记的事情

1.路径应该总是以正斜线(/)字符开头。

2.时间戳是发送数据时必须使用的,因为OnDataChanged()事件仅在数据真正更改时触发。将时间戳添加到数据将确保调用了该方法。

在文件AndroidManifest.xml的<application>元素下添加下列元数据:

  1. <meta-data android:name="com.google.android.gms.version" 
  2.  
  3. android:value="@integer/google_play_services_version" /> 

创建主程序项目

为了测试同步和数据发送,我们需要创建一个主安卓应用程序,由它来负责接收来自可穿戴设备的数据对象。其中,这个主程序将安装在手持设备(例如手机或平板电脑)中。

现在,右键单击解决方案项目,选择命令“Add->New Project”。在“Add->New Project”窗口中选择“Visual C# > Android > Blank App (Android)”。你应该能够看到如下图这个样子:

图 11: Android主程序项目

为简单起见,我命名项目为MainAppDemo。此后,只需单击OK命令让系统为您生成所需的文件。最终,你在解决方案界面中将看到如图所示结果。

图 12: 解决方案资源管理器

在我们开始将逻辑添加到主应用程序之前,我想突出强调以下两点:

第一点:你的可穿戴设备应用程序和主应用程序的命名空间应该是相同的。在此示例中,可穿戴应用程序使用的命名空间是"WearDemo"。所以,一定要重命名您的主程序的命名空间为"WearDemo",从而使之匹配。若要更改默认的命名空间,您可以参考下列这些步骤:

(1)转到命令“Project->Properties->Default Namespace”。

(2)若要更改其他项,你可以使用CTRL + H快捷键把默认命名空间替换为WearDemo。

(3)您也可以使用重构代码技术来更改命名空间。这只需要用鼠标右键单击命名空间并选择“Refactor->Rename”命令即可。

第二点:你的可穿戴设备程序和主应用程序的包名也应该相同。你可以找到该软件包的名称,这只需要右键单击该项目并选择选择“Properties->Android Manifest”命令,如下面的图像所示:

图 13: Android配置文件

请确保这两个软件包名称均被设置为"WearDemo.WearDemo"。一定要确保两个项目生成成功。一旦你做完这一步,你就可以继续往下操作——开始修改项目了。首先,要将"Compile using Android version"更改为"API Level 21 (Xamarin.Android v5.0 Support) "API。请参考下面的图片:

图 14 ︰应用程序设置

在引用位置,检查你是否引用了Xamarin.Android.Support.V4。如果没有这样做,你也可以右键单击References,然后选择Manage NUGET Packages。然后,在“Online> Nuget.Org”下搜索"Xamarin.Android.Support.V4"。你应该能够看到这样的内容:

图 15: NuGet程序包管理器

只需单击安装并等待,直到任务完成。现在,你需要做同样的操作并安装"Xamarin.Android.Wear-1.0.0"。

添加服务WearableListenerService

扩展WearableListenerService服务能够让你侦听数据层中的任何更新。由系统来管理服务的生命周期:当需要发送数据项或消息时实现绑定到服务;而当没有工作做时实现解除到服务的绑定。更多细节,请参考这个URL(https://developers.google.com/android/reference/com/google/android/gms/wearable/WearableListenerService)。

此后,我们将使用WearableListenerService来监听从数据层发来的更新事件并处理数据。那么,下一步是添加扩展WearableListenerService的类。为此,右键单击项目根目录并选择“Add->Class”,然后命名为"WearService"。下面给出此类完整的逻辑实现代码。

  1. using System.Linq; 
  2.  
  3. using Android.App; 
  4.  
  5. using Android.Content; 
  6.  
  7. using Android.Runtime; 
  8.  
  9. using Android.Gms.Wearable; 
  10.  
  11. using Android.Gms.Common.Apis; 
  12.  
  13. using Android.Support.V4.Content; 
  14.  
  15. namespace WearDemo 
  16.  
  17.  
  18. [Service] 
  19.  
  20. [IntentFilter(new[] { "com.google.android.gms.wearable.BIND_LISTENER" })] 
  21.  
  22. public class WearService : WearableListenerService 
  23.  
  24.  
  25. const string _syncPath = "/WearDemo/Data"
  26.  
  27. IGoogleApiClient _client; 
  28.  
  29. public override void OnCreate() { 
  30.  
  31. base.OnCreate(); 
  32.  
  33. _client = new GoogleApiClientBuilder(this.ApplicationContext) 
  34.  
  35. .AddApi(WearableClass.Api) 
  36.  
  37. .Build(); 
  38.  
  39. _client.Connect(); 
  40.  
  41. Android.Util.Log.Info("WearIntegrationreated"); 
  42.  
  43.  
  44. public override void OnDataChanged(DataEventBuffer dataEvents) { 
  45.  
  46. var dataEvent = Enumerable.Range(0, dataEvents.Count
  47.  
  48. .Select(i => dataEvents.Get(i).JavaCast<IDataEvent) 
  49.  
  50. .FirstOrDefault(x => x.Type == DataEvent.TypeChanged && x.DataItem.Uri.Path.Equals(_syncPath)); 
  51.  
  52. if (dataEvent == null
  53.  
  54. return
  55.  
  56. //get data from wearable 
  57.  
  58. var dataMapItem = DataMapItem.FromDataItem(dataEvent.DataItem); 
  59.  
  60. var map = dataMapItem.DataMap; 
  61.  
  62. string message = dataMapItem.DataMap.GetString("Message"); 
  63.  
  64. Intent intent = new Intent(); 
  65.  
  66. intent.SetAction(Intent.ActionSend); 
  67.  
  68. intent.PutExtra("WearMessage", message); 
  69.  
  70. LocalBroadcastManager.GetInstance(this).SendBroadcast(intent); 
  71.  
  72.  
  73.  

上面的代码实现了OnDataChanged事件,此事件负责过滤从"TypeChanged"事件发来的数据事件。具体地讲,它会检查"/ WearDemo /Data"这个数据对象路径,然后在本地进行广播。

创建主Activity

下面给出的是主Activity的代码部分:

  1. using Android.App;   
  2. using Android.Content;   
  3. using Android.Widget;   
  4. using Android.OS;   
  5. using Android.Support.V4.Content;   
  6.    
  7. namespace WearDemo   
  8. {   
  9.     [Activity(Label = "MainAppDemo", MainLauncher = true, Icon = "@drawable/icon")]   
  10.     public class MainActivity : Activity   
  11.     {   
  12.         TextView _txtMsg;   
  13.    
  14.         protected override void OnCreate(Bundle bundle) {   
  15.             base.OnCreate(bundle);   
  16.    
  17.             // Set our view from the "main" layout resource   
  18.             SetContentView(Resource.Layout.Main);   
  19.    
  20.             // Get our TextBox from the layout resource,   
  21.             _txtMsg = FindViewById<TextView>(Resource.Id.txtMessage);   
  22.    
  23.    
  24.             IntentFilter filter = new IntentFilter(Intent.ActionSend);   
  25.             MessageReciever receiver = new MessageReciever(this);   
  26.             LocalBroadcastManager.GetInstance(this).RegisterReceiver(receiver, filter);   
  27.         }   
  28.    
  29.         public void ProcessMessage(Intent intent) {   
  30.             _txtMsg.Text = intent.GetStringExtra("WearMessage");   
  31.         }   
  32.    
  33.         internal class MessageReciever : BroadcastReceiver   
  34.         {   
  35.             MainActivity _main;   
  36.             public MessageReciever(MainActivity owner) { this._main = owner; }   
  37.             public override void OnReceive(Context context, Intent intent) {   
  38.                 _main.ProcessMessage(intent);   
  39.             }   
  40.         }   
  41.     }   

在上面的代码中,我们进行了注册,以便在OnCreate()事件中接收来自于ListenerService的广播消息,然后定义了一个继承自BroadcastReceiver类的嵌套类,实现了OnReceive()方法并提取有关数据。最后,Process()方法负责处理显示数据到UI的问题。

布局

打开文件Main.xaml,并使用如下内容替换原来内容:

  1. <?xml version="1.0" encoding="utf-8"?> 
  2.  
  3. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
  4.  
  5. android:orientation="vertical" 
  6.  
  7. android:layout_width="fill_parent" 
  8.  
  9. android:layout_height="fill_parent"
  10.  
  11. <TextView 
  12.  
  13. android:id="@+id/txtMessage" 
  14.  
  15. android:layout_width="fill_parent" 
  16.  
  17. android:layout_height="wrap_content" 
  18.  
  19. android:layout_marginTop="50dp" 
  20.  
  21. android:gravity="center" 
  22.  
  23. android:textColor="@android:color/white" 
  24.  
  25. android:textSize="80sp" /> 
  26.  
  27. </LinearLayout> 

上面的标记中没有什么特别的内容。归纳来看,主要是包含了一个用于显示消息的文本框。

添加Google Play服务所需要的元数据

最后,我们在配置文件AndroidManifest.xml下的 <application>元素中添加元数据,代码如下:

  1. <meta-data android:name="com.google.android.gms.version" 
  2.  
  3. android:value="@integer/google_play_services_version" /> 

接下来,我要介绍如何把每一个应用程序部署到每一台设备并进行结果测试。

测试、调试与部署程序

在本节中,我们将学习如何在实际设备中部署和测试应用程序。首先需要说明的是,我使用Nexus 9和LG G Watch作为我的测试设备。

在我们开始之前,请确保在您的机器上为安卓系统安装了USB驱动程序。您可以通过右键单击命令“Computer > Manage > Device Manager > Other Devices”进行验证。如果驱动程序不在列表中,那么你必须先下载驱动程序(参考这个地址:http://developer.android.com/sdk/win-usb.htmland)并进行安装(安装方法请参阅链接: http://developer.android.com/tools/extras/oem-usb.html#InstallingDriver)。

接下来一个重要的事情是,在您的手持设备中启用USB调试。您可以使用命令“Settings > Developer Options > Usb Debugging”启用此功能。一旦你安装了必需的驱动程序并启用了您的设备的调试支持,那么你只需将您的手持设备插入您的PC或笔记本电脑即可。在Visual Studio中,你应该能够看到所连接的手持设备显示如下图所示:

图16: 接通手持设备后的项目界面

把主程序部署到手持设备

以下是部署主应用程序的主要步骤:

(1)右键单击主应用程序项目并选择“Properties > Android Options”选项。然后,在Packaging选项卡下取消选中" Use Fast Deployment (debug mode only)"。

(2)卸载项目。

(3)编辑.csproj文件并在其中添加PropertyGroup部分:

  1. <PropertyGroup> 
  2.  
  3. <JavaMaximumHeapSize>1G</JavaMaximumHeapSize> 
  4.  
  5. </PropertyGroup> 

(4)保存文件,然后加载该项目。

(5)右键单击主应用程序项目并将其设置为启动项目。

(6)构建应用程序。

(7)单击运行或播放按钮。系统将开始打包并将应用程序安装到设备上。只需稍等一会儿就会完成。

部署可穿戴设备应用程序

您可以按照如上面同样的过程来部署可穿戴应用程序。不过,如果你想要通过蓝牙来部署和调试您的应用程序的话,还需要如下一些额外的步骤:

1) 在您的手持设备中打开Android Wear关联程序。

2) 从右上角的菜单中选择Settings。

3) 启用"Debugging Over Bluetooth"。你应该能够看到像下面这样的输出状态:

Host: disconnected

Target: connected

4) 把掌上电脑通过USB连接到您的PC或笔记本电脑。

5) 在Visual Studio中,转到“Tools > Android > Android Adb Command Prompt”处,运行以下命令:

adb forward tcp:4444 localabstract:/adb-hub

adb connect localhost:4444

6) 然后,你应该能够看到可穿戴设备显示在设备列表中,如下面的图像所示:

图17:显示连接成功的设备

7) 重复与上面步骤1- 5相同的步骤,把主程序部署到手持设备。

8) 现在,设置你的可穿戴设备应用程序为启动项目,构建应用程序并运行起来。

一旦安装结束,你应该能够在Visual Studio 设置断点开始调试和测试您的应用程序。下面是示例应用程序的输出结果:

图18:最终的输出结果

最后,你可以从Github下载本文示范项目源码,供您学习参考。

小结

在这篇文章中,你已经学习了下列内容:

1.大概了解了可穿戴设备和安卓系统

2.设置开发环境

3.创建一个简单的Android可穿戴设备应用程序

4.创建了一个应用程序,它能够保持可穿戴设备和Android手持设备之间的数据同步

5.测试、调试和部署横跨可穿戴设备和手持设备的Android应用程序。

责任编辑:赵立京 来源: 51CTO
相关推荐

2021-02-03 00:18:05

可穿戴设备医疗应用物联网

2014-11-12 13:37:57

可穿戴设备英特尔

2014-05-05 11:02:02

2015-06-16 17:15:39

无线技术可穿戴设备

2020-02-25 16:48:11

物联网可穿戴设备智能眼镜

2018-11-16 09:00:05

可穿戴设备智能测试

2021-11-22 21:59:47

物联网医学可穿戴设备

2013-08-23 10:45:30

可穿戴设备

2015-10-22 11:08:19

2021-03-16 11:24:53

物联网可穿戴设备IOT

2015-10-20 15:31:23

可穿戴设备物联网

2013-08-19 09:05:31

大数据

2024-03-12 09:08:36

可穿戴技术人工智能

2014-02-17 09:15:20

MWC可穿戴设备

2014-07-03 14:38:50

2015-01-05 09:56:20

可穿戴设备

2014-04-24 09:42:06

乐跑手环运动手环健康手环

2014-05-20 17:04:08

智能硬件可穿戴设备反面教材

2015-08-27 10:42:00

可穿戴设备无线技术

2021-02-01 16:49:52

可穿戴设备COVID-19远程监控
点赞
收藏

51CTO技术栈公众号