构建Serverless架构的应用需要用到后端服务工具。AppGallery Connect为您提供了云存储,云数据库,云函数等诸多常用服务。利用这些服务,并在AppGallery Connect上启用按量付费,就可以构建一款聊天应用。

介绍

服务场景描述

通过构建一款聊天应用,您可以了解到华为生态系统的组成部分,如认证服务,云存储,云数据库和云函数等Serveless服务。此外,您还会了解到分享位置这一聊天应用的重要特性是如何利用定位服务,地图服务,和位置服务实现的。用户的注册流程会涉及手机号登录,动态口令(OTP认证),以及将用户资料存储到数据库中。

在这个Codelab中,我们将使用如下华为移动服务来实现应用的相关功能:

应用功能
华为移动服务
手机号登录 认证服务
聊天 云存储
分享图片 云存储和云数据库
推送通知 推送服务
分享当前位置或附近地标 地图服务,定位服务,和位置服务

您将建立什么

在这个Codelab中,您将会建立一个聊天应用的示例工程,调用前述服务的接口,并能学习到MVVM(Model–view–viewmodel)架构模式概念,以及将分享的消息和文件存储在云数据库中。您的应用可以:

  1. 该聊天应用完成后的效果如下图所示。应用打开后,首先展示开屏页面,接着展示引导页面,然后展示登录页面。

  2. 登录页面如下图所示。手机号输入并点击发送动态口令后,动态口令将会发送给用户。
  3. 首次登录成功后,用户需要填写下图所示的用户资料。应用会保存这些信息。
  4. 下图分别展示了添加的联系人列表,历史聊天记录,和用户资料页面。

  5. 通过聊天窗口可以分享图片,位置,和文件等。

分享成功后的聊天窗口如下图所示。

用户会收到消息通知,如下图所示。

注意:为保护用户隐私,本文档中涉及用户个人信息的图片都已做了模糊处理。

流程图

您将学到什么

在这个Codelab中,您将学到:

硬件要求

软件要求

要集成HMS Core相关服务,需要完成以下准备:

具体操作,请按照HMS Core集成准备中的详细说明来完成。
前往"项目设置"页面,选择"API管理"并开通下述服务:

集成认证服务

认证服务提供多种登录模式,用户可使用手机号,微信账号,微博账号等登录应用。本次Codelab中的示例应用的将启用手机号登录模式,这样我们可以接收动态口令。

1. 登录AppGallery Connect,开通认证服务。

添加如下依赖:

implementation 'com.huawei.agconnect:agconnect-auth: <auth_service_version>'

说明:请集成最新版本的SDK,示例集成的是1.6.0.300版本。

2. 验证手机号。

用户使用手机号注册应用账号需要提供验证码。验证码将会发送到用户的手机上,验证成功则证明当前确为用户本人在操作。首先调用VerifyCodeSettings.Builder设置验证码功能,接着调用AGConnectAuth.requestVerifyCode申请验证码。

导入认证服务需要用到的相关类:

import com.huawei.agconnect.auth.AGConnectAuth; import com.huawei.agconnect.auth.PhoneUser; import com.huawei.agconnect.auth.VerifyCodeResult; import com.huawei.agconnect.auth.VerifyCodeSettings;

AuthServiceViewModel.java

public void getOTP(String countryCodeStr, String phoneNumberStr) { VerifyCodeSettings settings = new VerifyCodeSettings.Builder() .action(VerifyCodeSettings.ACTION_REGISTER_LOGIN) .sendInterval(30) .locale(Locale.getDefault()) .build(); Task<VerifyCodeResult> task = AGConnectAuth.getInstance().requestVerifyCode(countryCodeStr, phoneNumberStr, settings); task.addOnSuccessListener(TaskExecutors.immediate(), verifyCodeResult -> { if (null != verifyCodeResult) { verifyCodeResultMutableLiveData.postValue(verifyCodeResult); } }); task.addOnFailureListener(e -> AppLog.logE(TAG, "onFailure: " + e.getCause())); }

LoginFragment.Java
使用如下代码段实现登录按钮:

Util.showProgressBar(getActivity()); bundle.putString(Constants.PHONE_NUMBER, etPhoneNumber.getText().toString()); bundle.putString(Constants.COUNTRY_CODE, "+91"); authServiceViewModel.getOTP("+91", etPhoneNumber.getText().toString()); authServiceViewModel.verifyCodeResultMutableLiveData.observe(requireActivity(), verifyCodeResult -> { loginPhoneFragmentListener.setLoginPhoneFragmentListener(bundle); Util.stopProgressBar(); }); } else { Toast.makeText(requireContext(), "Please enter number", Toast.LENGTH_SHORT).show(); }

3. 用户确认手机号后发送动态口令。

AuthServiceViewModel.java

