20190522のAndroidに関する記事は6件です。

FlutterでWebアプリが書けるようになった

https://9to5google.com/2019/05/07/flutter-apps-web-desktop-more/
↑が原文です。変なところあればご指摘いただけるとありがたいです。

Flutter doubles down on ‘write once, run anywhere’ apps w/ web, desktop, more

おおよそ全ての開発者は、モバイルアプリとウェブアプリ・デスクトップアプリ間で統一をもたせる、あるいはそれを維持することの難しさはよく知るところだろう。FlutterはiOSアプリとAndroidアプリの間のギャップを埋めるフレームワークとして知られているが、この度その領域をWebまで広げ、「一度書けばどこでも動く(write once, run anyware)」の夢へと更に近づいた。

これまでFlutterは、iOS/Android関係なく同じ見た目・振る舞いをするモバイルアプリを作るためのGoogleのフレームワークとして知られていた。昨年行われたFlutterの最初のイベントで、GoogleはFlutterをモバイルアプリ領域を飛び越えてWebまで飛躍するためのHummingbirdプロジェクトを公開した。

Google I/O 2019では、Googleは「FLutterはモバイルアプリだけ」という概念を取り払おうとしている。GoogleはFlutterを”portable UI toolkit”として企業や開発者がプラットフォームごとにアプリを作るのではなく、アプリを1度作るだけで全てのプラットフォームで動作することを可能にしようとしている。そのために、Webアプリやデスクトップアプリ、そしてRaspberry Piのような組み込み系のプラットフォームでも動作することを目標にしている。

本日のイベント前に、私は小規模なFlutter製Webアプリを自分のマシンにインストールしてもらう機会を頂いた。そのアプリを触ってみて私が最初に気づいたことは、動作速度の速さである。これは、Dartで書かれたFlutterがJavascriptのようなWeb言語にコンパイルされているから実現できることである。下記画像のように、ブラウザ上でネイティブアプリのように読み込まれる。

image.png

Flutter製Webアプリの印象的なところはもう一つある。それは、Hummingbirdデモであったスライドパズルのごとくアニメーションがヌルヌル動くことである。4K Lenovo Yoga Chromebookやthe Pixel 3で動かしてみても、動作が遅くなることはなかった。

デスクトップアプリの観点から見ても、Flutterの成長速度は著しいものである。Flutter製アプリはChromeOSのサポートの甲斐あってChromebookでもすでによく動作するが、キーボードやマウスのサポートはここ数ヶ月で少し落ち込んでいる。Windows、Mac、Linuxなどのコンピュータで動作させることが優先されているため、これらのサポートは先に持ち越されているのだ。

ゆくゆくは、GoogleはFlutterがおよそ全てのデバイスで動作させることを目標にしている。一つのFlutter製アプリが複数のプラットフォームで動作するのだ。”scenarios including home, automotive and beyond.”

Googleはあらゆるアプリ開発技術の中でFlutterを一番にしようとしている。あなたの作りたいアプリがAndroid向けであれ、iOSであれ、ChromeOS、Windows、Web、IoTその他なにであろうと関係ないのである。これは驚異的なことだ。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

WebViewとネイティブのメリットデメリット

App内で動作する簡易ブラウザ(WebView)と通信からUIまでアプリ内で完結するネイティブアプリのメリットデメリットをまとめてみました。

■WebViewのメリット・デメリット

メリット

  • コストが安い
    Web上にコンテンツがあり、それを流用したい場合、新規で開発することなく同じコンテンツを表示できます。

  • アプリストアへの申請・審査の不要
    ネイティブアプリの申請や審査が不要になります。
    ※ただし、部分的なWebView表示の場合ネイティブと変わらないので審査は必要です。

  • アプリアップデートの不要
    アプリの保守をする必要がないため、アプリ自体のアップデートがありません。
    ※仕様が変更された時は、アプリの動作を変更する可能性があるため、一概ではないです。

