Android-разработка для самых маленьких

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

Вступление

Привет, Хабр! Это мой первый пост на данной площадке, давно читаю, но писать все не решался, но, как говорится, когда-то все в жизни бывает в первый раз.

Коротко о том, что будет в статье

  • быстро настроим Gitlab для имитации CI-CD как у взрослых дядь (образно выражаясь)

  • напишем простое Hello world web-приложение (без излишеств, максимально быстро и просто)

  • превратим наше web-приложение в Android-приложение при помощи Cordova (как мне кажется, самый простой метод, для не вовлеченных в индустрию людей)

  • соберем тестовую версию .apk приложения и запустим на Android-телефоне

Дисклеймер

Web- и Android-разработка не являются моими прямыми должностными обязанностями, это мое хобби, на которое я предпочитаю тратить свободное от работы и прочих дел время, я понимаю, что в некоторых случаях могу показать антипаттерн в методах реализации демонстрационного проекта. Надеюсь, вы подсветите мне все нюансы в комментариях! Цель статьи — показать общую концепцию одного из вариантов реализации конвейера для быстрого выпуска простых Android-приложений

Глава 1. Gitlab CI-CD

Основная задача на данном этапе — получить локальный инстанс Gitlab-CE и раннеры для того, чтобы можно было хранить наш код, иметь систему контроля версий и быстро собирать продукт из исходников.

Воспользуемся контейнеризацией (Docker + Docker Compose) для того, чтобы упростить процесс деплоя Gitlab на локальную машину, нам понадобится собственно Gitlab-CE и несколько раннеров (в моем примере — 3), для того чтобы не только хранить код, но и собирать проект (таблица 1).

таблица 1 — Используемые образы

Наименование

Ссылка на Docker hub

Описание

Gitlab-CE

https://hub.docker.com/r/gitlab/gitlab-ce

gitlab-ce server 16.1.0-ce.0

Gitlab Runner

https://hub.docker.com/r/gitlab/gitlab-runner

gitlab build runner alpine3.18-v16.1.0 shell

Для того чтобы иметь возможность тестировать код и собирать web- и android-приложения, нам понадобится модифицировать образ Gitlab Runner под свои нужды; так как мы взяли за основу версию Alpine, сделать это будет совсем просто, напишем простой Dockerfile, для того чтобы добавить все необходимое:

FROM gitlab/gitlab-runner:alpine3.18-v16.1.0

# set LANG env var
ENV LANG=C.UTF-8

