AndroidとかiOSとかモバイル多め。その他技術的なことも書いていきます。

Kotlin Fest 2018に参加した

2018/8/25に品川で開催されたKotlin Fest 2018に参加してきました。

kotlin.connpass.com

f:id:phicdy:20180825132036j:plain

f:id:phicdy:20180825132200j:plain

飲み物とか食事。パンプディング美味しかった。

f:id:phicdy:20180825132327j:plain

f:id:phicdy:20180825132720j:plain

mixiさんブースでやってKotlinクイズの景品。7問中3問正解でプランニングポーカーもらいました。

f:id:phicdy:20180825132358j:plain

以下参加したセッションの感想

Kotlin で改善する Android アプリの品質

KotlinFest.pdf - Speaker Deck

非技術系の上の人にJava -> Kotlin を説得しにくいよねという話から始まり、Effective JavaにならってKotlinでの言語仕様での対応が聞けた。 kotlinにするだけで自然にEffective Javaのエッセンスを導入できる!というのが確かに・・・となってそれだけでもコストをかけて移行する価値があると感じた。Javaのつらみを書かなくていいし・・・。Effective Java読み直そう。

Kotlinアプリのリファクタリングポイント

Refactoring point of Kotlin application

nullが何を表しているかを考えることは重要だなと思った。状態数の話がまさに今直面している問題で、名前をつけたりすることを考えるきっかけになりそう

Kotlin linter

kotlin linter - Speaker Deck

カスタムルールは自然言語処理的なつらさがありそう。PsiViewerでだいぶ楽になる。カスタムフォーマットは更につらそう。android-lintが型が見れて強い。

Kotlin コルーチンを理解しよう

Kotlin コルーチンを 理解しよう - Speaker Deck

コルーチンを全然理解しないまま使っていたので、この発表でかなりすっきりした。実際にはステートマシンに変換されていることや直列・並列実行、待ち合わせなどがどう実現されているかの理解がかなり深まったと思う。

LT大会

3分短いなーと。個人的には5分くらいあってもよかったんじゃないかと。

おわりに

まずKotlinやっている人こんなにいるんだな〜というのが感想だった。Androidだけじゃなくサーバサイドの人も増えてきている印象だった。Java -> Kotlinに変換する話題が多かったのはなんだろう・・・まだ移行期で完全に移行できていない、もしくは上の人を説得できなくてJavaのままのプロダクトがまだまだあるのかなと感じました。 2019はまだ未定とのことですが、どのセッションも面白い話が多くて今日からでも使えそうな考え方や知見が得られたのでぜひ来年もやってほしい!

会場設備もよく、飲み物や軽食が充実していた。これだけの人数だと仕方ないけど立ち見はつらそうだった。 わいわいした雰囲気で楽しかったので、2019があればまた参加しようと思う。

【Android】他のアプリの上に重ねて表示まとめ

Androidの世界では基本的に1アプリのみが前面に出る。 しかし android.permission.SYSTEM_ALERT_WINDOW を宣言することで他のアプリが前面の場合でも Viewを表示できる

Android 6.0未満

制限はなく、 android.permission.SYSTEM_ALERT_WINDOW を宣言するだけで利用可能

Android 6.0以上

攻撃者でもこの権限を悪用し、ランサムウェアなどで常に画面表示することができてしまうため、ユーザの明示的な許可が必要になった。 ただしAndroid 6.0.1以上でGoogle Playアプリ6.0.5以上が入っている場合、Google Playからインストールしたアプリは「他のアプリの上に重ねて表示」の権限がデフォルトオンになる。恐らくGoogle的にはGoogle Playにあるアプリは安全という前提がある。 https://stackoverflow.com/questions/36016369/system-alert-window-how-to-get-this-permission-automatically-on-android-6-0-an/36019034

この権限はRuntime Perissionではなく設定の奥にあるのでIntentを飛ばし、 onActivityResult() でハンドリングする。

val intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:$packageName"))
startActivityForResult(intent, REQUEST_CODE_OVERLAY_PERMIISSION)

設定が有効かどうかは Settings.canDrawOverlays() を使う。

Settings  |  Android Developers

Android 8.0以上

targetSdkVersion 26以上にてViewを表示するときの指定でTYPE_APPLICATION_OVERLAYを使わなければいけなくなった。これによりシステムUIなどの重要なViewの上に描画できなくなり、アプリインストール時に権限の上にViewを表示して隠すなどの攻撃が不可能になった。 https://developer.android.com/about/versions/oreo/android-8.0-changes

Android 8.0では権限取得にバグがあり、変更がすぐに反映されないので監視する。 https://stackoverflow.com/questions/46173460/why-in-android-o-method-settings-candrawoverlays-returns-false-when-user-has

