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

AWS Amplify+IAM未認証ユーザでAppSyncを使う(Androidアプリ)

参考

Web版があったので、こちらを参考にさせてもらいました。
https://qiita.com/youn4101/items/9bca97083f07d1b9af12

Android Studioのプロジェクトを新規作成

Basic Activityを作成し、ここに従う。
https://aws-amplify.github.io/docs/android/start

amplifyの設定

amplify configure
amplify init
amplify add api

? Please select from one of the below mentioned services GraphQL
? Provide API name: amplifyguestaccess
? Choose an authorization type for the API API key
? Do you have an annotated GraphQL schema? No
? Do you want a guided schema creation? Yes
? What best describes your project: Single object with fields (e.g., “Todo” with ID, name, description)
? Do you want to edit the schema now? Yes
Selected default editor not found in your machine. Please manually edit the file created at /Users/tatsiida/Documents/mobile/AmplifyGuestAccess/amplify/backend/api/amplifyguestaccess/schema.graphql
? Press enter to continue 

ひとまず先述のURL( https://aws-amplify.github.io/docs/android/start
)をもとに、API Key でToDoのサンプルが動作するようにする。

Cognito, IAM

ここでは、 Cognito サービスにて、 amplify_guest_access というID プールを作成。
認証されていない ID に対してアクセスを有効にする にチェックを入れる

作成されたロールを確認し、 認証されていないロールCognito_amplify_guest_accessUnauth_Role)をIAMにて編集し、AppSyncへのアクセスを許可する。

{
    "Effect": "Allow",
    "Action": [
         "appsync:GraphQL"
    ],
    "Resource": "*"
}

awsconfiguration.json

    "CredentialsProvider": {
        "CognitoIdentity": {
            "Default": {
                "PoolId": "ap-northeast-1:b2bddc1a-f3bb-41fb-a93d-975f92457207",
                "Region": "ap-northeast-1"
            }
        }
    },
    "AppSync": {
        "Default": {
             (中略)
            "AuthMode": "AWS_IAM"
        }
    }

MainActivity.kt

        val credentialsProvider = CognitoCachingCredentialsProvider(applicationContext, AWSConfiguration(applicationContext))

        mAWSAppSyncClient = AWSAppSyncClient.builder()
            .context(applicationContext)
            .awsConfiguration( AWSConfiguration(applicationContext))
            .credentialsProvider(credentialsProvider)
            .build()

        runMutation() // 任意の場所で

上記が実行されると、DynamoDBのTableにItemが追加される。

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

複数Emulatorが起動している環境でApp Crawlerを使う

したかったこと

複数emulatorが起動している環境でApp Crawlerを用いようとしたら

Exception in thread “main” androidx.test.tools.crawler.launcher.exceptions.AdbExecutionException: More than one device/emulator found. Use ‘--device-name’ or ‘--device-serial-code’ option to pick a specific device/emulator.
       at androidx.test.tools.crawler.launcher.util.AdbExecutor.getDeviceSerialCode(AdbExecutor.java:155)
       at androidx.test.tools.crawler.launcher.util.AdbExecutor.<init>(AdbExecutor.java:26)
       at androidx.test.tools.crawler.launcher.CrawlLauncher.<init>(CrawlLauncher.java:88)
       at androidx.test.tools.crawler.launcher.CrawlLauncher.main(CrawlLauncher.java:93)

とエラーが出てしまった。

何かオプションをつけろとログでは言っている割に、公式ドキュメントにはこのオプションの説明が見当たらなくて、ハマってしまったのでメモ。

解決法

--device-serial-code  動かしたい端末のserialナンバー

をオプションに与える。

  • エラーログでは--device-nameオプションか--device-serial-codeオプションを要求されているが、自分の環境(app crawler : Beta1.1 / PC : MacOS Mojave バージョン10.14.4)では--device-nameオプションは正常に動かなかった。 (serial numberを与えても、端末名を与えてもエラーが出た。)

コード例