#add Glibc 2.34 for Alpine linux
RUN ALPINE_GLIBC_BASE_URL="https://github.com/sgerrand/alpine-pkg-glibc/releases/download" && \
    ALPINE_GLIBC_PACKAGE_VERSION="2.34-r0" && \
    ALPINE_GLIBC_BASE_PACKAGE_FILENAME="glibc-$ALPINE_GLIBC_PACKAGE_VERSION.apk" && \
    ALPINE_GLIBC_BIN_PACKAGE_FILENAME="glibc-bin-$ALPINE_GLIBC_PACKAGE_VERSION.apk" && \
    ALPINE_GLIBC_I18N_PACKAGE_FILENAME="glibc-i18n-$ALPINE_GLIBC_PACKAGE_VERSION.apk" && \
    apk add --no-cache --virtual=.build-dependencies wget ca-certificates && \
    echo \
        "-----BEGIN PUBLIC KEY-----\
        MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApZ2u1KJKUu/fW4A25y9m\
        y70AGEa/J3Wi5ibNVGNn1gT1r0VfgeWd0pUybS4UmcHdiNzxJPgoWQhV2SSW1JYu\
        tOqKZF5QSN6X937PTUpNBjUvLtTQ1ve1fp39uf/lEXPpFpOPL88LKnDBgbh7wkCp\
        m2KzLVGChf83MS0ShL6G9EQIAUxLm99VpgRjwqTQ/KfzGtpke1wqws4au0Ab4qPY\
        KXvMLSPLUp7cfulWvhmZSegr5AdhNw5KNizPqCJT8ZrGvgHypXyiFvvAH5YRtSsc\
        Zvo9GI2e2MaZyo9/lvb+LbLEJZKEQckqRj4P26gmASrZEPStwc+yqy1ShHLA0j6m\
        1QIDAQAB\
        -----END PUBLIC KEY-----" | sed 's/   */\n/g' > "/etc/apk/keys/sgerrand.rsa.pub" && \
    wget \
        "$ALPINE_GLIBC_BASE_URL/$ALPINE_GLIBC_PACKAGE_VERSION/$ALPINE_GLIBC_BASE_PACKAGE_FILENAME" \
        "$ALPINE_GLIBC_BASE_URL/$ALPINE_GLIBC_PACKAGE_VERSION/$ALPINE_GLIBC_BIN_PACKAGE_FILENAME" \
        "$ALPINE_GLIBC_BASE_URL/$ALPINE_GLIBC_PACKAGE_VERSION/$ALPINE_GLIBC_I18N_PACKAGE_FILENAME" && \
    mv /etc/nsswitch.conf /etc/nsswitch.conf.bak && \
    apk add --no-cache --force-overwrite \
        "$ALPINE_GLIBC_BASE_PACKAGE_FILENAME" \
        "$ALPINE_GLIBC_BIN_PACKAGE_FILENAME" \
        "$ALPINE_GLIBC_I18N_PACKAGE_FILENAME" && \
    \
    mv /etc/nsswitch.conf.bak /etc/nsswitch.conf && \
    rm "/etc/apk/keys/sgerrand.rsa.pub" && \
    (/usr/glibc-compat/bin/localedef --force --inputfile POSIX --charmap UTF-8 "$LANG" || true) && \
    echo "export LANG=$LANG" > /etc/profile.d/locale.sh && \
    \
    apk del glibc-i18n && \
    \
    rm "/root/.wget-hsts" && \
    apk del .build-dependencies && \
    rm \
        "$ALPINE_GLIBC_BASE_PACKAGE_FILENAME" \
        "$ALPINE_GLIBC_BIN_PACKAGE_FILENAME" \
        "$ALPINE_GLIBC_I18N_PACKAGE_FILENAME"

# set env vars
ENV ANDROID_SDK_ROOT "/opt/sdk"
ENV ANDROID_HOME ${ANDROID_SDK_ROOT}
ENV PATH $PATH:${ANDROID_SDK_ROOT}/gradle/6.5/bin:${ANDROID_SDK_ROOT}/cmdline-tools/latest_supported/bin:${ANDROID_SDK_ROOT}/platform-tools:${ANDROID_SDK_ROOT}/extras/google/instantapps:${ANDROID_SDK_ROOT}/build-tools/34.0.0

# add core apps and libs (jdk8, jdk11, cordova10)
RUN apk add --no-cache --update \
    docker docker-compose openrc doas nodejs-current npm curl python3 python3-dev py3-pip gcc musl-dev openjdk8 openjdk11 curl unzip wget
RUN python3 -m pip install --upgrade --no-cache semgrep && \
    npm install -g cordova@10
RUN rc-update add docker boot
RUN echo 'permit nopass gitlab-runner as root' > /etc/doas.d/doas.conf

