【多服務場景化解決方案】智慧家居(UrbanHome)

2022-09-06 06:03:00

介紹

UrbanHome是一款提供房屋維修服務的移動應用。如有維修需求,使用者可通過該應用聯絡所在城市的管道工,電工,保潔,漆匠,木匠,修理工等,或是搜尋導航附近的維修商店。

通過構建UrbanHome這款應用,您可以實現以下華為移動服務的功能:

1. 通過賬號服務完成使用者驗證。

2. 通過雲資料庫,不同城市的維修者們能夠增、刪、改、查自己的資訊。

3. 通過使用者身份服務,使用者可以管理多個地址並基於當前地址通過雲資料庫檢視維修者資訊。

4. 通過位置服務,定位服務和地圖服務,使用者可以搜尋並導航附近的維修店。

您將建立什麼

在這個Codelab中,您將會建立一個整合了賬號服務,地圖服務、定位服務、位置服務、雲資料庫和使用者身份服務的專案。在該專案中,您可以嘗試:

  • 使用賬號服務實現華為賬號登入。

cke_755375.png

  • 使用雲資料庫管理資料。

cke_765671.png

  • 使用定位服務獲取當前位置的座標並使用地圖服務繪製使用者前往修理店的路線。

cke_780131.png

  • 使用使用者身份服務管理地址。

cke_794742.png

  • 使用位置服務搜尋附近修理店。

cke_807776.png

您將學到什麼

在這個Codelab中,您將學到:

  • 如何使用賬號服務實現華為賬號登入。
  • 如何使用定位服務獲取使用者當前位置。
  • 如何使用地圖服務在HMS地圖上定位消費者所在位置。
  • 如何使用位置服務獲取周邊修理店位置。
  • 如何使用雲資料庫為修理者提供資訊的增、刪、改、查操作。
  • 如何使用使用者身份服務為使用者提供地址的增、刪、改、查操作。

 

您需要什麼

說明:需要一個華為帳號,並且此賬號身份已驗證。

硬體要求

請提前準備上述硬體環境和相關裝置。

  • 執行Windows 10作業系統的桌上型電腦或筆記型電腦。
  • 安裝HMS Core (APK) 5.1.0.309或以上版本的華為手機一部。

軟體要求

請提前準備上述軟體環境。

  • Android Studio 4.X
  • JDK 1.8或以上。
  • SDK Platform 28或以上。
  • Gradle 4.6或以上。

能力接入準備

要整合HMS Core相關服務,需要完成以下準備:

  • 登入AppGallery Connect並建立應用。
  • 建立Android Studio工程。
  • 生成簽名證書。
  • 生成簽名證書指紋。
  • 設定簽名證書指紋。
  • 新增應用包名並儲存組態檔。
  • 在專案級build.gradle檔案中,新增AppGallery Connect外掛以及Maven倉地址。
  • 在Android Studio中設定簽名證書。

具體操作,請按照HMS Core整合準備中的詳細說明來完成。

啟用相關服務

前往「專案設定」頁面,選擇「API管理」並開通下述服務。

說明:部分API預設是關閉的,您必須手動啟用。

  • 賬號服務
  • 定位服務
  • 位置服務
  • 地圖服務

cke_821010.png

至此,您已成功啟用應用所需的華為服務。

整合賬號服務

通過華為帳號開放服務,Android應用可以方便和安全地實現快速登入授權、讀取簡訊驗證碼等功能。憑藉使用者授權憑證(即Access Token),應用可快速呼叫華為開放介面。

在依賴程式碼塊中新增賬號服務SDK的依賴。

implementation 'com.huawei.hms:hwid:5.0.3.301'

使用賬號服務實現華為賬號登入

1.申請授權,獲取Token。

若使用者選擇使用華為賬號登入應用,請呼叫如下startAuthService()方法啟動授權。

/**
 * Start AuthService and request the scope parameters
 */
private fun startAuthService() {
    HuaweiIdAuthParamsHelper(HuaweiIdAuthParams.DEFAULT_AUTH_REQUEST_PARAM)
        .setUid()
        .setProfile()
        .setMobileNumber()
        .setEmail()
        .setIdToken()
        .setAccessToken()
        .setAuthorizationCode()
        .setScopeList(scopes)
        .createParams()
    startActivityForResult(service.signInIntent, AppConstants.LOGIN_AUTH_CODE)
}

