HOW TO INTEGRATE YOUR ANDROID PROJECT IN HUAWEI PUSH KIT SERVICE FOR SENDING PUSH NOTIFICATIONS | by Işılsu ÇİTİM | Huawei Developers | Medium

ed Новости

Почему стоит обратить внимание на экосистему huawei

Смартфоны Huawei очень популярны: в 2020 году в России они занимали почти 18% рынка (Рис.1), а в мире — 11% (Рис.2), (

). Huawei заявила, что более 490 млн человек в более чем в 170 странах мира пользуются AppGallery (

). Поскольку аудитория у Huawei-устройств огромная, мы не можем это игнорировать и решили поддержать пользователей нашего приложения. Далее поэтапно рассмотрим, что же нужно сделать.

HOW TO INTEGRATE YOUR ANDROID PROJECT IN HUAWEI PUSH KIT SERVICE FOR SENDING PUSH NOTIFICATIONS | by Işılsu ÇİTİM | Huawei Developers | Medium
Рис.1HOW TO INTEGRATE YOUR ANDROID PROJECT IN HUAWEI PUSH KIT SERVICE FOR SENDING PUSH NOTIFICATIONS | by Işılsu ÇİTİM | Huawei Developers | Medium
Рис.2

Configuring App Information in AppGallery Connect

Before you start developing an app, configure app information in AppGallery Connect.

1 Registering as a Developer

Before you get started, you must register as a HUAWEI developer and complete identity verification on the HUAWEI Developer website.

2 Creating an App

Create an app by referring to Creating an App in AppGallery Connect help center and upload a temporary APK. Set the following parameters as described:

· Package type: APK (Android app) or RPK (quick app)

· Device: Mobile phone

· App category: App or Game

– AppGallery Connect reads the temporary APK to obtain the APK name. Therefore, the temporary APK name must be the same as the released APK name.

3 Generating a Signing Certificate Fingerprint

A signing certificate fingerprint is used to verify the authenticity of an app when the app attempts to access HMS Core (APK) through the HMS SDK. Before using HMS Core (APK), you must locally generate a signing certificate fingerprint and configure it in AppGallery Connect.

Please ensure that the following conditions are acquired:

· The JDK has been installed on your PC.

Perform the following steps:

From Gradle on the right section open

My Application -> Tasks -> android -> signingInReport

double click signingInReport and copy SHA-256 fingerprint from console below.

4 Configuring the Signing Certificate Fingerprint

1. Sign in to AppGallery Connect and select My apps.

2. Find your app in the list and click the app name.

3. Go to Develop > Overview > App information and set SHA-256 certificate fingerprint to the SHA-256 fingerprint that is generated in the preceding section of Generating a Signing Certificate Fingerprint.

4. After completing configuration, click . The configuration takes effect in about 15 minutes.

5 Setting Data Storage Location

To use HUAWEI Push Kit, you must set the data storage location. For details, please refer to Setting Data Storage Location.

If the data storage location is not set, the following functions will be unavailable: topic-based messaging, messaging through the WebPush agent, and messaging to iOS apps.

6 Enabling Required Services

· Enable HUAWEI Push Kit.

· Set HUAWEI Push Kit parameters.

Configuring Obfuscation Scripts

Configure obfuscation scripts before compiling the APK to prevent the HMS SDK from being obfuscated. If obfuscation arises, the HMS SDK may not function properly.

The configuration file in Android Studio is proguard-rules.pro, and that in Eclipse is proguard-project.txt.

1. Open the obfuscation script file of your project.

2. Add configuration to exclude the HMS SDK from obfuscation.

3. If you have used AndResGuard, add it to the whitelist in the obfuscation script file.

Here is a link for the referance HMS Push Kit Library in my github account if you would like to check out.

That’s it for this article. Hopefully that will help you understand push kit technology better. See you in the next article soon. 🙂

Create a channel

Create the push channel to be used by Tuya IoT App SDK on Android Oreo. Example:

