はじめに
Androidアプリの中にWebサーバを設置する方法として、Nanohttpdを利用した方法を記載する。
1. 開発環境
- macOS High Sierra ver.10.13.6
- node.js: ver.10.15.1
- npm: ver.6.8.0
- yarn: ver.1.13.0
- vue.js (vue cli): ver.3.4.0
- Google Chrome (macOS): ver.74.0.3729.131
- Android Studio: ver. 3.3.1
- Androidスマホ:Moto G5 Plus (Android 8.1.0)
2. Kotlinの新規プロジェクトを作る
今回はKotlinベースのAndroidアプリを作る。
2-1. 新規プロジェクト作成 (Android Studio)
- Android StudioのFile -> New -> New Project... -> Empty Activity を選択、"Next"をクリック
- 適当な名前をつける。ここでは"AndroidWebApp"とした。
- Languageに"Kotlin"を選択。
- Minimum API level は "API 25: Android 7.1.1 (Nougat)"とした。動作させたいOSに合わせる。
- "Finish"をクリック
2-2. 必要なライブラリをインポートする
- Gradle Scripts -> build.gradle (Module: app)のdependenciesに、以下のように
implementation 'org.nanohttpd:nanohttpd-webserver:2.3.1'
を追記。
AndroidWebApp/app/build.gradleの一部
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
implementation 'org.nanohttpd:nanohttpd-webserver:2.3.1'
}
- Fileメニュー -> New -> Folder -> Assets Foldersを選択し、assetsフォルダーを作成する。
2-3. Webアクセスを有効にする。
AndroidManifest.xmlでACCESS_NETWORK_STATEとINTERNETを有効にする。
AndroidWebApp/app/src/main/AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="jp.mktshhr.androidwebapp">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>
2-4. NanoHTTPDの設定
ここではポート8888で待ち受けるHTTPDを設定する。
NanoHTTPDを継承したWebServerクラスを作り、serveをオーバーライドする。以下にコードをあげるが、必要に応じてMIMEタイプを追加すること。
AndroidWebApp/app/src/main/java/jp/mktshhr/androidwebapp/MainActivity.ktの一部を抜粋
package jp.mktshhr.androidwebapp
import android.content.Context
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import fi.iki.elonen.NanoHTTPD
import java.io.IOException
import java.io.InputStream
class MainActivity : AppCompatActivity() {
private var context: Context? = null
private var webServer: WebServer? = null
private val PORT = 8888
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
this.context = applicationContext
this.webServer = WebServer(applicationContext)
try {
this.webServer?.start()
} catch (e: IOException) {
e.printStackTrace()
}
}
override fun onDestroy() {
super.onDestroy()
if (this.webServer != null) {
this.webServer?.stop()
}
}
private inner class WebServer(private val context: Context) : NanoHTTPD(PORT) {
override fun serve(session: IHTTPSession): NanoHTTPD.Response {
try {
var uri = session.uri
if ("/".equals(uri)) {
uri = "index.html"
}
var filename = uri
if (uri.substring(0, 1).equals("/")) {
filename = filename.substring(1)
}
Log.d("AppInfo", filename)
val asssets = context.getResources().getAssets()
var fis: InputStream? = null
try {
fis = asssets.open(filename)
} catch (e: Exception) {
Log.d("AppErr", "File openning failed")
}
if (uri.endsWith(".ico")) {
return NanoHTTPD.newChunkedResponse(Response.Status.OK, "image/x-icon", fis)
} else if (uri.endsWith(".png") || uri.endsWith(".PNG")) {
return NanoHTTPD.newChunkedResponse(Response.Status.OK, "image/png", fis)
} else if (uri.endsWith(".jpg") || uri.endsWith(".JPG") || uri.endsWith(".jpeg") || uri.endsWith(".JPEG")) {
return NanoHTTPD.newChunkedResponse(Response.Status.OK, "image/jpeg", fis)
} else if (uri.endsWith(".js")) {
return NanoHTTPD.newChunkedResponse(Response.Status.OK, "application/javascript", fis)
} else if (uri.endsWith(".css")) {
return NanoHTTPD.newChunkedResponse(Response.Status.OK, "text/css", fis)
} else if (uri.endsWith(".html") || uri.endsWith(".htm")) {
return NanoHTTPD.newChunkedResponse(Response.Status.OK, "text/html", fis)
} else if (uri.endsWith(".map")) {
return NanoHTTPD.newChunkedResponse(Response.Status.OK, "application/json", fis)
} else {
return NanoHTTPD.newFixedLengthResponse(Response.Status.NOT_FOUND, "text/plain", uri)
}
} catch (ioe: IOException) {
ioe.printStackTrace()
return NanoHTTPD.newFixedLengthResponse(Response.Status.INTERNAL_ERROR, "text/plain", ioe.localizedMessage)
}
}
}
}
- ビルド(Build -> Make Project)してエラーが出ないことを確認する
3. Webアプリのプロジェクト("android-web-app")作成
別途、Webアプリを開発し、Androidアプリに組み込む。
ここではVueのデフォルトページをそのまま利用する。
3-1. vue create
-
vue create android-web-app
もしくはvue ui
でGUI付きセットアップ - Preset選択では
default (babel, eslint)
を選ぶ(そのままリターンすれば良い) cd android-web-app
- 特に断りがないときはandroid-web-appフォルダでnpmやyarnなどのコマンドを叩く。
3-2. AndroidアプリへのアクセスによるWebページの表示
yarn run build
- プロダクション用のhtmlファイル一式をdistフォルダに出力する。
- distフォルダの中身をKotlinプロジェクトのassetsフォルダ(AndroidWebApp/app/src/main/assets/)にコピーする。
- Android Studioでビルド(Build -> Make Project)してAndroidスマホにブラウザからアクセス( http://ipaddress:8888/ )する。AndroidスマホのIPアドレスは
adb shell ifconfig
でわかる。 - AndroidスマホのChromeで "http://localhost:8888/" を表示した例 ↓
4. まとめ
vue cli3 を使ったWebアプリのサンプルをAndroidアプリに組み込み、Nanohttpdを使ったWebサーバーにアクセスすることで、スマホやPCのブラウザでWebアプリを表示することができた。