华为HMS开发零基础上手

本文是学习过程记录,这一篇主要包含HMS的配置、APPGallery连接和广告服务、机器学习服务等内容。

几个小技巧

对于红色警告的单词,鼠标放到单词字母中间,然后Alt+Enter一般可以自动导入。
对于数字参数,可以选中它,然后Ctrl+Shift+C,会把它变为一个大写的变量。
要把哪个Activ作为启动页,只要打开AndroidManifest.xml文件,把其中的<intent-filter>...部分剪切到目标Activity内即可,例如:

<activity android:name=".TextActivity">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

准备工作

HMS,即Huawei Mobile Service华为移动服务,是和谷歌的GMS(Google Mobile Service)对标的另外一套安卓服务框架。

安卓是开源的,但其上的服务框架GMS则是完全的谷歌产品。HMS是华为搭建自有终端软件生态系统的关键产品,也是未来鸿蒙系统生态的重要前提。

要在安卓APP开发中使用HMS的各种强大功能,首先需要注册成为火花纹开发者联盟开发者,并完成用户身份认证,推荐使用银行卡自动认证,基本上可以实时完成。

华为开发者联盟传送门

注册并认证

创建在线项目和应用

在开发APP之前,应该先在华为开发者联盟网站的【管理中心】创建一个项目,并在项目内创建APP应用。

华为开发者管理中心传送门

华为的应用商店叫做AppGallery,所以我们进入【AppGallery Connect(AGC)】来管理项目和应用。

管理中心

进入【我的项目】,添加项目,名称任意。然后【添加应用】,如下图所示。注意应用包名建议用.huawei,如果遇到包名已经被占用,建议包名结合用户名类似user.demo.huawei或demo.user.huawei。

添加应用

然后我们回到【我的项目】首页,进入这个新建的项目,可以看到这里有开发者ID(Developer ID)和APP ID、API Key等信息。

进入【API管理】可以设定这个项目能够使用哪些HMS的服务功能。下面是一些常用服务的简介。

服务 | 说明
-|-|-
Analytics Kit | 分析服务,帮助开发者分析用户使用数据。
Auth Service | 第三方身份认证服务,比如苹果账号、微信账号、QQ账号等。
Remote Configuration | 远程配置服务,利用云端配置自动更改APP行为或外观。
App Linking | 跨应用的连接跳转,也帮助开发者跟踪用户连接点击行为。
APMS | 应用性能管理和监控。
App Messaging | 用于内信息,各种弹窗和提示。
Cloud Hosting | 云主机服务。
Cloud Storage | 云存储服务。
In-App Purchases | 应用内购买付费服务。
Account Kit | 华为账号服务,用华为账号登陆APP。
Game Service | 游戏服务,玩家成就、排行榜和存档。
Push Kit | 消息推送服务,向用户推送通知。
Wallet Kit | 钱包服务,卡、证、券、票、钥匙等各类凭证电子化。
Map Kit | 地图服务。
Drive Kit | 云空间服务。
ML Kit | 机器学习服务,涉及文字识别、图像识别、语音识别、人脸识别等。
Safety Detect | 安全检测服务,防病毒和恶意程序。
Site kit | 位置服务,基于地理定位的服务。
Nearby Service | 近距离通信服务,与附加的手机传数据。
FIDO | 线上快速身份验证服务,生物特征认证和快速线上身份验证。
Awareness Kit | 情景感知服务,包括位置、天气、用户状态、环境光等。

如果搞不清这个些服务的状况,那么可以先都打开。

配置开发项目

打开AndroidStudio新建项目。注意包名Package name要和在线的一致,如下图所示,注意不要有多余的空格,否则无法下一步。建议Minimum最小SDK不要故意选太低,建议20以上。

设置项目

1. agc配置文件

从网站【我的项目】找到对应项目,下载应用对应的agconnect-services.json文件,将AndroidStudio文件列表切换到Project模式,把这个json文件拖拽到app文件夹下,与src文件夹同一级别,弹窗move提示直接OK。

agc配置文件位置

然后在网站项目页面点击【添加SDK】,根据下面提示进行配置。

2. 项目级构建设置

然后再切换Project回到Android,找到【Gradle Scripts/build.gradle(Project…)】项目级构建配置,编辑添加两个maven仓库和一个classpath路径,完成后类似如下所示:

buildscript {
    repositories {
        google()
        jcenter()
        maven {url 'https://developer.huawei.com/repo/'}
    }
    dependencies {
        classpath "com.android.tools.build:gradle:4.0.1"
        classpath 'com.huawei.agconnect:agcp:1.3.1.300'
    }
}
allprojects {
    repositories {
        google()
        jcenter()
        maven {url 'https://developer.huawei.com/repo/'}
    }
}
task clean(type: Delete) {
    delete rootProject.buildDir
}

修改之后点击顶部弹出的Sync Now更新设置。也可以点击Sync with Gradle Files按钮,如下图所示。

更新设置

3. 模块级构建设置

然后找到找到【Gradle Scripts/build.gradle(Module…)】模块级构建配置,顶部添加一个app plugin插件,dependencies中添加一个implementation依赖,代码如下所示:

apply plugin: 'com.android.application'
apply plugin: 'com.huawei.agconnect'
android {
    compileSdkVersion 30
    buildToolsVersion "30.0.0"

    defaultConfig {
        ...
    }

    buildTypes {
        ...
    }
}
dependencies {
    ...
    implementation 'com.huawei.agconnect:agconnect-core:1.3.1.300'
}

修改之后点击顶部弹出的Sync Now更新设置。

添加广告服务

华为广告服务HUAWEI Ads可以让我们直接在APP页面内放入一个广告条,这个广告条内容是华为提供的,如果APP用户大量观看这个广告,华为就会向开发者支付一定的广告酬劳。

几乎所有的应用或游戏都可以放入广告条,广告条形式有很多种,常用的有banner广告位和应用启动画面广告位(开屏广告)。

华为广告服务首页

从广告服务首页点【查看文档】进入文档页面。然后左侧浏览到【广告服务/流量变现服务/应用开发/集成HMS Core SDK】,参照下面提示进行配置。

1. Project模式添加json配置

参见上面内容,在project模式下,确保agconnect-services.json文件被放到了src同级目录。

2 项目级build添加2个仓库和1个路径

参见上面内容,在【Gradle Scripts/build.gradle(Project…)】中:

  • 检查两处仓库maven {url 'https://developer.huawei.com/repo/'}是否有添加(jcenter()下面)。
  • 检查classpath 'com.huawei.agconnect:agcp:1.3.1.300'是否正确添加。

3. 模块级build添加2个实现和1个插件

参见上面内容,在【Gradle Scripts/build.gradle(Module…)】中:

  • 检查dependencies中是否添加implementation 'com.huawei.agconnect:agconnect-core:1.3.1.300'
  • 添加新的implementation 'com.huawei.hms:ads-lite:13.4.33.300'
  • 检查是否添加apply plugin: 'com.huawei.agconnect'

4. 创建AdSampleApplication类

在应用目录上右击,创建java类class,命名为AdSampleApplication

新建类

然后编辑内容为下面内容,注意第一行应该不同。

package hms.demo02.zhyuzh;
import android.app.Application;
import com.huawei.hms.ads.HwAds;
public class AdSampleApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        HwAds.init(this);
    }
}

同时修改目录文件【mainifests/AndroidManifests.xml】,向<application...>中添加以下两行:

<application
    android:usesCleartextTraffic="true"
    android:name=".AdSampleApplication"
    ...
    >
    ...
</application>

Banner广告

我们要在MainActivity界面上显示一个华为广告图片。

1. 修改activity_main.xml

改为LinearLayout,添加一个BannerView,代码如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:hwads="http://schemas.android.com/apk/res-auto"
    tools:context=".MainActivity">
    <com.huawei.hms.ads.banner.BannerView
        android:id="@+id/hw_banner_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        hwads:adId="testw6vs28auh3"
        hwads:bannerSize="BANNER_SIZE_360_144"/>
</LinearLayout>

注意上面LinearLayout中新增的xmlns:hwads="http://schemas.android.co,没有它的话下面hwads会报错。

注意最后的BANNER_SIZE_360_144,如果是BANNER_SIZE_360_57可能就不显示图像。

2. 修改MainActivity.java

主要修改onCreate方法的内容,使用loadAd()来获取广告,修改后的代码主要代码如下:

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        BannerView bannerView = findViewById(R.id.hw_banner_view);
        bannerView.setAdId("testw6vs28auh3");
        bannerView.setBannerAdSize(BannerAdSize.BANNER_SIZE_360_57);
        AdParam adParam = new AdParam.Builder().build();
        bannerView.loadAd(adParam);
    }
}