publicstaticfinal String[] channelIds = {
  "tuya_common",
  "tuya_shortbell",
  "tuya_longbell",
  "tuya_doorbell"
};
publicstaticfinal String[] channelNames = {
  context.getString(R.string.push_channel_common),
  context.getString(R.string.push_channel_shortbell),
  context.getString(R.string.push_channel_longbell),
  context.getString(R.string.push_channel_doorbell)
};
publicstaticfinal String[] channelSounds = {
  "android.resource://"   context.getPackageName()   "/"   R.raw.tuya_common,
  "android.resource://"   context.getPackageName()   "/"   R.raw.tuya_shortbell,
  "android.resource://"   context.getPackageName()   "/"   R.raw.tuya_longbell,
  "android.resource://"   context.getPackageName()   "/"   R.raw.tuya_doorbell
};

for (int i = 0; i < TuyaPushChnnels.channelIds.length; i  ) {
  createNotificationChannel(channelIds[i], channelNames[i], importance, soundPath);
}
@TargetApi(Build.VERSION_CODES.O)privatestaticvoidcreateNotificationChannel(String channelId, String channelName, int importance, String soundPath){
    NotificationChannel channel = new NotificationChannel(channelId, channelName, importance);
    channel.setSound(Uri.parse(soundPath), Notification.AUDIO_ATTRIBUTES_DEFAULT);
    channel.setVibrationPattern(newlong[]{
            300, 1000, 300, 1000
    });
    channel.canBypassDnd();
    channel.setBypassDnd(true);
    channel.setLockscreenVisibility(VISIBILITY_SECRET);
    NotificationManager notificationManager = (NotificationManager) MicroContext.getApplication().getSystemService(
            NOTIFICATION_SERVICE);
    notificationManager.createNotificationChannel(channel);
}

Hardware requirements

A computer (desktop or laptop) and a Huawei mobile phone with a USB cable, to be used for service debugging.

Hms server client push. custom realization. auth client password mode.

  • auto auth token refresher
  • sending push through hms

Documentation – api HMS pushSample code by huawei – push-servergosdk

How to use

package main

import (
	"context""net/http""time""github.com/nburmi/huawei-push/push""github.com/nburmi/huawei-push/token"
)

funcmain() {
	cli:=&http.Client{
		Timeout: time.Second,
	}

	hmsParams:=&token.Params{
		HTTPDoer:     cli,
		ClientID:     "Client ID(App ID)",
		ClientSecret: "Client Secret(App secret)",
	}
	
	tokener, err:=token.New().SetByParams(hmsParams).Build()
	iferr!=nil {
		//handle error
	}

	ctx:=context.Background()

	// updating the token before it expires. Clenttokener, err=token.NewRefresher(ctx, tokener).SetSubTime(time.Second*5).Build()
	iferr!=nil {
		//handle error
	}

	pusher:=push.New(hmsParams.ClientID, tokener, cli)

	//send pushresp, err:=pusher.Push(&push.Message{
		Data:   "data",
		Tokens: []string{"DEVICE TOKEN 1"},
	})

	iferr!=nil {
		//handle error
	}

	//check response by documentation https://developer.huawei.com/consumer/en/doc/development/HMS-References/push-sendapi/*	type Response struct {		StatusCode int    `json:"-"` //http status code		Code      string `json:"code"`		Message   string `json:"msg"`		RequestID string `json:"requestId"`	}	*/
}

License

Push kit sample code for android is licensed under the Apache License, version 2.0.

Polyutil. расшифровка с помощью polyline

Теперь нам нужно отображать перемещение курьера. Для этого нам нужно расшифровывать строку, которая приходит с сервера. Для этого воспользуемся тем же алгоритмом от Google, с помощью которого строка кодировалась.

После расшифровки мы получили список координат курьера.

Question or issues

If you want to evaluate more about HMS Core,
r/HMSCore on Reddit is for you to keep up with latest news about HMS Core, and to exchange insights with other developers.

If you have questions about how to use HMS samples, try the following options:

If you run into a bug in our samples, please submit an issue to the Repository. Even better you can submit a Pull Request with a fix.

Register pushtoken to the tuya iot cloud

After Huawei SDK is integrated and initialized, you can get the push token and register it to the Tuya IoT Cloud.