public void verifyContactDetails(String countryCodeStr, String phoneNumberStr, String code) { PhoneUser = new PhoneUser.Builder() .setCountryCode(countryCodeStr) .setPhoneNumber(phoneNumberStr) .setVerifyCode(code) .build(); AGConnectAuth.getInstance().createUser(phoneUser) .addOnSuccessListener(signInResult -> { if (signInResult != null) { User user = new User(); user.setUsername(signInResult.getUser().getDisplayName()); user.setPhoneNumber(phoneNumberStr); userMutableLiveData.postValue(user); } }) .addOnFailureListener(e -> { AppLog.logE(TAG, "verifyContactDetails: " + e.getStackTrace()); User user = new User(); user.setPhoneNumber(phoneNumberStr); userMutableLiveData.setValue(user); }); }

4. 实现片段级动态口令验证。

LoginWithOTPFragment.java

Util.showProgressBar(getActivity()); Bundle bundle = new Bundle(); bundle.putString(Constants.COUNTRY_CODE, strCountryCode); bundle.putString(Constants.PHONE_NUMBER, strPhone); if (otpEditText.length() == 6) { authServiceViewModel.verifyContactDetails(strCountryCode, strPhone, otpEditText.getText().toString()); authServiceViewModel.userMutableLiveData.observe(requireActivity(), user -> { if (user != null) { loginWithOTPFragmentListener.setLoginWithOTPFragmentListener(bundle); } Util.stopProgressBar(); }); } });

若手机号验证成功:

@Override public void setLoginWithOTPFragmentListener(Bundle bundle) { String number = bundle.getString(Constants.PHONE_NUMBER); Intent intent = new Intent(LoginActivity.this, UserProfileActivity.class); intent.putExtra(Constants.PHONE_NUMBER, number); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_CLEAR_TASK); startActivity(intent); ActivityCompat.finishAffinity(LoginActivity.this); }

应用将打开用户资料填写界面,用户需要填写姓名,手机号,状态等信息。

集成云数据库服务

1. 添加依赖。

  1. 添加如下依赖:
    dependencies { implementation 'com.huawei.agconnect:agconnectdatabase:<cloud_DB_zone≶' }
  2. 说明:请集成最新版本的SDK,示例集成的是1.2.3.301版本。
    关于使用云数据库,请参考官方文档

  3. 接入SDK后,在应用级build.gradle文件头部添加apply plugin:
    final StorageReference storageReference = ChitChatApplication.getStorageManagement().getStorageReference(fileName + name);

2. 创建含有特定字段的User对象。

创建一个User类用来和云数据库传递用户信息:

package com.huawei.chitchatapp.dbcloud; import com.huawei.agconnect.cloud.database.CloudDBZoneObject; import com.huawei.agconnect.cloud.database.Text; import com.huawei.agconnect.cloud.database.annotations.DefaultValue; import com.huawei.agconnect.cloud.database.annotations.NotNull; import com.huawei.agconnect.cloud.database.annotations.Indexes; import com.huawei.agconnect.cloud.database.annotations.PrimaryKeys; @PrimaryKeys({"user_phone"}) @Indexes({"user_id:user_id", "user_phone_id:user_phone,user_id"}) public final class User extends CloudDBZoneObject { private Integer user_id; private String user_phone; private String user_name; private String user_status; private String user_login_status; private String user_push_token; private String user_profile_url; public User() { super(User.class); } public void setUserId(Integer user_id) { this.user_id = user_id; } public Integer getUserId() { return user_id; } public void setUserPhone(String user_phone) { this.user_phone = user_phone; } public String getUserPhone() { return user_phone; } public void setUserName(String user_name) { this.user_name = user_name; } public String getUserName() { return user_name; } public void setUserStatus(String user_status) { this.user_status = user_status; } public String getUserStatus() { return user_status; } public void setUserLoginStatus(String user_login_status) { this.user_login_status = user_login_status; } public String getUserLoginStatus() { return user_login_status; } public void setUserPushToken(String user_push_token) { this.user_push_token = user_push_token; } public String getUserPushToken() { return user_push_token; } public void setUserProfileUrl(String user_profile_url) { this.user_profile_url = user_profile_url; } public String getUserProfileUrl() { return user_profile_url; } }

}

3. 接入云数据库工具类。

CloudDBHelper.java

import android.content.Context; import com.huawei.agconnect.cloud.database.AGConnectCloudDB; import com.huawei.agconnect.cloud.database.CloudDBZone; import com.huawei.agconnect.cloud.database.CloudDBZoneConfig; import com.huawei.agconnect.cloud.database.exceptions.AGConnectCloudDBException; import com.huawei.chitchatapp.dbcloud.ObjectTypeInfoHelper; import com.huawei.chitchatapp.utils.AppLog; import com.huawei.chitchatapp.utils.Constants; import com.huawei.chitchatapp.utils.OnDBZoneOpen; import com.huawei.hmf.tasks.Task;