# add Gradle 6.5
RUN wget -q https://services.gradle.org/distributions/gradle-6.5-all.zip -O /tmp/gradle.zip && \
    mkdir -p ${ANDROID_SDK_ROOT}/gradle && \
    unzip -qq /tmp/gradle.zip -d ${ANDROID_SDK_ROOT}/gradle && \
    mv ${ANDROID_SDK_ROOT}/gradle/* ${ANDROID_SDK_ROOT}/gradle/6.5 && \
    rm -v /tmp/gradle.zip

# add latest android command line tools
RUN wget -q https://dl.google.com/android/repository/commandlinetools-linux-9477386_latest.zip -O /tmp/tools.zip && \
    mkdir -p ${ANDROID_SDK_ROOT}/cmdline-tools && \
    unzip -qq /tmp/tools.zip -d ${ANDROID_SDK_ROOT}/cmdline-tools && \
    mv ${ANDROID_SDK_ROOT}/cmdline-tools/* ${ANDROID_SDK_ROOT}/cmdline-tools/latest_supported && \
    rm -v /tmp/tools.zip && \
    mkdir -p ~/.android/ && touch ~/.android/repositories.cfg

# add android platform tools, build tools 34.0.0
RUN yes | sdkmanager --sdk_root=${ANDROID_SDK_ROOT} --licenses && \
    sdkmanager --sdk_root=${ANDROID_SDK_ROOT} --install "platform-tools" "extras;google;instantapps" "build-tools;34.0.0"

# remove jdk11
RUN apk del openjdk11

# fix build tools "dx" bug
RUN mv /opt/sdk/build-tools/34.0.0/d8 /opt/sdk/build-tools/34.0.0/dx &&\
    mv /opt/sdk/build-tools/34.0.0/lib/d8.jar /opt/sdk/build-tools/34.0.0/lib/dx.jar

# set ownership of android sdk to gitlab-runner user
RUN chown gitlab-runner -R /opt/sdk

# set default jdk to jdk8
ENV JAVA_HOME "/usr/lib/jvm/java-1.8-openjdk"
ENV PATH $PATH:${JAVA_HOME}/bin

Из необычного хотелось бы отметить несколько моментов:

  • добавление библиотеки Glibc (строка 6)

  • добавление jdk8 и jdk11 (строка 52)

  • удаление jdk11 (строка 79)

  • фикс нэйминга файлов Android SDK (строка 82)

Теперь по порядку. Glibc нам понадобится, чтобы поставить все компоненты, необходимые для сборки Android-приложения, при помощи Cordova (я не стал искать сложных путей и воспользовался уже готовым решением из Github). Jdk11 нам понадобится для того, чтобы иметь возможность использовать Android command line tools, впоследствии мы его удалим, так как использовать будем Cordova 10, которая дружит с jdk8 (пожалуйста, не ругайте за легаси, да, я тоже слышал шутку Влада Тэна в подкасте, где он прерывает влажные фантазии о AI фразой в духе «Какой к черту AI, люди до сих пор 8 джавой пользуются!»). Фикс нэйминга файлов Android SDK нам понадобится для того, чтобы Gradle не падал с ошибкой на этапе сборки андроид-приложения.

Помимо вышеописанных действий мы также добавим в контейнер Nodejs, Python, Semgrep и Gradle и выполним ряд дополнительных манипуляций (в Dockerfile есть комментарии, не буду их дублировать)

Теперь у нас есть все для того, чтобы создать импровизированную версию конвейера в домашних условиях, давайте напишем docker-compose.yml

version: "3.9"

networks:
  gitlab_ce:
    driver: bridge

volumes:
  gitlab_conf:
  gitlab_data:
  gitlab_logs:
  build_runner_home:
  build_runner_conf:
  test_runner_home:
  test_runner_conf:
  deploy_runner_home:
  deploy_runner_conf:

services:
  # gitlab-ce
  gitlab-ce:
    image: gitlab/gitlab-ce:16.1.0-ce.0
    shm_size: '2gb'
    container_name: gitlab-ce
    hostname: gitlab-ce.local
    restart: always
    depends_on:
      - build-runner
      - test-runner
      - deploy-runner
    volumes:
      - gitlab_conf:/etc/gitlab
      - gitlab_data:/var/opt/gitlab
      - gitlab_logs:/var/log/gitlab
    ports:
      - "127.0.0.1:22:22"
      - "127.0.0.1:80:80"
    networks:
      - gitlab_ce
    deploy:
      resources:
        limits:
          cpus: "4"
          memory: 8G

  # gitlab-build-runner
  build-runner:
    build: ./
    container_name: gitlab-build-runner
    hostname: gitlab-build-runner.local
    restart: always
    volumes:
      - build_runner_conf:/etc/gitlab-runner
      - build_runner_home:/home/gitlab-runner
      - /var/run/docker.sock:/var/run/docker.sock:rw
    networks:
      - gitlab_ce
    deploy:
      resources:
        limits:
          cpus: "4"
          memory: 4G

  # gitlab-test-runner
  test-runner:
    build: ./
    container_name: gitlab-test-runner
    hostname: gitlab-test-runner.local
    restart: always
    volumes:
      - test_runner_conf:/etc/gitlab-runner
      - test_runner_home:/home/gitlab-runner
      - /var/run/docker.sock:/var/run/docker.sock:rw
    networks:
      - gitlab_ce
    deploy:
      resources:
        limits:
          cpus: "1"
          memory: 1G

  # gitlab-deploy-runner
  deploy-runner:
    build: ./
    container_name: gitlab-deploy-runner
    hostname: gitlab-deploy-runner.local
    restart: always
    volumes:
      - deploy_runner_conf:/etc/gitlab-runner
      - deploy_runner_home:/home/gitlab-runner
      - /var/run/docker.sock:/var/run/docker.sock:rw
    networks:
      - gitlab_ce
    deploy:
      resources:
        limits:
          cpus: "1"
          memory: 1G

Стоит обратить внимание на секцию deploy в каждом сервисе, в ней можно указать, сколько системных ресурсов Вы хотите утилизировать под каждый сервис, также внимание стоит обратить на build: здесь указывается, из какой директории собирать образ для сервиса (где находится Dockerfile), не пропустите shm_size — это поможет заметно повысить производительность сервера Gitlab.

В данной конфигурации заложена избыточность по количеству раннеров (можно обойтись и одним, или же для каждой стадии (build, test, deploy) использовать отдельный образ, оптимизированный под определенную задачу) альтернативным методом может быть использование раннера c типом docker, однако в данном примере, несмотря на то что все раннеры являются Docker-контейнерами, я буду использовать их как shell-раннеры. Да, здесь используется импровизированный docker in docker (у сообщества есть вопросы к целесообразности использования такого метода не в последнюю очередь из-за проблем с безопасностью, но в целях эксперимента, думаю, можно попробовать и такую конфигурацию).

Теперь давайте автоматизируем процесс регистрации раннера (как вы знаете, недостаточно просто запустить раннер, его еще нужно зарегистрировать на сервере Gitlab)

#!/bin/sh

RUNNER_CONTAINER_ID=$1
RUNNER_TAG=$2
GITLAB_RUNNER_REG_TOKEN=$3

GITLAB_SUBNET=$(docker network ls | grep gitlab_ce | cut -d " " -f 4)

echo "Registering ...";

docker exec -it $RUNNER_CONTAINER_ID bash -c 'gitlab-runner register \
  --non-interactive \
  --executor "shell" \
  --docker-image alpine:latest \
  --url "http://gitlab-ce.local/" \
  --registration-token "'$GITLAB_RUNNER_REG_TOKEN'" \
  --description "docker-'$RUNNER_TAG'-runner" \
  --maintenance-note "Docker runner for gitlab-ce" \
  --tag-list "docker,'$RUNNER_TAG'" \
  --run-untagged="true" \
  --locked="false" \
  --access-level="not_protected" > /dev/null 2>&1; \
  echo "    network_mode = \"'$GITLAB_SUBNET'\"" >> /etc/gitlab-runner/config.toml'; 

echo "Done"

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

Почти все готово для запуска своего инстанса Gitlab, финальный скрипт, связывающий все в единую картину и автоматизирующий рутину.

#!/bin/sh

echo "################### Build & deploy Gitlab-CE ###################"
docker compose -p gitlab-ce up -d --build --wait gitlab-ce
echo "###############################################################"

echo "..."

echo "#################### Check Gitlab-CE state ####################"
docker compose -p gitlab-ce ps
echo "###############################################################"

echo "..."

echo "############## Gitlab runner registration token ###############"
RUNNER_REG_TOKEN=$(docker exec -it gitlab-ce gitlab-rails runner -e production 'puts Gitlab::CurrentSettings.current_application_settings.runners_registration_token')
echo "Token: $RUNNER_REG_TOKEN"
echo "###############################################################"

echo "..."

echo "################ Gitlab initial root password #################"
docker exec -it gitlab-ce bash -c 'grep 'Password:' /etc/gitlab/initial_root_password 2>/dev/null || echo "Custom root password already exists."'
echo "###############################################################"

echo "..."

echo "################# Base runners registration ###################"
echo "Registering  gitlab-build-runner ..."
./reg_new_runner.sh gitlab-build-runner build $RUNNER_REG_TOKEN
echo "Registering  gitlab-test-runner ..."
./reg_new_runner.sh gitlab-test-runner test $RUNNER_REG_TOKEN
echo "Registering  gitlab-deploy-runner ..."
./reg_new_runner.sh gitlab-deploy-runner deploy $RUNNER_REG_TOKEN
echo "###############################################################"

В итоге получаем готовый к тестам Gitlab-инстанс, который может собирать приложения и тестировать код.

зарегестрированныые раннеры
зарегестрированныые раннеры

Глава 2. Простое web-приложение

Как вы уже, наверное, догадались, здесь мы сделаем очень простое web-приложение и соберем его при помощи Webpack, на эту тему есть очень много хороших материалов, официальная документация тоже на высоте.

От себя лишь добавлю пару скриншотов из своих pet-проектов для того, чтобы показать, как организована структура файлов у меня.

файлы web-приложения выделены красным
файлы web-приложения выделены красным
содержание папки src
содержание папки src

Не буду подробно останавливаться на этом пункте, единственное, на что обращу ваше внимание, задайте в качестве директории, куда будет собираться Ваш фронтенд, папку с названием www, это упростит понимание дальнейших процессов.

конфигурация webpack
конфигурация webpack

Глава 3. Hi Cordova, pls transform my website to android app

Подведем промежуточные итоги: у нас есть простое web-приложение и Gitlab, готовый собирать наш проект и тестировать его, давайте соберем все компоненты нашего пазла воедино.

Для начала добавим конфигурационный файл config.xml для нашего приложения (я положу его в папку Cordova).

<?xml version='1.0' encoding='utf-8'?>
<widget id="com.moviefinder.main" version="1.0.0" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0" xmlns:android="http://schemas.android.com/apk/res/android">
    <name>Hello-world-app</name>
    <description>My first app</description>
    <author email="username@domain.com" href="https://link-to-my.app">
        Name Surname
    </author>
    
    <preference name="android-targetSdkVersion" value="34" />
    <preference name="android-minSdkVersion" value="21" />
    
    <preference name="AndroidPersistentFileLocation" value="Compatibility" />
    
    <preference name="DisallowOverscroll" value="true" />
    
    <allow-navigation href="*" />
    <allow-intent href="*" />
    <access origin="*" />
    
    <edit-config file="app/src/main/AndroidManifest.xml" mode="merge" target="/manifest/application/activity">
        <activity android:exported="true"/>
    </edit-config>  

</widget>

Обратите внимание на секцию edit-config: в ней задается параметр android:exported="true" — это необходимо для того, чтобы ваше приложение было доступно на последних версиях Android.

Так как наше андроид-приложение — это по факту еще и обычный веб-сайт, добавим сборку web-приложения в Nginx-контейнере, для того чтобы мы сразу могли видеть результат нашей работы (например, если у нас нет под рукой android-эмулятора или устройства, на которое можно поставить приложение), для этого создадим Dockerfile со следующим содержанием:

FROM nginx
COPY ./www /usr/share/nginx/html

Таким образом мы получим на выходе веб-сервер с нашим web-приложением (ранее мы собирали наш web-проект в папку www)

Почти все готово, осталось только написать .gitlab-ci.yml конфигурацию для нашего пайплайна, давайте сделаем это!

stages:
  - build
  - test
  - deploy

build-code-job:
  stage: build
  tags:
    - build
    - docker
  before_script:
    - curl "https://api.telegram.org/bot$TELEGRAM_BOT_TOKEN/sendMessage?chat_id=$TELEGRAM_CHAT_ID&text=%20						
Источник: https://habr.com/ru/articles/749342/


Интересные статьи

Интересные статьи

Продолжим изучение компьютерного зрения, которое мы начали на прошлом уроке. Напомню кратко, что там было:• Этапы обработки и анализа изображений.• Установка OpenCV.• Простая программа на OpenCV – ото...
Приветствую читателей! В рамках текущей серии статей я рассказываю о том, как настроить сервер для простых проектов. Имеется ввиду сервер для работы нескольих сайтов, с небольшой нагрузкой под наибол...
Реверс-инжинеринг (англ. Reverse Engineering) - метод исследования устройств или программного обеспечения с целью понять принцип его работы или обнаружить недокументирова...
Все ПК из списка ниже в свое время выглядели, как гости из будущего. Возможно, так и есть, ведь они стали началом современной компьютерной индустрии. Всего в списке 20 моделей разны...
Disclaimer Этот текст не привязан к существующим криптопроектам и представляет собой "сферического коня в вакууме" для самого первого погружения в тему. Специалистам, скорее всего, такие прописн...