最后,你需要有一台华为的手机,打开开发者模式,授权USB调试,并连接到AndroidStudio,安装调试才能看到华为广告出现在画面上。

非华为手机都不能正常显示这个广告内容。

文本识别

如何开发一个能够识别拍照照片中文字的APP?这要用到HMS的机器学习服务ML Kit。

从HMS文档页面浏览到【机器学习服务/Android/应用开发/开发准备/集成HMS Core SDK】页面,参照以下步骤进行。

1. 添加AGC配置文件

如前所述,确保agconnect-services.json被正常下载和添加。

2. 配置Maven仓库

如前所述,确保在项目级构建设置【Gradle Scripts/build.gradle(Project…)】中的两个jcenter()后面添加了新的仓库地址。

3. 添加依赖实现

往下层浏览【集成HMS Core SDK/添加编译依赖/集成文本识别服务SDK】,参照下面说明继续。

如前所述,在模块级构建设置【Gradle Scripts/build.gradle(Module…)】
中添加几行新代码:

  implementation 'com.huawei.hms:ml-computer-vision-ocr:2.0.1.300'
  implementation 'com.huawei.hms:ml-computer-vision-ocr-latin-model:2.0.1.300'
  implementation 'com.huawei.hms:ml-computer-vision-ocr-jk-model:2.0.1.300'
  implementation 'com.huawei.hms:ml-computer-vision-ocr-cn-model:2.0.1.300'

这里包含了拉丁日韩中英文识别工具包。

别忘了检查插件行apply plugin: 'com.huawei.agconnect'被添加在结尾。

4. 云端识别开发

切换页面到【应用开发/文本类服务开发/文本识别】页面,参照下面内容继续。

HMS的文本识别基本步骤是:

  • 创建一个分析器MLTextAnalyzer,并设置分析器参数
  • 创建一个机器学习框架MLFrame
  • MLFrame传递给分析器MLTextAnalyzer进行识别,利用onSuccess回调执行操作
  • 识别完成后停止分析器,释放资源。

我们创建一个新的Activity,把这个逻辑写入java代码。由于MLFrame需要使用到位图进行识别,所以我们利用照相机获取拍摄的图片。

界面xml部分:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical"
    tools:context=".textActivity">
    <TextView
        android:id="@+id/text"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:hint="请先拍照"
        android:minHeight="100dp" />
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <Button
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_height="100dp"
            android:onClick="shot"
            android:text="SHOT!" />
        <Button
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_height="100dp"
            android:onClick="recog"
            android:text="RECOG!"
            />
    </LinearLayout>
</LinearLayout>

逻辑代码java部分:

public class textActivity extends AppCompatActivity {
    public static final int REQUEST_CODE = 5;
    Bitmap bitmap=null;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_text);
    }
    public void shot(View view) {
        Intent intent=new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        startActivityForResult(intent, REQUEST_CODE);
    }
    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if(requestCode==REQUEST_CODE && resultCode==RESULT_OK){
            assert data != null;
            bitmap=(Bitmap) Objects.requireNonNull(data.getExtras()).get("data");
            ImageView imageView=findViewById(R.id.image_shot);
            imageView.setImageBitmap(bitmap);
        }
    }
    public void read2(View view) {
        final MLTextAnalyzer analyzer = MLAnalyzerFactory.getInstance().getRemoteTextAnalyzer();
        MLFrame frame = MLFrame.fromBitmap(bitmap);
        Task<MLText> task = analyzer.asyncAnalyseFrame(frame);
        task.addOnSuccessListener(new OnSuccessListener<MLText>() {
            @Override
            public void onSuccess(MLText text) {
                TextView textView=findViewById(R.id.text_Recog);
                textView.setText(text.getStringValue());
                Toast.makeText(textActivity.this, text.getStringValue(), Toast.LENGTH_LONG).show();
                try {
                    analyzer.stop();
                } catch (IOException e) {
                    Toast.makeText(textActivity.this, e.getMessage(), Toast.LENGTH_LONG).show();
                }
            }
        });
    }
}

运行后点击SHOT按钮拍照,照片会显示在画面上;然后点击RECOG按钮进行识别。

非华为手机都不能正常运行这个内容。

实时语音识别

如何利用HMS服务实现实时的语音转文字?仍然是机器学习服务的功能。

在文档中浏览到【应用开发/语音语言类服务开发/实时语音识别/开发步骤-实时语音识别(有界面)】,参照下面提示进行。

我们仍然先创建新的Activity用来测试。

1. 集成语音服务

