4
6

ElixirDesktop Android Exampleを参考にAndroidアプリを1から作ってみる

Posted at

はじめに

本記事は以下を参考にAndroidStudioでプロジェクトを作るところから初めて
実際に起動するまでの手順の備忘録です

プロジェクトを新規に作成

Native C++ を選択
スクリーンショット 2023-10-07 21.18.22.png

Save Locationは phoenix_pj/native/android に設定
Minumum SDKはお好きに自分は10.0にしました
gradleはElixirDesktopに合わせてgrooby DSLにする .ktsにしない

スクリーンショット 2023-10-07 0.21.38.png

サンプルPJからファイルをコピー

app/libs/erlang.jarを
native/android/app/libs/ にコピー

app/src/main/assets を
native/android/app/src/main/ にコピー

app/src/main/res/xml/provider_paths.xml を
native/android/app/src/main/res/xml にコピー

app/src/main/res/layout/activity_main.xml を
native/android/app/src/main/res/layout/activity_main.xml と差し替える

main/java のファイルをコピー & fix

app/src/main/java/io/elixirdesktop/example/Bridge.ktを
native/android/app/src/main/java/com/example/trareco/ にコピー

packageを差し替え

native/android/app/src/main/java/com/example/trareco/Bridge.kt
- package io.elixirdesktop.example
+ package com.example.trareco

import android.annotation.SuppressLint
...

package 以外を差し替え
コピー後
databinding.ActivityMainBindingの前半分を差し替え

native/android/app/src/main/java/com/example/trareco/MainActivity.kt
- package io.elixirdesktop.example
+ package com.example.trareco

import android.os.Bundle
import android.system.Os
import android.app.Activity
import android.view.KeyEvent
import android.view.View
- import io.elixirdesktop.example.databinding.ActivityMainBinding
+ import com.example.trareco.databinding.ActivityMainBinding
import java.io.*
import java.util.*
import android.webkit.WebView

import android.webkit.WebViewClient

class MainActivity : Activity() {
    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
        binding.browser.webViewClient = object : WebViewClient() {
            override fun onPageFinished(view: WebView, url: String) {
                if (binding.browser.visibility != View.VISIBLE) {
                    binding.browser.visibility = View.VISIBLE
                    binding.splash.visibility = View.GONE
                }
            }
        }

        if (bridge != null) {
            // This happens on re-creation of the activity e.g. after rotating the screen
            bridge!!.setWebView(binding.browser)
        } else {
            // This happens only on the first time when starting the app
            bridge = Bridge(applicationContext, binding.browser)
        }
    }

    override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
        if (event.action == KeyEvent.ACTION_DOWN) {
            when (keyCode) {
                KeyEvent.KEYCODE_BACK -> {
                    if (binding.browser.canGoBack()) {
                        binding.browser.goBack()
                    } else {
                        finish()
                    }
                    return true
                }
            }
        }
        return super.onKeyDown(keyCode, event)
    }

    companion object {
        var bridge: Bridge? = null
    }
}

manifestファイル変更

native/android/app/src/main/AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
+    android:installLocation="internalOnly">

+   <uses-permission android:name="android.permission.INTERNET" />

    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.Trareco"
+        android:extractNativeLibs="true"
+        android:usesCleartextTraffic="true"
        tools:targetApi="31"
        >

+        <provider
+            android:name="androidx.core.content.FileProvider"
+            android:authorities="com.example.trareco.fileprovider"
+            android:exported="false"
+            android:grantUriPermissions="true">
+            <meta-data
+                android:name="android.support.FILE_PROVIDER_PATHS"
+                android:resource="@xml/provider_paths" />
+        </provider>


        <activity
+            android:configChanges="orientation|screenSize|screenLayout|keyboardHidden|density|fontScale|keyboard|layoutDirection|mcc|mnc|navigation|smallestScreenSize|touchscreen|uiMode"
            android:name=".MainActivity"
            android:exported="true"
            android:label="@string/app_name"
            android:theme="@style/Theme.Trareco">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

run_mixを作成

native/android/app/run_mix
#!/bin/bash
set -e

BASE=`pwd`
APP_FILE="$BASE/src/main/assets/app.zip"
export MIX_ENV=prod
export MIX_TARGET=android


cd ../../../

if [ ! -d "deps/desktop" ]; then
  mix local.hex --force
  mix local.rebar
  mix deps.get
fi

#if [ ! -d "assets/node_modules" ]; then
#  cd assets && npm i && cd ..
#fi

if [ -f "$APP_FILE" ]; then
  rm "$APP_FILE"
fi

mix assets.deploy && \
  mix release --overwrite && \
  cd "_build/${MIX_TARGET}_${MIX_ENV}/rel/trareco" && \
  zip -9r "$APP_FILE" lib/ releases/ --exclude "*.so"

proguardに修正

以下を追加

native/android/app/proguard-rules.pro
-dontskipnonpubliclibraryclasses
-dontobfuscate
-forceprocessing
-optimizationpasses 5

-keep class * extends android.app.Activity
-assumenosideeffects class android.util.Log {
    public static *** d(...);
    public static *** v(...);
}

cpp ファイルの改修

CMakeLists.txtの改修

native/android/app/src/main/cpp/CMakeLists.txt
project("trareco")
# 以下差し替え
add_library(native-lib SHARED native-lib.cpp)

# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.

find_library(log-lib log)

# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.

target_link_libraries(native-lib ${log-lib})

native-lib.cppの改修

一旦全部コピーして環境依存の箇所のみ修正します

native/android/app/src/main/cpp/native-lib.cpp:L160
extern "C" JNIEXPORT jstring JNICALL
- Java_io_elixirdesktop_example_Bridge_startErlang(
+ Java_com_example_trareco_Bridge_startErlang(

build.gradleの修正

色々足していきます

native/android/app/build.gradle
plugins {
    id 'com.android.application'
    id 'org.jetbrains.kotlin.android'
}

android {
    namespace 'com.example.trareco'
    compileSdk 33

+    task buildNum(type: Exec, description: 'Update Elixir App') {
+        commandLine './run_mix', 'package.mobile'
+    }

+    tasks.withType(JavaCompile) {
+        compileTask -> compileTask.dependsOn buildNum
+    }

    defaultConfig {
        applicationId "com.example.trareco"
        minSdk 29
        targetSdk 33
        versionCode 1
        versionName "1.0"

+        ndk {
+            abiFilters "armeabi-v7a", "arm64-v8a" , "x86_64"
+        }
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+        externalNativeBuild {
+            cmake {
+                cppFlags ''
+            }
+        }
    }

    buildTypes {
        release {
+            minifyEnabled true
+            shrinkResources true
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = '1.8'
    }
+    externalNativeBuild {
+        cmake {
+            path file('src/main/cpp/CMakeLists.txt')
+            version '3.22.1'
+        }
+    }
+    buildFeatures {
+        viewBinding true
+    }
+    packagingOptions {
+        jniLibs {
+            useLegacyPackaging = true
+        }
+    }
}

dependencies {
+    implementation fileTree(dir: "libs", include: '*.jar')
    
    implementation 'androidx.core:core-ktx:1.9.0'
    implementation 'androidx.appcompat:appcompat:1.6.1'
    implementation 'com.google.android.material:material:1.8.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.5'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
}

全部終わったら右上のsync gradleで同期してからビルドを実行します

スクリーンショット 2023-10-07 21.42.53.png

動いた!

無事Androidも0から構築してElixirDesktopのアプリを起動できました!

スクリーンショット 2023-10-07 21.12.11.png

4
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
6