4. 添加如下代码段。

private AGConnectCloudDB agConnectCloudDB; private CloudDBZone cloudDBZone; private static final String TAG = CloudDBHelper.class.getSimpleName(); private static CloudDBHelper cloudDBHelper; public static CloudDBHelper getInstance() { if (cloudDBHelper == null) { cloudDBHelper = new CloudDBHelper(); } return cloudDBHelper; }

5. 初始化云数据库SDK。初始化完成后,在Application类中调用如下方法:

public void init(Context context) { AGConnectCloudDB.initialize(context); try { agConnectCloudDB = AGConnectCloudDB.getInstance(); agConnectCloudDB.createObjectType(ObjectTypeInfoHelper.getObjectTypeInfo()); } catch (AGConnectCloudDBException e) { e.printStackTrace(); } }

6. 建立与云数据库的连接。

public void openDb(OnDBZoneOpen onDBZoneOpen) { CloudDBZoneConfig mConfig = new CloudDBZoneConfig(Constants.DB_ZONE_NAME, CloudDBZoneConfig.CloudDBZoneSyncProperty.CLOUDDBZONE_CLOUD_CACHE, CloudDBZoneConfig.CloudDBZoneAccessProperty.CLOUDDBZONE_PUBLIC); mConfig.setPersistenceEnabled(true); Task openDBZoneTask = agConnectCloudDB.openCloudDBZone2(mConfig, true); openDBZoneTask.addOnSuccessListener(cloudDBZone -> { AppLog.logI(TAG, "cloudDBZOne Open"); this.cloudDBZone = cloudDBZone; onDBZoneOpen.isDBZoneOpen(true, this.cloudDBZone); }).addOnFailureListener(e -> { AppLog.logW(TAG, "open cloudDBZone failed for " + e.getMessage()); onDBZoneOpen.isDBZoneOpen(false, null); }); }

7. 关闭与云数据库的连接。

public void closeDb(Context context) { try { agConnectCloudDB.closeCloudDBZone(cloudDBZone); } catch (AGConnectCloudDBException e) { AppLog.logW(TAG,"close the DBZone "+e.getMessage()); } }

8. 输入用户信息。

private void updateProfileData(String name, String status, Uri profileUri) { ChitChatSharedPref.initializeInstance(UserProfileActivity.this); User user = new User(); user.setUserId(userId); user.setUserPushToken(pushToken); user.setUserPhone(phoneNumber); user.setUserName(name); user.setUserLoginStatus(Constants.STATUS_ONLINE); user.setUserStatus(status); user.setUserProfileUrl(String.valueOf(profileUri)); userProfile.saveUser(user, UserProfileActivity.this); userProfile.userMutableLiveData.observe(UserProfileActivity.this, aBoolean -> { if (aBoolean) { ChitChatSharedPref.initializeInstance(getApplicationContext()); ChitChatSharedPref.getInstance().putString(Constants.PHONE_NUMBER, phoneNumber); ChitChatSharedPref.getInstance().putString(Constants.USER_NAME, name); ChitChatSharedPref.getInstance().putString(Constants.ALREADY_LOGIN, "1"); Toast.makeText(UserProfileActivity.this, getString(R.string.showMessageSuccess), Toast.LENGTH_SHORT).show(); Intent intent = new Intent(UserProfileActivity.this, MainActivity.class); startActivity(intent); } else { Toast.makeText(UserProfileActivity.this, getString(R.string.showMessageFailed), Toast.LENGTH_SHORT).show(); } }); }

9. 保存用户信息并检查保存是否成功。

public void saveUser(User user, Context context) { CloudDBHelper.getInstance().openDb((isConnected, cloudDBZone) -> { if (isConnected && cloudDBZone != null) { if (cloudDBZone == null) { return; } else { Task insertTask = cloudDBZone.executeUpsert(user); insertTask.addOnSuccessListener(integer -> { userMutableLiveData.setValue(true); CloudDBHelper.getInstance().closeDb(context); }).addOnFailureListener(e -> { userMutableLiveData.setValue(false); CloudDBHelper.getInstance().closeDb(context); }); } } }); }

输出效果

集成云存储服务

1. 添加依赖。

添加如下依赖:

dependencies{ implementation "com.huawei.agconnect:agconnect-storage:" }

说明: 请集成最新版本的SDK,示例集成的是1.3.1.100版本。
关于使用云存储,请参考官方文档

2. 初始化云存储SDK。

final StorageReference storageReference = ChitChatApplication.getStorageManagement().getStorageReference(fileName + name);

3. 实现ViewModel。