publicstaticfinal String PUSH_PROVIDER_HUAWEI = "huawei";
String pushtoken = HmsInstanceId.getInstance(activity).getToken(appId, "HCM");
if (!TextUtils.isEmpty(pushtoken)) {
    Log.i(TAG, "get token:"   pushtoken);
    TuyaHomeSdk.getPushInstance().registerDevice(pushtoken, PUSH_PROVIDER_HUAWEI, new IResultCallback() {
        @OverridepublicvoidonError(String code, String error){
            Log.e(TAG, "registerDevice error: "   code   "   "   error);
        }

        @OverridepublicvoidonSuccess(){
            Log.i(TAG, "register push token success");
        }
    });
}

Apply the following configurations to the activity of the splash page, such as the splash activity:

The smart device triggers push notifications and the message will be displayed on the notification bar.

Supported environment

Android SDK Version >= 23 and JDK version >= 1.8 is recommended.

Проблема: «карта не работает»

HOW TO INTEGRATE YOUR ANDROID PROJECT IN HUAWEI PUSH KIT SERVICE FOR SENDING PUSH NOTIFICATIONS | by Işılsu ÇİTİM | Huawei Developers | Medium

Однажды нам сообщили о баге. Пользователь с устройством Huawei, находившийся в центре Москвы (Рис.3), открыл приложение, нажал на кнопку «Переместиться на своё местоположение», и его перенесло в пустоту (Рис.4). Пользователь не видит, ни улиц, ни зданий, и он решил, что карта не работает.

Мы попробовали воспроизвести у себя эту проблему. И действительно попадали в неопределённое пространство. Когда попробовали чуть-чуть уменьшить масштаб карты, то оказалось, что мы попали в пригород Мариуполя (Рис.5). То есть из московских координат (55.819207, 37.493424) перенеслись в мариупольские (47.187447, 37.593137).

Мы были в полном недоумении. Может быть, где-то у нас с числами что-то не то происходит. Возможно, происходят некие вычитания наших координат. Очень долго искали решение этой проблемы или хотя бы причину. Оказалось, что мы заменили импорты из Google-карт, и поэтому всё перестало работать. В конце концов мы добрались до padding’а.

Давайте быстро вспомним, что такое padding у карты. На (Рис.6) показан экран авторизации, карта занимает всю область экрана, даже под плашкой ручного ввода адреса. В таком случае, если мы не добавим padding карте, её центр будет находиться на месте зелёного треугольника, но мы хотим, чтобы он был в центре рабочей области карты.

Padding сужает рабочую область (Рис.7). Не видимую, а именно рабочую. Карта будет по-прежнему занимать весь экран, но размер её рабочей области изменится. И когда вы будете переходить в новую координату, она будет принимать положение новой рабочей карты. Как оказалось, баг был именно из-за этого.

Первое решение: убрать padding. Как вы понимаете, такой вариант нам не подошёл. Мы хотели, чтобы всё отображалось красиво.

Второе решение проблемы: использовать анимированное перемещение, но с масштабированием.

val zoom = map.cameraPosition.zoom
map.animateCamera(CameraUpdateFactory.newLatLngZoom(position, zoom))

При переходе с изменением масштаба карты всё работало правильно. Здорово! Мы подумали, что это нам подходит. На самом деле нет. У нас ещё есть третий экран, на котором нужно увеличивать карту относительно двух маркеров, чтобы

zoom

сам рассчитывался, поэтому мы не можем задать какое-то константное масштабирование. То есть такой вариант нам тоже не подошёл. Начали думать дальше и нашли новое решение.

Третье решение проблемы: вообще отказаться от анимации. Как оказалось, если вместо animateCamera сделать просто move, то перемещение будет происходить правильно. Так мы и сделали. Надеемся, в скором времени Huawei устранит эту проблему.

Реализация поддержки двух карт

Для поддержки нескольких карт необходимо создать обёртку для самих карт и для объекта.

Добавляем общий интерфейс, например, IMapWidget. Не забываем сделать общий класс для LatLng — список координат курьера. У Google он лежит в пакете com.google.android.gms.maps.model.LatLng, а у Huawei в com.huawei.hms.maps.model.LatLng. Кладём список в PolyLineOptions и задаём ширину и цвет линии маршрута.