# 起動しているデバイスを取得
# 1行目はのぞいて[端末名 device]となっているコマンド結果から端末名だけ取り出して配列に入れる
DEVICES=$(adb devices | sed “1d;s/[[:space:]].*$//“)
echo $DEVICES

DATE=date +%y-%m-%d_%H-%M-%S

# 1台ずつapkをインストールしてテストを行う
for device in $DEVICES
do
   #app-crawlerのapp-crawlerオプションはちゃんと動かなかったのでインストールは個別に行う
   adb -s $device install ./app/build/outputs/apk/debug/hogehoge-debug.apk
   # 結果を収納するフォルダを作成して移動
   mkdir -p AppCrawler_${device}/${DATE}
   cd ./AppCrawler_${device}/${DATE}
   java -jar ../../app-crawler/crawl_launcher.jar --android-sdk $ANDROID_HOME --device-serial-code $device --app-package-name com.kkkkan.hogehoge 

done

その他・感想

--apk-fileオプションも、実機ならちゃんと動いたけどemulatorだと全然動かなくて先に adb -s installでapkをインストールしてから--app-package-nameオプションで別途app crawlerを起動しなきゃいけなかったし、betaというだけあってまだまだバグだらけっぽい。

参考にしたもの

https://qiita.com/kafumi/items/fff5324a4bb70b7b7986
https://qiita.com/muran001/items/ffd56f8a7dd76e3968fb

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

[Kotlin用DIフレームワーク] Koinの使い方

Koinとは

タイトルにモロに書いてありますが、

A pragmatic lightweight dependency injection framework for Kotlin developers.
Kotlin開発者向けの実用的な軽量のDI(依存性注入)フレームワーク

です。
Daggerと比べてわかりやすいと感じたので、備忘録のために使い方をまとめます。

使い方(3ステップ)

1. Moduleを宣言する

// いくつかのクラスがあるとします。
class Controller(val service : BusinessService) 
class BusinessService() 
class MyViewModel(context: Application) : AndroidViewModel(context)

// DSLでそれらを指定してモジュール宣言をするだけです。
val myModule = module { 
  factory { Controller(get()) } 
  single { BusinessService() } 

  viewModel { MyViewModel(androidContext() as Application) }
} 

modulefactory などの書き方はDSLですので、詳しくはこちらを参照してください。

コンストラクタがある場合は、get()を使用します。
注入したいクラスをSingletonにしたい場合はsingleを使用します。

また、ViewModelの宣言はDSLでviewModel と指定できます。このとき、Activity-Fragment間でViewModelを共有したいときに用いるAndroidViewModelを継承しているViewModelの場合はコンストラクタにApplicationを持っているので、get()でもいいですが、androidContext() as Applicationでも取得できるらしいので、こちらを使っておきます。

モジュールを宣言する場所は、例えば、diなどのパッケージを切って、その中にmyModuleなどのファイルを作成してその中でトップレベルプロパティとして宣言するのが良いかと。

2. Koinを開始する

class MyApplication : Application() {
  override fun onCreate(){
    super.onCreate()
    // `startKoin` でKoinを開始します。
    startKoin {
      // Contextを宣言し、
      androidContext(this@MyApplication)
      // 先ほど宣言したmoduleを指定します。
      modules(myModule)
    }
  } 
} 

Applicationクラスを継承したクラス内で色々と宣言して startKoin するだけです。

3. インスタンスを注入する

// あとは、Activity内でインスタンスを注入するだけです。
class MyActivity() : AppCompatActivity() {

    // serviceプロパティの中へBusinessServiceを遅延注入する。
    val service : BusinessService by inject()
    val viewModel: MyViewModel by viewModel()

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

        // もしくは、直接インスタンスを取得する。
        val service : BusinessService = get()
    }
}

最後は、 by inject() でインスタンスを注入するだけです。
そのプロパティが初めて呼ばれたときに注入しています。
もしくは、get() でも取得可能なのですね。(知らなかった)

viewModelを注入したいときは、by viewModel()を使用します。
DI前だったら
lateinit var vm: MyVeiwModelからの
vm = ViewModelProviders.of(this).get(MyViewModel::class.java)
とかわんさかコード書かないといけなかったから、これがなくなるのは助かる。

Activity, Fragment, Service以外のクラスで注入したい場合は、KoinComponentインターフェースを実装すれば、注入できるようになります(by inject() が使用できるようになります)

終わりに

Koin万歳!!!
Daggerも慣れてしまえば問題ないですが、Koinより学習コストが高めなので(私の理解力の問題かも...)、Daggerを使用しなければならない場合以外は、Koin使いたいなぁ。

※追記

と思っていましたが、シニアエンジニアの方にKoinは動作が遅いということをお聞きし、調べてみました。次のサイトで様々なDIフレームワークでのパフォーマンスを調べてくれてました。
https://github.com/Sloy/android-dependency-injection-performance
Dagger2めちゃくちゃ早くて驚いた。。。
これは学習コスト云々以前に、一人のエンジニアとして品質の面でDaggerを使うべきだなと感じさせられました。

参考(というか公式)

https://insert-koin.io/

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

WebRTCとWebViewの組み合わせについて

WebRTCとWebViewの組み合わせについて