2.呼叫如下回撥方法檢測使用者授權是否成功。

說明:更多詳細資訊請參考賬號服務接入流程

/**
*Check authentication is successful or not.
*/
override fun onActivityResult(requestCode: Int, resultCode: Int, @Nullable data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)
    if (resultCode == RESULT_OK)
        when (requestCode) {
            AppConstants.LOGIN_AUTH_CODE -> {
                AGConnectAuth.getInstance().signOut()
                val authHuaweiIdTask: Task<AuthHuaweiId> =
                    HuaweiIdAuthManager.parseAuthResultFromIntent(data)
                if (authHuaweiIdTask.isSuccessful) {
                    huaweiAccount = authHuaweiIdTask.result
                    Log.d(
                        TAG,
                        AppConstants.LOGIN_GET_ACCESS_TOKEN
                    )
                    val credential =                     HwIdAuthProvider.credentialWithToken(huaweiAccount?.accessToken)
                    agConnectAuth.signIn(credential)
                        ?.addOnSuccessListener {
                            it.user.displayName
                            it.user.email
                            it.user.uid
                            it.user.providerInfo
                            mCloudDBZoneWrapper
                                .setmUiCallBack(this)
                            mCloudDBZoneWrapper
                                .openCloudDBZoneV2()
                        }
                        ?.addOnFailureListener {
                            Log.e(TAG, AppConstants.LOGIN_FAILED)
                        }
                } else {
                    Log.e(
                        TAG,
                        AppConstants.LOGIN_FAILED
                    )
                }
            }

cke_836834.png

接入地圖服務

地圖服務給您提供一套地圖開發呼叫的SDK,地圖資料覆蓋超過200個國家和地區,支援70多種地圖展示與搜尋語言,方便您輕鬆地在應用中整合地圖相關的功能,全方位提升使用者體驗。

在本專案中,該服務用於在地圖上展示使用者當前位置和修理店位置併為使用者規劃路線。

接入地圖服務具體如下:

1.新增下述依賴整合地圖服務。

implementation 'com.huawei.hms:maps:5.0.5.301'

2.初始化Map View。

MapsInitializer.setApiKey(AppConstants.API_KEY)
mMapView.onCreate(mapViewBundle)
mMapView.getMapAsync(this)

3.載入Map View。

/**
*To load Map View
*/
Override fun onMapReady(huaweiMap: HuaweiMap) {
     latLng1 = LatLng(Utils.curentLatitude, Utils.currentLongitude)
     latLng2 = lat?.let { lng?.let { it1 -> LatLng(it, it1) } }
     hMap = huaweiMap
     hMap?.isMyLocationEnabled = true
     val build = CameraPosition.Builder().target(latLng1).zoom(3f).tilt(45f).build()
     val cameraUpdate = CameraUpdateFactory.newCameraPosition(build)
     hMap?.apply {
         animateCamera(cameraUpdate)
         moveCamera(cameraUpdate)
     }
     addOriginMarker(latLng1!!)
     latLng2?.let { addDestinationMarker(it) }
     removePolylines()
     hMap?.moveCamera(CameraUpdateFactory.newLatLngZoom(latLng1, 10f))
     mMarkerOrigin?.showInfoWindow()
     hMap?.apply {
         moveCamera(CameraUpdateFactory.newLatLngZoom(latLng2, 10f))
         resetMinMaxZoomPreference()
     }
     mMarkerDestination?.showInfoWindow()
     
 }

4.在地圖上展示使用者/修理者位置。

說明:更多詳細資訊請參考地圖服務接入流程

/**
* This method shows a marker for Consumer location on Map.
*/
private fun addOriginMarker(latLng: LatLng) {
     if (null != mMarkerOrigin) {
         mMarkerOrigin?.remove()
     }
     val address = getCompleteAddressString(Utils.curentLatitude, Utils.currentLongitude)
     mMarkerOrigin = hMap?.addMarker(
         MarkerOptions().position(latLng)
             .anchorMarker(0.5f, 0.9f)
             .title("Current Location")
             .snippet(address)
     )
 }

cke_852739.png

整合定位服務

Android定位SDK採用全球衛星導航系統(Global Navigation Satellite System,簡稱GNSS)、Wi-Fi、基站等多途徑的混合定位元型樣進行定位,賦予您的應用靈活的全球定位能力。目前,該服務提供融合定位、活動識別、地理圍欄等主要能力。

1.新增下述依賴整合定位服務。

implementation 'com.huawei.hms:location:5.0.4.300'

2.獲取使用者當前位置座標(經度和緯度)。

/**
*This method shows a marker for Service Provider location on HMS Map
*/
private fun addDestinationMarker(latLng: LatLng) {
     if (null != mMarkerDestination) {
         mMarkerDestination?.remove()
     }
     mMarkerDestination = hMap?.addMarker(
         MarkerOptions().position(latLng).anchorMarker(0.5f, 0.9f).title(storeName)
             .snippet(storeAddress)
     )
 }

3.在Android manifest檔案中新增位置許可權。

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />

4.獲取使用者當前更新位置座標(經度和緯度)。

/**
*This method fetches location updates
*/
private fun startLocationUpdates() {
     fusedLocationClient.requestLocationUpdates(locationRequest, locationCallback, null)
 }

private val locationCallback = object : LocationCallback() {
     override fun onLocationResult(locationResult: LocationResult?) {
         locationResult ?: return
         for (location in locationResult.locations) {
             setLocationData(location)
         }
     }
 }

 /**
* To set location data.
*/
private fun setLocationData(location: Location) {
     value = LocationModel(longitude = location.longitude, latitude = location.latitude)
 }

5.獲取最新位置資訊。

說明:更多詳細資訊請參考定位服務接入流程

/**
* To fetch last location information
*/
override fun onActive() {
     super.onActive()
     fusedLocationClient.lastLocation
         .addOnSuccessListener { location: Location? ->
             location?.also {
                 setLocationData(it)
             }
         }
     startLocationUpdates()
 }

整合使用者身份服務

使用者身份服務為使用者提供統一的地址管理服務,包括地址錄入、編輯、刪除和查詢,支援使用者一鍵授權應用使用地址資訊,高效便利。

1.新增下述依賴整合使用者身份服務。

implementation 'com.huawei.hms:identity:5.1.0.300'

2.通過使用者身份服務獲取使用者地址資訊。

/**
* To fetch user address from Identity kit
*/
 
private fun getUserAddress() {
     val task = Address.getAddressClient(this@MainActivity).getUserAddress(UserAddressRequest())
     task.addOnSuccessListener {
         Log.i(TAG, AppConstants.LOGIN_USER_DATA_SUCCESS)
         try {
             startActivityForResult(it)
         } catch (ex: IntentSender.SendIntentException) {
             Log.d(TAG, "SendIntentException")
         }
     }.addOnFailureListener {
         Log.i(TAG, AppConstants.LOGIN_USER_DATA_FAILED)
     }
 }

3.在使用者首次登入時呼叫下述回撥從使用者身份服務提供的地址列表中選擇地址。

/**
* To fetch user address result.
*/
private fun startActivityForResult(result: GetUserAddressResult) {
     val status = result.status
     if (result.returnCode == 0 && status.hasResolution()) {
         Log.i(TAG, AppConstants.LOGIN_RESULT_RES)
         status.startResolutionForResult(
             this@MainActivity, AppConstants.LOGIN_GET_ADDRESS_REQUESTCODE
         )
     } else {
         Log.i(TAG, AppConstants.LOGIN_RESULT_RES_FAILED)
         Utils.showToast(this@MainActivity, getString(R.string.msg_failed_user_resolution))
     }
 }

4.讀取使用者地址。

說明:更新詳細資訊,請參考使用者身份服務接入流程

/**
* To read user address result.
*/
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
     super.onActivityResult(requestCode, resultCode, data)
     when(requestCode) {
         AppConstants.LOGIN_GET_ADDRESS_REQUESTCODE -> {
             onGetAddressResult(resultCode, data)
             fragmentCommunicator?.passDataToFragment(data)
         }
     }
 }