public class ChitChatStorageViewModel extends ViewModel { private static final String TAG = "ChitChatStorageViewModel"; private MutableLiveData<Uri≶ uploadFileLiveData; private OnApiError onApiError; public LiveData<Uri≶ uploadFileLiveData() { if (uploadFileLiveData == null) { uploadFileLiveData = new MutableLiveData<≶(); } return uploadFileLiveData; } public void uploadFile(StorageReference reference, String fileName, File filePath, OnApiError onApiError) { this.onApiError = onApiError; UploadTask task = reference.putFile(filePath); task.addOnSuccessListener(uploadSuccessListener) .addOnFailureListener(uploadFailureListener); } OnSuccessListener<UploadTask.UploadResult≶ uploadSuccessListener = new OnSuccessListener<UploadTask.UploadResult≶() { @Override public void onSuccess(UploadTask.UploadResult uploadResult) { uploadResult.getStorage().getDownloadUrl().addOnSuccessListener(downloadLink) .addOnFailureListener(downloadUriFailureListener); } }; OnSuccessListener<Uri≶ downloadLink = new OnSuccessListener<Uri≶() { @Override public void onSuccess(Uri uri) { uploadFileLiveData.postValue(uri); } }; OnFailureListener uploadFailureListener = new OnFailureListener() { @Override public void onFailure(Exception e) { if (onApiError != null) { onApiError.onError("Error in uploading file to server", e); } } }; OnFailureListener downloadUriFailureListener = new OnFailureListener() { @Override public void onFailure(Exception e) { onApiError.onError("Failed in getting uri", e); } }; }

4. 上传文件至云端。

上传文件代码如下所示:

public void uploadImage(File path) { userProfileImage.uploadFileLiveData().observe(UserProfileActivity.this, uri -> { updateProfileData(mName.getText().toString(), mStatus.getText().toString(), uri); }); final String name = phoneNumber + ".png"; final StorageReference storageReference = ChitChatApplication.getStorageManagement().getStorageReference("chitchatapp/profile_pic/" + name); userProfileImage.uploadFile(storageReference, phoneNumber + ".png", path, (errorMessage, e) -> AppLog.logE("ProfileFragment", "filePAth--->Error" + e)); }

5. 检查上传结果。

集成推送服务

推送服务搭建云端和设备端之间的消息通道,为您处理消息的推送。集成推送服务后,您的应用可以实时向用户推送消息,这有助于您和您的用户维持更紧密的联系,并帮助您提升用户活跃度与粘度。使用推送服务推送消息流程如下图所示。

1. 登录AppGallery Connect并开通推送服务。

2. 添加依赖。

添加如下依赖:

dependencies { implementation 'com.huawei.hms:push:<lastest_push_kit_version≶' }

说明:请集成最新版本的kit,示例集成的是5.1.1.301版本。

3. 配置Manifest文件。

在AndroidManifest.xml文件的appliction中注册自己的"service"来接收透传消息和获取相关Token。该"service"需继承HmsMessageService类并重写其中的方法。此处以类名为DemoHmsMessageService的"service"为例(类名由您自定义)。Android 11版本修改了用于查询和交互用户设备上其他应用的方法。如果targetSdkVersion大于或等于30,在AndroidManifest.xml文件中的manifest元素下添加queries元素来让您的应用访问HMS Core(APK)。

4. 在appliction中接收Token。

获取Token:
在AndroidManifest.xml文件中的appliction下添加meta-data元素。


    <meta-data 
    android:name="push_kit_auto_init_enabled"
    android:value="true" 
    />    
          

推送服务SDK的版本要求至少为4.0.0.300。应用启动后,推送服务获取Token。应用只接收token.name信息。请勿更改meta-data元素下value的值。

接收Token:
下图所示的ChitChatPushService类继承了HmsMessageService类,并重写了onNewToken(String token, Bundle bundle)方法来获取Token。
如果继承的推送服务SDK的版本低于5.0.4.302,需重写onNewToken(String token)方法。

public class ChitChatPushService extends HmsMessageService { private static final String TAG = ChitChatPushService.class.getSimpleName(); private static final String CHANNEL1 = "Push_Channel_01"; private Data data = null; private Bitmap mBitmap; @Override public void onNewToken(String s) { super.onNewToken(s); } }

在Android 10及以上版本,您也可以使用下方的代码库来接收Token。在activity中可以调用AsyncTask。

