Привет всем, в этом блоге я объясню вам, что такое функция динамических способностей и как ее использовать.

Что такое динамическая способность?

Dynamic Ability — это сервис, в котором HUAWEI AppGallery реализует динамическую загрузку на основе технологии Android App Bundle. Приложения, интегрированные с динамической возможностью, могут динамически загружать функции или языковые пакеты из HUAWEI AppGallery по запросу пользователя. Его преимущества заключаются в сокращении ненужного потребления сетевого трафика и объема памяти устройства. В настоящее время SDK Dynamic Ability поддерживает мобильные телефоны, смарт-экраны и динамики с экранами.

Как это работает?

После загрузки только что созданного AAB-файла приложения в HUAWEI AppGallery платформа разделяет приложение на несколько APK-файлов по трем параметрам: язык, разрешение экрана и >Архитектура ABI. Когда пользователь загружает приложение, HUAWEI AppGallery предоставляет APK-файл, который подходит для пользовательского устройства в зависимости от языка устройства, плотности экрана и архитектуры ABI. Это снижает потребление дискового пространства и сетевого трафика на пользовательском устройстве, не влияя на функции приложения. Когда пользователь загружает приложение в первый раз, загружается только базовый функциональный модуль приложения, а динамические функции загружаются только при необходимости.

Процесс развития

Сначала мы создаем проект Android Studio и настраиваем файлы build.gradle.

allprojects {

repositories {

maven {url 'http://developer.huawei.com/repo/'}

...

}

}

dependencies {

implementation 'com.huawei.hms:dynamicability:1.0.11.302'

...

}

После этого нам нужно добавить в наш проект Dynamic Feature Module.

Мы создаем имя модуля и имя пакета.

Настраиваем параметры загрузки модуля.

И синхронизируем проект.

Это структура нашего основного проекта и наша динамическая функция.

Мы переопределяем метод attachBaseContext в проекте и в действии динамического функционального модуля и вызываем FeatureCompat.install для инициализации SDK Dynamic Ability.

override fun attachBaseContext(newBase: Context?) {

super.attachBaseContext(newBase)

// Start the Dynamic Ability SDK.

FeatureCompat.install(newBase)

}

Мы вызываем метод FeatureInstallManagerFactory.create для создания экземпляра FeatureInstallManager в методе onCreate нашего основного приложения.

override fun onCreate(savedInstanceState: Bundle?) {

mFeatureInstallManager = FeatureInstallManagerFactory.create(this)

...

}

Строим запрос на динамическую загрузку, можно добавить одну или несколько динамических фич. И мы вызываем метод installFeature для установки функций.

fun installFeature(view: View?) {

...

// start install

val request = FeatureInstallRequest.newBuilder()

.addModule("dynamicfeature")

.build()

val task = mFeatureInstallManager!!.installFeature(request)

...

}

Мы регистрируем прослушиватель для отслеживания состояния запроса на установку функции. Существует три типа слушателей: OnFeatureCompleteListener, OnFeatureSuccessListener и OnFeatureFailureListener.

– OnFeatureCompleteListener: обратный вызов запускается независимо от того, является ли статус успешным или нет. Нам нужно определить, успешен ли запрос или нет. Если запрос не выполнен, при вызове FeatureTask.getResult будет выдано исключение.

fun installFeature(view: View?) {

...

task.addOnListener(object : OnFeatureCompleteListener<Int>() {

override fun onComplete(featureTask: FeatureTask<Int>) {

if (featureTask.isComplete) {

Log.d(TAG, "complete to start install.")

if (featureTask.isSuccessful) {

val result = featureTask.result

sessionId = result

Log.d(TAG, "succeed to start install. session id :$result")

} else {

Log.d(TAG, "fail to start install.")

val exception = featureTask.exception

exception.printStackTrace()

}

}

}

})

...

}

OnFeatureSuccessListener: обратный вызов запускается только после того, как HUAWEI AppGallery успешно ответит на запрос. Результат обратного вызова содержит sessionId, уникальный идентификатор задачи динамической загрузки. С помощью sessionId мы можем получить информацию о ходе динамической загрузки и отменить задачу в любое время.

fun installFeature(view: View?) {

...

task.addOnListener(object : OnFeatureSuccessListener<Int>() {

override fun onSuccess(integer: Int) {

Log.d(TAG, "load feature onSuccess.session id:$integer")

}

})

...

}

OnFeatureFailureListener: обратный вызов запускается только в том случае, если HUAWEI AppGallery не отвечает.