cke_870584.png

整合位置服務

位置服務提供位置查詢服務,幫助您的使用者更加方便地使用位置相關服務,包括關鍵字搜尋、周邊搜尋、地點詳情、及地點搜尋建議等,有助於您的App吸引更多使用者,提升使用者粘性。

1.新增下述依賴整合位置服務。

implementation 'com.huawei.hms:site:5.0.2.300'

2.初始化位置服務。

searchService = SearchServiceFactory.create(this, Utils.getApiKey())

3.通過使用者身份服務獲取使用者地址資訊。

說明:更多詳細資訊,請參考位置服務接入流程

/**
*To fetch nearby stores based on user's current location
*/
intent.let {
     val request = NearbySearchRequest().apply {
         queryString=it.getStringExtra(AppConstants.REQUEST_QUERY).toString()
         setQuery(queryString)
         setLocation(
             Coordinate(
                 it.getDoubleExtra(AppConstants.SERVICE_LAT_KEY, 0.0),
                 it.getDoubleExtra(AppConstants.SERVICE_LNG_KEY, 0.0)
             )
         )
     }
     imageString = it.getStringExtra(AppConstants.PROVIDER_IMAGE_KEY).toString()
     searchService?.nearbySearch(request, searchResultListener)
 }

cke_889359.png