interface IMapWidget {

    void animateCamera(...);

    void setListener(OnMapEventListener listener);

    void setMapPadding(...);

    MapMarker addMarker(...);

    ...
}


Добавляем Custom Map View реализующего интерфейс

IMapWidget

Добавляем обёртку, которая позволяет нам указать, где мы хотим отрисовывать карту:

class MapWrapper : FrameLayout() {

    fun setupMap(widget: IMapWidget) {
        removeAllViews()
        addView(widget as View)
    }
}

И в нужном месте вызываем метод добавления карты.

override fun onCreateView(...) {
    ...
    val map: IMapWidget = MapFactory.createMap()
    viewMapWrapper.setupMap(map)
    ...
}


Такие обёртки класса нужно создать для всего: объектов, маркеров,

PolyUtilPolyLine

и т.д.

Этап 1: проверка наличия services

Если у вас в приложении при входе есть проверка наличия Google Services, то придётся от этого отказаться, и проверять наличие соответствующих сервисов только по мере необходимости.

Этап 2: карты


В приложении Delivery Club три основные страницы:

На устройствах Huawei все эти карты не работают. Чтобы это исправить, можно просто заменить зависимости: вместо пакета

com.google.android.gms

использовать

com.huawei.hms

Конечно, есть нюансы, но мы уже сделали большу́ю часть работы. Huawei сделала Maps SDK с контрактами, по большей части соответствующий Google Maps SDK. Однако у Google есть deprecated-методы, если вы их используете, то аналогов у Huawei может и не найтись. Например, для получения местоположения пользователя мы используем:

LocationServices.FusedLocationApi.getLastLocation(googleApiClien)

Такой подход считается deprecated, и если мы просто скопируем код для Huawei Maps и заменим зависимости, то работать не будет. Нужно поменять так:

LocationServices.getFusedLocationProviderClient(…)
.getLastLocation()
.addOnSuccessListener(…)

Этап 3: push-сервис

Идём дальше. На Huawei-устройства не приходят уведомления нашего приложения. Дело в том, что мы не можем получить токен. Давайте его получим. В Google мы получаем задачу и извлекаем токены так:

FirebaseMessaging.getInstance().token.addOnCompleteListener { task ->
    if (task.isSuccessful) {
        val token = task.result
    }
}

Наше решение:

class ImplementationHuaweiMessagingService : HmsMessageService() {

    override fun onNewToken(token: String?) {
        val commonApi = getComponentFactory().get(CommonApi::class.java)
        commonApi.settingsManager().setPushToken(token)
    }

    override fun onMessageReceived(message: RemoteMessage?) {
        message?.let {
            val appManagersComponent = getComponentFactory().get(AppManagersApi::class.java)
            appManagersComponent.pushManager().handle(it.dataOfMap)
        }
    }


Выглядит всё так же, как и с реализацией

FirebaseMessagingService()

, даже есть

callbackonNewTokenonMessageReceived

. Однако без нюансов не обойтись. Случается, что на некоторых редких устройствах

onMessageReceived

вызывается в главном потоке, поэтому лучше не использовать здесь долго выполняющиеся задачи.

Получаем токены на Huawei:

val token = HmsInstanceId.getInstance(context)
    .getToken(appId, com.huawei.hms.push.HmsMessaging.DEFAULT_TOKEN_SCOPE)



public static final String DEFAULT_TOKEN_SCOPE = "HCM";

Обратите внимание, что метод выполняется в главном потоке. И для получения токена нужно отдельно реализовать поток. У Google такой подход уже считается устаревшим, возможно, Huawei придёт к тому же.

Мы можем вообще не использовать getToken, а прописать в манифесте автоматическую инициализацию или в коде методом setAutoInitEnabled() и всегда получать token в onNewToken (подробнее). Это решит ещё одну проблему: getToken в версиях EMUI ниже 10 вообще возвращает null.

Этап 4: chrome custom tabs

Наше приложение при запуске регулярно вылетает с ошибкой

ActivityNotFoundException

. Чтобы от этого избавиться, нужно обработать отсутствие Chrome Tabs.

fun Context.openLink(url: String, customTabsSession: CustomTabsSession? = null): Boolean {
    try {
        openLinkInCustomTab(url, customTabsSession)
        return true
    } catch (throwable: Throwable) {
        Timber.tag("Context::openLink").e(throwable, "CustomTabsIntent error on url: $url")
    }

    return openLinkInBrowser(url)
}

@Throws(Throwable::class)
fun Context.openLinkInCustomTab(url: String, customTabsSession: CustomTabsSession? = null) {
    CustomTabsIntent.Builder(customTabsSession)
        .build()
        .launchUrl(this, Uri.parse(url))
}

private fun Context.openLinkInBrowser(url: String): Boolean {
    val intent: Intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)).apply {
        addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY or Intent.FLAG_ACTIVITY_NEW_DOCUMENT)
    }

    if (intent.resolveActivity(packageManager) != null) {
        startActivity(intent)
        return true
    }

    return false
}