fun installFeature(view: View?) {

...

task.addOnListener(object : OnFeatureFailureListener<Int?>() {

override fun onFailure(exception: Exception) {

if (exception is FeatureInstallException) {

val errorCode = exception.errorCode

Log.d(TAG, "load feature onFailure.errorCode:$errorCode")

} else {

exception.printStackTrace()

}

}

})

...

}

Чтобы использовать динамическую способность, пользователь должен согласиться с соглашением HUAWEI AppGallery. Перед загрузкой динамической функции нашему приложению необходимо убедиться, что пользователь согласен с соглашением.

  • Если пользователь соглашается с соглашением, процесс установки продолжается.
  • Если пользователь отклоняет соглашение, процесс установки прерывается.

private val mStateUpdateListener = InstallStateListener {

...

if (it.status() == FeatureInstallSessionStatus.REQUIRES_USER_CONFIRMATION) {

try {

mFeatureInstallManager!!.triggerUserConfirm(it, this, 1)

} catch (e: SendIntentException) {

e.printStackTrace()

}

return@InstallStateListener

}

...

}

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

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

private val mStateUpdateListener = InstallStateListener {

...

if (it.status() == FeatureInstallSessionStatus.REQUIRES_PERSON_AGREEMENT) {

try {

mFeatureInstallManager!!.triggerUserConfirm(it, this, 1)

} catch (e: SendIntentException) {

e.printStackTrace()

}

return@InstallStateListener

}

...

}

Мы можем отслеживать ход загрузки динамической функции.

private val mStateUpdateListener = InstallStateListener {

...

if (it.status() == FeatureInstallSessionStatus.DOWNLOADING) {

val process: Long = it.bytesDownloaded() * 100 / it.totalBytesToDownload()

Log.d(TAG, "downloading percentage: $process")

Toast.makeText(applicationContext, "downloading percentage: $process", Toast.LENGTH_SHORT).show()

return@InstallStateListener

}

...

}

Созданный слушатель должен быть зарегистрирован и снят с регистрации в нужное время.

override fun onResume() {

super.onResume()

mFeatureInstallManager?.registerInstallListener(mStateUpdateListener)

}

override fun onPause() {

super.onPause()

mFeatureInstallManager?.unregisterInstallListener(mStateUpdateListener)

}

Мы можем проверить статус установки.

private val mStateUpdateListener = InstallStateListener {

...

if (it.status() == FeatureInstallSessionStatus.INSTALLED) {

Log.d(TAG, "installed success ,can use new feature")

Toast.makeText(applicationContext, "installed success , can test new feature ", Toast.LENGTH_SHORT).show()

startfeature.isEnabled = true

installfeature.isEnabled = false

return@InstallStateListener

}

if (it.status() == FeatureInstallSessionStatus.UNKNOWN) {

Log.d(TAG, "installed in unknown status")

Toast.makeText(applicationContext, "installed in unknown status ", Toast.LENGTH_SHORT).show()

return@InstallStateListener

}

if (it.status() == FeatureInstallSessionStatus.FAILED) {

Log.d(TAG, "installed failed, errorcode : ${it.errorCode()}")

Toast.makeText(applicationContext, "installed failed, errorcode : ${it.errorCode()}", Toast.LENGTH_SHORT).show()

return@InstallStateListener

}

...

}

Если динамическую функцию не нужно устанавливать мгновенно, мы можем выбрать отложенную установку. При этом эту функцию можно установить, когда приложение работает в фоновом режиме. Получив запрос на отсрочку, HUAWEI AppGallery задержит установку в зависимости от состояния работы устройства.

fun delayedInstallFeature(view: View?) {

val features = arrayListOf<String>()

features.add("dynamicfeature")

val task = mFeatureInstallManager!!.delayedInstallFeature(features)

task.addOnListener(object : OnFeatureCompleteListener<Void?>() {

override fun onComplete(featureTask: FeatureTask<Void?>) {

if (featureTask.isComplete) {

Log.d(TAG, "complete to delayed_Install")

if (featureTask.isSuccessful) {

Log.d(TAG, "succeed to delayed_install")

} else {

Log.d(TAG, "fail to delayed_install.")

val exception = featureTask.exception

exception.printStackTrace()

}

}

}

})

}

Мы можем отложить удаление динамической функции, которая больше не используется. Удаление не выполняется мгновенно, оно выполняется, когда приложение работает в фоновом режиме.

