20190816のAndroidに関する記事は13件です。

adbコマンドを使用してスマホで撮影した画像をローカルPCにコピーしてみた

やること

最近は画像系の仕事をする機会が多く、今回はスマホで撮影した画像をPC側に(自動で)送る方法を検証したので、その結果を記事にします。
android開発のお作法にのっとっていない可能性はありますが、とりあえずこの方法でできたという方法を記載します。

前提としてPCとスマホはケーブルでつながっていて、スマホで撮影した画像をPC側にコピーするところまでとします。
(本来はその後、PC側で撮影した画像を加工したり、機械学習で使用したりとするが、まずは撮影して画像を持ってくるところまで)

ちなみに、当職はandroidアプリの開発経験はなく、今回たまたまandroidのスマホで画像を取らなければならなくなった為にコマンドを調べていたという経緯があります。

環境

PC:windows10
Python 3.7.3
スマホ:Galaxy S8
Android:バージョン 9

コマンドの実行に関しては、基本的にPythonのNotebookで確認してます。(たまにコマンドラインで実行)
もし、うまく実行できないなどあれば教えてくださると助かります。修正します。

adbコマンドという外部からAndroid 端末と通信できるコマンドライン・ツールを用いてスマホ(Galaxy S8)を操作しました。

adbコマンドリファレンス
https://developer.android.com/studio/command-line/adb.html?hl=ja

準備

■モジュール

準備として使用しているPCでadbコマンドが打てるようになる必要があります
下記からモジュールをダウンロードしました

https://developer.android.com/studio/releases/platform-tools.html

上記モジュールはzipファイルなので解凍してパスを通してください
最終的にコマンドラインでadbコマンドが打てるようになっていればOKです

adbコマンド(コマンドラインから実行)
> adb
Android Debug Bridge version 1.0.41
Version 29.0.1-5644136
Installed as C:\work\util\adb-tool\platform-tools\adb.exe

global options:
 -a         listen on all network interfaces, not just localhost
 -d         use USB device (error if multiple devices connected)
 -e         use TCP/IP device (error if multiple TCP/IP devices available)
 -s SERIAL  use device with given serial (overrides $ANDROID_SERIAL)
 -t ID      use device with given transport id
 -H         name of adb server host [default=localhost]
 -P         port of adb server [default=5037]
 -L SOCKET  listen on given socket for adb server [default=tcp:localhost:5037]


<省略>

■設定
スマホ側の「開発者向けオプション」で下記設定をONにしました。
 ・USBデバッグ

■接続時
PCにスマホを接続すると下記の警告が出ますが、「許可」してください。

手順

下記の順番で検証していきます。

①カメラアプリを起動・停止
②撮影
③撮影した画像をPCにコピー
④スマホ内の画像を削除
⑤最後にコードまとめ

①カメラアプリを起動・停止

まずは、カメラアプリを起動します。
Pythonから外部プロセスを呼び出すにはsubprocessモジュールを使用するのが推奨されているらしく、その方法で実行しました。コマンドは引数を含めすべてをcmdの変数にいれて、最終的にsubprocess.callで実行しています。

他にも方法はたくさんあると思いますが、色々試した結果今回はcmdにコマンドを入れてから実行する方法で統一しました

カメラ起動
import subprocess

cmd = ('adb', 'shell', 'am', 'start', '-a', 'android.media.action.STILL_IMAGE_CAMERA')
subprocess.call(cmd)

うまくいけば、カメラアプリが起動しているはずです。

カメラの停止は下記で停止しました。
何故か"am" "stop"というコマンドがなく?カメラアプリのパッケージ名を調べて、それを停止しました。

カメラアプリのパッケージは下記のコマンドで確認しました。
cameraっぽい名前のアプリ(package:com.sec.android.app.camera)を指定して、"force-stop"したらうまく停止してくれました。

カメラアプリのパッケージ名確認(コマンドラインから実行)
> adb shell pm list packages | findstr camera
結果
package:com.sec.factory.camera
package:com.samsung.android.app.camera.sticker.facear.preload
package:com.sec.factory.iris.usercamera
package:com.samsung.android.app.camera.sticker.stamp.preload
package:com.sec.android.app.camera
カメラ停止
import subprocess

cmd = ('adb', 'shell', 'am', 'force-stop', 'com.sec.android.app.camera')
subprocess.call(cmd)

うまくいっていれば、起動したカメラアプリが停止しているはずです。

②撮影

次に撮影を行います。
撮影に関しても、cmdの変数にすべて入れてから実行します

カメラ撮影
import subprocess

cmd = ('adb', 'shell', 'input keyevent KEYCODE_CAMERA')
subprocess.call(cmd)

成功すると画像が撮影されているはずです。
ちなみに、カメラを起動していない状態でこのコマンドを実行すると、カメラが起動します。
起動コマンドと撮影コマンドが同じというのも気持ち悪いと思ったので、今回は起動と撮影のコマンドは一応分けています。

③撮影した画像をPCにコピー

次に撮影した画像をPC側にコピーします。

ちなみに、私のスマホでは下記の「/storage/self/primary/DCIM/Camera/」ディレクトリに画像が保存されていました。
(androidはGalaxy S8しか触ったことないので、他のAndroidスマホで同じ構成かどうかは不明。)

撮影された画像確認(コマンドラインで確認)
> adb shell ls -l /storage/self/primary/DCIM/Camera/
結果
total 16984
-rw-rw---- 1 root sdcard_rw 3595120 2019-08-16 18:18 20190816_181802.jpg
-rw-rw---- 1 root sdcard_rw 4586823 2019-08-16 18:36 20190816_183614.jpg
-rw-rw---- 1 root sdcard_rw 4590726 2019-08-16 18:36 20190816_183618.jpg
-rw-rw---- 1 root sdcard_rw 4613650 2019-08-16 18:36 20190816_183652.jpg

撮影された画像をPCにコピーします
lsで取得したファイル名がバイナリ?だったので、decodeなど色々工夫して1ファイルづつに分割しました
(もしかしたらうまくファイル名を取得できる方法があるかもしれませんが、今回はこの方法しか思いつかなかった・・・)

Camera配下を「*」にしているのはすべての画像を一度に持ってきたかったからです。
(特定の画像だけとか、lsのオプションによってうまくできると思います)

画像のファイル名取得
cmd=('adb', 'shell', 'ls', '/storage/self/primary/DCIM/Camera/*')
fnames=subprocess.check_output(cmd).decode('utf-8').rstrip().split('\r\n')
fnames
結果
['/storage/self/primary/DCIM/Camera/20190816_181802.jpg',
 '/storage/self/primary/DCIM/Camera/20190816_183614.jpg',
 '/storage/self/primary/DCIM/Camera/20190816_183618.jpg',
 '/storage/self/primary/DCIM/Camera/20190816_183652.jpg']

画像をローカルPCの「C:/work/pic」にコピーします

画像をPCにコピー
for fname in fnames:
    cmd = ('adb', 'pull', fname, 'C:/work/pic')
    subprocess.call(cmd)

PC側にコピーされたことを確認します

screenshot.386.jpg

④スマホ内の画像を削除

今回は撮影した画像をすべてPC側に持ってくるという仕様にしているので、撮影後はスマホ側の画像はすべて削除することにしました。
下記でスマホ側の画像を削除することが可能です

スマホ側の画像を削除
for fname in fnames:
    cmd = ('adb', 'shell','rm', fname)
    subprocess.check_output(cmd)

コマンド自体はうまくいくのだが、たまにPCからCamera配下のスマホ画像を確認しようとすると反映が遅かったり、遅延しているように見えるなど調子が悪いときがあった。
(調子が良いときは即時反映されて見えるのでスマホ側の問題かもしれません)

⑤最後にコードまとめ

今までの①~④のコードを関数化してまとめてみました。
少し改良し渡した引数(num)分の撮影を2秒おきに行い、PC側にコピーするというものです。
(元々撮影対象をターンテーブルに置いて、ターンテーブルを回しながら一定の間隔で360°画像を取りたかったので、今回のようなコードになりました)

簡易的に作成しましたので、参考にする場合は用途に合わせて修正してご利用ください。
今回のコードはスマホ側の画像が0枚の状態からじゃないとうまく動かないようになっています。

関数定義
import subprocess
import time
import os

PICDIR='C:/work/pic'

# 関数定義
def pic_shot(num):
    """
    渡された引数の回数分をandroid端末でカメラ撮影を実施
    カメラ画像をPC側へコピー
    android端末の画像を削除(0枚の状態がデフォルト)
    最後に撮影した画像ファイルの名前をリターンする
    """

    # カメラアプリ起動用のコマンド
    print("カメラを起動します")
    cmd = ('adb', 'shell', 'am', 'start', '-a', 'android.media.action.STILL_IMAGE_CAMERA')
    subprocess.call(cmd)
    time.sleep(1)

    # num回撮影
    print('撮影を' + str(num) + '回実行します')
    for i in range(1,num+1):
        cmd = ('adb', 'shell', 'input keyevent KEYCODE_CAMERA')
        subprocess.call(cmd)
        time.sleep(2)

    # アンドロイド側の画像名を取得
    print("画像のコピー処理を行います")
    cmd = ('adb', 'shell', 'ls', '/storage/self/primary/DCIM/Camera/*')
    # バイナリの為decodeして1つ1つのファイル名に分割しファイルがnumファイルあればOK
    fnames=subprocess.check_output(cmd).decode('utf-8').rstrip().split('\r\n')

    if len(fnames) != num:
        print('画像が' + str(num) + '枚ではありませんスマホのフォルダを確認してください')
        return 1
    else:
        # 撮影したnum枚の画像をPCへコピーする
        print('スマホの画像が' + str(num) + '枚です。PCにコピー処理を行います')
        for fname in fnames:
            cmd = ('adb', 'pull', fname, PICDIR)
            subprocess.check_output(cmd)

        # android内の撮影したnum枚の画像を削除する
        for fname in fnames:
            cmd = ('adb', 'shell','rm', fname)
            subprocess.check_output(cmd)
            time.sleep(1)

        # カメラアプリ停止用のコマンド
        print('カメラを停止します')
        cmd = ('adb', 'shell', 'am', 'force-stop', 'com.sec.android.app.camera')
        subprocess.call(cmd)

        # 画像名を取得しリターンする
        print('画像のファイル名出力します')
        files=os.listdir(PICDIR)

        return files

実際に関数を実行して画像を撮影

実行
pic_shot(5)
実行結果
カメラを起動します
撮影を5回実行します
画像のコピー処理を行います
スマホの画像が5枚です。PCにコピー処理を行います
カメラを停止します
画像のファイル名出力します

['20190816_234209.jpg',
 '20190816_234212.jpg',
 '20190816_234215.jpg',
 '20190816_234218.jpg',
 '20190816_234220.jpg']

指定した枚数分撮影され、PC側に画像がコピーされています

screenshot.389.jpg

一旦今回はここまでで終わりとします。
初めてadbコマンドというものを触ったが結構癖のあるコマンドだった。。。

また何かの機会に触ることがあれば第2弾として記事にしたいと思います。

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

猿でもわかるAndroidの環境構築[ReactNative]

ReactNativeの環境構築について

超初心者でもわかる、Androidの環境構築がなかったので、記事にして残しておきます~~~!
(2019年8月更新)

開発環境

MacOS : Mojave/10.14.6
node : v10.15.3
react-native-cli : 2.0.1
react-native : 0.60.4
npm : 6.4.1

対象者