Мы просто обернули

openLinkInCustomTab()try catch

и в случае ошибки пытаемся открыть в браузере. Но бывает такого, чтобы на устройстве не было подходящего браузера, способного обработать наш неявный

intent

. Поэтому если метод

openLinkInBrowser()

возвращает

false

, мы открываем страницу в

webview

Этап 5: аналитика


Аналитика у Huawei похожа на Google Analytics. Покажу замену на примере Firebase. Сначала инициализируем:

HiAnalytics.getInstance(context)

. Затем с помощью

HAEventType.STARTCHECKOUT

копируем все наши события из Firebase в отдельный файл

huaweiAnalytics

huaweiAnalytics.onEvent(name, bundle)


Системные параметры:

HAParamType.PRICEHAParamType.CURRNAME

Даже если у вас нет Firebase, добавить аналитику в Huawei очень просто. У них отличная документация, контракт соблюдается. Также у Huawei есть отличные инструменты для исследования аудитории.

Этап 6: crashlytics

Следующий инструмент, который нам тоже стало интересно попробовать, это Crashlytics от Huawei, которая называется

AGConnectCrash

. Она позволяет с минимальными усилиями собирать и анализировать информацию о падении приложения.

Инициализируем crashlytics:

AGConnectCrash.getInstance().enableCrashCollection(true)

Добавляем свои ключи и журналируем нужные события:

Этап 7: покупки в приложении

Если в вашем приложении есть встроенные покупки, вы должны подписать согласие на передачу персональных данных, и отослать письмом в конверте или через курьера в московский офис Huawei. И только через пару дней они вам дадут доступ для реализации этой функциональности.

Всё очень похоже на реализацию Google. При запуске приложения запрашиваем все прошлые покупки пользователя:

fun getOwnedPurchases(
    activity: Activity,
    ownedPurchasesResultOnSuccessListener: OnSuccessListener<OwnedPurchasesResult>,
    failureListener: OnFailureListener
) {
    val ownedPurchasesReq = OwnedPurchasesReq()

    // priceType: 0: consumable; 1: non-consumable; 2: auto-renewable subscription
    ownedPurchasesReq.priceType = IapClient.PriceType.IN_APP_SUBSCRIPTION

    // To get the Activity instance that calls this API.
    val task: Task<OwnedPurchasesResult> = Iap.getIapClient(activity)
        .obtainOwnedPurchases(ownedPurchasesReq)

    task.addOnSuccessListener(ownedPurchasesResultOnSuccessListener)
        .addOnFailureListener(failureListener)
}


Если какой-то товар был куплен, мы разблокируем его функциональность. Потом запрашиваем подробности по товарам, доступным для продажи — цену и описание:

fun loadProduct(
    context: Context,
    productInfoResultOnSuccessListener: OnSuccessListener<ProductInfoResult>,
    onFailureListener: OnFailureListener
) {
    // obtain in-app product details configured in AppGallery Connect, and then show the products
    val iapClient: IapClient = Iap.getIapClient(context)
    val task: Task<ProductInfoResult> = iapClient.obtainProductInfo(createProductInfoReq())
    task.addOnSuccessListener(productInfoResultOnSuccessListener)
        .addOnFailureListener(onFailureListener)
}