fun delayedUninstallFeature(view: View?) {

val features = arrayListOf<String>()

features.add("dynamicfeature")

val task = mFeatureInstallManager!!.delayedUninstallFeature(features)

task.addOnListener(object : OnFeatureCompleteListener<Void?>() {

override fun onComplete(featureTask: FeatureTask<Void?>) {

if (featureTask.isComplete) {

Log.d(TAG, "complete to delayed_uninstall")

if (featureTask.isSuccessful) {

Log.d(TAG, "succeed to delayed_uninstall")

} else {

Log.d(TAG, "fail to delayed_uninstall.")

val exception = featureTask.exception

exception.printStackTrace()

}

}

}

})

}

Каждая задача динамической загрузки имеет уникальный идентификатор, указанный в sessionId. Мы можем отменить текущую задачу на основе sessionId в любое время.

fun abortInstallFeature(view: View?) {

Log.d(TAG, "begin abort_install : $sessionId")

val task = mFeatureInstallManager!!.abortInstallFeature(sessionId)

task.addOnListener(object : OnFeatureCompleteListener<Void?>() {

override fun onComplete(featureTask: FeatureTask<Void?>) {

if (featureTask.isComplete) {

Log.d(TAG, "complete to abort_install.")

if (featureTask.isSuccessful) {

Log.d(TAG, "succeed to abort_install.")

} else {

Log.d(TAG, "fail to abort_install.")

val exception = featureTask.exception

exception.printStackTrace()

}

}

}

})

}

Мы можем получить статус выполнения задачи.

fun getInstallState(view: View?) {

Log.d(TAG, "begin to get session state for: $sessionId")

val task: FeatureTask<InstallState> = mFeatureInstallManager!!.getInstallState(sessionId)

task.addOnListener(object : OnFeatureCompleteListener<InstallState>() {

override fun onComplete(featureTask: FeatureTask<InstallState>) {

if (featureTask.isComplete) {

Log.d(TAG, "complete to get session state.")

if (featureTask.isSuccessful) {

val state: InstallState = featureTask.result

Log.d(TAG, "succeed to get session state.")

Log.d(TAG, state.toString())

} else {

Log.d(TAG, "failed to get session state.")

val exception = featureTask.exception

exception.printStackTrace()

}

}

}

})

}

Мы также можем получить статус выполнения всех задач в системе.

fun getAllInstallStates(view: View?) {

Log.d(TAG, "begin to get all session states.")

val task = mFeatureInstallManager!!.allInstallStates

task.addOnListener(object : OnFeatureCompleteListener<List<InstallState>>() {

override fun onComplete(featureTask: FeatureTask<List<InstallState>>) {

Log.d(TAG, "complete to get session states.")

if (featureTask.isSuccessful) {

Log.d(TAG, "succeed to get session states.")

val stateList = featureTask.result

for (state in stateList) {

Log.d(TAG, state.toString())

}

} else {

Log.d(TAG, "fail to get session states.")

val exception = featureTask.exception

exception.printStackTrace()

}

}

})

}

Во время фактического использования приложения язык пользователя может отличаться. Мы можем динамически загружать один или несколько языковых пакетов в наше приложение одновременно.

Языковой пакет не обязательно должен содержать код страны. Например, чтобы загрузить французский пакет, нужно ввести только fr. SDK Dynamic Ability автоматически загружает французские ресурсы из нескольких стран и регионов. Чтобы уменьшить двусмысленность, рекомендуется использовать метод Locale.forLanguageTag(lang) для изменения исходного значения языка.

fun loadLanguage(view: View?) {

if (mFeatureInstallManager == null) {

return

}

// start install

val languages = arrayListOf<String>()

languages.add("fr")

val builder = FeatureInstallRequest.newBuilder()

for (lang in languages) {

builder.addLanguage(Locale.forLanguageTag(lang))

}

val request = builder.build()

val task = mFeatureInstallManager!!.installFeature(request)

task.addOnListener(object : OnFeatureSuccessListener<Int>() {

override fun onSuccess(result: Int) {

Log.d(TAG, "onSuccess callback result $result")

}

})

task.addOnListener(object : OnFeatureFailureListener<Int?>() {

override fun onFailure(exception: java.lang.Exception) {

if (exception is FeatureInstallException) {

Log.d(

TAG, "onFailure callback "

+ exception.errorCode

)

} else {

Log.d(TAG, "onFailure callback ", exception)

}

}

})

task.addOnListener(object : OnFeatureCompleteListener<Int?>() {

override fun onComplete(task: FeatureTask<Int?>) {

Log.d(TAG, "onComplete callback")

}

})

}

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

использованная литература

https://developer.huawei.com/consumer/en/doc/development/AppGallery-connect-Guides/agc-featuredelivery-introduction