react-nativeを使用して、iOSを立ち上げることは成功したけど、Androidの立ち上げがうまくいかない人。

それでは、Start!!!!!

①JDK 8をインストール

まずは、Androidを動かすために必要なJavaを入れます。
※注意:Javaのバージョンは最新のものではなく、8を使用します!
(うp主が、他のバージョンだとうまく動かせないからです。)

  • Oracleからダウンロードしたいので、アカウントを作成する必要があります。

スクリーンショット 2019-08-16 16.59.20.png

  • アカウントが完成したならば、以下の記事を参考にして、Java8をインストールしていきます。

macOS に Oracle Java 8 (JDK) をインストールする手順

スクリーンショット 2019-08-16 17.12.23.png

  • インストールが完了したら、ターミナルやiTermsでJavaが8入っているか確認しましょう!
java -version
//下記が確認できればOK!
java version "1.8.0_221"

②パスを通す!

初心者だと、「パスってなんやねん」、「それしなくてもいけそう〜」みたいな感覚になりがちになるのですが、めっちゃ大事なので、絶対にして下さい!(今回はやり方の解説だけします)
※注意 : 今回は、bash(初心者の人は大抵これなので安心してこの注意文は無視して下さい)向けの人に解説します!
(zsh使ってる人は解説いらないでしょう)

  • ターミナル、iTermを開いて準備する
    スクリーンショット 2019-08-16 17.25.40.png

  • vimを使用して、ターミナル上で編集していきます。

//コピペする
vi ~/.bash_profile

そうすると以下の画面になると思います。

スクリーンショット 2019-08-16 17.36.43.png

  • キーボードの矢印を使用して、カーソルを移動することができるので、適当に空いてる行に移動し、編集していきます
//そのままでは編集できないので,「i」を押しましょう→編集可能になります。
//下のものをコピペしましょう。Javaだけでなく、後々使うAndroidSdkのパスも一緒に通しておきます。
export JAVA_HOME=`/usr/libexec/java_home -v 1.8`
export PATH=/usr/local:$PATH:$NODEBREW_HOME/bin:$ANDROID_HOME:$ANDROID_NDK_HOME:$ANDROID_HOME/tools:$ANDROID_HOME/platform-tools
export ANDROID_HOME=$HOME/Library/Android/sdk
export PATH=$PATH:$ANDROID_HOME/tools
export PATH=$PATH:$ANDROID_HOME/tools/bin
export PATH=$PATH:$ANDROID_HOME/platform-tools
  • コピペができたら、escボタンを押して、編集状態を解除し、編集した内容を保存していきます
//編集した内容を保存して、vimの編集を終了したいので以下のコマンドを今の画面のまま打ちます。
//注意:escボタンを押したあとに必ずやって下さい!
:wq
  • 上記ことを行うと元のターミナルの画面に戻ってきます。
  • 変更したないようを設定ファイルに反映したいので、ターミナルで以下のコマンドを実行します。
source ~/.bash_profile

→これにてパスの設定は完了!

③Android StudioのDownloadとinstall

スクリーンショット 2019-08-16 18.01.36.png

  • ダウンロードが完了したらインストールしていきます。
    インストールタイプを選択するように言われるので"Custom"を選択します。
    Android SDK
    Android SDK Platform
    Performance (Intel ® HAXM)
    Android Virtual Device
    この4つにチェックが入っていることを確認したら"next"をクリックしてインストールします。

  • インストールが完了すると以下の画面が現れます。(でてこなかったら、Android Studioを立ち上げましょう!)

スクリーンショット 2019-08-16 18.08.48.png

  • SDK等さらに必要なファイルをインストールしていきます。
  • 初期の画面の下の部分から、Configure -> SDK Manager を起動し、SDK Platforms → Android 6.0 (Marshmallow)をインストールします。以下の画面に沿って同じ箇所にチェックして下さい。

スクリーンショット 2019-08-16 18.13.11.png

スクリーンショット 2019-08-16 18.16.32.png

スクリーンショット 2019-08-16 18.20.00.png

  • 最後に、"Apply"をClickしてSDKやビルドツールをdownloadしてinstallします。

- 次にAVDの作成をしていきます!画像通りに進めて下さい。

スクリーンショット 2019-08-16 18.33.56.png
スクリーンショット 2019-08-16 18.36.06.png
スクリーンショット 2019-08-16 18.38.15.png
スクリーンショット 2019-08-16 18.38.51.png

スクリーンショット 2019-08-16 18.39.31.png
→AVD Nameの最後の2はいりません(うp主が既に、1つ作ってるので勝手に2と振られてしまっています)

⑤最後にエミュレーターを動かす

react-nativeでは、iOSはシュミレーター(エミュレーター)を立ち上げなくても、cliでアプリを起動することができるのですが、

ターミナル.
react-native run-ios

Androidは、先にエミュレーターを立ち上げておかないと、アプリを起動することができません!
ここポイント!!!!
なので、エミュレーターを立ち上げてから、アプリを起動していきたいと思います

  • エミュレーター起動 スクリーンショット 2019-08-16 18.33.56.png スクリーンショット 2019-08-16 18.50.25.png

スクリーンショット 2019-08-16 19.03.59.png

  • アプリを起動する
//サーバーを立ち上げる
npm start
//Androidでアプリを立ち上げる
react-native run-android

※注意 : サーバーを立ち上げるターミナルと、アプリを立ち上げるターミナルは分ける
[おすすめの方法]
ターミナル上でcmd+Dをすると分割することができるので、どちらでもプロジェクトのディレクトリに入って、1つはサーバーを立ち上げる、もう一つはアプリを立ち上げるにすることです。

参考記事

https://qiita.com/EBIHARA_kenji/items/2f6938c4fda7cecbeb19
https://qiita.com/takaishota/items/4db36a806a257582fa1f
https://qiita.com/tekoneko1997/items/ab1254e4472802514190#android-studio%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB
https://qiita.com/ponnjinnka/items/006b632a0b56369451b9

もし、わからないことがあれば、コメントで質問受け付けます〜〜〜

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

Android JavaでWAVデータをバイト配列で読み込む

この記事の内容

AndroidでWAVファイルの音を再生するだけであればMediaPlayerクラスなどが使えますが、
WAVデータを「事前に」読み込んでデータをいじりたい場合があります。

「事前に」というのは「音を再生する前に」という意味です。
というのもMediaPlayerクラスを使えばVisualizer.setDataCaptureListenerで生データとFFTを取得できますが、
これは再生中の音なのでリアルタイム処理になってしまいます。

外部リンク:VISUALIZERを使ってイコライザーや波形を表示する

そこで本記事ではWAVデータを事前にバイト配列で読み込んでヘッダやらに注意しながら扱う方法を紹介します。

※適当に調べてサクッと作ったものなので、もっと簡単な方法があれば教えてください。

WAVデータの読み込み

res/rawフォルダにWAVファイル(例:music.wav)が格納されていればInputStreamでバイト配列を取得できます。

        try {
            InputStream is = getResources().openRawResource(R.raw.music);
            wavData = new byte[is.available()];
            String readBytes = String.format(Locale.US, "read bytes = %d", is.read(wavData));
            Log.e(TAG, readBytes);
            is.close();
        } catch (Exception e){
            e.printStackTrace();
        }

参考外部リンク: WAV音声ファイルをAudioTrackで再生する

ヘッダの分析

WAVファイルはヘッダが44バイトほどあり、そのあとにデータが続くという形式になっています。
ただ、この44バイトというのが色々追加されたりすることがあるようなので、きちんとヘッダの中身を確認しながらデータを取得したほうがよさそうです。

参考外部リンク:音ファイル(拡張子:WAVファイル)のデータ構造について

ヘッダの種類はchunkと呼ばれており、'fmt'と'data'がわかればよさそうです。(詳細は上の外部リンクを見てもらえればと思います)
そこでこの'fmt ' == 0x6d7420と'data'==0x64617461を確認してそれぞれのインデックスを保存しておきます。

        int fmtIdx = 0;
        for(int i = 0; i < wavData.length - 4; i ++){
            if(wavData[i] == 0x66 && wavData[i + 1] == 0x6d
                    && wavData[i + 2] == 0x74 && wavData[i + 3] == 0x20){ // 'fmt ' chunk
                fmtIdx = i;
                Log.i("Test", "fmtIdx:" + fmtIdx);
                break;
            }
        }
        if(fmtIdx == 0){
            Log.e(TAG, "No fmt chunk");
        }

        int dataIdx = 0;
        for(int i = 0; i < wavData.length - 4; i ++){
            if(wavData[i] == 0x64 && wavData[i + 1] == 0x61
                    && wavData[i + 2] == 0x74 && wavData[i + 3] == 0x61){ // 'data' chunk
                dataIdx = i;
                Log.i("Test", "dataIdx:" + dataIdx);
                break;
            }
        }
        if(dataIdx == 0){
            Log.e(TAG, "No data chunk");
        }

次にチャネル数、サンプリングレート、ビット数(ここではバイト数)、データサイズを取得しましょう。
データサイズは基本的にはヘッダを除いた部分ということですが、WAVフォーマットはフッタも自由につけてよいということらしいので取得しておきます。
実際に私が試した音源ではフッタが結構な量で含まれていました。

        int wavChannel = (int)(wavData[fmtIdx + 10]);
        Log.i("Test", "wavChannel:" + wavChannel);

        int wavSamplingRate = ((int)(wavData[fmtIdx + 15]) << 24) + ((int)(wavData[fmtIdx + 14]) << 16)
                + ((int)(wavData[fmtIdx + 13]) << 8) + (int)(wavData[fmtIdx + 12]);
        Log.i("Test", "wavSamplingRate:" + wavSamplingRate);

        int wavByte = (int)(wavData[fmtIdx + 22]) / 8;
        Log.i("Test", "wavByte:" + wavByte);

        int wavDataSize = ((int)(wavData[dataIdx + 7]) << 24) + ((int)(wavData[dataIdx + 6]) << 16)
                + ((int)(wavData[dataIdx + 5]) << 8) + (int)(wavData[dataIdx + 4]);
        Log.i("Test", "wavDataSize:" + wavDataSize);

        int wavHeaderSize = dataIdx + 8;

        int[] musicDataRight = new int[wavDataSize / wavByte / wavChannel];
        int[] musicDataLeft = new int[wavDataSize / wavByte / wavChannel];

4バイトで格納されているところはシフト演算でintにしています。
また、あとで使うのでヘッダサイズを計算しています。
チャネルが2のときは左右の音源が取得できますので、これをmusicDataRight、musicDataLeftに入れていきます。

        if(wavByte == 1 && wavChannel == 1){
            for(int i = 0, j = wavHeaderSize; i < musicDataRight.length; i ++, j ++){
                musicDataRight[i] = (int)wavData[j];
                musicDataLeft[i]  = (int)wavData[j];
            }
        } else if(wavByte == 1 && wavChannel == 2){
            for(int i = 0, j = wavHeaderSize; i < musicDataRight.length; i ++, j += 2){
                musicDataRight[i] = (int)wavData[j];
                musicDataLeft[i]  = (int)wavData[j + 1];
            }
        } else if(wavByte == 2 && wavChannel == 1){
            for(int i = 0, j = wavHeaderSize; i < musicDataRight.length; i ++, j += 2){
                musicDataRight[i] = ((int)wavData[j + 1] << 8) + (int)wavData[j];
                musicDataLeft[i]  = ((int)wavData[j + 1] << 8) + (int)wavData[j];
            }
        } else if(wavByte == 2 && wavChannel == 2){
            for(int i = 0, j = wavHeaderSize; i < musicDataRight.length; i ++, j += 4){
                musicDataRight[i] = ((int)wavData[j + 1] << 8) + (int)wavData[j];
                musicDataLeft[i]  = ((int)wavData[j + 3] << 8) + (int)wavData[j + 2];
            }
        }