private fun createProductInfoReq(): ProductInfoReq {
    val req = ProductInfoReq()
    // 0: consumable ; 1: non-consumable ; 2: auto-renewable subscription
    req.priceType = IapClient.PriceType.IN_APP_SUBSCRIPTION

    val productIds = ArrayList<String>()
    productIds.add("PRODUCT_ID")
    req.productIds = productIds
    return req
}

Когда пользователь кликает на товар, мы открываем страницу с оплатой. Она не такая красивая, как у Google, и не выезжает снизу.

fun gotoPay(activity: Activity, productId: String, type: Int) {
    val client: IapClient = Iap.getIapClient(activity)
    val task: Task<PurchaseIntentResult> = client.createPurchaseIntent(createPurchaseIntentReq(type, productId))
    task.addOnSuccessListener { result ->
        result?.let {
            val status: Status = result.status
            if (status.hasResolution()) {
                try {
                    status.startResolutionForResult(activity, PAY_RESULT_ARG)
                } catch (exception: SendIntentException) {
                    Timber.e(exception)
                }
            } else {
                Timber.d("intent is null")
            }
        }
    }.addOnFailureListener { exception ->
        Timber.e(exception)
    }
}

Так как это Activity, мы передаём ему аргумент, по которому можно отловить

OnActivityResult

и понять, успешно ли прошла оплата и как закончилась транзакция:

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)
    if (resultCode == PAY_RESULT_ARG) {
        val purchaseResultInfo: PurchaseResultInfo = Iap.getIapClient(this).parsePurchaseResultInfoFromIntent(data)
        when (purchaseResultInfo.returnCode) {
            OrderStatusCode.ORDER_STATE_SUCCESS -> {
                successResult(purchaseResultInfo)
            }

            OrderStatusCode.ORDER_STATE_CANCEL -> {

            }

            OrderStatusCode.ORDER_PRODUCT_OWNED -> {

            }
        }
    }
}


У нас есть специальные статусы:

ORDER_SUCCESSCANCELOWNED

. Первый означает успешную оплату. Второй — пользователь просто закрыл страницу без покупки, тогда мы обрабатываем этот callback и предлагаем скидку, чтобы уговорить на покупку. А третий статус означает, что товар уже куплен пользователем. Если товар разовый или подписочный, то на этом моменте нужно остановиться, в противном случае виртуально доставить покупку.

В случае успешной оплаты доставляем пользователю купленный товар:

private fun successResult(purchaseResultInfo: PurchaseResultInfo) {
    val inAppPurchaseData = InAppPurchaseData(purchaseResultInfo.inAppPurchaseData)
    val req = ConsumeOwnedPurchaseReq()
    req.purchaseToken = inAppPurchaseData.purchaseToken

    val client: IapClient = Iap.getIapClient(this)
    val task: Task<ConsumeOwnedPurchaseResult> =
        client.consumeOwnedPurchase(req)

    task.addOnSuccessListener {
        // Consume success
    }.addOnFailureListener { exception ->
        Timber.e(exception)
    }
}

Если не сделать доставку, то функциональность товара будет у пользователя заблокирована, а деньги возвращены. В Google Play Billing Library до третьей версии этого делать не нужно было, но потом Google тоже это добавил, и если мы не доставим товар, через 48 часов покупка отменится, а деньги вернутся пользователю. То есть в Huawei покупки реализованы как в третьей версии Google Play Billing.

Выводы

На реализацию поддержки Huawei-устройств не уйдёт много времени. Даже без реальных устройств вы сможете проверить работоспособность вашего приложение: у Huawei есть своя тестовая лаборатория с виртуальными устройствами наподобие

. Количество пользователей быстро растёт, и бизнесу может оказаться выгодным вложиться в доработку продуктов, а отличная документация поможет разработчикам всё сделать быстро. Поддержка HMS активно отвечает на любые вопросы, если вы не сможете в документации что-то найти.

Видеозапись доклада с конференции Mobius 2020.

Оцените статью
Huawei Devices
Добавить комментарий