public class GetToken extends AsyncTask { private static final String TAG = GetToken.class.getSimpleName(); @SuppressLint("StaticFieldLeak") private Context context; private String appId; private GetTokenListener getTokenListener; public GetToken(String appId, Context context) { this.appId = appId; this.context = context; } public void setGetTokenListener(GetTokenListener getTokenListener) { this.getTokenListener = getTokenListener; } @Override protected void onPreExecute() { super.onPreExecute(); } @Override protected String doInBackground(Void... voids) { try { String pushToken = HmsInstanceId.getInstance(context).getToken(appId, HmsMessaging.DEFAULT_TOKEN_SCOPE); AppLog.logD(TAG, pushToken); getTokenListener.getToken(pushToken); return pushToken; } catch (ApiException e) { AppLog.logE(TAG,e.getMessage()); } return null; } @Override protected void onPostExecute(String s) { super.onPostExecute(s); } }

5. 在appliction中接收通知。

接收到Token后您可以采用如下代码处理消息通知和图片通知:

public class ChitChatPushService extends HmsMessageService { private static final String TAG = ChitChatPushService.class.getSimpleName(); private static final String CHANNEL1 = "Push_Channel_01"; private Data data = null; private Bitmap mBitmap; @Override public void onNewToken(String s) { super.onNewToken(s); } @Override public void onMessageReceived(RemoteMessage remoteMessage) { super.onMessageReceived(remoteMessage); AppLog.logE(TAG, remoteMessage.getData()); try { JSONObject jsonObject = new JSONObject(remoteMessage.getData()); data = new Gson().fromJson(jsonObject.toString(), Data.class); } catch (JSONException e) { AppLog.logE(TAG,e.getMessage()); } NotificationManager mNotificationManager = (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { int importance = NotificationManager.IMPORTANCE_HIGH; NotificationChannel mChannel = new NotificationChannel( CHANNEL1, CHANNEL1, importance); if (mNotificationManager != null) { mNotificationManager.createNotificationChannel(mChannel); if (Objects.requireNonNull(remoteMessage.getDataOfMap().get("message")).equalsIgnoreCase(Constants.MESSAGE_TYPE_TEXT)) { Intent notificationIntent = new Intent(getApplicationContext(), MessageActivity.class); notificationIntent.putExtra("roomId", remoteMessage.getDataOfMap().get("roomId")); notificationIntent.putExtra("name",remoteMessage.getDataOfMap().get("sender_name")); notificationIntent.putExtra("phone",remoteMessage.getDataOfMap().get("sender_phone")); notificationIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP); PendingIntent intent = PendingIntent.getActivity(getApplicationContext(), 0, notificationIntent, 0); NotificationCompat.Builder mBuilder; mBuilder = new NotificationCompat.Builder(getApplicationContext(), CHANNEL1) .setSmallIcon(R.drawable.profile) .setContentTitle(remoteMessage.getDataOfMap().get("sender_name")) .setContentText(remoteMessage.getDataOfMap().get("message_data")); mBuilder.setContentIntent(intent); mBuilder.setAutoCancel(true); mNotificationManager.notify(0, mBuilder.build()); } else if (Objects.requireNonNull(remoteMessage.getDataOfMap().get("message")).equalsIgnoreCase(Constants.MESSAGE_TYPE_IMAGE)) { new GeneratePictureStyleNotification(this, data).execute(); } else if (Objects.requireNonNull(remoteMessage.getDataOfMap().get("message")).equalsIgnoreCase(Constants.MESSAGE_TYPE_MAP)) { Intent notificationIntent = new Intent(getApplicationContext(), MessageActivity.class); Bundle passBundle = new Bundle(); passBundle.putString("roomId", remoteMessage.getDataOfMap().get("roomId")); PendingIntent intent = PendingIntent.getActivity(getApplicationContext(), 0, notificationIntent, 0); NotificationCompat.Builder mBuilder; mBuilder = new NotificationCompat.Builder(getApplicationContext(), CHANNEL1) .setSmallIcon(R.drawable.profile) .setContentTitle(remoteMessage.getDataOfMap().get("sender_name")) .setContentText(Constants.LOCATION_DATA); mBuilder.setContentIntent(intent); mBuilder.setAutoCancel(true); mNotificationManager.notify(0, mBuilder.build()); } } } } private class GeneratePictureStyleNotification extends AsyncTask<String, Void, Bitmap> { Context mContext; private Data data; public GeneratePictureStyleNotification(Context context, Data data) { super(); this.mContext = context; this.data = data; } @Override protected Bitmap doInBackground(String... params) { Glide.with(mContext) .asBitmap() .load(data.messageData) .into(new CustomTarget<Bitmap>() { @Override public void onResourceReady(@NonNull Bitmap resource, @Nullable Transition transition) { mBitmap = resource; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { showNotification(data); } } @Override public void onLoadCleared(@Nullable Drawable placeholder) { } }); return null; } @Override protected void onPostExecute(Bitmap result) { super.onPostExecute(result); } } private void showNotification(Data data) { NotificationManager mNotificationManager = (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE); int notificationId = 1; // String channelName = CHANNEL1; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { int importance = NotificationManager.IMPORTANCE_HIGH; NotificationChannel mChannel = new NotificationChannel( CHANNEL1, CHANNEL1, importance); if (mNotificationManager != null) { mNotificationManager.createNotificationChannel(mChannel); Intent notificationIntent = new Intent(getApplicationContext(), MessageActivity.class); Bundle passValue = new Bundle(); passValue.putString("roomId", data.getRoomId()); notificationIntent.setAction("android.intent.action.MAIN"); notificationIntent.addCategory("android.intent.category.LAUNCHER"); notificationIntent.putExtras(passValue); notificationIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP); PendingIntent intent = PendingIntent.getActivity(getApplicationContext(), 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT); NotificationCompat.Builder mBuilder; if (mBitmap != null) { NotificationCompat.BigPictureStyle style = new NotificationCompat.BigPictureStyle() .bigPicture(mBitmap); mBuilder = new NotificationCompat.Builder(getApplicationContext(), CHANNEL1) .setSmallIcon(R.mipmap.ic_launcher) .setContentTitle(getResources().getString(R.string.app_name)) .setLargeIcon(mBitmap) .setStyle(style) .setContentText(data.getMessageData()); } else { mBuilder = new NotificationCompat.Builder(getApplicationContext(), CHANNEL1) .setSmallIcon(R.mipmap.ic_launcher) .setContentTitle(getResources().getString(R.string.app_name)) .setContentText(data.getMessageData()); } mBuilder.setContentIntent(intent); mBuilder.setAutoCancel(true); mNotificationManager.notify(notificationId, mBuilder.build()); } } else { Intent notificationIntent = new Intent(getApplicationContext(), MessageActivity.class); Bundle passValue = new Bundle(); passValue.putString("roomId", data.getRoomId()); notificationIntent.setAction("android.intent.action.MAIN"); notificationIntent.addCategory("android.intent.category.LAUNCHER"); notificationIntent.putExtras(passValue); notificationIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP); PendingIntent intent = PendingIntent.getActivity(getApplicationContext(), 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT); NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this); Uri soundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION); if (mBitmap != null) { NotificationCompat.BigPictureStyle style = new NotificationCompat.BigPictureStyle() .bigPicture(mBitmap); mBuilder.setSmallIcon(R.mipmap.ic_launcher).setContentTitle(getString(R.string.app_name)) .setStyle(style) .setLargeIcon(mBitmap).setAutoCancel(true).setSound(soundUri); } else { mBuilder.setSmallIcon(R.mipmap.ic_launcher).setContentTitle(getString(R.string.app_name)) .setStyle(new NotificationCompat.BigTextStyle() .bigText(data.message)).setContentText(data.getMessage()).setAutoCancel(true).setSound(soundUri); } mBuilder.setAutoCancel(true); NotificationCompat.InboxStyle inBoxStyle = new NotificationCompat.InboxStyle(); inBoxStyle.setBigContentTitle(getString(R.string.app_name)); mBuilder.setContentIntent(intent); mBuilder.setStyle(inBoxStyle); Notification notification = mBuilder.build(); notification.flags |= Notification.FLAG_AUTO_CANCEL; mNotificationManager.notify(notificationId, notification); } } }

6. 推送通知给安卓用户。

采用如下代码实现通知的推送:

public class PushApis { private Context context; private static final String TAG = PushApis.class.getSimpleName(); public PushApis(Context context) { this.context = context; } public void sendPushNotification(String chatId, String message, String appId, String messageData, String userPushTokens) { try { StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build(); StrictMode.setThreadPolicy(policy); String response = ""; URL url = new URL(Constants.TOKEN_URL); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.setDoOutput(true); connection.setInstanceFollowRedirects(false); connection.setRequestMethod("POST"); connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); connection.setRequestProperty("POST", "/oauth2/v3/token HTTP/1.1"); connection.setRequestProperty("Host", "oauth-login.cloud.huawei.com"); HashMap<String, String> params = new HashMap<>(); params.put("grant_type", "client_credentials"); params.put("client_secret", Constants.CLIENT_SECRET); params.put("client_id", Constants.CLIENT_ID); String postDataLength = getDataString(params); OutputStream os = connection.getOutputStream(); BufferedWriter writer = new BufferedWriter( new OutputStreamWriter(os, "UTF-8")); writer.write(postDataLength); writer.flush(); writer.close(); os.close(); int responseCode = connection.getResponseCode(); if (responseCode == HttpsURLConnection.HTTP_OK) { String line; BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream())); while ((line = br.readLine()) != null) { response += line; } } else { response = ""; } AppLog.logE("Response", response); Gson gson = new Gson(); BearerRequest bearerRequest = gson.fromJson(response, BearerRequest.class); triggerPush(bearerRequest.getAccessToken(), appId, chatId, message, messageData, userPushTokens); } catch (Exception e) { AppLog.logE(TAG, e.getMessage()); } } private String getDataString(HashMap<String, String> params) throws UnsupportedEncodingException { StringBuilder result = new StringBuilder(); boolean first = true; for (Map.Entry<String, String> entry : params.entrySet()) { if (first) { first = false; } else { result.append("&"); } result.append(URLEncoder.encode(entry.getKey(), "UTF-8")); result.append("="); result.append(URLEncoder.encode(entry.getValue(), "UTF-8")); } return result.toString(); } private void triggerPush(String bearer, String appId, String chatId, String messageType, String messageData, String userPushTokens) { try { StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build(); StrictMode.setThreadPolicy(policy); String response = null; URL url = new URL("https://push-api.cloud.huawei.com/v1/" + appId + "/messages:send"); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.setDoOutput(true); connection.setInstanceFollowRedirects(false); connection.setRequestMethod("POST"); connection.setDoOutput(true); connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); connection.setRequestProperty("Authorization", "Bearer " + bearer); connection.setRequestProperty("Host", "oauth-login.cloud.huawei.com"); connection.setRequestProperty("POST", "/oauth2/v2/token HTTP/1.1"); OutputStream os = connection.getOutputStream(); Data data = new Data(); data.message = messageType; data.roomId = chatId; data.messageData = messageData; data.sender_name = "xxxx xxxx"; data.sender_phone = "xxxxxxxx"; data.title = context.getResources().getString(R.string.app_name); ArrayList<String> token = new ArrayList><(); token.add(userPushTokens); Message message = new Message(); message.tokens = token; message.data = data.toString(); PushMessageRequest pushMessageRequest = new PushMessageRequest(); pushMessageRequest.message = message; pushMessageRequest.validate_only = false; BufferedWriter writer = new BufferedWriter( new OutputStreamWriter(os, "UTF-8")); Gson gson = new Gson(); JSONObject jsonObject = new JSONObject(gson.toJson(pushMessageRequest, PushMessageRequest.class)); writer.write(jsonObject.toString()); writer.flush(); writer.close(); os.close(); int responseCode = connection.getResponseCode(); String line = null; BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream())); while ((line = br.readLine()) != null) { response += line; } AppLog.logE(TAG, response); } catch (Exception e) { AppLog.logE(TAG, e.getMessage()); } } }