デメリット

  • 課金ができない
    Apple Store審査ガイドラインの規約違反になり、リジェクトされる恐れがあります。
    ※Webブラウザでの課金は可能(リンクをブラウザへ飛ばす必要あり)
    引用:Apple Store審査ガイドライン:3.1.1 App内課金

  • レスポンシブ対応
    画面解像度に応じたUI設計の対応が必要。
    Webの表示をそのまま、アプリに適応させると表示崩れが起こる可能性がある。

  • 脆弱性

    1. 自社以外の外部アクセス

      URL直打ちで、自社ではないサイトにアクセスできる場合、悪用される恐れがある。
      外部アクセスできるサイトに制限を設ける必要がある。

    2. 端末内のファイルストリームにアクセスできる

      「file://」で始まるURI(※URLではない)でアクセスすることで、端末内部のファイルにアクセスすることができる。
      アクセスログからユーザー情報が漏洩されてしまう。

    3. iOSは、Cookieを保存しない

      アプリが終了するとCookieが消えるので、永続化する必要がある

    4. XSSの恐れ

      攻撃スクリプトを入力できてしまう。
      サニタイジング(無害化)する必要がある。

  • 通信エラー場合の表示
    通信エラー場合のWebView画面を表示する前に、レスポンスチェックを行い、ネイティブの別画面を表示させる。
    WebViewは通信した結果の表示なので、真っ白になったり、URLエラーになったりしてUI的に作りが悪い。

  • 環境によっては表示が遅い
    Web通信が伴うため、通信環境によっては表示が遅い場合があります。

  • 導線が不可能
    WebViewの誘導はできない。
    alt

■ネイティブ画面のメリット・デメリット

メリット

  • Webではできない画面設計・操作設計ができる
    アプリ内のUIを使用するため、自由に画面設計を行うことが可能

  • 導線設計が可能
    アプリに誘導できる

  • セキュリティ
    URL直打ちではないため、外部のサイトへアクセスさせることはないです。

デメリット

  • コストが高い
    iOS / Androidの開発者が必要
    開発言語が異なるため、それぞれの開発者が必要

  • アプリストアへの申請・審査
    ネイティブアプリの申請や審査が必要です。

  • アプリアップデートが必要
    保守でアプリ修正があった場合は、アップデートが必要

  • 課金
    App内課金を(30%)手数料(30%)が発生する

引用

https://appbu.jp/webapps-nativeapps
https://ja.developer.box.com/docs/android-security-guidelines
https://appkitbox.com/knowledge/android/20130819-84
https://teratail.com/questions/100872
https://mexess.blog/2018/08/03/post-304/
https://qiita.com/noboru_i/items/240ffcb2036f3b5cbc3b
https://qiita.com/noboru_i/items/bc39d95638e9e55437fa#cookie%E3%81%AE%E8%A8%AD%E5%AE%9A
https://qiita.com/i_nak/items/be0fac91bdc68aa165db
https://backapp.co.jp/blog/11594/
https://support.ebis.ne.jp/search_service/15033/

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

そろそろ Kotlin Coroutine をちゃんと使ってみよう

TL;DR

launch {
    val result = opTakesLongEnoughToHaveMyNeckLong()
    showOffResult(result)
}

そろそろ、 Kotlin でも非同期処理はこんな風に書きたい時代


ならば、実際にそう書いてみよう!


そもそもコルーチンとは

みたいな話をし始めると日が暮れてしまったり居眠りしてしまったり首が伸びてしまったりするので、ばっさり割愛。

(JVM レベルにどう落とし込まれているかなど、興味深い話題もありますが)


導入

今回は、実践的なものを作ってみるという趣旨から、 Android アプリをターゲットとしてみます。

ということで、まずは app/build.gradle でコルーチン関連の基礎的なライブラリへの依存関係を追加します。

dependencies {
    // ...
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.2.1'
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.2.1'
    // ...
}

※バージョンは本稿執筆時点(2019/05/22)の最新


なぜライブラリ?

なぜでしょうね?