Android 8.1では修正済み。https://issuetracker.google.com/issues/66072795

if (Build.VERSION.SDK_INT == Build.VERSION_CODES.O) {
    val appOpsManager = getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager
    appOpsManager.startWatchingMode(AppOpsManager.OPSTR_SYSTEM_ALERT_WINDOW, packageName, object : AppOpsManager.OnOpChangedListener {
        override fun onOpChanged(op: String?, packageName: String?) {
            if (Build.VERSION.SDK_INT == Build.VERSION_CODES.O) {
                appOpsManager.stopWatchingMode(this)
                onOverlayPermissionResult(Settings.canDrawOverlays())
            }
        }
    })
}

【Android】Google AnalyticsからFirebaseへの乗り換え

Firebaseを使ってみたかったので置き換えてみた。

プロジェクトのbuild.gradle

    dependencies {
         ...
        classpath 'com.google.gms:google-services:4.0.0'
    }

アプリのbuild.gradle

dependencies {
    ...
    implementation "com.google.firebase:firebase-core:16.0.1"
}

apply plugin: 'com.google.gms.google-services'

イベントを送るクラスの置き換え

    companion object DefaultTracker {
-        fun setUp(context: Context): Tracker {
-            val analytics = GoogleAnalytics.getInstance(context)
-            return analytics.newTracker(R.xml.global_tracker)
+        fun setUp(context: Context): FirebaseAnalytics {
+            return FirebaseAnalytics.getInstance(context)

必要ないコードの削除

スクリーンは screen_view というイベントで勝手に送ってくれるので削除

-        tracker.setScreenName(screenName)
-        tracker.send(HitBuilders.ScreenViewBuilder().build())

クリックイベントの置き換え

Firebase側である程度のイベントを用意してくれている。FirebaseAnalytics.Param.CONTENT_TYPEFirebaseAnalytics.Param.VALUE で色々指定できそうなのでヘルパーメソッドを作った。

    fun sendButtonEvent(itemId: String) {
        sendEvent(itemId, "button", FirebaseAnalytics.Event.SELECT_CONTENT)
    }

    fun sendSettingEvent(setting: String, value: String) {
        sendEvent(setting, "setting", FirebaseAnalytics.Event.SELECT_CONTENT, value)
    }

    private fun sendEvent(itemId: String, contentType: String, event: String, value: String = "") {
        if (BuildConfig.DEBUG) return
        val params = Bundle().apply {
            putString(FirebaseAnalytics.Param.ITEM_ID, itemId)
            putString(FirebaseAnalytics.Param.CONTENT_TYPE, contentType)
            putString(FirebaseAnalytics.Param.VALUE, value)
        }
        tracker.logEvent(event, params)
    }

デバッグ

コンソールでDebugViewを開く。あとは端末を繋いで adb shell setprop debug.firebase.analytics.app <package_name> を実行すればデバッグができる。

support.google.com

ErgoDoxのキースイッチを交換する

最近どうもdが押しにくく、ファームウェアをアップデートしても直らなかったため、キースイッチを交換することにしました。

okapies.hateblo.jp

を参考にやりました。

必要なもの

Cherry MX Black Keyswitch - Linear - 5 pack by Cherry

  • キースイッチを引き抜く器具 ($6.99+送料$15=$21.99=2403円) *1ドル約109円として計算

MX Switch Top Removal Tool (Set of 2) by Mechanical Keyboards Inc

当初↑の記事を読んで100均のクリップを改造すればOKと書いてあったので、キースイッチを引き抜く器具は買っていなかったが、私には難しすぎたので結局注文。送料がもったいない・・・ 海外発送なので届くまでに約1ヶ月ほどかかるので注意。

手順

1.キーキャップを取る

2 キースイッチを取る

f:id:phicdy:20180608194313g:plain

3 ばねとばねについているパーツを取る

f:id:phicdy:20180608194604j:plain

4 新しいキースイッチを分解する

5 新しいキースイッチのばねとパーツをつけ、カバーをする

おわりに

100均のクリップを改造したものでは1時間かかってもキースイッチが取れなかったが、器具を買ってやったら1分でできたので素直に器具を買うことをおすすめします・・・

targetSdkVersion 26でAndroid 2.3をサポートすることのコストの高さ

今年の11月以降、Google PlayにはtargetSdkVersion26(Android 8.0)以上のアプリしかアップロードできなくなる(新規アプリは8月)。

developers-jp.googleblog.com

それでも状況によってはAndroid 2.3をサポートしなければならない場合がある・・・

しかし、古いAndroidをサポートするためのサポートライブラリは基本的には targetSdkVersion のバージョンと合わせなければならず、 26.0.0からminSdkVersion が14(Android 4.0)になってしまった・・・

それでもAndroid 2.3 をサポートする方法

  • サポートライブラリを使わない
  • サポートライブラリ 25.4.0を使う
  • Android 2.3向けとAndroid 4.0以上向けでビルド分け、2.3向けではサポートライブラリ 25.4.0, 4.0以上向けでは最新のサポートライブラリを使う

の3つが考えられるがどれも非常にコストがかかる。

まず1つ目のサポートライブラリを使わない方法。これはGoogleがサポートライブラリで吸収してくれるはずのAPI差分やバグ修正などの恩恵を全て受けられないことになる。OSバージョンをカバーするテスト工数がかさむし、予期せぬバグが発生することが多々起きることが想像できる・・・(そしてバグ修正もつらそう・・・)

2つ目はサポートライブラリ 25.4.0を使う。これがコスト的には一番ましかもしれない。ただ Googleが推奨するように targetSdkVersion のバージョンと合わないため、こちらでも予期せぬバグが起きうる。個人的には、1つ目よりはまだましなのではないかと信じたい。

3つ目が1番安全で、Android 2.3向けとAndroid 4.0以上向けでビルド分け、サポートライブラリのバージョンを分ける方法。これはバグが1番起きにくいし、全バージョンをサポートライブラリで網羅できる。ただし、サポートライブラリ 26.0.0以上から入るAPI(例えば Android 8.0からの通知チャンネル用コンストラクタ NotificationCompat.Builder(Context context. String title) など) を使うとコードを分けなければならない。終わりゆくAndroid 2.3をサポートするためにそのようなコードを書かなければならないのはかなりつらいし、ビルドが2つになって管理・テストもつらい。

NotificationCompat.Builder  |  Android Developers

もうAndroid 2.3をサポートするのはやめよう

もはやAndroid 2.3をサポートする時代ではないと個人的には思う。

Android 2.3は0.3%しかユーザがおらず、そのユーザをサポートして得られる利益が上記のような対策をとるコストを超える場合はないのではと思う(BtoBtoCで包括的な契約だと話は変わってきそう)。

developer.android.com

Googleの各種ライブラリはAndroid 4.0, 4.1以上が当たり前だし、クックパッドは minSdkVersionを21(Android 5.0)にしている。

techlife.cookpad.com

minSdkVersion 21で開発していきたい・・・!

おわりに

スマートニュースは Android 2.2をサポートしているらしいがどうやってやっているのだろう・・・

speakerdeck.com

【Android】リリース署名の設定をプロジェクト外から上書きする

GitHubでソースを公開していてリリース署名の設定をしたい場合、公開用にはダミーの署名を置き、手元ではそれを上書きしたい。かつ git status でnot stagedのところに上書き内容がいちいち出ないようにプロジェクト外から上書きしたい。

gradle.propertiesプロジェクト直下/gradle.properties が先に読まれ、次に ~/.gradle/gradle.properties が読み込まれるためプロジェクト外から値を上書きできる。

app/build.gradle

変数を囲むのはダブルクオーテーションじゃないと動かない。

android {
    ....
    signingConfigs {
        release {
            keyAlias "$RELEASE_KEY_ALIAS"
            storeFile file("$RELEASE_STORE_FILE_PATH")
            keyPassword "$RELEASE_KEY_PASSWORD"
            storePassword "$RELEASE_STORE_PASSWORD"
        }
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
            signingConfig signingConfigs.release
        }
    }
    ...
}

プロジェクト直下のgradle.properties

dummy-release-keyはapp以下に置く

RELEASE_KEY_ALIAS=key
RELEASE_STORE_FILE_PATH=dummy-release-key
RELEASE_KEY_PASSWORD=testtest
RELEASE_STORE_PASSWORD=testtest

そして ~/.gradle/gradle.properties に本当のリリース署名の設定を書く。

【Android】Toolbarの矢印をタップしたら画面を閉じる

ポイントはonOptionsItemSelected()android.R.id.homeをハンドリングしないと反応してくれない点

class MainActivity: AppCompatActivity { 
        ...

    override fun onCreateOptionsMenu(menu: Menu): Boolean {
        // Inflate menu resource file.
        menuInflater.inflate(R.menu.main, menu)
        return true
    }
    
    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        when (item.itemId) {
            // For arrow button on toolbar
            android.R.id.home -> finish()
        }
        return super.onOptionsItemSelected(item)
    }
    
    override fun initToolbar() {
        val toolbar = findViewById(R.id.toolbar) as Toolbar
        setSupportActionBar(toolbar)
        val actionBar = supportActionBar
        if (actionBar != null) {
            // Show back arrow icon
            actionBar.setDisplayHomeAsUpEnabled(true)
            actionBar.setDisplayShowHomeEnabled(true)
            actionBar.setTitle(R.string.app_name)
        }
    }
}