输出效果

集成定位服务

1. 添加依赖。

添加如下依赖:

dependencies { implementation 'com.huawei.hms:location:<latest_version_code>' }

说明: 请集成最新版本的kit,示例集成的是6.0.0.302版本。

2. 配置Manifest文件。

在Manifest文件中添加如下权限:


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

添加运行时权限:

if (checkLocationPermission()) { Intent intent = new Intent(MessageActivity.this, LocationActivity.class); launcherForLocation.launch(intent); } else { requestPermissionForLocation(); }

添加checkLocationPermission()所需的检查权限和requestPermissionforLocation()所需的请求权限:
checkLocationPermission():

private boolean checkLocationPermission() { int locationPermission = ContextCompat.checkSelfPermission(this.getApplicationContext(), Manifest.permission.ACCESS_FINE_LOCATION); int coursePermission = ContextCompat.checkSelfPermission(this.getApplicationContext(), Manifest.permission.ACCESS_COARSE_LOCATION); return locationPermission == PackageManager.PERMISSION_GRANTED && coursePermission == PackageManager.PERMISSION_GRANTED; }

requestPermissionForLocation():

private void requestPermissionForLocation() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { requestPermissions(new String[]{Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION }, Constants.LOCATION_PERMISSION); } }

}

处理权限:

@Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); switch (requestCode) { case Constants.LOCATION_PERMISSION: if (grantResults.length > 0) { boolean fineLoc = grantResults[0] == PackageManager.PERMISSION_GRANTED; boolean coarseLoc = grantResults[1] == PackageManager.PERMISSION_GRANTED; if (fineLoc && coarseLoc) { Intent intent = new Intent(MessageActivity.this, LocationActivity.class); launcherForLocation.launch(intent); } else { Toast.makeText(this, getResources().getString(R.string.allow_location_permission), Toast.LENGTH_SHORT).show(); } } } }

3. 添加如下代码库来接收位置。

public void getCurrentLocation(Context context) { FusedLocationProviderClient mFusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(context); SettingsClient mSettingsClient = LocationServices.getSettingsClient(context); LocationRequest mLocationRequest = new LocationRequest(); mLocationRequest.setInterval(5000); mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY); mFusedLocationProviderClient.getLastLocation().addOnSuccessListener(location -> { AppLog.logD(TAG, "Lat long--->Fushed" + location.getLongitude() + "," + location.getLatitude() + "," + location.getAccuracy()); if (location != null) { locationMutableLiveData.postValue(location); } }).addOnFailureListener(e -> { AppLog.logE(TAG, "error" + e.getMessage()); }); }