Android/iOS共にWebViewでWebRTCの映像が受信できるか検証してみました。
WebRTCの配信基盤にはagora.ioを利用します。

前提

今回の検証についてはWebView側からの映像送信は試しません。
getUserMedia()あたりが正常に動作しないとの記事をいくつか見かけている為です。
(もしかしたら動いているかもしれませんが)
agora.ioでの動作状況については以下にドキュメントがあります。
https://docs.agora.io/en/faqs/web_on_mobile

環境

配信側ブラウザ:Chrome76.0.3809.132
視聴側デバイス:
・Huawei P20 lite/Android9.0
・Xperia Z5 Premium/Android5.1
・iPhone7/iOS12.4.1
SDK:agora.io WebSDK2.8.0

WebRTCの実装

配信側、受信側共にagora.ioのWebSDKを利用しています。
サンプルコードはこちら
host.html:配信側
audience.html:受信側(WebView側で指定するページ)

ポイントとしてはコーデックをVP8に指定しているところです。
廉価版のHUAWEI端末ではH264のデコードに対応していないもの(P20 lite等)がある為です。
尚、iOSのSafariは12.1からVP8対応しております。

host.html
  client = AgoraRTC.createClient({mode: 'live',codec:'vp8'});

Androidの実装

Androidについての実装については特に難しい点はありませんでした。
Webアプリケーション側の修正をすぐに反映させる為にsetCacheMode(WebSettings.LOAD_NO_CACHE);にてキャッシュを読み込まないようにする程度かと思います。
サンプルコードはこちら

MainActivity.java
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        WebView  myWebView = (WebView)findViewById(R.id.webView1);

        myWebView.setWebViewClient(new WebViewClient());
        myWebView.setWebChromeClient(new WebChromeClient());
        myWebView.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE);

        myWebView.loadUrl("URL");//URLはaudience.htmlを指定

        myWebView.getSettings().setJavaScriptEnabled(true);

    }

結果画面

特に問題なく動作しています。

iOSの実装

サンプルコードはこちら
一番最初はWKWebViewを利用して試していました。
「iOS」「WebView」で検索するとよくヒットします。
結果としてはこのWKWebViewでは映像の視聴はできませんでした。
(やる方法はあるのかもしれませんが。。。)

ViewController.swift
let config = WKWebViewConfiguration()
config.allowsInlineMediaPlayback = true
let webView = WKWebView(frame: self.view.frame, configuration: config)

一応、javascriptは動いているようですが、映像が映りません。またalert()等も動作せず、少しデバッグがしにくい状況でした。
そこで、SFSafariViewControllerというものを見つけたのでこれで動作確認をしてみました。

ViewController.swift
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        let url = URL(string:"URL")
        if let url = url{
            let vc = SFSafariViewController(url: url) //URLはaudience.htmlを指定
            present(vc, animated: true, completion: nil)
        }
    }

このコードで無事に動作しました。

結果画面

「SFSafariViewControllerはiOS9.0以降使用可能でSafariの標準機能を備えたViewControllerを作成できます。」とのこと。

最後に

実際にプロダクトとして利用するには他の問題があるかもしれませんが、ひとまずWebViewでの映像再生は可能ということが分かりました。


agora.ioに関するお問い合わせはこちらから
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Android WebView から別アプリを開く

AndroidのWebViewではtelmailtoなどのリンクを踏んでも電話アプリやメールアプリが起動しないためそれに対応するための実装が必要です。

また暗黙的Intentに対応する場合も、そのための実装が必要です。

これらの実装方法を紹介します。

URL判定処理

URLからIntentを作成します。

class WebViewRequestValidation {
    fun isTransitionToOtherApp(url: Uri): TransitionToOtherApp {
        return when (url.scheme) {
            "tel" -> TransitionToOtherApp.Yes(Intent(Intent.ACTION_DIAL, url))
            "mailto" -> TransitionToOtherApp.Yes(Intent(Intent.ACTION_SENDTO, url))
            "intent" -> TransitionToOtherApp.Yes(Intent.parseUri(url.toString(), Intent.URI_INTENT_SCHEME))
            else -> TransitionToOtherApp.No()
        }
    }
    sealed class TransitionToOtherApp {
        class Yes(val intent: Intent):TransitionToOtherApp()
        class No():TransitionToOtherApp()
    }
}

Intent起動処理