チャネル数とバイト数別にデータを格納しています。
やろうと思えば式をまとめることもできる(実際やってみた)のですが、可読性が落ちてしまうので分けて書いています。
チャネル数が1のときは左右に同じ音を入れています。
また、チャネル数やバイト数が3以上の場合は別途追加する必要があります。

以上でWAVデータをバイト配列で読み込む手順の説明を終わります。

おわりに

読み込んだ波形をグラフで表示したり、データを加工したりといったことがこれで可能になります。
ただ、冒頭にも述べましたがもっと簡単な方法があれば教えてください。

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

React NativeとExpoで作るiOS・Androidアプリ開発入門 - 3/3 Kindle版 のサンプルアプリを修正

ナカノヒトシさんのKindle本で学ぶサンプルアプリケーションはなかなかのもので勉強にもこれから作ろうと思うアプリの見本にもなるのですが、react-native-elementsがアップデートしたことによりそのままでは動かない。

React NativeとExpoで作るiOS・Androidアプリ開発入門 - これ一冊でストアリリースまで進める本格的入門書 - 3/3

FormInput

前回の記事で書いたとおり。

Avatar

<ListItem>のプロパティとしてroundAvataravatar={item.user.profile_image_url}ではダメで、次のように書く。

<ListItem
    onPress={()=>{this.props.navigation.navigate('WebViewPage', {url:item.url,title:item.title})}
    }
    key={item.id + ":" + item.user.id}
    title={item.title}
    leftAvatar={{
      title: item.title,                           
      source: {uri: item.user.profile_image_url},
      rounded: true
    }   

リスナー

サンプルコードではリスナーされる関数が発火せず。

    componentDidMount() {
        this.props.navigation.addListener('didFocus', this.componentDidFocus()
        this.loadTags();
    }

    componentDidFocus() {
       this.loadTags();
   }

以下のようにした。

    componentDidMount() {
        this.props.navigation.addListener('didFocus', () => this.loadTags()) // 変更。
        this.loadTags();
    }
    /*
    componentDidFocus()を削除
    */    

KeywordForm.js

this.refs["form-input"].clearText()
では動かず、
his.refs["form-input"].clear()
で正解
<Input ref="form-input" label="新しく登録するキーワード" onChangeText={this.onChangeInputText} />
でInputValueを関数に渡せず、
<Input ref="form-input" label="新しく登録するキーワード" onChangeText={(text)=>{this.setState({input: text})}} />
で、渡せた。


他にもあっただろうか?

いくつか自分の見落としもあると思うがとりあえず修正して動かせたソースコードをGithubにアップしたのでご参考に。

https://github.com/atomyah/reactnative_practice3


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

51歳からのプログラミング 備忘 Service 編集中

https://developer.android.com/guide/components/services

Serviceとは

Service:ユーザーインターフェイスを使わず、バックグラウンドで長時間処理を実行できるアプリケーションコンポーネントです。

他のアプリケーションコンポーネントを開始することもできるし、ユーザーが他のアプリケーションに変更しても、バックグラウンドで処理し続けます。

さらに、コンポーネントとサービスをバインドして交信し、プロセス間通信もできます。例えば、ネットワーク処理、音楽再生、ファイルI/O、コンテンツプロバイダー通信など、すべてバックグラウンドで処理できます。

3種類のService

Foreground

ForegroundServiceは、ユーザーに通知されている処理を実行します。例えば、オーディオアプリはForegroundServiceを使って、オーディオトラックで再生します。ForegroundServiceでは通知を表示させます。ユーザーがアプリを操作してない時は、ForegroundServiceで処理し続けます。

Background

BackgroundServiceはユーザーによって直接気が付かない処理を実行します。例えば、アプリのストレージを圧縮するサービスの場合、それはバックグラウンド処理となります。

Note:
アプリがAPI26以降の時、アプリがforegroundになければ、システムはバックグラウンドサービスの実行に制約を課します。このようなケースでは、代わりにスケジュールされた作業を行います。
Bound

bindService()でサービスをアプリケーションコンポーネントにバインドします。バインドされたサービスは、コンポーネントと通信するため、クライアントサーバーインターフェイスを提供します。例えば、リクエスト送信、結果受信、プロセス通信など。

バインドされたサービスは、他のアプリケーションコンポーネントがサービスにバインドされている限り、終了することはありません。

サービスは、明示的な開始、バインド、どちらも使えますが、ドキュメントでは開始とバインドを分けて説明します。

実装は、onStartCommand()でサービス開始を要求するか、onBind()でバインドを許可するか、のどちらかで行います。

どのアプリケーションコンポーネントも、任意のアクティビティを使うのと同じ方法で、Intentを使って同じサービスを開始できます。マニュフェストに他のアプリからの利用禁止を定義することも可能です。詳しくは>https://developer.android.com/guide/components/services#Declaring

COUTION:
 特に方法を指定しない限り、Serviceが自身のスレッドを作成することなく、別のスレッドで実行されることもありません。
 なので、MP3再生やネットワーク処理など負担のかかる処理では、フリーズエラーを予防するために、サービス内に新しいスレッドを作成する必要があります。

ServiceとThreadどっちを使う?

・ Serviceはバックグラウンド処理する場合
・ Threadはメインスレッド以外で処理する必要がある場合

例えば、アクティビティを実行してる間だけ音楽再生する場合は、onCreate()にスレッドを作成し、onStart()で実行開始、onStop()で停止。 Threadの代わりに、AsyncTascやHandlerThreadも使えます。

Service作成にあたって重要なOverrideメソッド

onStartCommand() / onBind() / onCreate() / onDestroy()

onStartCommand()

アクティビティなどの他のコンポーネントが、startService()を呼び出して、Serviceの開始を要求したときに、システムがonStartCommand()を呼び出します。onStartCommand()が実行されるとServiceが開始されます。

onStartCommand()を実装する時は、Service完了時にstopSelf()かstopServece()で、自身でサービスを停止させます(バインドのみを提供する場合には、onStartCommand()は実装しなくてよい)。

onBind()

bindService()を呼び出して、他のコンポーネントをServiceにバインドさせるときに、システムがonBind()を呼び出します。

onBind()実装時にはIBinderを返して、クラインとがServiceとの通信に使うインターフェースを提供する必要があります。

onBind()の実装は常に必要だけれど、バインドを許可しない場合はnullを返す必要があります。

onCreate()

onStartCommand()かonBind()を呼び出す前で、サービスが始めて作成された時に、1度限りのセットアップ処理をするために呼び出されるメソッド。

onDestroy()

Serviceが長時間使われなかったり、破棄されたときに呼ばれるメソッド。スレッドや登録済みリスナー、レシーバーのリソースをクリーンアップするのに、Serviceはこのメソッドを実装します。このメソッドは、Serviceが受け取る最後の呼び出しです。

Serviceの終了

stopSelf() stopService()

onStartCommand()のstartService()によってコンポーネントがServiceを開始したら、サービスがstopSelf()で自身を終了するか、他のコンポーネントがstopService()を実行するまで、サービスは稼働し続けます。

unbind

コンポーネントが、onStartCommand()を呼ばずに、bindService()によってServiceを生成したら、コンポーネントがServiceにバインドされてる限り、Serviceは稼働し続けます。Serviceがクライアントから全てアンバインドされたら、システムはServiceを破棄します。

Android System によるServiceの破棄

メモリーが下がり、ユーザーが操作してるアクティビティのシステムリソースを回復させなければならない場合にのみ、アンドロイドシステムはServiceを停止します。ユーザーが使っているアクティビティにバインドされたServiceは、破棄される可能性は低くなります。Foregroundで実行するよう定義したServiceは滅多に破棄されません。

システムは、時間の経過とともに、Serviceのバックグラウンド・タスク・リストでの位置付けを下げます。開発者はシステムによるServiceの復帰処理をデザインしてください。onStartCommand()の戻り値にもよりますが、もしシステムがServiceを破棄したら、システムはリソースが回復するなり、Serviceを復帰させます。詳しくは>https://developer.android.com/guide/components/processes-and-threads.html

Service作成

マニフェスト宣言

<manifest ... >
  ...
  <application ... >
      <service android:name=".ExampleService" />
      ...
  </application>
</manifest>

~で、ServiceやServiceを実行するプロセスを開始するために必要なプロパティを定義します。

android:name

Serviceのクラス名を指定する。唯一の必須属性、変更不可
アプリの安全性を保つため、Serviceを開始したりバインドするときは、常に明示的Intentを使います。

COUTION:
アプリの安全性を確保するため、Serviceを開始するときには常に明示的Intentを使い、ServiceのIntentフィルタは宣言しません。暗黙的なインテントは危険です。開発者がインテントから返されるServiceを確認できなかったり、ユーザーがServiceの開始を認識できなかったりします。Android5.0(API21)以降では、bindService()を呼び出すと、システムは例外を投げます。

どのServiceを開始するかについて、あいまい性を残す場合には、ServiceにIntentフィルタを定義して、Intentからコンポーネント名を除外して、その後にターゲットのサービスのあいまい性を解消するsetPackage()でIntentのパッケージを設定する必要があります。

android:exported

android:exported属性を含めて"false"に設定すると、Serviceを自身のアプリでしか利用できないようにできます。たとえ明示的Intentを使っても、Serviceを開始できなくなります。

要求されたServiceを生成する

要求されたServiceとは:
他のコンポーネントがstartService()を呼び出して、onStartCommand()メソッドを使って、生成を要求されたService。

Serviceの終了
onStartCommand()で開始したServiceは、
 自身で終了する場合には、stopSelf()
 他のコンポーネントで終了する場合には、stopService()

Serviceの開始とIntent
アクティビティなどのアプリケーションコンポーネントは、startService()でServiceを指定しIntentでServiceが使うデータを渡してServiceを開始し、Serviceは、onStartCommand()を使ってIntentを受け取って処理します。

要求されたサービスを生成するための拡張可能なクラス

Service
全てのServiceの基本クラス
Serviceはデフォルトでアプリのメインスレッドを使用するため、Serviceの処理によって、アクティビティのパフォーマンスを低下させることがあります。

そのため、このクラスを拡張するときは、Service全ての作業のための、新しいスレッドを作成することが重要です。

IntentService
全てのServiceの生成要求を、一件ずつ処理するワーカースレッドを使った、Serviceのサブクラス。Serviceで同時に複数の生成要求を処理する必要がない場合に最適。

onHnadleIntent()を実装するだけで、それぞれの生成要求のIntentを受け取り、バックグラウンド処理ができるようになります。

IntentServiceクラスの拡張 省略
Serviceクラスの拡張 省略

Serviceの開始

アクティビティや他のアプリケーションコンポーネントからIntent(開始Serviceを指定)を、startService()に渡して、Serviceを開始。

Androidシステムが、ServiceのonStartCommand()メソッドを使ってIntentを渡します(絶対に、onStartCommand()を直接呼び出してはいけません)

<参考>

Intent intent = new Intent(this,HelloService.class);
startService(intent);

AndroidシステムがServiceのonStartCommand()を呼び出しています。
Serviceが実行されてないときは、システムはonCreate()を呼び出してから、onStartCommand()を呼び出します。

Serviceでバインドが提供されてないときは、startService()で配信されたIntentがアプリケーションコンポーネントとServiceの唯一の通信手段となります。

ただし、Serviceから結果を返す場合、Serviceを要求するクライアントが、ブロードキャスト用にPendingIntentを作成でき(getBroadcast()を使う)、要求されたServiceのIntentに配信できます。その後、Serviceはブロードキャストを使って結果を配信できます。

Service要請が複数ある場合、それに対応するために、onStartCommand()に複数の呼び出しがはっせいしますが、停止するためには、stopSelf()やstopServiceは1つのみでOK。

Serviceの停止

stopSelf()やstopServiceで停止要求すると、androidシステムはServiceを破棄します。

ServiceがonStartCommand()への複数要求を同時処理している場合は、新しい生成要求がある可能性があるので、複数要求の同時処理後もServiceは停止させないようにします。1つの停止で全て停止します。

複数要求の停止は、stopSelf(int)を使って、最新の要求に基づいてServiceの停止要求を行います。具体的には、停止したい要求にID(onStartCommand()に配信したstartId)を渡します。

COUTION:
Serviceのバインドが有効な場合でも、onStartCommand()への呼び出しを受け取ったときは、常に自身でサービスを停止する必要があります。stopSelf()を使ってね。

バインドされたServiceを作成

bindService()でServiceにバインドすると、アプリケーションコンポーネントが長時間接続できます(通常では、コンポーネントがstarService()を呼び出すことではServiceは開始できない)。

バインドされたServiceを作成

・ onBind()を実装
  Serviceとの通信用のインターフェイスを定義するIBinderを返す
・ アプリケーションコンポーネントがbindService()を呼び出
  インターフェイスIBinderを取得しServiceのメソッドの呼び出しを開始

バインドされたコンポーネントがなくなると、システムによってServiceが破棄されます(バインドされたサービスは、onStartCommand()でサービスが開始された時と同じ方法で停止する必要はありません)。

アンバインド

unbindService()
Serviceにバインドされているクライアントがなくなったら、システムがサービスを破棄。

Foreground Sercice

ForegroundServiceでは通知を表示する必要があり、Service停止か、ForegroundからServiceを除去しない限り、通知を消すことはできません。

Create Foreground Service

startForeground(Id,notification)を実行
・ Notification
・ Intent
・ PendingIntent
・ notification.setLatestEventInfo(this,title,text,PendingIntent)
・ startForeground(Id,notification)

<参考>

Notification notification = new Notification(
                                R.drawable.icon,
                                getText(R.string.ticker_text),
                                System.currentTimeMillis()
                                );
Intent notificationIntent = new Intent(this, ExampleActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
notification.setLatestEventInfo(
                              this, 
                              getText(R.string.notification_title),
                              getText(R.string.notification_message), 
                              pendingIntent);
startForeground(ONGOING_NOTIFICATION_ID, notification);

startForeground()に渡すIDには、0はNG

Foregourd Serviceの除去

stopForeground()
・ ServiceをForegroundから除去
・ 通知を除去するかを示すbool値も受け付けます
・ Serviceは停止しない
  ただし、ServiceがまだForegroundで実行中に停止した場合は、通知は削除される

Serviceのライフサイクル管理

省略

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

51歳からのプログラミング 備忘 Service

自分用です。
間違ってるかもしれないので、下記を直接見てね。

https://developer.android.com/guide/components/services

Serviceとは

Service
ユーザーインターフェイスを使わず、バックグラウンドで長時間処理を実行できるアプリケーションコンポーネントです。

他のアプリケーションコンポーネントを開始することもできるし、ユーザーが他のアプリケーションに変更しても、バックグラウンドで処理し続けます。

さらに、コンポーネントとサービスをバインドして交信し、プロセス間通信もできます。例えば、ネットワーク処理、音楽再生、ファイルI/O、コンテンツプロバイダー通信など、すべてバックグラウンドで処理できます。


3種類のService

・Foreground

foregroundServiceは、ユーザーに通知されている処理を実行します。例えば、オーディオアプリはForegroundServiceを使って、オーディオトラックで再生します。ForegroundServiceでは通知を表示させます。ユーザーがアプリを操作してない時は、ForegroundServiceで処理し続けます。

・Background

BackgroundServiceはユーザーによって直接気が付かない処理を実行します。例えば、アプリのストレージを圧縮するサービスの場合、それはバックグラウンド処理となります。

Note:
アプリがAPI26以降で、アプリがforegroundに設定されてなければ、システムはバックグラウンドサービスの実行に制約を課します。このようなケースでは、代わりにスケジュールされた作業を行います。

・Bound

bindService()でサービスをアプリケーションコンポーネントにバインドします。バインドされたサービスは、コンポーネントと通信するため、クライアントサーバーインターフェイスを提供します。例えば、リクエスト送信、結果受信、プロセス通信など。

バインドされたサービスは、他のアプリケーションコンポーネントがサービスにバインドされている限り、終了することはありません。


サービスは、明示的な開始、バインド、どちらも使えますが、ドキュメントでは開始とバインドを分けて説明します。

実装は、onStartCommand()でサービス開始を要求するか、onBind()でバインドを許可するか、のどちらかで行います。

どのアプリケーションコンポーネントも、任意のアクティビティを使うのと同じ方法で、Intentを使って同じサービスを開始できます。マニュフェストに他のアプリからの利用禁止を定義することも可能です。詳しくは>https://developer.android.com/guide/components/services#Declaring

COUTION:
 特に方法を指定しない限り、Serviceが自身のスレッドを作成することなく、別のスレッドで実行されることもありません。

 なので、MP3再生やネットワーク処理など負担のかかる処理では、フリーズエラーを予防するために、サービス内に新しいスレッドを作成し、別スレッドで処理する必要があります。

ServiceとThreadどっちを使う?

・ Serviceはバックグラウンド処理する場合
・ Threadはメインスレッド以外で処理する必要がある場合

 例えば、アクティビティを実行してる間だけ音楽再生する場合は、onCreate()にスレッドを作成し、onStart()で実行開始、onStop()で停止。 Threadの代わりに、AsyncTascやHandlerThreadも使えます。

Service作成にあたって重要なOverrideメソッド

onStartCommand() / onBind() / onCreate() / onDestroy()

・onStartCommand()

アクティビティなどの他のコンポーネントが、startService()を呼び出して、Serviceの開始を要求したときに、システムがonStartCommand()を呼び出します。onStartCommand()が実行されるとServiceが開始されます。

onStartCommand()を実装する時は、Service完了時にstopSelf()stopServece()で、自身でサービスを停止させます(バインドのみを提供する場合には、onStartCommand()は実装しなくてよい)。

・onBind()

bindService()を呼び出して、他のコンポーネントをServiceにバインドさせるときに、システムがonBind()を呼び出します。

onBind()実装時にはIBinderを返して、クラインとがServiceとの通信に使うインターフェースを提供する必要があります。

onBind()の実装は常に必要だけれど、バインドを許可しない場合はnullを返す必要があります。

・onCreate()

onStartCommand()かonBind()を呼び出す前で、サービスが始めて作成された時に、1度限りのセットアップ処理をするために呼び出されるメソッド。

・onDestroy()

Serviceが長時間使われなかったり、破棄されたときに呼ばれるメソッド。スレッドや登録済みリスナー、レシーバーのリソースをクリーンアップするのに、Serviceはこのメソッドを実装します。このメソッドは、Serviceが受け取る最後の呼び出しです。


Serviceの終了

・stopSelf() stopService()

onStartCommand()のstartService()によってコンポーネントがServiceを開始したら、サービスがstopSelf()で自身を終了するか、他のコンポーネントがstopService()を実行するまで、サービスは稼働し続けます。

・unbind

コンポーネントが、onStartCommand()を呼ばずに、bindService()によってServiceを生成したら、コンポーネントがServiceにバインドされてる限り、Serviceは稼働し続けます。Serviceがクライアントから全てアンバインドされたら、システムはServiceを破棄します。

・Android System によるServiceの破棄

メモリーが下がり、ユーザーが操作してるアクティビティのシステムリソースを回復させなければならない場合にのみ、アンドロイドシステムはServiceを停止します。ユーザーが使っているアクティビティにバインドされたServiceは、破棄される可能性は低くなります。Foregroundで実行するよう定義したServiceは滅多に破棄されません。

システムは、時間の経過とともに、Serviceのバックグラウンド・タスク・リストでの位置付けを下げます。開発者はシステムによるServiceの復帰処理をデザインしてください。onStartCommand()の戻り値にもよりますが、もしシステムがServiceを破棄したら、システムはリソースが回復するなり、Serviceを復帰させます。詳しくは>https://developer.android.com/guide/components/processes-and-threads.html


マニフェスト宣言

<manifest ... >
  ...
  <application ... >
      <service android:name=".ExampleService" />
      ...
  </application>
</manifest>

~で、ServiceやServiceを実行するプロセスを開始するために必要なプロパティを定義します。

・android:name

Serviceのクラス名を指定する。唯一の必須属性、変更不可
アプリの安全性を保つため、Serviceを開始したりバインドするときは、常に明示的Intentを使います。

COUTION:
 アプリの安全性を確保するため、Serviceを開始するときには常に明示的Intentを使い、ServiceのIntentフィルタは宣言しません。

 暗黙的なインテントは危険です。開発者がインテントから返されるServiceを確認できなかったり、ユーザーがServiceの開始を認識できなかったりします。

 Android5.0(API21)以降では、bindService()を呼び出すと、システムは例外を投げます。

・android:exported

android:exported属性にfalseを定義して追加することで、そのアプリにのみServiceを使えるようにできます。この方法なら、明示的Intentを使っていても、他のアプリからServiceが呼ばれません。

android:description

Note:
 ユーザーはディバイス上で、どのServiceが稼働しているか確認できます。ユーザーが許可してない、もしくは信頼していないServiceを見つけたら、ユーザーはそのServiceを停止できます。

 ユーザーが誤ってServiceを停止するのを避けるには、アプリのマニフェストの要素に、android:description属性を追加して、そのServiceが必要/有用であることを短文で説明してください。

もっと詳しくServiceのライフサイクル管理をしりたかったら>https://developer.android.com/guide/components/services#Lifecycle

どのServiceを開始するかについて、あいまい性を残す場合には、ServiceにIntentフィルタを定義して、Intentからコンポーネント名を除外して、その後にターゲットのサービスのあいまい性を解消するsetPackage()でIntentのパッケージを設定する必要があります。

要求されたServiceを生成する

要求されたServiceとは:
他のコンポーネントがstartService()を呼び出し、onStartCommand()メソッドを使って、生成を要求されたService。

Serviceは、Serviceが開始されると、呼び出したコンポーネントから独立して、独自のライフサイクルを持ちます。

呼び出し元のコンポーネントが破棄されても、Serviceはバックグラウンドで無期限に稼働します。ですから処理が終了したら、stopSelf()でServiceが自身を終了させるか、stopService()で、他のコンポーネントからServiceを終了させます。

アクティビティなどのアプリケーションコンポーネントは、startService()の呼び出し、Intentの受け渡し、によってServiceを開始できます。IntentはスタートするServiceを指定し、そのServiceが使うデータを受け渡します。ServiceはonStartCommand()メソッドで、このIntentを受け取ります。

例えば、オンラインデータベースでアクティビティがデータ保存すると仮定します。そのアクティビティはコンパニオンサービスを開始し、startService()にIntentを渡すことで、保存するデータを配信できるようにします。

ServiceはonStartCommand()によってIntentを受け取り、インターネットを通じてデータベース処理を実行します。処理が終了すると、Serviceは自身を終了させ、Serviceは破棄されます。

COUTION:
 Serviceは宣言されているアプリケーションと同じプロセスで稼働し、デフォルトではそのアプリケーションのメインスレッドで稼働します。

 ユーザーが同じアプリケーションのアクティビティを操作してる最中に、Serviceが集中的に稼働したり、ブロック操作を実行したら、アクティビティのパフォーマンスが低下します。

 パフォーマンスの低下を防ぐために、Service内で新しくスレッドを作成し、スレッド処理させてください。

要求されたサービスを生成するための拡張可能なクラス

Service

全てのServiceの基本クラス。

Serviceはデフォルトでアプリのメインスレッドを使用するため、Serviceの処理によって、アクティビティのパフォーマンスを低下させることがあります。

そのため、このクラスを拡張するときは、Service処理を完結する別スレッドを新しく作成することが重要です。

IntentService

全てのServiceの生成要求を、一件ずつ処理するワーカースレッドを使った、Serviceのサブクラス。Serviceで同時に複数の生成要求を処理する必要がない場合に最適。

onHandleIntent()を実装するだけで、それぞれの生成要求のIntentを受け取り、バックグラウンド処理ができるようになります。


IntentServiceクラスの拡張

Serviceクラスの拡張に比べIntentServiceクラスを拡張したほうが、要求されたServiceをシンプルに実装できます。Serviceクラスを拡張してインテントを処理する方法が適したケースとは、Serviceが(作業キューを介して開始要求を処理するのではなく)複数のスレッドを実行する必要があるケースです。

開始要求されたServiceのほとんどは、同時に複数のリクエストを扱う必要がありません。マルチスレッドを使う設計は危険を伴います。IntentServiceクラスを使ってServiceを実装するのが最善でしょう。

IntentServiceクラスの働き。

・ onStartCommand()に配信されたすべてのインテントを実行するためのスレッドを生成します。このスレッドはアプリケーションのメインスレッドから区分します。
・ マルチ(複数)スレッドを作成しなくてもいいように、一つずつインテントをonHandleIntent()に渡していく作業キューを生成します。
・ 全ての開始要求を処理した後でServiceを停止しますので、stopSelf()<を使う必要はありません
・ onBind()をデフォルトで実装。実装されたonBind()はnullを返します。
・ onStartCommand()をデフォルトで実装。実装されたonStartCommand()は、作業キューにインテントと実装されたonHandleIntent()を送信します。

クライアントから求められた作業を完了するために、onHandleIntent()を実装しますが、そのためにはServiceにコンストラクタが必要です。

<参考>IntenServiceの実装例

public class HelloIntentService extends IntentService {

  /**
   * the super <code><a href="/reference/android/app/IntentService.html#IntentService(java.lang.String)">IntentService(String)</a></code>
   * コンストラクタでスーパをコール(必須)
   */

  public HelloIntentService() {
      super("HelloIntentService");
  }

  /**
   * IntentServiceは、サービスを開始したインテントで、
   * デフォルトのワーカースレッドからこのメソッドを呼び出します。
   * このメソッドが戻ると、IntentServiceは必要に応じてサービスを停止します
   */

  @Override
  protected void onHandleIntent(Intent intent) {
      // 普通はこのメソッドで、ファイルをダウンロードするような作業をします
      // サンプルでは、5秒スリープする処理をコードしてます
      try {
          Thread.sleep(5000);
      } catch (InterruptedException e) {
          Thread.currentThread().interrupt();
      }
  }
}

onHandleIntent()の実装やコンストラクタなど、IntentService()で必要な処理はこれで全てです。

onCreate(),onStartCommend(),onDestory()など、他のコールバックメソッドもオーバーライドするのなら、スーパーインプリメンテーション(親の実装)を呼び出して、IntentServiceのライフサイクルを適切に管理してください。

onStartCommand()は、onHandleIntent()にインテントを配信した方法で、デフォルトの実装を返します。

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();
    return super.onStartCommand(intent,flags,startId);
}

onHandleIntent()の他に、スーパークラスを呼び出す必要のないメソッドはonBind()のみです。これらの実装は、Serviceにバインドを許可する時に必要です。


Serviceクラスの拡張
マルチスレッドを使ったサービスを実行するには、Serviceクラスを継承して各Intentを取り扱います。(以下、いろいろ省略)

public class HelloService extends Service {
  private Looper serviceLooper;
  private ServiceHandler serviceHandler;

  // Handlerはスレッドからメッセージを受け取ります
  private final class ServiceHandler extends Handler {
      public ServiceHandler(Looper looper) {
          super(looper);
      }
      @Override
      public void handleMessage(Message msg) {
          // 普通は、ファイルをダウンロードするように、ここで処理を行います
          // 5秒スリープするサンプルコードを下記します
          try {
              Thread.sleep(5000);
          } catch (InterruptedException e) {
              Thread.currentThread().interrupt();
          }
          // 複数の処理要求がある場合には
          // スタートIDを使ってサービスを停止します
          // スタートIDを使わないと停止できません
          stopSelf(msg.arg1);
      }
  }

  @Override
  public void onCreate() {
    // サービスを稼働させるスレッドを開始します
    // メインスレッドのパフォーマンスを確保するため、別スレッドでサービス処理します
    HandlerThread thread = new HandlerThread("ServiceStartArguments",
            Process.THREAD_PRIORITY_BACKGROUND);
    thread.start();

    // ハンドルのスレッドルーパーを取得してハンドルに渡します。
    serviceLooper = thread.getLooper();
    serviceHandler = new ServiceHandler(serviceLooper);
  }

  @Override
  public int onStartCommand(Intent intent, int flags, int startId) {
      Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();

      // リクエストを開始したら、開始した処理にメッセージを送り、スタートIDも送信します。
      // 処理が終了した時、停止要求する作業をIDによって確定できます。
      Message msg = serviceHandler.obtainMessage();
      msg.arg1 = startId;
      serviceHandler.sendMessage(msg);

      // サービスが強制破棄されたら、ここに戻ってから再起動します
      return START_STICKY;
  }

  @Override
  public IBinder onBind(Intent intent) {
      // We don't provide binding, so return null
      return null;
  }

  @Override
  public void onDestroy() {
    Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show();
  }
}

Serviceの開始

アクティビティや他のアプリケーションコンポーネントからIntent(開始Serviceを指定)を、startService()、もしくはstartForegroundService()に渡して、Serviceを開始。

Androidシステムが、ServiceのonStartCommand()メソッドを呼び出し、Intentを渡します。Intentは開始するサービスを指定します。

Note:
アプリがAPI26以降の場合、アプリがForegroundになければ、システムはバックグラウンドServiceの使用や生成に制約を課します。もしアプリをForegroundServiceで生成するのなら、startForegroundService()を使ってください。一度Serviceが生成されると、このServiceは5秒以内にstartForegroun()メソッドを呼ばなければなりません。

<参考>
startService()で明示的Intentを使って
Serviceを開始するサンプルコードです

Intent intent = new Intent(this,HelloService.class);
startService(intent);

startService()メソッドにより、AndroidシステムはServiceのonStartCommand()メソッドを実行します。もしServiceがまだ稼働してなければ、システムは最初にonCreate()を実行してからonStartCommand()を実行します。

Serviceでバインドが提供されてないときは、startService()で配信されたIntentがアプリケーションコンポーネントとServiceの唯一の通信手段となります。

ただし、Serviceから結果を返す場合、Serviceを要求するクライアントが、ブロードキャスト用にPendingIntentを作成でき(getBroadcast()を使う)、要求されたServiceのIntentに配信できます。その後、Serviceはブロードキャストを使って結果を配信できます。

Service要請が複数ある場合、それに対応するために、onStartCommand()に複数の呼び出しが発生しますが、停止するためには、stopSelf()stopService()は1つのみでOK。


Serviceの停止

Serviceを開始したら、Serviceのライフサイクルを管理してください。onStartCommand()実行後は、Serviceは無期限実行し、システムメモリーをリカバーする場合を除いて、システムはServiceを停止や破棄しません。Serviceの停止は、Service自身がstopSelf()を実行するか、他のコンポーネントからstopService()を実行して行います。

一度、stopSelf()stopServiceが実行されると、システムは速やかにServiceを破棄します。

ServiceがonStartCommand()への複数の要求を処理する場合は、1回の停止処理で他の全ての要求が停止しますので、1つの要求が完了しても、Serviceを停止しないでください。

複数の要求を処理するServiceの停止は、stopSelf(int)を使います。このメソッドは、Service停止要求が、いつも最新の開始要求に基づいて行われることを確認します。

具体的には、stopSelf(int)は、停止したい要求に応じた開始要求のIDを渡します(onStartCommand()に送られたstartId)。stopSelf(int)が実行できるようになる前に、Serviceが新しい開始要求を受け取ると、開始要求のIDと停止供給のIDが一致しないので、そのServiceは停止しません。

COUTION:
 システムリソースの無駄遣いや、電池の消耗を避けるため、アプリケーションが終了したら、必ずServiceを停止します。

 必要であれば、他のコンポーネントからstopService()を実行すうことで、Serviceを停止します。

 Serviceのバインドを有効にしていても、ServiceがonStartCommand()で実行されてるなら、stopSelf()を使って、Service自身でServiceを停止してください。

バインドされたServiceを作成

bindService()でServiceにバインドすると、アプリケーションコンポーネントが長時間接続できます(通常では、コンポーネントがstarService()を呼び出すことではServiceは開始できない)。

アプリケーション内のアクティビティや、他のコンポーネントとServiceとで応答したり、プロセス間通信を介してアプリケーション機能を、他のアプリケーションに提供するときに、バインドされたServiceを生成します。


バインドされたServiceを作成

・ onBind()を実装
  Serviceとの通信用のインターフェイスを定義するIBinderを返す
・ アプリケーションコンポーネントがbindService()を呼び出
  インターフェイスIBinderを取得しServiceのメソッドの呼び出しを開始

バインドされたコンポーネントがなくなると、システムによってServiceが破棄されます(バインドされたサービスは、onStartCommand()でサービスが開始された時と同じ方法で停止する必要はありません)。

バインドされたServiceを生成するには、インターフェイスを定義する必要があります。このインターフェイスで、クライアントとServiceのと通信方法を指定します。クライアントとServiceの通信を担うインターフェイスは、IBinderを実装する必要があり、ServiseがonBind()コールバックメソッドによって返すIBinderでなければなりません。クライアントがIBilderを受け取ったら、インターフェイスを通じてServiceを操作できるようになります。

複数のクライアントが同時にサービスにバインド可能です。あるクライアントがService操作を終了すると、unbindService()を実行し、バインドを解除します。Serviceにバインドするクライアントが無くなれば、システムはServiceを破棄します。

バインドされたサービスを実装する方法はいくつかあり、その実装はServiceを開始する方法よりも複雑ですので、詳しくはこちらのドキュメントで>https://developer.android.com/guide/components/bound-services.html

ユーザーへ通知を送る

Serviceが稼働したら、トータス通知かステータスバー通知で、ユーザーに通知できます。トータス通知は画面に一瞬出てくるメッセージです。ステータスバーの通知はステータスバーにメッセージとともにアイコンを表示します。ユーザーは通知から操作するアクションを選べます(Serviceを開始するとか)。

通常、ステータスバー通知は、ファイルダウンロードを完結させるような作業を、バックグラウンドで行うのに、最善のテクニックと言えます。ユーザーが展開ビューから通知を選択したとき、(ダウンロードしたファイルを表示するように)通知からアクティビティを開始できます。

トースト通知とステータスバー通知について、もっと詳しくは>

もっと詳しくトースト通知を>https://developer.android.com/guide/topics/ui/notifiers/toasts.html
もっと詳しくステータスバー通知は>https://developer.android.com/guide/topics/ui/notifiers/notifications.html

ServiceをForegroundで稼働させる

ForegroundServiceは、ユーザーがServiceを認識できる状態であるので、メモリ不足になっても、システムによる強制終了の候補にはなりません。ForegroundServiceは、ステータスバーに通知を出さなければならず、「進行中」という見出しの下に表示されます。Serviceが停止されるかForegroundから削除されない限り、通知は消えません。

COUTION:ForegroundServiceの利用制限
 ForegroundServiceは、ユーザーがアプリを直接操作してなくても、アプリが軌道してることが分かる状態であって、そのアプリを実行させる必要がある場合にのみ利用できます。

 ForegroundServiceでは、ユーザーが確実にアプリの実行内容を把握できるように、通知の重要度を設定して、ステータスバーに通知を表示させます。

 重要度を最も低く設定するようなアクションには、Serviceを使わず、スケジュールされた処理を使うことを検討すべきです。

 Serviceを使うアプリは、システムに負荷を加え、システムリソースを消費します。重要度を低く設定し、通知を非表示にすると、ユーザーが使っているアプリのパフォーマンスを損なう可能性がありますので、重要度を最低に設定してServiceを実行する場合には、通知ドロワーの下部でアプリを呼び出します。
(通知ドロワーの下部は、システムの都合などにより強制終了される可能性が高まるということかな?)

Serviceで音楽再生する音楽プレイヤーは、ユーザーに明示的にアプリの実行を認識させるために、Foregroundで実行するように設定すべきです。ステータスバーの通知は、再生中の音楽を表示したり、音楽プレイヤーを操作して、任意のアクティビティを起動させます。同様に、ユーザーの走行を追跡するアプリは、ユーザーの位置を追跡するForegroundServiceが必要です。

Note:
 Android9(API28)以降でForegroundServiceを使ってるアプリは、FOREGROUND_SERVICEの許可を要求します。これは通常の許可なので、システムは要求元のアプリに自動的に許可します。

 もし、API28以下のレベルで、FOREGROUND_SERVICEを要求せずに、ForegroundServiceを生成した場合は、システムは安全性の例外を投げます。

Foreground Sercice

ForegroundServiceでは通知を表示する必要があり、Service停止か、ForegroundからServiceを除去しない限り、通知を消すことはできません。


Foreground Serviceを作成する

startForeground(Id,notification)を実行
・ Notification
・ Intent
・ PendingIntent
・ notification.setLatestEventInfo(this,title,text,PendingIntent)
・ startForeground(Id,notification)

<参考>

Notification notification = new Notification(
                                R.drawable.icon,
                                getText(R.string.ticker_text),
                                System.currentTimeMillis()
                                );
Intent notificationIntent = new Intent(this, ExampleActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
notification.setLatestEventInfo(
                              this, 
                              getText(R.string.notification_title),
                              getText(R.string.notification_message), 
                              pendingIntent);
startForeground(ONGOING_NOTIFICATION_ID, notification);

startForeground()に渡すIDには、0はNG


Foregourd Serviceの除去

stopForeground()
・ ServiceをForegroundから除去
・ 通知を除去するかを示すbool値も受け付けます
・ Serviceは停止しない
  ただし、ServiceがまだForegroundで実行中に停止した場合は、通知は削除される

Serviceのライフサイクル管理

省略

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

51歳からのプログラミング 備忘 Service [写経]

https://developer.android.com/guide/components/services

Serviceとは

Service:ユーザーインターフェイスを使わず、バックグラウンドで長時間処理を実行できるアプリケーションコンポーネントです。

他のアプリケーションコンポーネントを開始することもできるし、ユーザーが他のアプリケーションに変更しても、バックグラウンドで処理し続けます。

さらに、コンポーネントとサービスをバインドして交信し、プロセス間通信もできます。例えば、ネットワーク処理、音楽再生、ファイルI/O、コンテンツプロバイダー通信など、すべてバックグラウンドで処理できます。

サービスには3種類あります。

Foreground

ForegroundServiceは、ユーザーに通知されている処理を実行します。例えば、オーディオアプリはForegroundServiceを使って、オーディオトラックで再生します。ForegroundServiceでは通知を表示させます。ユーザーがアプリを操作してない時は、ForegroundServiceで処理し続けます。

Background

BackgroundServiceはユーザーによって直接気が付かない処理を実行します。例えば、アプリのストレージを圧縮するサービスの場合、それはバックグラウンド処理となります。

Note:
アプリがAPI26以降の時、アプリがforegroundになければ、システムはバックグラウンドサービスの実行に制約を課します。このようなケースでは、代わりにスケジュールされた作業を行います。

Bound

bindService()でサービスをアプリケーションコンポーネントにバインドします。バインドされたサービスは、コンポーネントと通信するため、クライアントサーバーインターフェイスを提供します。例えば、リクエスト送信、結果受信、プロセス通信など。

バインドされたサービスは、他のアプリケーションコンポーネントがサービスにバインドされている限り、終了することはありません。

サービスは、明示的な開始、バインド、どちらも使えますが、ドキュメントでは開始とバインドを分けて説明します。

実装は、onStartCommand()でサービス開始を要求するか、onBind()でバインドを許可するか、のどちらかで行います。

どのアプリケーションコンポーネントも、任意のアクティビティを使うのと同じ方法で、Intentを使って同じサービスを開始できます。マニュフェストに他のアプリからの利用禁止を定義することも可能です。詳しくは>https://developer.android.com/guide/components/services#Declaring

COUTION:
 特に方法を指定しない限り、Serviceが自身のスレッドを作成することなく、別のスレッドで実行されることもありません。
 なので、MP3再生やネットワーク処理など負担のかかる処理では、フリーズエラーを予防するために、サービス内に新しいスレッドを作成する必要があります。

ServiceとThreadどっちを使う?

・ Serviceはバックグラウンド処理する場合
・ Threadはメインスレッド以外で処理する必要がある場合

例えば、アクティビティを実行してる間だけ音楽再生する場合は、onCreate()にスレッドを作成し、onStart()で実行開始、onStop()で停止。 Threadの代わりに、AsyncTascやHandlerThreadも使えます。

Service作成にあたって重要な@Overrideメソッド

onStartCommand() / onBind() / onCreate() / onDestroy()

onStartCommand()

アクティビティなどの他のコンポーネントが、startService()を呼び出して、Serviceの開始を要求したときに、システムがonStartCommand()を呼び出します。onStartCommand()が実行されるとServiceが開始されます。

onStartCommand()を実装する時は、Service完了時にstopSelf()かstopServece()で、自身でサービスを停止させます(バインドのみを提供する場合には、onStartCommand()は実装しなくてよい)。

onBind()

bindService()を呼び出して、他のコンポーネントをServiceにバインドさせるときに、システムがonBind()を呼び出します。

onBind()実装時にはIBinderを返して、クラインとがServiceとの通信に使うインターフェースを提供する必要があります。

onBind()の実装は常に必要だけれど、バインドを許可しない場合はnullを返す必要があります。

onCreate()

onStartCommand()かonBind()を呼び出す前で、サービスが始めて作成された時に、1度限りのセットアップ処理をするために呼び出されるメソッド。

onDestroy()

Serviceが受け取る最後の呼び出し。

Serviceの強制終了

androidシステムは、ユーザーが現に使ってるアクティビティ用のリソースを確保するため、サービスを強制終了させることがあります。

ユーザーが現に使ってるアクティビティにServiceがバインドされてる場合には、強制終了される可能性は低く、Foregroundが宣言されてる場合は、強制終了の可能性は、ほぼありません。

Serviceは長時間実行されていると、バックグラウンドタスクのリスト位置が低まり、強制終了される可能性が高まっていきますので、強制終了時に備えて、システムによる再起動処理をデザインする必要があります。

システムがServiceを強制終了すると、リソースが回復し次第、Serviceが再起動します(onStartCommand()の戻り値による)。

Service作成

マニフェスト宣言

<manifest ... >
  ...
  <application ... >
      <service android:name=".ExampleService" />
      ...
  </application>
</manifest>

android:name
唯一の必須属性、変更不可
アプリの安全性を保つため、Serviceを開始したりバインドするときは、常に明示的Intentを使います。

どのServiceを開始するかについて、あいまい性を残す場合には、ServiceにIntentフィルタを定義して、Intentからコンポーネント名を除外して、その後にターゲットのサービスのあいまい性を解消するsetPackage()でIntentのパッケージを設定する必要があります。

android:exported
android:exported属性を含めて"false"に設定すると、Serviceを自身のアプリでしか利用できないようにできます。たとえ明示的Intentを使っても、Serviceを開始できなくなります。

要求されたServiceを生成する

要求されたServiceとは:
他のコンポーネントがstartService()を呼び出して、onStartCommand()メソッドを使って、生成を要求されたService。

Serviceの終了
onStartCommand()で開始したServiceは、
 自身で終了する場合には、stopSelf()
 他のコンポーネントで終了する場合には、stopService()

Serviceの開始とIntent
アクティビティなどのアプリケーションコンポーネントは、startService()でServiceを指定しIntentでServiceが使うデータを渡してServiceを開始し、Serviceは、onStartCommand()を使ってIntentを受け取って処理します。

要求されたサービスを生成するための拡張可能なクラス

Service
全てのServiceの基本クラス
Serviceはデフォルトでアプリのメインスレッドを使用するため、Serviceの処理によって、アクティビティのパフォーマンスを低下させることがあります。

そのため、このクラスを拡張するときは、Service全ての作業のための、新しいスレッドを作成することが重要です。

IntentService
全てのServiceの生成要求を、一件ずつ処理するワーカースレッドを使った、Serviceのサブクラス。Serviceで同時に複数の生成要求を処理する必要がない場合に最適。

onHnadleIntent()を実装するだけで、それぞれの生成要求のIntentを受け取り、バックグラウンド処理ができるようになります。

IntentServiceクラスの拡張 省略
Serviceクラスの拡張 省略

Serviceの開始

アクティビティや他のアプリケーションコンポーネントからIntent(開始Serviceを指定)を、startService()に渡して、Serviceを開始。

Androidシステムが、ServiceのonStartCommand()メソッドを使ってIntentを渡します(絶対に、onStartCommand()を直接呼び出してはいけません)

<参考>

Intent intent = new Intent(this,HelloService.class);
startService(intent);

AndroidシステムがServiceのonStartCommand()を呼び出しています。
Serviceが実行されてないときは、システムはonCreate()を呼び出してから、onStartCommand()を呼び出します。

Serviceでバインドが提供されてないときは、startService()で配信されたIntentがアプリケーションコンポーネントとServiceの唯一の通信手段となります。

ただし、Serviceから結果を返す場合、Serviceを要求するクライアントが、ブロードキャスト用にPendingIntentを作成でき(getBroadcast()を使う)、要求されたServiceのIntentに配信できます。その後、Serviceはブロードキャストを使って結果を配信できます。

Service要請が複数ある場合、それに対応するために、onStartCommand()に複数の呼び出しがはっせいしますが、停止するためには、stopSelf()やstopServiceは1つのみでOK。

Serviceの停止

stopSelf()やstopServiceで停止要求すると、androidシステムはServiceを破棄します。

ServiceがonStartCommand()への複数要求を同時処理している場合は、新しい生成要求がある可能性があるので、複数要求の同時処理後もServiceは停止させないようにします。1つの停止で全て停止します。

複数要求の停止は、stopSelf(int)を使って、最新の要求に基づいてServiceの停止要求を行います。具体的には、停止したい要求にID(onStartCommand()に配信したstartId)を渡します。

COUTION:
Serviceのバインドが有効な場合でも、onStartCommand()への呼び出しを受け取ったときは、常に自身でサービスを停止する必要があります。stopSelf()を使ってね。

バインドされたServiceを作成

bindService()でServiceにバインドすると、アプリケーションコンポーネントが長時間接続できます(通常では、コンポーネントがstarService()を呼び出すことではServiceは開始できない)。

バインドされたServiceを作成

・ onBind()を実装
  Serviceとの通信用のインターフェイスを定義するIBinderを返す
・ アプリケーションコンポーネントがbindService()を呼び出
  インターフェイスIBinderを取得しServiceのメソッドの呼び出しを開始

バインドされたコンポーネントがなくなると、システムによってServiceが破棄されます(バインドされたサービスは、onStartCommand()でサービスが開始された時と同じ方法で停止する必要はありません)。

アンバインド

unbindService()
Serviceにバインドされているクライアントがなくなったら、システムがサービスを破棄。

Foreground Sercice

ForegroundServiceでは通知を表示する必要があり、Service停止か、ForegroundからServiceを除去しない限り、通知を消すことはできません。

Create Foreground Service

startForeground(Id,notification)を実行
・ Notification
・ Intent
・ PendingIntent
・ notification.setLatestEventInfo(this,title,text,PendingIntent)
・ startForeground(Id,notification)

<参考>

Notification notification = new Notification(
                                R.drawable.icon,
                                getText(R.string.ticker_text),
                                System.currentTimeMillis()
                                );
Intent notificationIntent = new Intent(this, ExampleActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
notification.setLatestEventInfo(
                              this, 
                              getText(R.string.notification_title),
                              getText(R.string.notification_message), 
                              pendingIntent);
startForeground(ONGOING_NOTIFICATION_ID, notification);

startForeground()に渡すIDには、0はNG

Foregourd Serviceの除去

stopForeground()
・ ServiceをForegroundから除去
・ 通知を除去するかを示すbool値も受け付けます
・ Serviceは停止しない
  ただし、ServiceがまだForegroundで実行中に停止した場合は、通知は削除される

Serviceのライフサイクル管理

省略

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

#6 Kotlin で TODO アプリを開発・リリースするまでの記録

前回のあらすじ

前回までの記事

#1 Kotlin で TODO アプリを開発・リリースするまでの記録
#2 Kotlin で TODO アプリを開発・リリースするまでの記録
#3 Kotlin で TODO アプリを開発・リリースするまでの記録
#4 Kotlin で TODO アプリを開発・リリースするまでの記録
#5 Kotlin で TODO アプリを開発・リリースするまでの記録

前回のあらすじ

  • RecyclerView でリスト表示ができた

今回の目標

  • ソースを読み解く
  • 実装に沿って修正する

カードとリスト

初めに気になったのが、リスト一覧というよりカード一覧だなあ、です。
ものすごくどうでもいい気がしますが、リストっぽい表示にできないのでは CardView の使用自体を断念せざるを得なくなってしまいます。

androidx.cardview.widget.CardView から margin を削除します。
list_item.xml の Text タブを開いて直接修正します。

android:layout_margin="8dp"
list_item.xml
<androidx.cardview.widget.CardView
        xmlns:card_view="http://schemas.android.com/apk/res-auto"
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        card_view:cardElevation="2dp"
        android:foreground="?android:attr/selectableItemBackground">

image.png
境界のない塊と化してしまいました。
きっと border の設定がどこかにあるはずです。

今度は list_item.xml の Design タブを開いてみます。
各 Card ごとの入れ物になっているのは LinearLayout です。
image.png
Attributes をざっと見ましたが、border らしき設定が見当たりません。
調べているとこのような記事を見つけました。
Viewにborder的な罫線をつける方法

え、border の設定ないの?
(時代の最先端を行くと border 設定が消えるバグ)

その時、RecyclerView を調べていて見た記事の中に、border の設定について記載があったことを思い出します。

RecyclerViewを使ってSimple List Itemのような表示をするまで
// 区切り線の表示
recycler_view.addItemDecoration(DividerItemDecoration(this, DividerItemDecoration.VERTICAL))

すがるような気持ちで MainActivity に書き加えます。

MainActivity.kt
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val hoges: List<String> = mutableListOf("a", "b", "c")

        mainRecyclerView.adapter = RecyclerAdapter(this, this, hoges)
        mainRecyclerView.layoutManager = LinearLayoutManager(this, RecyclerView.VERTICAL, false)

        // 区切り線の表示
        mainRecyclerView.addItemDecoration(DividerItemDecoration(this, DividerItemDecoration.VERTICAL))
    }

image.png
リストっぽい border を引くことができました!

余談:RecyclerView で区切り線を引く

RecyclerViewで行間に区切り線を表示する

ListView では簡単に設定できる区切り線ですが、RecyclerView ではコードベースで実装しないといけないようです。
LinearLayout で設定できるよう粘らなくて良かったです。
あと、ListView に限った話なのか、アプリ的に一般的なのかわかりませんが、区切り線は border ではなく divider というプロパティになるようですね。

Androidアプリ開発 RecyclerView DividerItemDecorationで下線を引く

以前までは下線を引くのも、クラスを継承して独自で実装しなければならなかったのが、最新の RecyclerView ではたった一行で実装できるようになったそうです。ありがたい。

タイトルを設定する

いつやってもいいので、このタイミングでやっちゃいましょう。

Android アプリケーションのウィンドウタイトルを変更する
タイトルは静的なので、AndroidManifest.xml を直接変更しました。

android:label="@string/app_name"
 ↓
android:label="TODO"
AndroidManifest.xml
    <application
            android:allowBackup="true"
            android:icon="@mipmap/ic_launcher"
            android:label="TODO"
            android:roundIcon="@mipmap/ic_launcher_round"
            android:supportsRtl="true"
            android:theme="@style/AppTheme">

image.png

閑話休題

迷走しました。
次にやってみる内容として、リストにチェックボックスを追加し、チェックの値を取得しようと考えます。
ですが、調べても調べても、参考記事を見つけることができず、どうにもうまくいきませんでした。

そこで iOS アプリ担当の友人に相談しました。
リストの値を配列に保持しておき、画面ではなく配列から取得する方法を勧めてもらいます。

現在のソースコードを見る限り、どの位置で、どのように値を配列へ格納するのかわかりません。
表示しているテキストデータは、配列でべた書きで渡しているだけです。
リストにチェックボックスを持たせても、この配列ではチェックボックスの値を管理できません。
リアルタイムの値を保持する配列を別途用意する必要があります。

この配列を用意するにはどうしたらよいのか?
わからないなりの入り口として、リストのデータを追加、削除できるようにしてみれば良いのではないか、と考えます。
実装上、どこかに値を保持しているはずなので、その方法を知ることができるはず。

まとめ

今回はここまで。
リストデータの追加・登録は次回の記事で進めます。

成果

  • リストに区切り線ができた
  • タイトルを設定した

参考記事に感謝

たくさんの記事を引用させて頂きました。ありがとうございます。
著者が一部のみを引用したことにより、引用元の意図しない情報として受けられることがないよう、できれば引用元の記事を読んでいただけますようお願いいたします。
またいずれの記事の感想も、著者個人の意見に基づくものですのでご注意ください。

次の記事

#7 Kotlin で TODO アプリを開発・リリースするまでの記録

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

androidxのPreferenceでcustom dialogを表示させたみがある

環境

  • androidx.preference:preference:1.1.0-rc01
  • kotlin 1.3.31

経緯

ImagePickっぽいPreferenceを作りたくて色々やってたんだけどうまくいかなくて落ち着いてやり直したらできたのでそれの忘備録
supoort libraryのバージョン違いやandroidxなど色々あるが惑わされてはいけない

結論

  • PreferenceFragmentCompatを継承したクラスでonDisplayPreferenceDialogをオーバーライド

そもそも

アーキテクチャはMVPを意識して下記構成になっている

SettingsActivity extends AppCompatActivity
└ SettingsFragment extends PreferenceFragmentCompat
//同ContractとPresenter

WallpaperPreference //表示させたいやつ
└ WallpaperPreferenceDialogFragmentCompat extends PreferenceDialogFragmentCompat

class SettingsFragment : PreferenceFragmentCompat(){

    companion object{
        const val DIALOG_FRAGMENT_TAG = "androidx.preference.PreferenceFragment.DIALOG"
    }

    //onCreate等省略

    override fun onCreatePreferences(savedInstanceState : Bundle?, rootKey : String?) {
        setPreferencesFromResource(pref_root, rootKey)
    }

    override fun onDisplayPreferenceDialog(preference : Preference?) {

        //ここでハンドリングする
        if(preference is WallpaperPreference) {
            var dialogFragment : DialogFragment = WallpaperPreferenceDialogFragmentCompat.newInstance(preference.key)
            dialogFragment.setTargetFragment(this, 0)
            dialogFragment.show(fragmentManager!!, DIALOG_FRAGMENT_TAG)
        }else {
            super.onDisplayPreferenceDialog(preference)
        }
    }

}

補足

表示させたいPreferenceFragmentCompat、PreferenceDialogFragmentCompatはファクトリーパターンにしておかないと落ちます

公式のサンプルが珍しく役にたたない

終わりに

間違い等あれば指摘ください 5km走って出直してきます(修正します)

引用元

Exploring Android Jetpack: Preferences
設定
公式サンプル

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

Navigationで最初のFragmentにActivityから引数を渡す

「Navigationで最初のFragmentにActivityから引数を渡す」とは?

Navigationを使ってAndroidアプリを開発している時に、
「別のActivityから渡された引数をNavigationで設定した最初のFragmentに渡したい!」
なんて事がある場合の実装方法になります。

:computer:環境構築


    // Navigation
    def navi_version = "2.2.0-alpha01"
    implementation "androidx.navigation:navigation-fragment-ktx:$navi_version"
    implementation "androidx.navigation:navigation-ui-ktx:$navi_version"

Navigation 1.0.0-alpha07以上のバージョンで動作可能

:pencil: 実装


ActivityとFragmentの構成

MainActivityにNavHostFragmentを指定しており、FirstFragmentがNavGraphに指定してある。

  • res/navigation/nav_graph.xml
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
            xmlns:app="http://schemas.android.com/apk/res-auto"
            xmlns:tools="http://schemas.android.com/tools"
            android:id="@+id/nav_graph"
            app:startDestination="@id/firstFragment"
            tools:ignore="UnusedNavigation">

    <fragment android:id="@+id/firstFragment" 
                             android:name="com.xxxx.xxxxx.FirstFragment"
                             android:label="fragment_first"
                             tools:layout="@layout/fragment_first"/>
</navigation>
  • MainActivity

レイアウトファイル

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context="com.xxxx.xxxx.MainActivity">

    <fragment
            android:id="@+id/nav_host_fragment"
            android:name="androidx.navigation.fragment.NavHostFragment"
            android:layout_width="0dp"
            android:layout_height="0dp"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:defaultNavHost="true"/>

</androidx.constraintlayout.widget.ConstraintLayout>

navGraphの指定をする app:navGraph="@navigation/nav_graph" は設定しない

class MainActivity : AppCompatActivity() {

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

        findNavController(R.id.nav_host_fragment)
            .setGraph(R.navigation.nav_graph, intent.extras)
    }

    companion object {
        private val TAG = MainActivity::class.java.simpleName
    }
}

コード上でNavGraphを設定し、その時に引数を渡してあげる。
↑の場合、MainActivityに渡された引数をそのままFirstFragmentに渡している。

以上

:link: 参考になったURL


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

Androidで、jacksonを使うことになったのでメモ

Jacksonは、javaでjsonをjavaオブジェクトに扱うためのユーテリティライブラリです。
Jacksonは、genericsをサポートしているので、jsonを、MAPへと楽に変換できます。

それでは、以下の、配列、ネストしたオブジェクトを含む複雑なJSONを、javaのオブジェクトに変換しましょう。

{
  "id": 123,
  "name": "Pankaj",
  "permanent": true,
  "address": {
    "street": "Albany Dr",
    "city": "San Jose",
    "zipcode": 95129
  },
  "phoneNumbers": [
    123456,
    987654
  ],
  "role": "Manager",
  "cities": [
    "Los Angeles",
    "New York"
  ],
  "properties": {
    "age": "29 years",
    "salary": "1000 USD"
  }
}

jsonに対応する以下のクラスを持ちます。

package com.journaldev.jackson.model;

public class Address {

    private String street;
    private String city;
    private int zipcode;

    public String getStreet() {
        return street;
    }
    public void setStreet(String street) {
        this.street = street;
    }
    public String getCity() {
        return city;
    }
    public void setCity(String city) {
        this.city = city;
    }
    public int getZipcode() {
        return zipcode;
    }
    public void setZipcode(int zipcode) {
        this.zipcode = zipcode;
    }

    @Override
    public String toString(){
        return getStreet() + ", "+getCity()+", "+getZipcode();
    }
}

Adressクラスは、JSON内部のadressプロパティに対応したクラスです。

package com.journaldev.jackson.model;

import java.util.Arrays;
import java.util.List;
import java.util.Map;

public class Employee {

    private int id;
    private String name;
    private boolean permanent;
    private Address address;
    private long[] phoneNumbers;
    private String role;
    private List<String> cities;
    private Map<String, String> properties;

    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public boolean isPermanent() {
        return permanent;
    }
    public void setPermanent(boolean permanent) {
        this.permanent = permanent;
    }
    public Address getAddress() {
        return address;
    }
    public void setAddress(Address address) {
        this.address = address;
    }
    public long[] getPhoneNumbers() {
        return phoneNumbers;
    }
    public void setPhoneNumbers(long[] phoneNumbers) {
        this.phoneNumbers = phoneNumbers;
    }
    public String getRole() {
        return role;
    }
    public void setRole(String role) {
        this.role = role;
    }

    @Override
    public String toString(){
        StringBuilder sb = new StringBuilder();
        sb.append("***** Employee Details *****\n");
        sb.append("ID="+getId()+"\n");
        sb.append("Name="+getName()+"\n");
        sb.append("Permanent="+isPermanent()+"\n");
        sb.append("Role="+getRole()+"\n");
        sb.append("Phone Numbers="+Arrays.toString(getPhoneNumbers())+"\n");
        sb.append("Address="+getAddress()+"\n");
        sb.append("Cities="+Arrays.toString(getCities().toArray())+"\n");
        sb.append("Properties="+getProperties()+"\n");
        sb.append("*****************************");

        return sb.toString();
    }
    public List<String> getCities() {
        return cities;
    }
    public void setCities(List<String> cities) {
        this.cities = cities;
    }
    public Map<String, String> getProperties() {
        return properties;
    }
    public void setProperties(Map<String, String> properties) {
        this.properties = properties;
    }
}

employeeは、ルートのjsonに対応したクラスです。
それでは、JSONをjavaのオブジェクトに変換しましよう。

package com.journaldev.jackson.json;

import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.journaldev.jackson.model.Address;
import com.journaldev.jackson.model.Employee;


public class JacksonObjectMapperExample {

    public static void main(String[] args) throws IOException {

        //read json file data to String
        byte[] jsonData = Files.readAllBytes(Paths.get("employee.txt"));

        //create ObjectMapper instance
        ObjectMapper objectMapper = new ObjectMapper();

        //convert json string to object
        Employee emp = objectMapper.readValue(jsonData, Employee.class);

        System.out.println("Employee Object\n"+emp);

        //convert Object to json string
        Employee emp1 = createEmployee();
        //configure Object mapper for pretty print
        objectMapper.configure(SerializationFeature.INDENT_OUTPUT, true);

        //writing to console, can write to any output stream such as file
        StringWriter stringEmp = new StringWriter();
        objectMapper.writeValue(stringEmp, emp1);
        System.out.println("Employee JSON is\n"+stringEmp);
    }

    public static Employee createEmployee() {

        Employee emp = new Employee();
        emp.setId(100);
        emp.setName("David");
        emp.setPermanent(false);
        emp.setPhoneNumbers(new long[] { 123456, 987654 });
        emp.setRole("Manager");

        Address add = new Address();
        add.setCity("Bangalore");
        add.setStreet("BTM 1st Stage");
        add.setZipcode(560100);
        emp.setAddress(add);

        List<String> cities = new ArrayList<String>();
        cities.add("Los Angeles");
        cities.add("New York");
        emp.setCities(cities);

        Map<String, String> props = new HashMap<String, String>();
        props.put("salary", "1000 Rs");
        props.put("age", "28 years");
        emp.setProperties(props);

        return emp;
    }

}

上のプログラムを走らせると、以下の出力が表示されます。

Employee Object
***** Employee Details *****
ID=123
Name=Pankaj
Permanent=true
Role=Manager
Phone Numbers=[123456, 987654]
Address=Albany Dr, San Jose, 95129
Cities=[Los Angeles, New York]
Properties={age=29 years, salary=1000 USD}
*****************************
Employee JSON is
//printing same as above json file data

ObjectMapperは、 Jackson APIの中で一番重要なAPIでreadValueと、writeValueというJSONをjavaに変換するメソッドと、JAVAをJSONに変換するメソッドを提供する。

ObjectMapperクラスは、Singletonオブジェクトとして初期化され再利用される。

JSONをMAPへと変換する

時々、JSONをMAPへ変換したくなる時がある。

{
  "name": "David",
  "role": "Manager",
  "city": "Los Angeles"
}

jacksonを使えば、jsonをダイレクトにMapに変換出来る。

//converting json to Map
byte[] mapData = Files.readAllBytes(Paths.get("data.txt"));
Map<String,String> myMap = new HashMap<String, String>();

ObjectMapper objectMapper = new ObjectMapper();
myMap = objectMapper.readValue(mapData, HashMap.class);
System.out.println("Map is: "+myMap);

//another way
myMap = objectMapper.readValue(mapData, new TypeReference<HashMap<String,String>>() {});
System.out.println("Map using TypeReference: "+myMap);

上記のスニペットを実行すると以下の出力が得られる。

Map is: {name=David, role=Manager, city=Los Angeles}
Map using TypeReference: {name=David, role=Manager, city=Los Angeles}

jsonの特定のpropertyのみjavaのオブジェクトに変換する

時々、特定のpropertyのみjavaのオブジェクトに変換したい時がある。

//read json file data to String
byte[] jsonData = Files.readAllBytes(Paths.get("employee.txt"));

//create ObjectMapper instance
ObjectMapper objectMapper = new ObjectMapper();

//read JSON like DOM Parser
JsonNode rootNode = objectMapper.readTree(jsonData);
JsonNode idNode = rootNode.path("id");
System.out.println("id = "+idNode.asInt());

JsonNode phoneNosNode = rootNode.path("phoneNumbers");
Iterator<JsonNode> elements = phoneNosNode.elements();
while(elements.hasNext()){
    JsonNode phone = elements.next();
    System.out.println("Phone No = "+phone.asLong());
}

上記のコードを実行すると、以下の出力を得ます。

id = 123
Phone No = 123456
Phone No = 987654

他にも、JSONドキュメントを編集したり、JSONストリームを暑かったりとかが出来ます。
詳しくは以下の参考を読んでください。

参考

https://www.journaldev.com/2324/jackson-json-java-parser-api-example-tutorial

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

Fragmentのオプションメニューがタップされて画面遷移することをテストする

やりたいこと

BottomNavigationViewを使っていてToolbar自体はActivityのレイアウトにあります。
しかしオプションメニューはタブのFragment毎に異なるので、Fragmentの単体テストに含めたい。
ですがlaunchFragmentInContainerで起動したFragmentでwithId(R.id.hoge_menu)でアクセスすると、ビューが見つからないと言われる。
まぁそうか。

解決方法

少し発想を変えて、メニューのタップでなく、onOptionsItemSelected の呼び出しをテストすることにした。
テストコードはこんな感じ。

val navController = mock(NavController::class.java)
val scenario = launchFragmentInContainer<TopFragment>(Bundle(), R.style.AppTheme)
scenario.onFragment {
    Navigation.setViewNavController(it.view!!, navController)
    it.onOptionsItemSelected(RoboMenuItem(R.id.hoge_menu))
    verify(navController).navigate(TopFragmentDirections.actionTopFragmentToSubFragment())
}

RoboMenuItem(R.id.hoge_menu) といのがRobolectricが用意しているMenuItem用のクラスを使用する。
これで無事に TopFragmentからオプションメニューをタップするとSubFragmentに移動することをテストすることができました。

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

【Google Play】定期購入の無料試用の適用範囲の設定が追加されていた件

いつの間にやら、定期購入の無料試用で適用範囲の設定がPlay Consoleに追加されていたので、ざっくり説明してみる。(いつから追加されたのかは不明です。)

どこに?

Google Play Consoleの「定期購入」画面にある「定期購入の設定を管理」にありました。
スクリーンショット_2019-08-15_18_26_51.png

「定期購入の設定」の一番下
スクリーンショット_2019-08-15_18_13_36.png

何が設定できるのか

「定期購入ごとの無料試用」
アプリ内で利用可能な各定期購入の無料試用にユーザーが登録できるようにします。これを設定しない場合、ユーザーはアプリ内で 1 個のみの無料試用に登録できます。

ということで、無料試用の適用範囲を アプリ内で一度にするのか or 定期購入ごとにするのか が設定できるようになります。(この設定が追加される前は「定期購入ごとの無料試用」だった?)

例えば、同じような機能を提供する定期購入が複数ある場合は、アプリ内で一度だけ無料試用できたほうが望ましいのかなと思いますし、雑誌の定期購入のような種類の異なるものの場合には、定期購入ごとに無料試用ができたほうが良い、などユースケースに合わせて設定することができるようになった形ですね。

まとめ

定期購入の無料試用の設定が追加されていた件をざっくり説明しました。
この設定をうまく調整することで、適切な無料試用の運用ができるようになるのではないかと思います。

ちなみに自分はこの設定のことを知らず、「無料試用できるはずなのにできない涙」となりました。(Play Consoleで設定を変更したら、反映されるまでに4〜5時間かかました。)

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