Androidのスクリーンショットを10秒ごとに撮る
一瞬だけ出るPush通知をスクショにしたかったが、ずっと画面を見ておくのも大変なのでスクショを定期的にとるshellを作成した
#!/bin/bash set -eux # スクリーンキャプチャを撮る script_dir=$(cd $(dirname ${BASH_SOURCE:-$0}); pwd) cd ${script_dir} while true; do adb shell screencap -p /sdcard/screen.png adb pull /sdcard/screen.png mv screen.png `date +%Y%m%d_%H-%M-%S`.png adb shell rm /sdcard/screen.png sleep 10 done
各処理の説明
ループ処理
while true; do ~~~ ~~~ sleep 10 done
trueにしてるので止めるまで無限ループしている
sleepで処理を10秒間隔で実行するようにしている
スクショを撮る
adb shell screencap -p /sdcard/screen.png
画像は端末内に保存される
-p で端末内のどこに保存するか指定する
端末内の画像をPCに持ってくる
adb pull /sdcard/screen.png
shellが置かれている場所にコピーされる
今の時間にリネームする
mv screen.png `date +%Y%m%d_%H-%M-%S`.png
いつのスクショかわかりやすくするのとサイド実行したときに上書きされないようにするため
端末内のスクショを削除する
adb shell rm /sdcard/screen.png
処理の後始末
実行結果
PCにAndroid端末をつなげた状態で実行する
./auto_screencap.sh # 1分程待つ # 別コンソールで開く ls 20200128_11-36-13.png 20200128_11-36-34.png 20200128_11-36-23.png auto_screencap.sh
止め忘れるとディスクを圧迫するので忘れないようにする
保存先の枚数が100枚を超えたら~とかで止めたほうが良いかもしれない
クライアント側の文字数制限を書ける時に悩むこと
Android/iOSのネイティブアプリやフロントエンドでテキストフィールドを設置する際、文字数にバリデーションをかける事がある。
その扱いについて少し悩んでいるので考えを列挙する
色んな所で言われていることをまとめてるだけ。
何に悩んでいるのか
・ネイティブ側でこのバリデーションをかける必要があるのか?
・テキストフィールドにその文字数以上入力できないようにするのか、送信時にチェックをしてエラーにするのがいいのか
ネイティブ側でこのバリデーションをかける必要があるのか?
この文字数制限は、無制限に送れるようにしてしまうとサーバやDBの負荷になるため書けることがほとんどだと思う。
そのため、必ずサーバ側で受け取った時にも文字数にバリデーションはかける。(クライアント側しかないならそれは不具合と言っていい)
そうなると、ネイティブ側はなにもしなくてもエラーハンドリングさえしていればいいことになる。
これに関しては、わざわざ通信しないとエラー結果が出ないことが気になるなら文字数チェックをかけたほうがいい。
ユーザの利便性を考えていくと、通信しなくてもエラーがわかるならわかったほうがいい。
文字数の数をころころ変えて仕様の調整をしたいようならサーバ側のみじゃないとだめだけど。
テキストフィールドにその文字数以上入力できないようにするのか、送信時にチェックをしてエラーにするのがいいのか
文字数制限をネイティブ側で掛ける場合、maxlength等でテキストフィールドにその文字数以上入力させない方法がある。
一方で、送信ボタン押した時にjavascriptやネイティブコード上で文字数をチェックして、超えるならエラーにするという方法がある。
これに関しては悩みが多くて、
maxlengthを使う方法は、ユーザがコピペでテキストを入力した場合、ペーストする文字列の数が多い場合勝手に切られちゃうため(100文字の制限がかかってるテキストフィールドに101文字をペーストすると最後の文字は切られて無くなるため)不具合というか、ユーザの意図に反する動きになる。
maxlengthをパスワードにかけた結果、意図したパスワードとは違うパスワードが設定されてトラブルになったケースをブログで見たことがあるのであまり使わないほうが良さそう。
送信ボタン押した時にチェックするのはわりと普通のやり方でいい。仕様としてそこの文字数制限が変わらないなら問題ない。
ちょっとエラーにするためのラベル欄の出し分けとかがめんどくさいくらい。
他には10/100的な感じで入力文字数/文字数制限を可視化して超えるなら赤くするのもあって、リッチでいい。
まとめとしては
・サーバに文字数制限はかならず書け、クライアントだけにしない
・クライアントでは利便性を良くするのを目的にする
になる
Firefoxのヘッドレスブラウザ+Seleniumでログイン後ページのスクリーンショットを撮る
目的
サービスの計測値を日時で確認必要があり、それを自動化したかった
APIが提供されていないので直接見るしかなく、
ログインが必要なためnokogiriによるスクレイピングでは難しかった
そのためSeleniumを使ってログインし、スクリーンショットを定期的に撮るようにした
コード
screen_shot.rb
require 'selenium-webdriver' require 'time' url = "...ログインが必要なサービスのログインページ" #適宜変更 options = Selenium::WebDriver::Firefox::Options.new options.add_argument('-headless') driver = Selenium::WebDriver.for :firefox, options: options driver.navigate.to url sleep 3 #seleniumはブラウザ表示が間に合わなくてエラーになるため3秒待つ email = driver.find_element(id: 'email')#DevToolでID入力欄を指定できるID/Classを探す email.send_keys "ID"#IDを入力 password = driver.find_element(id: 'password')#DevToolでパスワードを指定できるID/Classを探す password.send_keys "パスワード"#パスワードを入力 submit = driver.find_element(class: "sign-in")#DevToolでログインボタンを指定できるID/Classを探す submit.submit sleep 7 #遷移時の処理が遅い場合があるため待つ date = Time.now.strftime("%Y%m%d_%H%M%S") driver.save_screenshot('log_' + date + '.png') driver.quit
Gemfile
source 'https://rubygems.org' gem 'selenium-webdriver'
コンソールコマンド
bundle install bundle exec ruby screen_shot.rb
実行したディレクトリ内にlog_{現在時刻}.png というファイルが表示されます。 例えばGmailみたいに、「未ログイン時に遷移した時に、ログインページに飛び、ログイン後にもともと遷移したかったページが表示される」サービスの場合は表示したいページを直接指定すればそのスクリーンショットが取れる
shellでフォルダ内の全てのファイルに対してfor処理をする
目的
screenshot ├── スクリーンショット10月 │ └── 01.png │ ├── 02.png │ ├── 03.png │ ├── 04.png │ ├── 05.png │ ├── 06.png │ ├── 07.png │ └── 08.png └── スクリーンショット11月 └── 01.png ├── 02.png ├── 03.png ├── 04.png ├── 05.png ├── 06.png ├── 07.png └── 08.png
こんな感じのディレクトリ構成になっていて、各フォルダ内の全ての画像をjpgに変えたかった
作成したshell
#!/bin/bash #set -eux while read -d $'\0' d; do dir=`basename "${d}"` echo dir="${dir}" while read -d $'\0' file; do echo file="${file}" convert "${file}" -quality 100 "${file}" done < <(find "$dir" -mindepth 1 -maxdepth 1 -print0) done < <(find . -type d -mindepth 1 -maxdepth 1 -print0)
説明
while read -d $'\0' d; do #今いる場所のフォルダ分loopを回す # ${d}でフォルダのpathが取れる done < <(find . -type d -mindepth 1 -maxdepth 1 -print0)
while read -d $'\0' d; do
は正直良くわかっていないが、いろいろ試した結果ディレクトリ内のすべてのファイルをループする - Qiitaの記事で書かれているやり方が一番良く動いたので使っている
done < <(find . -type d -mindepth 1 -maxdepth 1 -print0)
find . で今いる場所を検索する
-type d でディレクトリのみに検索範囲を指定
-mindepth 1 -maxdepth 1 で今いるディレクトリのみを検索範囲にする(深く探さない)
-print0 で空白文字を含むディレクトリも検索できるようにする参考URL→findとxargsコマンドで-print0オプションを使う理由(改) - Qiita
中身のloop
dir=`basename "${d}"` echo dir="${dir}" while read -d $'\0' file; do echo file="${file}" convert "${file}" -quality 100 "${file}" done < <(find "$dir" -mindepth 1 -maxdepth 1 -print0)
dir=`basename "${d}"`
ディレクトリパスからディレクトリ名を取得している
done < <(find "$dir" -mindepth 1 -maxdepth 1 -print0)
で-type d をなくしてloopを回しているため、ファイルも検索対象になっている
ファイルは画像のみなので、convert "${file}" -quality 100 "${file}"
でImageMagickのconvertコマンドを叩いて変換ができる
Android+Jacocoでテストカバレッジを出力する(Android Studio3.2対応)
しばらく放置していたJacocoでのカバレッジ取得を更新しようとしたらハマった。
参考にしたサイト: http://phicdy.hatenablog.com/entry/jacoco-code-coverage
上の記事が一年前に書かれているが、Gradleの更新によって動かなくなったらしい
タスク自体は成功するが、出力結果が0件となっていた
classDirectories = files( fileTree( - dir: "${buildDir}/intermediates/classes/uiTest/debug", + dir: "${buildDir}/intermediates/javac/uiTestdebug", exclude: coverageExcludeFiles)) }
jacocoのカバレッジ計測に使っている、classファイル群が生成されるフォルダを指定してあげたら動いた
RxJava2へ移行時のUnitTest
RxJava1 から2へ移行すると、TestSubscriberがうまく使えなくなった。
代わりにObservar.test()というメソッドが追加された。
RxJava1
public void test(){ //... TestSubscriber<UserResult> testSubscriber = TestSubscriber.create(); repository.getUser().subscribe(testSubscriber); testSubscriber.awaitTerminalEvent(); testSubscriber.assertCompleted(); UserResult result = testSubscriber.getOnNextEvents().get(0); assertEquals(result.getName(), "田中"); assertEquals(result.getAge(), 13); //... }
RxJava2
public void test(){ //... UserResult result = repository .getUser() .test() .assertComplete() .assertValueCount(1) .values().get(0); assertEquals(result.getName(), "田中"); assertEquals(result.getAge(), 13); //... }
メソッドチェーンで記述できることで、型指定の煩わしさが少なくなる。
JobSchedulerで◯時間後からXX時間ごとに定期実行する方法
概要
Android Oreo対応の時に、定期実行処理をJobSchedulerに設定したら1回目が即時実行されて困ったので対応した。
コード
アプリ起動のActivity
class MainActivity : AppCompatActivity(){ override fun onCreate(savedInstanceState: Bundle?) { // 略 startJob() } override fun startJob() { val componentName = ComponentName(this, FirstRunJobService::class.java) val periodic = 15 * 60 * 1000L // 15分 val scheduler = getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler val jobInfo = JobInfo.Builder(1, componentName) .setMinimumLatency(periodic) // サービス起動遅延時間を設定する .build() scheduler.schedule(jobInfo) } }
Activityから起動するService
class FirstRunJobService : JobService() { override fun onStartJob(p0: JobParameters?): Boolean { startJob() return false } override fun onStopJob(p0: JobParameters?): Boolean { return false } private fun startJob() { val componentName = ComponentName(this, MyJobService::class.java) // デバッグ時は15分,リリース版は6時間ごと val periodic = if (BuildConfig.DEBUG) 15 * 60 * 1000L else 6 * 60 * 60 * 1000L val scheduler = getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler val jobInfo = JobInfo.Builder(2, componentName) .setPeriodic(periodic) // サービス実行間隔を設定する .build() scheduler.schedule(jobInfo) } }
定期実行するService
class MyJobService : JobService() { override fun onStartJob(p0: JobParameters?): Boolean { // 定期実行したい処理 return true } override fun onStopJob(p0: JobParameters?): Boolean { return true } }
説明
ActivityでFirstRunJobServiceを15分後に起動するように処理を書く。 setMinimumLatencyメソッドで遅延時間を設定する(コードでは15分後に実行する)
FirstRunJobServiceでは、MyJobServiceを起動するための設定だけを行なっている setPeriodicメソッドで実行間隔を設定する(コードでは15分か6時間後に実行する)
setMinimumLatencyとsetPeriodicは同時には設定できない(例外が吐かれる)
参考URL
公式のリファレンス
https://developer.android.com/reference/android/app/job/JobInfo.Builder