整合雲資料庫

在這個步驟中,您可以學到:

  • 如何使用雲資料庫開發應用
  • 如何向雲資料庫中寫入應用資料
  • 如何查詢資料
  • 如何實時監聽資料變化
  • 端側和雲側的資料同步
  • 華為雲資料庫是一款端雲協同的資料庫產品,提供端雲資料的協同管理、統一的資料模型和豐富的資料管理API介面等能力。

該雲資料庫十分契合本工程專案,有利於我們進行資料的增、刪、查、改操作。

開發準備

使用雲資料庫構建應用服務需完成下述準備工作:

  • 在AppGallery Connect註冊賬號並通過實名認證。
  • 登入AppGallery Connect建立專案及應用。
  • 開通AppGallery Connect匿名帳號認證服務,使應用有認證使用者的相關許可權。
  • 本地上安裝Android Studio。

開通服務

使用雲資料庫前,您需要先開通服務。

1.登入AppGallery Connect,點選「我的專案」。

2.在專案列表中選擇專案和需要開通雲資料庫的應用。

3.在導航樹上選擇「構建」,點選「雲資料庫」。

4.點選「立即開通」。 

cke_905689.png

5.如果您之前沒有選擇資料處理位置,開通雲資料庫後首先將會設定資料處理位置。 

cke_919451.png

6.雲資料庫初始化完成後,該服務成功開通。

新增和匯出物件型別

下述範例將展示如果在AppGallery Connect上新增和匯出用於Android應用開發的java格式物件型別檔案。

1.登入AppGallery Connect,選擇「我的專案」。

2.在專案列表頁面中選擇專案,單擊專案下需要建立物件型別的應用。

3.在導航樹上選擇「構建」,點選「雲資料庫」。

4.點選「新增」,進入建立物件型別頁面。

cke_933347.png

5.輸入物件型別名為LoginInfo後,點選「下一步」。

6.點選「+新增欄位」,新增如下欄位後,單擊「下一步」。

欄位名稱

型別

主鍵

非空

加密

預設值

user_id

Integer

user_email

String

user_name

String

user_phone

Double

photo_uri

String

device_token

Date

shadowFlag

Boolean

true

7.(可選)點選「新增索引」。

8.按照如下要求設定各角色許可權後,點選「下一步」。

角色

query

upsert

delete

所有人

認證使用者

資料建立者

管理員

9.點選「確定」。

在物件型別列表中可以看到已建立的物件型別。

重複上述步驟,完成Service type、Service Category物件型別的建立。

10.點選「匯出」。

cke_971517.png

11.設定匯出檔案格式為java格式。

12.設定java檔案型別為「android」。

13.輸入java檔案中的包名。

包名只能包含以下3種字元:

字母(A-Z或a-z)

數位(0-9)

特殊字元:_和.

14.    點選「匯出」。

檔案將會匯出至本地,其內包含該版本中所有的物件型別。匯出的java格式檔案在後續步驟用於新增至本地開發環境。

新增儲存區

您可基於AppGallery Connect在雲側建立資料儲存區,請您遵循操作步驟建立一個儲存區名稱為「UrbanHomeServices」的儲存區。