输出效果

地图上的蓝色标记表示当前位置。

集成地图服务

地图服务SDK提供了诸多用于开发安卓应用地图功能的接口,涵盖中国大陆以外大部分国家或地区的地图信息,并支持多语言功能。地图服务采用WGS84 GPS坐标系,满足绝大多数中国大陆以外地图开发的需求。您可以轻松地为您的安卓应用接入地图相关功能。

1. 登录AppGallery Connect并开通地图服务。

登录AppGallery Connect,点击"我的项目"。
在项目列表中选择您的项目,在项目中点击需要开通地图服务的应用。

2. 添加依赖。

添加如下依赖:

dependencies { implementation 'com.huawei.hms:maps:<map_service>' }

说明: 请集成最新版本的kit,示例集成的是6.0.0.31版本。

3. 集成地图服务。

在Manifest文件中声明如下权限:

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

在布局资源中添加Mapview:

<com.huawei.hms.maps.MapView xmlns:map="http://schemas.android.com/apk/res-auto" android:id="@+id/mapView" android:layout_width="match_parent" android:layout_height="match_parent" map:cameraTargetLat="51" map:cameraTargetLng="10" map:cameraZoom="8.5" map:mapType="normal" map:uiCompass="true" map:uiZoomControls="true" />

在java文件中实现如下代码:

@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_location); stMapView(savedInstanceState); } private void stMapView(Bundle savedInstanceState) { Bundle mapViewBundle = null; if (savedInstanceState != null) { mapViewBundle = savedInstanceState.getBundle(Constants.MAP_VIEW_BUNDLE_KEY); } MapsInitializer.setApiKey(Constants.MAP_VIEW_API_KEY); mMapView.onCreate(mapViewBundle); mMapView.getMapAsync(this); } @Override public void onMapReady(HuaweiMap huaweiMap) { hMap = huaweiMap; hMap.setMyLocationEnabled(true); hMap.getUiSettings().setMyLocationButtonEnabled(true); }

输出效果

集成位置服务

位置服务SDK提供更加便利的位置查询等服务,帮助您的应用吸引更多的用户。通过集成Site Kit的核心能力,您可以快速构建基于位置服务的产品,在以下场景中满足用户对探索周边地点的需求:

本次Codelab中将要用到周边搜索功能。

1. 添加依赖。

添加如下依赖:

dependencies { implementation 'com.huawei.hms:site:<site_kit_lastest_code>' }

2. 开通位置服务。

3. 集成搜索服务。

创建一个搜索对象:

private SearchService searchService; try { searchService = SearchServiceFactory.create(this, URLEncoder.encode(Constants.MAP_VIEW_API_KEY, "utf-8")); } catch (UnsupportedEncodingException e) { AppLog.logE(TAG, "encode apikey error"); }

创建一个用于获取所有附近区域的函数:

public void getNearbyData(double latitude, double longitude, SearchService searchService, String locationType) { NearbySearchRequest request = new NearbySearchRequest(); Coordinate location = new Coordinate(latitude, longitude); request.setLocation(location); request.setQuery(locationType); request.setRadius(5); request.setHwPoiType(HwLocationType.ADDRESS); request.setLanguage("en"); request.setPageIndex(1); request.setPageSize(10); request.setStrictBounds(false); SearchResultListener<NearbySearchResponse> resultListener = new SearchResultListener<NearbySearchResponse>() { @Override public void onSearchResult(NearbySearchResponse results) { arrayListMutableLiveData.postValue(new ArrayList<>(results.getSites())); } @Override public void onSearchError(SearchStatus status) { AppLog.logE("TAG", "Error : " + status.getErrorCode() + " " + status.getErrorMessage()); } }; searchService.nearbySearch(request, resultListener); }

}

在使用位置服务获取到位置后,在activity中获取LiveData:

private void updateDetails(Location location) { float zoom = 14.0f; LatLng latLng1 = new LatLng(location.getLatitude(), location.getLongitude()); CameraUpdate cameraUpdate = CameraUpdateFactory.newLatLngZoom(latLng1, zoom); hMap.animateCamera(cameraUpdate); currentLocationAccuracy.setText(String.format("Accurate to %s meters", location.getAccuracy())); Util.stopProgressBar(); }

}

输出效果

干得好,您已成功构建出一款聊天应用并学到了:

您可以通过下方链接,了解更多相关信息。

免责声明:本Codelab可用做单项目中集成多个HMS服务的参考。您需要验证并确认相关源码是否合法和安全。

本Codelab中的示例代码下载地址如下:
源码下载

Code copied