言語としての Kotlin には非同期処理を表現するに足る最低限のキーワードのみ持たせて、実装はプラットフォームによって自由にすげ替えることのできるような構造を目指した、といったところでしょうか。


実践

非同期といえば?

通信!

ですよね。
ということで、 Java 系界隈で REST API を抽象化するライブラリとしては定番中の定番、 Retrofitを組み合わせてみます。


app/build.gradle に追加するのはこんな感じ。

dependencies {
    implementation 'com.squareup.retrofit2:retrofit:2.5.0'
    implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2'
    implementation 'com.squareup.retrofit2:converter-gson:2.5.0'
}

ちなみに、 converter-gson は、これも定番の JSON パーサー gson を Retrofit から透過的に使えるようにするためのもの。コルーチンを使う使わないには関係しないので、別のパーサーの方が好きならばそちらを使えばいいです。


REST API を Kotlin の interface

Retrofit は、アノテーションをベースに REST API を記述するタイプのライブラリです。
例えば、某サービスのログイン API はこのような記述になります。

import com.google.gson.annotations.SerializedName
import kotlinx.coroutines.Deferred
import retrofit2.http.POST
import retrofit2.http.Query

interface Login {
    @POST("/api/member/logins")
    fun login(
        @Query("mail") mail: String
        , @Query("password") password: String
        , @Query("keep_me") keepMe: Boolean
    ): Deferred<LoginResult>
}

data class LoginResult(
    @SerializedName("r") val resultCode: String
    //, @SerializedName("d") val resultData: LoginData?
)

本当は login(...) そのものを suspend fun にしてしまいたいところですが、 Retrofit は直接対応していないので、 CoroutineCallAdapterFactory の助けを借りつつ、一旦 kotlinx.coroutines.Deferred<T> を返すものとして宣言してしまいます。


Deferred<T> を返す Retrofit のメソッドを suspend fun に仕立て上げる

object ApiService {
    private val okHttpClient = OkHttpClient.Builder()
        // ...
        .build()

    @PublishedApi internal val retrofit = Retrofit.Builder()
        .baseUrl("https://api.example.com")
        .addConverterFactory(GsonConverterFactory.create())
        .addCallAdapterFactory(CoroutineCallAdapterFactory.invoke())
        .client(okHttpClient)
        .build()

    suspend inline fun <reified T, reified U> request(block: T.() -> Deferred<U>): U =
        block(retrofit.create(T::class.java)).await()
}

これで、 val res = ApiService.request<Login, LoginResult> { login("aaa", "pass", keepMe = true) } のような記法が可能になります。


suspend fun とそうじゃないコードとのつなぎ目

CoroutineScopeCoroutineContext と...

:confused:

通常の fun から suspend fun を直接呼ぶことはできないので、一番最初に非同期処理を始めるところはどう書けばいいんだ? となってしまいます。

CoroutineScope インタフェースを実装したオブジェクトなら launch などの suspend なブロックを取れるメソッドが使えるようになるので、なんとかして手頃なオブジェクトに CoroutineScope になってもらう作戦になります。


単純な例: ActivityCoroutineScope になれればいいじゃない

class MainActivity: Activity(), CoroutineScope {
    // onDestroy() で 未完了のコルーチン呼び出しをまとめて cancel しまくるための CoroutineContext
    private val supervisor = SupervisorJob()

    // CoroutineScope になるためにはこの抽象プロパティーを実装しないといけない
    override val coroutineContext: CoroutineContext
        // メインスレッドを表すグローバルな CoroutineContext と、 supervisor スコープを合成した CoroutineContext が、
        // CoroutineScope としてのこの Activity の CoroutineContext となるようにする。
        get() = Dispatchers.Main + supervisor

    override fun onDestroy() {
        super.onDestroy()
        // 未完了のコルーチン呼び出しをまとめて始末
        supervisor.cancelChildren()
    }

    // ...
}

これで、 Activity の中なら launch その他が使えるようになりました。


