Как создать свой собственный магазин приложений

Как создать свой собственный магазин приложений

24 октября 2022 г.

AdTech Holding привержен передовым технологиям, которые обеспечивают первоклассное качество всей его продукции. Наш бизнес-подход во многом зависит от разработки программного обеспечения и, таким образом, подразумевает наличие команды сильных ИТ-специалистов, которые обладают не только огромным опытом, но и вдохновением для роста и творчества. Мы всегда поощряем наши технические команды выходить за рамки своих обычных ежедневных, ежеквартальных и годовых задач — и это превращается в совершенно новые идеи и идеи.

n Сегодня мы рады поделиться одним из замечательных примеров такого понимания. Наша команда AppLab отвечает за разработку мобильных приложений, но недавно они вышли за рамки и создали собственную платформу магазина приложений. Мы поговорили с Артемом Ковардиным, руководителем отдела разработки продуктов App Lab, чтобы узнать, зачем вам может понадобиться ваша платформа для магазина приложений, и подробное руководство по ее созданию.

n Причины создания собственного магазина приложений

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

n Причина 1. Создание корпоративной инфраструктуры

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

Причина 2: помощь пожилым членам семьи

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

n Создание платформы App Store: готовое руководство

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

n Шаг 1. Планирование

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

| Интерфейс | Флаттер | |----|----| | Собственная логика | Котлин | | Скачивание APK-файлов | Собственный API компании | | API | Язык Go |

Еще один важный момент – это то, как мы собираемся добавлять приложения в магазин. Работа платформы магазина подразумевает, что нам нужны приложения в виде APK-файлов. Некоторые из них имеют прямые ссылки, опубликованные разработчиками. Однако большинство приложений доступны только через Google Play, а это значит, что для проекта требуется анализатор Google Play.

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

Шаг 2. Разработка приложения

Flutter позволяет создать магазин приложений с минимальными затратами времени и усилий. Таким образом, мы упомянем самые сложные моменты, которые могут вызвать затруднения.

Для управления состоянием в приложении наша команда использовала два пакета: provider и уведомление_состояния. Такое решение оказалось самым простым и удобным для управления состоянием приложения Flutter.

Еще один важный момент: прежде чем пользователь сможет установить приложение из хранилища, ему нужно сначала его загрузить — и этот шаг требует тестирования. Чтобы проверить, как это работает, команда добавила APK-файл на сервер, чтобы сделать его доступным для скачивания. Теперь вам нужно скачать его в приложении:

void download(String url, {Function? success, Function? error}) async {
    var name = basename(url);
    var request = http.Request('GET', Uri.parse(url));
    var response = client.send(request);
    String dir = (await getApplicationDocumentsDirectory()).path;

    List<List<int>> chunks = [];
    int downloaded = 0;

    response.asStream().listen((http.StreamedResponse r) {
        r.stream.listen((List<int> chunk) {
                print('downloaded: ${downloaded * Megabyte}');

                chunks.add(chunk);
                downloaded += chunk.length;
            }, onDone: () async {
              print('downloaded: ${downloaded * Megabyte}');

                var path = '$dir/$name';

                File file = new File(path);
                final Uint8List bytes = Uint8List(downloaded);
                int offset = 0;
                for (List<int> chunk in chunks) {
                    bytes.setRange(offset, offset + chunk.length, chunk);
                    offset += chunk.length;
                }

                await file.writeAsBytes(bytes);

                if (success != null) {
                    success(path);
                }

                return;
            }, onError: (err) async {
                if (error != null) {
                    error();
                }
        });
    });
}

Функция `getApplicationDocumentsDirectory()` возвращает путь к каталогу, в который нужно скачать APK-файл. С помощью функции basename(url) получаем имя файла; чтобы начать фоновую загрузку, мы используем `response.asStream()`, а когда загрузка завершена, мы используем обратный вызов `success(path);`.

n В результате у нас есть файл, и мы можем его установить. Здесь нам нужна часть нативного кода Kotlin. Чтобы получить этот код, нам нужно установить пакет __pigeon__ и создать определенные контракты в файле pideons/install/apk.dart.

