20210410のAndroidに関する記事は7件です。

AdaptiveIcon を作る

はじめに 業務のなかでAdaptive Iconを作ることがあったので自分用のメモです。 といっても、Adaptive Iconを作るというよりは、 Androidでアイコンを設定する方法と認識した方がいいと思います。 今回は、POに「Androidのアイコンを設定してほしい、デザイナーさんに元ネタは作ってもらっているよ」 という前提で手順を記載します。 手順、気をつけること 前提 今回、使う画像はこちらにしました(いずれもイラストや) foreground background 手順 1 Android Studio の Image Assets を選択 手順 2 Foregroundの画像を設定、調整 Scalling の Resize はデザイナーさんと相談していい感じに調整してください。 手順 3 Backgroundの画像を設定、調整 手順 4 Options を設定 API25を含めた場合 API25を含めない場合 手順 5 作成 あとは「Finish」ボタンを押して完了です。 手順 6 おまけ background を color 指定にすると、背景白のアイコンも作成できます。 まとめ android アプリ開発でアイコンを作ることは機会が少ないので、 手順を一度まとめて整理してみました。 これから行う誰かの参考になれば幸いです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Android】HttpUrlConnectionを使ってみる

【Android】 HttpUrlConnectionを使ってみる 概要 Androidで通信を行う際は、Retrofitがデファクトかなと思いますが、 HttpUrlConnectionを試しに触ってみたものの記録です。 とりあえず公式を読もう まずは公式ドキュメントを読みました。 ドキュメントを読んで分かったこと 以下は、公式の最初に出てくるサンプルコードです。 やっていることは、URLに対してGETリクエスト実行して、レスポンスを受けとるという処理です。 公式ドキュメントのサンプルをKotlinに変換.kt val url = URL("http://www.android.com/") val urlConnection : HttpUrlConnection = url.openConnection() as HttpUrlConnection try { val inputStream = BufferedInputStream(urlConnection.getInputStream()) readStream(inputStream) // ※この部分を、コピペしても動かない } finally { urlConnection.disconnect() } まず以下で、URLからURLクラスのオブジェクトを作成して、 作成したURLオブジェクトで、openConnection()を実行します。 openConnection()について openConnection()によって、リクエスト先とのコネクションを確立します。 サンプルコードの上半分.kt val url = URL("http://www.android.com/") val urlConnection : HttpUrlConnection = url.openConnection() as HttpUrlConnection 次に、レスポンスの処理方法について まずurlConnection.getInputStreamで、レスポンスのボディを受け取ります。 そして、readStreamは、おそらく自分で定義するメソッド? なので、そのままコピペしても動かないです。 受け取ったInputStreamをStringなどに変換する処理を実行するものと思われます。 そして、finallyで、urlConnection.disconnect()でリークしないように、コネクションを切断します。 サンプルコードのtry以下.kt try { val inputStream = BufferedInputStream(urlConnection.getInputStream()) readStream(inputStream) } finally { urlConnection.disconnect() } 追加で分かったこと 追加で分かったこと.kt val url = URL("http://www.android.com/") val urlConnection : HttpUrlConnection = url.openConnection() as HttpUrlConnection urlConnection.setRequestMethod("GET") // "POST"などにすることで、HTTPメソッドを変更できる。デフォルトはGET urlConnection.connectTimeout(6000) // msで接続のタイムアウトを設定可能 urlConnection.readTimeout(6000) // レスポンスの読み取りタイムアウトを設定 urlConnection.setRequestProperty("Key", "value") // リクエスト ヘッダーにContent-Typeを設定。 // 例: setRequestProperty("Content-Type", "application/json; charset=utf-8") 感想 公式でもRetrofitが推されているように、HttpUrlConnectionでやる必要はないかなと思いました。 参考 HttpUrlConnection Androidの通信ライブラリの歴史を振り返る
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[問題]スマホアプリにお試し期間の概念は必要か?

はじめに 本題に入る前に、マネタイズについてお話します。 スマホアプリでマネタイズするには、大きく2つパターンがあります。 1. 利用料無料で、広告代で得る 2. 有料(買い切り、サブスクリプション、追加課金制) (自分が知らないだけで、他にもあるかも知れませんが、ここでは触れません) 1. の利用料無料の場合は特に問題ありません。ずっと無料ですから、飽きたり気に入らなかったら、アプリを削除すれば問題無いと思います。 問題は、2.です。 2. の有料の場合、買わないとそのアプリが遊べない・利用できないわけですから、自ずと買わざるを得ません。ですが、買ってつまらなかった・想像していたものと違うなど理由によってはお金を出し惜しみして買ってくれない人も少なくありません。そこで登場するのが、お試し期間です。 お試し期間とは? 有料アプリを使わせるために、無料のお試し期間を設け、その間のみ有料時のサービスが利用できる仕組みのこと。 ※ただし、ここで無料期間としていましたが、無料ポイント、初回のみチケット無料のようなものも含まれます。 何が問題か ここまでの話だけでは、特に問題ないのではないか? と思う人も居るでしょう。 アプリ運用する側(メーカー)が困ること・・・ それは何か・・・ 無料期間(無料ポイント、初回のみ無料、1週間無料)が何度も利用できてしまう ってことです。 ????? 何を言っているのか、分からない人もいると思います。 ゲームアプリでよくあるのですが、いわゆるリセマラ(リセットマラソン)を指します。 リセマラとは リセットマラソン(Reset Marathon)とは、主にソーシャルゲームにおいて、ソフトのインストールとアンインストールを何度も繰り返すこと、及びそれにより自分が目的とするアイテムを入手する方法のこと (Wikipediaより) ゲームアプリだとなじみのある行為だと思いますが、一般的なツールアプリ・ライフスタイルアプリなどにおいては、ガチャではなく、無料期間(無料ポイント、初回のみ無料、1週間無料)を言います。そして、企業側からすると無料期間(無料ポイント、初回のみ無料、1週間無料)が何度も利用できてしまうと、損失につながります。 そして、この「無料期間(無料ポイント、初回のみ無料、1週間無料)が何度も利用できてしまう」問題をエンジニアは解決しなくてはいけません。 どうやって? 実際にアプリを制作してみるとぶち当たる壁が存在します。 このアプリをアンインストールしたとしても、過去にインストールされた経歴があることを判断しなくてはいけません。 ですが、端末内データに何かしらのフラグをキャッシュとして保存したとしてもアプリ削除したら消えてしまいます。また、メールアドレスで判断しようとしても複製できてしまうため、意味がありません。 そこで登場するのが、端末固有IDです! 端末固有IDとは? 端末それぞれに不変のIDとして存在する識別子のことを指します。そして、その識別子を元にインストール経歴があるのか判断します。 一般的に多くのAndroid/iOSアプリにおいては以下の識別子を利用します。 Android ANDROID_ID iOS AdvertisingIdentifier ただし、これらのIDは端末を継続して利用している間のみ値が変更されないです。 各詳細について見てみましょう。 ANDROID_ID The value may change if a factory reset is performed on the device or if an APK signing key changes. デバイスで出荷時設定へのリセットが実行された場合、またはAPK署名鍵が変更された場合、値は変更される可能性があります。 AdvertisingIdentifier Unlike the identifierForVendor property of the UIDevice, the same value is returned to all vendors. This identifier may change. For example, if the user erases the device. For this reason you should not cache it. UIDeviceのidentifierForVendorプロパティとは異なり、すべてのベンダーに同じ値が返されます。 この識別子は変更される可能性があります。 たとえば、ユーザーがデバイスを消去した場合などです。 このため、キャッシュしないでください。 と記載されています。 つまり、端末を工場出荷状態(端末リセット)に戻すことで、これらのIDが初期化されてしまいます。 では、工場出荷状態に戻しても変わらないIDを利用すればよいのではないか・・・? と思う方が居ると思います。 残念ながら、それはできません。 Androidに関しては、永続的なデバイス ID(IMEI / MEID、IMSI、SIM、ビルドシリアル)はJava/Kotlinから取得できない制限がかかっています。そのため利用できないのです。 また、iOSも同様でMACアドレスは取得できません。 こんなものも・・・ 工場出荷状態に戻して、端末固有IDをリセットするのがめんどうである時にこんなアプリもあります。 同じ端末で2つのLINEアカウントが使い分けられるようにできる禁断のアプリ「並行世界」の使い方 このアプリを利用するとこのアプリ自体が仮想OSの役割を果たし、同じアプリを2つ起動することができます。 つまり、並行世界を起動した上でLINEを起動すると、1端末で2つのアカウントを利用することができます。 これは実質、端末固有IDを複製しています。 このアプリを利用すると簡単に端末固有IDを複製できてしまうので、何かしら対策しないといけません。 では、どうするか? 対策として考えられることが、Android/iOSアプリで決済する時のカード情報をサーバー側で管理しておき、同じカード情報であった場合、無料期間(無料ポイント、初回のみ無料、1週間無料)を利用できなくすれば問題ないと思います。 ですが、無料期間(無料ポイント、初回のみ無料、1週間無料)リセマラのために、カード情報を入力するアプリは多くありません。 そもそもの問題として、お試し期間という概念は必要でしょうか。という問題定義に戻ります。 無料期間(無料ポイント、初回のみ無料、1週間無料)の機能を追加したが故に、無限に悪用できてしまい、本来有料ユーザーを増やしたいがためにやった行為なのに、逆に減らしてしまうのではないか。と懸念されます。 そこまでのリスクを追うくらいであれば、無料期間(無料ポイント、初回のみ無料、1週間無料)は要らないのではないか。と私は思います。 以上です。 参考 iOS/Androidで端末を識別するIDまとめ https://iridge.jp/blog/201404/4836/ デバイス ID https://source.android.google.cn/devices/tech/config/device-identifiers?hl=ja 一意の識別子に関するベスト プラクティス https://developer.android.com/training/articles/user-data-ids?hl=ja ANDROID IDがどのように生成されているかざっくり調べた https://qiita.com/operandoOS/items/b41fa4a1c6c009c64a10 SSAID取得 https://gist.github.com/Koronaa/d204bc29a122f9adef857d8935fd7cbc
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【moshi】moshi 1.12.0でデシリアライズが高速化したため比較・解説する【Kotlin】

TL;DR moshi 1.12から条件付きでKotlinのdata classへのデシリアライズが高速化した 条件は以下の通り moshi-kotlin-codegenを利用していて、デフォルト値が有りうるクラスを、引数全てが設定されたJSONからデシリアライズする場合 moshi-kotlinを利用していて、デフォルト値を用いず(= プロパティが全て揃った状態で)デシリアライズする場合 本文 moshi 1.12.0でKotlinのdata classへのデシリアライズが条件付きで高速化したので、比較・解説していきます。 高速化する条件 高速化に関する変更はmoshi-kotlin-codegenとmoshi-kotlinそれぞれに入っています。 この内moshi-kotlin-codegenが高速化する条件は限定的なものですが、moshi-kotlinが高速化する条件は緩く、多くの場合高速化すると思われます。 moshi-kotlin-codegenでのデシリアライズが高速化する条件 moshi-kotlin-codegenでのデシリアライズが高速化するのは、デフォルト値が有りうるクラスを、引数全てが設定されたJSONからデシリアライズする場合です。 例えば以下のようなクラスをデシリアライズするとします。 高速化しうるデシリアライズ先 import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) data class Sample(val foo: Int, val bar: Int = 0) このクラスを、foo, barそれぞれのプロパティを持つJSONからデシリアライズすると高速化します。 高速化する { "foo": 1, "bar": 1 } 以下のように、barがプロパティに存在しない(= デフォルト値が用いられる)場合は高速化しません。 高速化しない { "foo": 1 } また、以下のように、そもそもデフォルト値が設定されていない場合も高速化しません。 高速化しないデシリアライズ先 import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) data class Sample(val foo: Int, val bar: Int) moshi-kotlinでのデシリアライズが高速化する条件 moshi-kotlinでのデシリアライズが高速化するのは、デフォルト値を用いずにデシリアライズを行う場合です。 例えば以下のようなクラスをデシリアライズするとします。 data class Sample(val foo: Int, val bar: Int) このクラスを、foo, barそれぞれのプロパティを持つJSONからデシリアライズすると高速化します。 { "foo": 1, "bar": 1 } moshi-kotlinの場合、デフォルト値が設定されているかに関係なく、引数全てを揃えてデシリアライズした場合高速化します。 どれ位高速化するか 3引数・デフォルト値有りの条件でJMHのベンチマークを作成し、同じベンチマークをmoshi 1.11.0とmoshi 1.12.0で実行して比較を行いました。 ベンチマークに用いたコードは以下の通りです。 codegen用 import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) data class Codegen( val foo: Int, val bar: Int, val baz: Int = 0 ) reflect用 data class Reflect( val foo: Int, val bar: Int, val baz: Int = 0 ) ベンチマーク import com.squareup.moshi.JsonAdapter import com.squareup.moshi.Moshi import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory import com.wrongwrong.dto.Codegen import com.wrongwrong.dto.Reflect import org.openjdk.jmh.annotations.* import java.util.concurrent.ThreadLocalRandom @State(Scope.Benchmark) open class BenchMark { private val moshiCodegenAdapter: JsonAdapter<Codegen> = Moshi.Builder().build().adapter(Codegen::class.java) private val moshiReflectAdapter: JsonAdapter<Reflect> = Moshi.Builder().addLast(KotlinJsonAdapterFactory()).build().adapter(Reflect::class.java) private var targetJsonString: String? = null @Setup(Level.Iteration) fun setupTarget() { val i = ThreadLocalRandom.current().nextInt(5) // CodegenとReflectの内容は同じため、同じJSONからデシリアライズして比較できる targetJsonString = moshiCodegenAdapter.toJson(Codegen(i, i + 1, i + 2)) } @Benchmark fun codegen() = moshiCodegenAdapter.fromJson(targetJsonString!!) @Benchmark fun reflect() = moshiReflectAdapter.fromJson(targetJsonString!!) } 補足 JMHにの実行に関する設定を含めたbuild.gradle.ktsは以下の通りです。 build.gradle.kts plugins { kotlin("jvm") version "1.4.32" kotlin("kapt") version "1.4.32" id("me.champeau.gradle.jmh") version "0.5.3" } group = "com.wrongwrong" version = "1.0-SNAPSHOT" repositories { mavenCentral() } dependencies { jmhImplementation(kotlin("stdlib-jdk8")) val moshiVersion = "1.11.0" jmhImplementation("com.squareup.moshi:moshi:${moshiVersion}") jmhImplementation("com.squareup.moshi:moshi-kotlin:${moshiVersion}") kaptJmh("com.squareup.moshi:moshi-kotlin-codegen:${moshiVersion}") } tasks { compileKotlin { kotlinOptions { jvmTarget = "1.8" } } compileJmhKotlin { kotlinOptions { jvmTarget = "1.8" } } compileTestKotlin { kotlinOptions { freeCompilerArgs = listOf("-Xjsr305=strict") jvmTarget = "1.8" } } // https://qiita.com/wrongwrong/items/16fa10a7f78a31830ed8 jmhJar { exclude("META-INF/versions/9/module-info.class") } } jmh { warmupForks = 2 warmupBatchSize = 3 warmupIterations = 3 warmup = "1s" fork = 2 batchSize = 3 iterations = 2 timeOnIteration = "1500ms" failOnError = true isIncludeTests = false resultFormat = "CSV" } その他ベンチマークの作り方は以下を参照ください。 ベンチマーク結果 ベンチマーク結果は以下のようになりました。 codegenはmoshi-kotlin-codegen、reflectはmoshi-kotlinのスコアで、スコアは高いほど良いです。 1.12.0 Benchmark Mode Cnt Score Error Units BenchMark.codegen thrpt 4 1018336.030 ± 55897.668 ops/s BenchMark.reflect thrpt 4 949243.442 ± 106377.587 ops/s 1.11.0 Benchmark Mode Cnt Score Error Units BenchMark.codegen thrpt 4 896153.959 ± 107707.744 ops/s BenchMark.reflect thrpt 4 754958.982 ± 33874.740 ops/s このベンチマークから見るそれぞれの高速化幅は以下の通りです。 codegen -> 1018336.030/896153.959 = 1.1倍 reflect -> 949243.442/754958.982 = 1.3倍 高速化の原理 moshi-kotlin-codegenとmoshi-kotlinでの高速化の原理をそれぞれ解説します。 moshi-kotlin-codegenが高速化した原理 Kotlinのデフォルト引数を用いた呼び出しは、実体としてはデフォルト引数に関する処理を含むコンストラクタを呼び出す形で行われます。 この辺りの詳細は以下の資料を参照ください。 今回の高速化は、生成されるJsonAdapterに引数が揃っている場合に通常のコンストラクタを呼び出す処理を追加し、デフォルト引数に関する処理をスキップすることで達成されています。 moshi-kotlinが高速化した原理 moshi-kotlinのマッピングはkotlin-reflectのKFunctionを呼び出すことで行われていますが、引数が全て揃っていた場合の呼び出しをcallで行うようにすることで達成されています。 終わりに moshi-kotlinの高速化は、自分の出したPRによって行われたものです。 moshi-kotlin-codegenの高速化は、このPRのアイデアを元に行われたものです。 このPRは自分にとって初めて有名OSSに取り込んでもらえたソースコードのPRになったので、嬉しくて解説記事を書いてみた次第です。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AndroidのボタンUIを角丸にしたい

画面デザイン資料をもらった時、ボタンUIが角丸で枠線が付いていた為、次のような対応を行った。 リソースのlayout まずはlayoutファイルにButtonを配置。 <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Button" android:textColor="#ffffffff" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> リソースのdrawable つぎにshapeを使ったdrawableファイルを作成。 button_style.xml <?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <corners android:radius="10dip" /> <solid android:color="#ff262626" /> <stroke android:width="1dp" android:color="#ffffffff" /> </shape> 作成したdrawableをlayoutに適用 Buttonのbackground属性に作成したdrawableを設定する。 <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Button" android:textColor="#ffffffff" android:background="@drawable/button_style" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> イメージ
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

jarsigner: jarに署名できません: java.util.zip.ZipException: invalid entry compressed size (expected XXXX but got XXXX bytes)

Androidでapk作成して署名するときに以下のエラーが出る jarsigner: jarに署名できません: java.util.zip.ZipException: invalid entry compressed size (expected XXXX but got XXXX bytes) ./gradlew assembleReleaseで作成されるファイルに署名がされてしまっているのが原因。以下の設定して再度実行すれば未署名のファイルが出力される。(ファイル名はapp-release-unsigned.apkとなる。) ## android/aopp/build.gradele buildTypes { release { .... //signingConfig signingConfigs.release signingConfig null }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

CoordinatorLayoutを使う(Snackbarをスワイプで消す)

Androidアプリを趣味程度にいじっています。 ポップアップメッセージを出す手段として、Toastに替わってSnackbarというのがあるそうです。 短い時間の通知に加え、アクションを追加できるのはいいなと思って使うことにしましたが、googleのドキュメントそのままにやってみたら失敗したのでそれについてメモします。 Android Studio 4.1.2 Runtime version: 1.8.0_242-release-1644-b3-6915495 x86_64 VM: OpenJDK 64-Bit Server VM by JetBrains s.r.o macOS 10.15.7 失敗 ↑を参考に。『CoordinatorLayout を使用する』の章で「CoordinatorLayout にアタッチされている場合、Snackbar で次の追加機能が使えるようになります。……Snackbar をスワイプして消すことができる。…… 」とあるので書いてある通り、現在のレイアウトをCoordinatorLayoutでラッピングしました。 ↑に載っているサンプルコードがこちら <android.support.design.widget.CoordinatorLayout android:id="@+id/myCoordinatorLayout" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent"> <!-- Here are the existing layout elements, now wrapped in a CoordinatorLayout --> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <!-- …Toolbar, other layouts, other elements… --> </LinearLayout> </android.support.design.widget.CoordinatorLayout> するとandroid.support.design.widget.CoordinatorLayoutが赤文字になって、赤ランプマークを押すとAdd dependency androidx.coordinatorlayout:coordina...と出てきます。そこをクリックしたらgradleに dependencies { ... implementation 'androidx.coordinatorlayout:coordinatorlayout:1.1.0' ... } と追加されました。しかしレイアウトファイルのcoordinatorLayoutのところは赤文字のままで、うまく動きません。 解決 色々調べてみたのですが解決方法がわからないでいたところ、とある解説記事に遭遇しました これをよくみると、レイアウトファイルのところで <androidx.coordinatorlayout.widget.CoordinatorLayout ... としています。 この通りに書き換え、つまり <androidx.coordinatorlayout.widget.CoordinatorLayout android:id="@+id/myCoordinatorLayout" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent"> <!-- Here are the existing layout elements, now wrapped in a CoordinatorLayout --> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <!-- …Toolbar, other layouts, other elements… --> </LinearLayout> </androidx.coordinatorlayout.widget.CoordinatorLayout> としたら動作するようになりました。スワイプで消すのも無事できるようになりました。 参考 ポップアップ メッセージの作成と表示 | Android デベロッパー | Android Developers 【Android Studio4.1】Snackbar を表示する方法
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む