いよいよ、 onClickListener から接続!

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        loginButton.setOnClickListener {
            performLogin()
        }
    }

    private fun performLogin() {
        val mail = mailForm.text.toString()
        val password = passwordForm.text.toString()

        launch {
            // ここからコルーチンの世界!
            val loginResult = ApiService.request<Login, LoginResult> {
                login(mail, password, keepMe = false)
            }
            if (loginResult.resultCode != "00101") {
                // 失敗
                return@launch
            }
            // ...
        }
    }

その他

例外? 例外!

コルーチンの中で例外が起きたらどうする? 問題。

端的には、 suspend fun 本体から別の suspend fun を呼んでいて、その中で例外が起きうる時にその例外を捕まえたければ、通常の同期的な書き方と同じ要領で普通に try {} catch {} でくるめば意図通りの意味になります。

    val loginResult = try {
        ApiService.request<Login, LoginResult> {
            login(mail, password, keepMe = false)
        }
    } catch (e: IOException) {
        Log.d(TAG, "login failed", e)
        return@launch
    }
    // ...

いくつかのリクエストを並行させたい

CoroutineScope#awaitAll() など。キャンセル可能にする範囲を細かく管理したい場合はまた違った戦略が必要ですが、単純なケースならこれで十分でしょう。

    awaitAll(
        async { foo() }
        , async { bar() }
        , async { baz() }
    )

Rx とどちらがいいか?

そもそも基本思想が違うので、適材適所といったところ。

  • 複雑なタイミング制御などが必要なら、データフローを宣言的に記述できる Rx が有利。
  • シンプルな一連の非同期処理だけなら、逐次処理に近い記法で記述できるコルーチンが有利。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AdMob for Unity の更新でハマったこと

前提

現象

Android実機で起動したとたんにクラッシュしました。
見た感じ「起動したハズなのに何も起きない」という体です。
端末側で起動すると、OSが「さっきからクラッシュしてるんだけど?」と言ってきます。

必要だったこと

導入案内に「AdMob アプリ ID を設定する」と書かれている通りにする必要がありました。
ご丁寧に「設定しないと17.xではクラッシュする」と書かれています。

Assets/Plugins/Android/GoogleMobileAdsPlugin/AndroidManifest.xml
<manifest>
    <application>
        <!-- Your AdMob app ID will look similar to this
        sample ID: ca-app-pub-3940256099942544~3347511713 -->
        <meta-data
            android:name="com.google.android.gms.ads.APPLICATION_ID"
            android:value="[YOUR_ADMOB_APP_ID]"/>
    </application>
</manifest>
  • テスト広告を使う分には、[YOUR_ADMOB_APP_ID]の部分を直上のsample IDに置き換えて、android:value="ca-app-pub-3940256099942544~3347511713"/>とすればOKです。
  • 本番広告では、AdMobコンソールで生成された実際のアプリIDにする必要があります。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Unity個人的メモ】Androidプロジェクトをビルド時に出力する。

Gradleで躓いたのでどこに原因があるのか調べたく、
--infoとか--debugとか--stackなどのオプションをつけて検証したかった。

Export Project
BuildSettingsのExportProjectにチェックを入れるとAndroidのプロジェクトを出力してくれるらしい
スクリーンショット 2019-05-22 13.54.17.png

既に他の方の記事もあった
リンク

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

UnityでSQLiteをAndroid(64bit対応)向けに導入する

概略の手順

  • 公式サイトから最新版を取ってきます。
  • パッケージ内のプラグイン名に合わせてDllImportの引数を書き換えるか、逆にAARパッケージ内のプラグイン名を変更します。
  • AARパッケージのままで、"Assets\Plugins\sqlite3\Android"とかに配置します。

留意点

  • AARパッケージはZIPファイルなので、ファイル名を「sqlite-android-xxxxxxx.aar.zip」などと変更することで内部にアクセスできます。
  • パッケージからフォルダ構造ごとファイルを取り出してAssetフォルダに配置することも可能ですが、そのままのファイル名だとビルドの際にUnityの同名チェックに引っかかります。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む