上記で作成したIntentを起動します。処理の流れは以下の通りです。

  • 対象Activityが存在すれば、startActivityを実行する。
    • 存在しないActivityをstartActivityしてしまうと、アプリがクラッシュします。
  • intentからbrowser_fallback_urlが取得できれば、そのURLを表示する。
  • GooglePlayStoreアプリがアクティブであれば(存在すれば)、GooglePlayStoreアプリを起動する。
  • 上記全てがヒットしない場合、https://play.google.com/store/apps/details?id=$applicationIdを表示する。

最後のhttps://play.google.com/store/apps/details?id=$applicationIdは、表示してもアプリをダウンロードできないので、なにかユーザにメッセージを表示するほうが適切かもしれません。

class ImplicitIntentStarter(private val wContext: WeakReference<Context>) {

    fun startActivity(intent: Intent) {
        val context = wContext.getNullable() ?: return
        val packageManager = context.packageManager

        if (intent.resolveActivity(packageManager) != null) {
            context.startActivity(intent)
            return
        }

        val fallbackUrl = intent.getStringExtra("browser_fallback_url")
        if (fallbackUrl != null) {
            // ブラウザ起動など
            return
        }

        startGooglePlayStoreActivity(intent.`package`)
    }

    fun startGooglePlayStoreActivity(applicationId: String) {
        val context = wContext.getNullable() ?: return
        val packageManager = context.packageManager

        val marketIntent = Intent(Intent.ACTION_VIEW).setData(Uri.parse("market://details?id=$applicationId"))
        if (marketIntent.resolveActivity(packageManager) != null) {
            context.startActivity(marketIntent)
            return
        }
        context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("https://play.google.com/store/apps/details?id=$applicationId")))
    }
}

// WeakReferenceから取得する値を null safe に扱うための拡張です。
fun <T> WeakReference<T>.getNullable(): T? = get()

WebViewのリクエストをインターセプト

最後に、WebViewのリクエストをインターセプトし、上記の処理を実装します。

    private val validation = WebViewRequestValidation()
    private val implicitIntentStarter = ImplicitIntentStarter(wContext)
    val webViewClient = object : WebViewClient() {
        override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean {

            // WebViewの画面遷移をさせたくない場合は true を返す。
            val allowedRequest = false
            val notAllowedRequest = true

            val urlString = url ?: return notAllowedRequest
            val url = Uri.parse(urlString)

            validation.isTransitionToOtherApp(url).let {
                when (it) {
                    is WebViewRequestValidation.TransitionToOtherApp.Yes -> {
                        implicitIntentStarter.startActivity(it.intent)
                        return notAllowedRequest
                    }
                    is WebViewRequestValidation.TransitionToOtherApp.No -> {}
                }
            }

            return allowedRequest

        }
    }

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

Raspberry Pi 3B+でAndroidOSを起動させる時の注意点

Raspberry Pi 3B+でAndroidOSを起動させる時の注意点

ラズパイでAndroidOSを起動する記事や情報が多く流れています。
どの記事を読んでも公開されているOSイメージをコピーして起動するだけというものでした。
そしてすんなりと起動できています。

これは私もRaspberry Pi 3B+を購入したのでAndroidOSが動作するか確認していた時のお話です。

USBブートの罠

「Raspberry Pi 3B+はUSBブートが標準で対応」というメリットが今回非常に足をひっぱりました。
最初に購入したラズパイが3B+なので、OSの起動はUSBスロットからという固定観念をもっていました。

これが迷宮への誘いでした。。。

試したAndroidOSの数々

・LineageOS
・emteria.OS
・Android Things
・その他野良のようなOS
これらのOSイメージをSDに焼いて、毎回USBブートしていましたが、、、
・そもそも画面に何も映らない
・レインボー画面から遷移しない
・何かしらファイルを探しているプロンプトだけが出る
という状況でした。

困った時は

こんな状況でほぼ諦めかけていましたが、他の人に状況だけ雑談しているときにあるページを見つけました。
そこには、「オペレーティングシステムの読み込みおよびデータ保存用のMicro SDポート」なるものが3B+にあるとのこと。
声に出して誰かに伝えると自分で突然解決法が見つかる法則が発動しました。

そもそも

Raspberry Pi 3B以前から利用している方からすれば当たり前のポートかもしれません。
私の運が悪かったのは「Raspberry Pi 3B+はUSBブートが標準で対応」=USBブートのみ という概念を持ってしまったからです。

ということで、、、

Micro SDポートに素直にカードを挿して、LineageOSは無事に動作しました。あといくつかの野良OSも起動しました。
(Android Thingsはまだ未対応のようです)

やりたい事

AndroidOSが無事に稼働したので、agora.io SDKを利用した映像配信システムを構築しようと検討中です。

agora.ioに関するお問い合わせはこちらから
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む