1.登入AppGallery Connect,點選「我的專案」。

2.在專案列表頁面中選擇專案,單擊專案下需要建立儲存區的應用。

3.在導航樹上選擇「構建 > 雲資料庫」。

4.選擇「儲存區」頁籤。

5.點選「新增」,進入建立儲存區頁面。 

cke_997362.png

6.輸入儲存區名稱為「UrbanHomeSevices」。

7.點選「確定」。

8.建立完成後返回儲存區列表中,可以檢視已建立的儲存區。

設定開發環境

1.在專案級的/app/build.gradle檔案中dependencies節點新增Cloud DB SDK。

implementation 'com.huawei.agconnect:agconnect=database:1.2.3.301'

2.在build.gradle檔案中設定Java原始碼相容模式為JDK1.8版本。

compileOptions {
sourceCompatibility = 1.8
targetCompatibility = 1.8
}

新增物件型別檔案

本地應用開發無需再次建立物件型別。

1.將已在AppGallery Connect上匯出的全部java格式檔案新增至本地開發環境。

2.Initialize Cloud DB. 通過AGConnectCloudDB類中的createObjectType()方法實現物件型別的定義和建立。

初始化

在新增物件型別檔案後,您就可以使用雲資料庫進行應用開發。開發應用時,需要先初始化AGConnectCloudDB,然後建立Cloud DB zone和物件型別。

1.在應用的CloudDBZoneWrapper類中初始化AGConnectCloudDB。

/**
* To initialize AGConnectCloudDB
*/
public static void initAGConnectCloudDB(Context context) { 
    AGConnectCloudDB.initialize(context); 
}

2.獲取AGConnectCloudDB範例和建立物件型別。

mCloudDB = AGConnectCloudDB.getInstance(); 
mCloudDB.createObjectType(ObjectTypeInfoHelper.getObjectTypeInfo());

3.建立Cloud DB zone設定物件,並開啟該Cloud DB zone。

/**
* This method is used to open Cloud DB zone.
*/
public void openCloudDBZoneV2() {
     mConfig = new CloudDBZoneConfig(AppConstants.URBAN_HOME_SERVICES,
 CloudDBZoneConfig.CloudDBZoneSyncProperty.CLOUDDBZONE_CLOUD_CACHE, CloudDBZoneConfig.CloudDBZoneAccessProperty.CLOUDDBZONE_PUBLIC);
     mConfig.setPersistenceEnabled(true);
     Task<CloudDBZone> openDBZoneTask = mCloudDB.openCloudDBZone2(mConfig, true);
     openDBZoneTask.addOnSuccessListener(cloudDBZone -> {
         Log.w(TAG, "open clouddbzone success");
         mCloudDBZone = cloudDBZone;
         // Add subscription after opening cloudDBZone success
         mUiCallBack.onInitCloud();
         addSubscription();
     }).addOnFailureListener(e ->
             Log.w(TAG, "open clouddbzone failed for"));
 }

cke_423080.gif寫入資料

您可以使用executeUpsert()介面向當前Cloud DB zone中寫入一個或一組物件。

/**
* This method is used to insert data into Cloud DB.
*/
public void insertDbZoneInfo(T objectInfo) {
     if (mCloudDBZone == null) {
         Log.w(TAG, "CloudDBZone is null, try re-open it");
         return;
     }
     Task<Integer> upsertTask = mCloudDBZone.executeUpsert(objectInfo);
     upsertTask.addOnSuccessListener(cloudDBZoneResult -> {
         mUiCallBack.onInsertSuccess(cloudDBZoneResult);
     }).addOnFailureListener(e -> {
         mUiCallBack.updateUiOnError("Insert table info failed");
     });
 }

檢視資料

使用者在應用介面中新增的資料,將會被儲存在雲側。在端側註冊資料變化監聽器,當雲側資料發生變化時,端側能夠感知資料變化,及時重新整理本地應用資料。

呼叫subscribeSnapshot()方法並設定查詢條件可以指定監聽物件。當監聽物件的資料發生變化時,端側會收到通知,根據快照獲取資料變化資訊,從雲側同步資料至端側應用。