import 'package:pigeon/pigeon.dart';


class InstallRequest {
  String? file;
}

class InstallResponse {
  bool? status;
}

@HostApi()
abstract class InstallApk {
  @async
  InstallResponse install(InstallRequest request);
}

Затем мы генерируем интерфейсы для нативного кода и кода Dart:

flutter pub run pigeon 
  --input pigeons/install/apk.dart 
  --dart_out lib/pigeons/install/apk.dart 
  --java_out ./android/app/src/main/java/ru/kovardin/getapp/pigeons/install/Apk.java 
  --java_package "ru.kovardin.getapp.pigeons.install"

Pigeon генерирует файлы Java, но мы можем использовать их в нативном коде Kotlin. Теперь важно поддерживать интерфейсы `InstallApk` из сгенерированного Java-кода:

package ru.kovardin.getapp.install

import android.app.Activity
import android.content.Intent
import android.content.pm.PackageManager
import android.content.pm.ResolveInfo
import android.net.Uri
import android.os.Build
import android.util.Log
import androidx.core.content.FileProvider
import ru.kovardin.getapp.pigeons.install.Apk
import java.io.File


open class Apk(val context: Activity): Apk.InstallApk {
    override fun install(request: Apk.InstallRequest, result: Apk.Result<Apk.InstallResponse>?) {
        Log.d("APK", "install apk ${request.file}")

        try {
            val file = File(request.file ?: "")
            val intent = Intent(Intent.ACTION_VIEW)
            val authority =  context.getApplicationContext().getPackageName().toString() + ".provider"
            val type = "application/vnd.android.package-archive"

            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                val apk: Uri = FileProvider.getUriForFile(
                        context,
                      authority,
                        file
                )

                intent.setDataAndType(apk, type)
                val infos: List<ResolveInfo> = context.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY)
                for (info in infos) {
                    context.grantUriPermission(authority, apk, Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION)
                }
                intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION)
                intent.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true)

            } else {
                intent.setAction(Intent.ACTION_VIEW)
                intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_ACTIVITY_NEW_TASK)
                intent.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true)
                intent.setDataAndType(Uri.fromFile(file), type)
            }
            context.startActivity(intent)
        } catch (e: Exception) {
            e.printStackTrace()
        }

        val response = Apk.InstallResponse();
        response.status = true;
        result?.success(response)
    }
}

В целом установка приложения — это запуск намерения с правильным типом и APK-файлом в параметрах. Остальную работу Android делает автоматически.


Следующее, что мы рекомендуем сделать, это проверить версию SDK. Дальнейшая логика, которую мы будем использовать для работы с файлом APK, будет зависеть от этой версии SDK. Для SDK 23 или более ранних версий мы можем использовать стандартный доступ к файлу: `Uri.fromFile(file)`. Этот код выполняется в разделе else:

intent.setAction(Intent.ACTION_VIEW)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_ACTIVITY_NEW_TASK)
intent.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true)
intent.setDataAndType(Uri.fromFile(file), type)

Все становится сложнее, когда вы работаете с SDK 24 или более поздними версиями. Чтобы сделать все правильно, нам нужно будет добавить отдельный раздел в файл AndroidManifest.xml:

<application
        android:allowBackup="true"
        android:label="@string/app_name">
        <provider
            android:name="android.support.v4.content.FileProvider"
            android:authorities="${applicationId}.authorityStr"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/paths"/>
        </provider>
</application>

`@xml/paths` — это отдельный файл ресурсов: paths.xml из папки xml. Нам нужно определить содержимое этого файла:

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path
            name="files_root"
            path="Android/data/${applicationId}" />
    <external-path
            name="external_files"
            path="." />
    <root-path name="root" path="/data/" />
</paths>

Подробное руководство по работе с файлами в Android SDK 24+ вы найдете на странице для разработчиков. документация.