在【Gradle Scripts/build.gradle(Module…)】中添加两个依赖。

implementation 'com.huawei.hms:ml-computer-voice-asr-plugin:2.0.3.300'
implementation 'com.huawei.hms:ml-computer-voice-asr:2.0.3.300'

2. 添加授权

因为语音识别要用到很多权限,所以要在AndroidManifest.xml中添加授权,如果分不清的话就把下面的授权都添加。详细说明地址在【应用开发/开发准备/制定权限】中有详细说明。

<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />

但只在这里授权还不够,后面还要使用动态授权。

3. 界面代码

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:gravity="center"
    tools:context=".LangActivity">
    <TextView
        android:id="@+id/text_lang"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:hint="请先拍照"
        android:minHeight="200dp" />
    <Button
        android:layout_width="200dp"
        android:layout_height="100dp"
        android:text="GO"
        android:onClick="go"/>
</LinearLayout>

4. 逻辑代码

public class LangActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_lang);
        MLApplication.getInstance().setApiKey("CgB6e3x9gLm4...aaxYEnxvjg9");
        ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.RECORD_AUDIO}, 1);
    }
    public void go(View view) {
        // 通过intent进行识别设置。
        Intent intent = new Intent(this, MLAsrCaptureActivity.class)
                .putExtra(MLAsrCaptureConstants.LANGUAGE, "en-US")
                .putExtra(MLAsrCaptureConstants.FEATURE, MLAsrCaptureConstants.FEATURE_WORDFLUX);
        startActivityForResult(intent, 100);
    }
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        String text = "";
        if (requestCode == 100) {
            switch (resultCode) {
                case MLAsrCaptureConstants.ASR_SUCCESS:
                    if (data != null) {
                        Bundle bundle = data.getExtras();
                        if (bundle != null && bundle.containsKey(MLAsrCaptureConstants.ASR_RESULT)) {
                            text = bundle.getString(MLAsrCaptureConstants.ASR_RESULT);
                            TextView textView=findViewById(R.id.text_lang);
                            textView.setText(text);
                        }
                    }
                    break;
                case MLAsrCaptureConstants.ASR_FAILURE:
                    if(data != null) {
                        String msg="";
                        Bundle bundle = data.getExtras();
                        if(bundle != null && bundle.containsKey(MLAsrCaptureConstants.ASR_ERROR_CODE)) {
                            int errorCode = bundle.getInt(MLAsrCaptureConstants.ASR_ERROR_CODE);
                        }
                        if(bundle != null && bundle.containsKey(MLAsrCaptureConstants.ASR_ERROR_MESSAGE)){
                            String errorMsg = bundle.getString(MLAsrCaptureConstants.ASR_ERROR_MESSAGE);
                            msg=errorMsg;
                        }
                        if(bundle != null && bundle.containsKey(MLAsrCaptureConstants.ASR_SUB_ERROR_CODE)) {
                            int subErrorCode = bundle.getInt(MLAsrCaptureConstants.ASR_SUB_ERROR_CODE);
                        }
                        Toast.makeText(LangActivity.this,msg,Toast.LENGTH_LONG).show();
                    }
                default:
                    break;
            }
        }
    }
}

注意这里的ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.RECORD_AUDIO}, 1);是动态添加录音权限。MLApplication.getInstance().setApiKey("CgB6e3x9gLm4...aaxYEnxvjg9");是添加ApiKey验证。
运行后点击GO按钮启动语音识别。

非华为手机都不能正常运行这个内容。

内容汇总

使用HMS配置很麻烦,既要遵循官方文档,又要随时解决各种奇怪问题,而且几乎所有功能都是必须在华为手机上才能进行测试。一般要注意下面几点:

  • 本地项目的包名要和线上应用完全一致。
  • 要下载AGC配置文件,放到project模式app根目录。
  • 最好先设置好AGC的几个配置,再配置一遍具体服务。
  • 要修改项目级构建文件,添加classpath和2个maven仓库。
  • 要修改模块级构建文件,添加plugin行,添加各种dependencies。
  • 可能需要给Mainifest配置中添加用户权限,还可能要在每个Activity里重复动态添加权限。
  • 可能需要在Activity里面添加API key信息。

未完待续。
欢迎批评指正,交流学习。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,529评论 5 475
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,015评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,409评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,385评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,387评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,466评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,880评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,528评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,727评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,528评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,602评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,302评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,873评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,890评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,132评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,777评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,310评论 2 342