/**
* This listener is used to get snapshot
*/
private OnSnapshotListener<T> mSnapshotListener = (cloudDBZoneSnapshot, e) -> {
     if (e != null) {
         Log.w(TAG, "onSnapshot" );
         return;
     }
     CloudDBZoneObjectList<T> snapshotObjects = cloudDBZoneSnapshot.getSnapshotObjects();
     List<T> dbZoneList = new ArrayList<>();
     try {
         if (snapshotObjects != null) {
             while (snapshotObjects.hasNext()) {
                 T objectInfo = snapshotObjects.next();
                 dbZoneList.add(objectInfo);
             }
         }
         mUiCallBack.onSubscribe(dbZoneList);
     } catch (AGConnectCloudDBException snapshotException) {
         Log.w(TAG, "onSnapshot:(getObject)");
     } finally {
         cloudDBZoneSnapshot.release();
     }
 };

查詢資料

您可以通過executeQuery()addOnSuccessListener()addOnFailureListener()方法組合,實現非同步方式查詢資料。

/**
* This method is used to query all data from Cloud DB.
*/
public void queryAllData(CloudDBZoneQuery<T> query) {
     if (mCloudDBZone == null) {
         Log.w(TAG, "CloudDBZone is null, try re-open it");
         return;
     }
     Task<CloudDBZoneSnapshot<T>> queryTask = mCloudDBZone.executeQuery(query,
             CloudDBZoneQuery.CloudDBZoneQueryPolicy.POLICY_QUERY_FROM_CLOUD_ONLY);
     queryTask.addOnSuccessListener(new OnSuccessListener<CloudDBZoneSnapshot<T>>() {
         @Override
         public void onSuccess(CloudDBZoneSnapshot<T> snapshot) {
             processQueryResult(snapshot);
         }
     }).addOnFailureListener(new OnFailureListener() {
         @Override
         public void onFailure(Exception e) {
             mUiCallBack.updateUiOnError("Query failed");
         }
     });
 }

刪除資料

您可以使用executeDelete()方法刪除一個或一組物件。刪除資料時,Cloud DB會根據傳入物件主鍵刪除相應的資料,不會比對該物件其它屬性與儲存的資料是否一致。刪除一組物件時,刪除操作是原子的,即物件列表中的物件要麼全部刪除成功,要麼全部刪除失敗。

/**
* This method is used delete table data on Cloud DB.
*/
 public void deleteTableData(List<T> tableObject) {
         if (mCloudDBZone == null) {
             Log.w(TAG, "CloudDBZone is null, try re-open it");
             return;
         }
         Task<Integer> deleteTask = mCloudDBZone.executeDelete(tableObject);
         if (deleteTask.getException() != null) {
             mUiCallBack.updateUiOnError("Delete service type table failed");
             return;
         }
         mUiCallBack.onDelete(tableObject);
     }
 }

編輯資料

您可以使用editService()方法編輯修理工的資訊。

/**
* This method is used to edit Service details.
*/
override fun editService(listObject: ServiceType) {
     val intent = Intent(this, AddServiceActivity::class.java)
     intent.apply {
         putExtra(AppConstants.CATEGORY_NAME, listObject.cat_name)
         putExtra(AppConstants.PROVIDER_PH_NUM, listObject.phone_number.toString())
         putExtra(AppConstants.PROVIDER_MAIL_ID, listObject.email_id)
         putExtra(AppConstants.PROVIDER_COUNTRY, listObject.country)
         putExtra(AppConstants.PROVIDER_ID, listObject.id)
         putExtra(AppConstants.PROVIDER_NAME, listObject.service_provider_name)
         putExtra(AppConstants.PROVIDER_CITY, listObject.city)
         putExtra(AppConstants.PROVIDER_STATE, listObject.state)
     }
     startActivity(intent)
 }

cke_1018241.png

提示:

  • 請務必使用最新版本的依賴。
  • 請務必在物件型別中為使用者分配合理的角色。

說明:更多詳細資訊,請參考雲資料庫接入檔案

恭喜您

祝賀您,您已成功構建UrbanHome。

參考

有關更多資訊,請參閱以下官方檔案。

本Codelab中的範例程式碼下載地址如下:原始碼下載

欲瞭解更多更全技術文章,歡迎存取https://developer.huawei.com/consumer/cn/forum/?ha_source=zzh