Теперь мы можем вызывать нативный код из кода Dart. Pigeon сгенерировал класс InstallApk, который будет использоваться в службе.

final apk = InstallApk();
final response = await apk.install(InstallRequest(
    file: apk,
));

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

Permission.storage.request().then((value) {
    print(value);

    Permission.requestInstallPackages.request().then((value) {
        print(value);
    });
});

Шаг 3. API

Самый простой способ создать API — придерживаться языка Go, который идеально подходит для этих нужд. Для работы с HTTP наша команда рекомендует использовать chi: очень простой, но эффективный маршрутизатор .

func (a *Api) Route(r chi.Router) {
    r.Route("/v1", func(r chi.Router) {

            r.Route("/apps", func(r chi.Router) {
                r.Route("/{bundle}", func(r chi.Router) {
                        r.Get("/", a.apps.One)
                        r.Get("/download", a.apps.Download)
                })
                r.Get("/search", a.apps.Search)
                r.Post("/updates", a.apps.Updates)
            })

        r.Get("/file/*", a.static.File)

            // ...

    })
}

Все файлы APK хранятся в AWS. Для корректного обмена ими сервису нужен обработчик, который будет проксировать все запросы к файлам и добавлять специальный заголовок: `headers.Set("Content-Type", "application/vnd.android.package-archive"). Этот заголовок необходим для загрузки файлов именно в формате .apk: если вы его не используете, некоторые Android-устройства будут скачивать их в виде .zip-архивов.

func (h Static) File(w http.ResponseWriter, r *http.Request) {
    headers := http.Header{}

    ext := path.Ext(r.URL.Path)
    if ext == ".apk" {
            headers.Set("Content-Type", "application/vnd.android.package-archive")
    }

    proxy := httputil.ReverseProxy{
            Director:  func(r *http.Request) {},
            Transport: responseHeadersTransport(headers),
    }

    target := h.config.Scheme + path.Join(h.config.Cloud, strings.Replace(r.URL.Path, "file/", "", 1))
    req, _ := http.NewRequest("GET", target, nil)
    proxy.ServeHTTP(w, req)
}

В остальном работа с API стандартная, поэтому на дальнейшие действия особого внимания не обращаем.

Часть 4. Синтаксический анализатор Чтобы создать магазин приложений, вам, очевидно, нужны приложения. Все они доступны в готовом магазине, и проще всего получить их оттуда. Мы говорим о Google Play, который нам нужно разобрать — только для информации, конечно.

На GitHub есть готовая библиотека для загрузки файлов APK: googleplay.

Чтобы начать загрузку файлов, вам необходимо войти в Google Play:

googleplay -email ЭЛЕКТРОННАЯ ПОЧТА -пароль ПАРОЛЬ

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

googleplay -device

После того, как все будет сделано, вы получите доступ к информации о приложении, которое хотите загрузить:

> googleplay -a com.google.android.youtube
Title: YouTube
Creator: Google LLC
UploadDate: 2022-05-12
VersionString: 17.19.34
VersionCode: 1529337280
NumDownloads: 11.822 B
Size: 46.727 MB
File: APK APK APK APK
Offer: 0 USD

И загрузить конкретную версию любого приложения:

googleplay -a com.google.android.youtube -v 1529337280

Результат

В результате всех этих усилий команда AppLab получила следующую платформу:

Он выглядит и работает как обычный магазин приложений. И, в конце концов, его оказалось сравнительно легко разработать. Артем уверяет, что большая часть работы лежит на самом Android, а разработчику нужно правильно скачать APK-файлы, получить все разрешения от пользователя и запустить стандартную установку для Android.

Результат, который вы видите на картинках, представляет собой магазин приложений с ограниченным набором функций. Все, что он позволяет вам делать, — это устанавливать приложения на устройства Android, что, впрочем, уже неплохо. Однако вы можете пойти дальше и добавить дополнительные функции: Push-уведомления через пакет firebase_messaging или installed_apps для реализации логики обновлений.

н

н


Оригинал
PREVIOUS ARTICLE
NEXT ARTICLE