20201117のAndroidに関する記事は8件です。

【Android】なんとかAndroidアプリのaabファイルのビルドに成功した件

【Android】GooglePlayアプリのkeystoreパスがわからず新規アプリ登録した件
https://qiita.com/technopixelinc2019/items/e7150caeadd1aff6ba4d

上記の記事の続きです。
初心者向け、かつ自分への備忘録として記載残しておきます。

ビルドしようとすると「Gradle build failed」と出るので調べてみる。
【参考URL】
https://nn-hokuson.hatenablog.com/entry/2017/09/05/202327#Gradle-build-failed%E3%81%8C%E5%87%BA%E3%82%8BUnity2019%E3%81%8B%E3%82%89

参考ページによると、Unity2019からはBuild Settingsの項目から「Build System」が削除されたとの事。以前のver.なら「Systemの項目をInternalに設定」で良かったらしい。

Unity2019以降の場合は、Build Settingsの「Export Project」にチェックを入れて、AndoroidStudio用にExportして、Gradleをアップグレードさせると良いとの事。
ここは結局Unityに戻す方法がわからず、挫折。

【参考URL:動画】
https://www.youtube.com/watch?v=RNDYJPMEo98

代わりに、GradleをDLできるページを発見。
https://services.gradle.org/distributions/
→最新と思われる物(この時は6.8)を落としてきて「※任意のフォルダ」に入れる

unityに戻り下記から参照パスを変更
Edit→Preferences→Gradle installed with Unity(recommenceded)

もともと参照していた所から自分でいれたフォルダへ参照パスを変更
C:/Program Files/Unity/Hub/Editor/2019.2.12f1/Editor/Data/PlaybackEngines/AndroidPlayer/Tools/gradle

「※任意のフォルダ」のパス

これで無事、aabファイルのビルドに成功しました!

■反省点
コマンドプロンプトでバッチなどを叩いたりする前に、そのパスの中身を確認する
batファイルはあるかなど、先に見て置く
SDKも同様、参照元にデータがあるのか見て置くと良いかも

■今後
とりあえずビルド成功したので、GooglePlayコンソールでリリースしてみる

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

【Android】GooglePlayアプリのkeystoreパスがわからず新規アプリ登録した件

【Android】GooglePlayのアプリを更新しようとしたらkeystoreのパスワードを忘れていた件
https://qiita.com/technopixelinc2019/items/754adba7fb151c5c62eb

上記の記事の続きです。
結局パスは思い出せずじまい、新規アプリで登録する事にしました。
ただ、ビルドしようとするとエラーばかり。

参考になるかわかりませんが、自分と同じようなプログラマ初心者に向けて記載しておきます。

unityでビルドしようとしたら下記エラー。
unable to install additional SDK platform. please run the SDK Manager manually make sure you have the latest set of tools and the required platforms installed. See Console for details.

ググって調べてみると、2020/8/3からandroidAPI29以上が必要になったとの事。

参考URL
【Unity】Android BuildでAPIレベル29以降にする方法
http://blog.livedoor.jp/toropippi/archives/54941822.html

コマンドプロンプトでSDKを入れようとするも、うまくいかず。
cd C:\Program Files\Unity\Hub\Editor\2019.2.12f1\Editor\Data\PlaybackEngines\AndroidPlayer\SDK\tools\bin
sdkmanager "build-tools;29.0.3"
sdkmanager "build-tools;30.0.2"
sdkmanager "platforms;android-29"
sdkmanager "platforms;android-30"
→ダウンロードされてない様子

他のページ等を見て、下記を試す
cd C:\Program Files\Unity\Hub\Editor\2019.2.12f1\Editor\Data\PlaybackEngines\AndroidPlayer\SDK\tools\bin
sdkmanager.bat "platforms;android-29"

すると次のエラー文が出る
Exception in thread "main" java.lang.NoClassDefFoundError

コマンドプロンプトを諦めて、Android StudioのSDKmanagerを試して見たらインストール完了できました
こちらで入れたのはuser以下のフォルダのようなので、unityの方へコピー

■user以下のフォルダ
C:\Users\※ユーザ名※\AppData\Local\Android\Sdk\platforms

■unity以下おフォルダ
C:\Program Files\Unity\Hub\Editor\2019.2.12f1\Editor\Data\PlaybackEngines\AndroidPlayer\SDK\platforms

unityでビルドをしてみると、さらに「Gradle build failed」と出る
今度はGradleがない(足りない?)らしいです。

(つづく)

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

Flutter実装時にお世話になったページ 2020/11/17

なにか

毎回、同じ問題に、同じ検索して、同じページで解決してるので。。。

TabBarViewのスワイプによる切り替えを無効にしたかった

Stack Overflow "disable swiping tabs in TabBar flutter"

Firebase の Cloud Messaging による通知がiOSで受け取れなかった

公式ドキュメントをちゃんと読めってことですね。
iOS Integration のステップ2で、以下を読み落としてたのと

Don't follow the steps named "Add Firebase SDK" and "Add initialization code" in the Firebase assistant.

Dart/Flutter Integration のここを実装せずにテストしてました。(Androidは無くても届く)

import 'package:firebase_messaging/firebase_messaging.dart';

final FirebaseMessaging _firebaseMessaging = FirebaseMessaging();

Pub.dev firebase_messaging

iOS風のモーダル表示

まだ、Flutterとしては実装されていない模様

Github flutter "Support for sheet presentation style"

上記のissue内に記載されている、ライブラリを使用

Pub.dev modal_bottom_sheet

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

Camera2+AudioRecord+MediaCodec+MediaMuxerでMP4録画するときの "こけポイント"

TL;DR

AndroidのCamera2,AudioRecord,MediaCodec,MediaMuxerを組み合わせてMP4録画機能を実装する中で,
『こけポイント』(ハマりポイント)に何個かぶつかったので,記録として残しておきます.
ぐぐってもあまり出てこなかったものばかりなので,このTextが誰かの助けになればと.

(Code全体の説明は大規模になりすぎるので,こけポイントだけ書いておきます)

全体構成

↓ のような一番シンプルな構成を実現したときの,こけポイントを書きます.
blueprint.png

こけポイント

GL/EGLでMediaCodec Input SurfaceにRenderした画がEncodeされない

VideoのEncodeのために,MediaCodecのInput Surfaceに画を描く方法としてGL/EGLがAndroidDeveloperに書かれていますが,実際にRenderした画をEncodeさせるために必要な設定が書かれてませんでしたので,メモ.

.cpp
const EGLint eglConfigAttrs[] = {
    EGL_RENDERABLE_TYPE,    EGL_OPENGL_ES2_BIT,
    EGL_RED_SIZE,           8,
    EGL_GREEN_SIZE,         8,
    EGL_BLUE_SIZE,          8,
    EGL_RECORDABLE_ANDROID, 1,
    EGL_NONE
};
eglChooseConfig(display, eglConfigAttrs, &config, 1, &numConfigs)

MediaCodecのInput Surfaceを使ってGL/EGLを初期化するときにeglChooseConfig()にわたすConfig Attributesですが,この
EGL_RECORDABLE_ANDROID, 1,
の設定が無いと,SurfaceのFrame BufferをMediaCodecから読めないらしく,画が1枚もInputされてない状態になるようです.

このEGL_RECORDABLE_ANDROIDについて,↓ のKhronosのサイトに仕様?が置かれていました.
ANativeWindowの実装が複数あり,Video Recordに対応したConfigを明示的に選択するために使うもののようです.
https://www.khronos.org/registry/EGL/extensions/ANDROID/EGL_ANDROID_recordable.txt

Android supports a number of different ANativeWindow implementations that
can be used to create an EGLSurface.  One implementation, which records the
rendered image as a video each time eglSwapBuffers gets called, may have
some device-specific restrictions.  Because of this, some EGLConfigs may be
incompatible with these ANativeWindows.  This extension introduces a new
boolean EGLConfig attribute that indicates whether the EGLConfig supports
rendering to an ANativeWindow that records images to a video.

AudioRecordの録音で先頭Bufferにノイズが乗る

AudioRecordはstartRecording()後にread()を使ってPCM16とかのAudio Bufferを取得します.
取得したBufferをMediaCodecに渡してEncodeすることになりますが,↓ のような処理でAudioRecordが中で持っている(と思われる)Ring Bufferの初期値を一回捨てないとノイズ音として最終のMP4に記録されてしまう問題が発生しました.

.kt
val minBufSize = AudioRecord.getMinBufferSize(
        44100, // Sampling rate,
        AudioFormat.CHANNEL_IN_MONO,
        AudioFormat.ENCODING_PCM_16BIT)

val audioRecord = AudioRecord.Builder()
        .setBufferSizeInBytes(minBufSize * 2)
        .build()

audioRec.startRecording()

// Flush buffer.
val buf = ByteArray(minBufSize * 2)
audioRec.read(buf, 0, buf.size, AudioRecord.READ_BLOCKING)

最後の2行が(本来必要ないはずだが)追加したものです.Readしたbufは使わないので,すぐにGCの回収対象になります.
Bufferの初期値が不定なのが根本原因では無いかもしれませんし,機種依存かもしれませんので,この対処が正しくない可能性はあります.

MediaCodecの同期モードを使うと処理が止まる

MediaCodecのAndroidDeveloperの公式Referenceには
- 同期モード (Callbackセットしない)
- 非同期モード (Callbackセットする)
の2つのサンプルが書かれています.

が,Audio Encode用のMediaCodecを同期モードで実装したところ,
MediaCodec.dequeueInputBuffer()
から制御が戻ってこず,処理が進まない,という問題が発生しました.
(MediaCodec.queueInputBuffer()してないとかではないです)

公式サンプルそのままのため,理由がよくわからず,深堀りする前に非同期モードに実装変更してしまいました.

MediaCodecのOutput Buffer Infoのpresentation Timestampが巻き戻る

AudioのMediaCodec処理で,MediaCodec.queueInputBuffer()にPresentation Timestampを渡します.
このPresentation Timestampは通常,System.nanoTime() / 1000とかの値を使って,単調増加(monotonic)になるようにします.

MediaCodec.queueInputBuffer()に渡したPresentation TimestampがOutput Buffer InfoのPresentation Timestampとして渡ってきます.
これを元に計算した値をMediaMuxer.writeSampleData()に渡すことになりますが,このときMediaMuxerに渡すPresentation Timestampが単調増加になってないと例外発生でCrashします.

Input BufferのPresentation Timestampが単調増加ならMediaCodecを通ったあとのOutput BufferのPresentation Timestampも単調増加になるように思えますが(実際,ほとんどのケースではそうなりますが),Output BufferのPresentation Timestampが巻き戻る問題が発生することがありました.
巻き戻った値はInput Bufferに入れたどのPresentation Timestampとも一致しなかったため,出所不明の値です.

↓ の処理でOutput BufferのPresentation Timestampを単調増加になるように調節して対応しました.

.kt
private var previousOutBufPresentationTimeUs = 0L

private fun getValidNextPresentationTimeUs(outBufPresentationTimeUs: Long): Long {
    if (outBufPresentationTimeUs < previousOutBufPresentationTimeUs) {
        // Output buffer presentation time must be monotonic. (increase only)
        return previousOutBufPresentationTimeUs
    }
    previousOutBufPresentationTimeUs = outBufPresentationTimeUs
    return outBufPresentationTimeUs
}

override fun onOutputBufferAvailable(codec: MediaCodec, index: Int, info: MediaCodec.BufferInfo) {

    ....

    info.presentationTimeUs = getValidNextPresentationTimeUs(info.presentationTimeUs)

    muxer.writeSampleData(audioTrackIndex, outBuf, info)

    ....

}

最後にMediaMuxerにInputしたTimestampよりも前の(昔の)Timestampが渡されたときは,直前にMediaMuxerにInputしたTimestampを使う,という処理です.
これも,根本原因が不明なため,正しい対処なのかどうか不明です.

普通に考えてPresentation Timeが同じAudio Frameがあると音が重なってノイズに聞こえるような気がしますが,出来上がるMP4に不自然な音は聞こえません.
(1 Frameの異常には人は気付けない,というだけかもしれません)

GL/EGLからMediaCodecのInput SurfaceにRenderした画のPresentation Timestampの基準点が不明

AudioのほうはMediaCodec.queueInputBuffer()のPresentation Timestampを自分で制御できるので単位だけ考えておけば問題ないですが,VideoのほうはGL/EGLからRenderするため,InputされるPresentation Timestampが不明です.
このTimestampの基準点(Timestap == 0はいつか)が不明だったので調べてみました.公式ReferenceにMONOTONIC TIMEという記載は出てきますが,具体的な関数など書かれていません.

↓ のKhronosのサイトにAndroidのPresentation Timeについての仕様書?が置かれていました.
https://www.khronos.org/registry/EGL/extensions/ANDROID/EGL_ANDROID_presentation_time.txt

Clock(Timestamp)の取得方法についての記述があり,

2. How can the current value of the clock that should be used for the
presentation time when an absolute time is needed be queried on Android?

RESOLVED: The current clock value can be queried from the Java
System.nanoTime() method, or from the native clock_gettime function by
passing CLOCK_MONOTONIC as the clock identifier.

JAVA側は
System.nanoTime()
で取れる値,

Native側は ↓ の関数で取れる値と同じ基準になっているようです.

.cpp
timespec ts{};
clock_gettime(CLOCK_MONOTONIC, &ts);

long nanoTime = ts.tv_sec * 1000 * 1000 * 1000 + ts.tv_nsec;

どちらもMediaMuxerにInputするために単位をMicro Secondに変換する必要はあります.

MediaMuxer.addTrack()するタイミングがシビア

MediaMuxer.addTrack()にVideo FormatやAudio Formatを渡しますが,MediaCodecを作るときに使ったFormatだとパラメータが足りないのか,例外発生でCrashします.
(Parameter Invalid的なエラーメッセージがlogcatに出てたと思います)

そのため,MediaMuxer.addTrack()MediaCodec.Callbac.onOutputFormatChanged()で渡されるMedia Formatを使う必要がありました.

ただし,MediaMuxer.addTrack()MediaMuxer.start()の前に呼ばなければいけないため,VideoとAudio両方のonOutputFormatChanged()完了後にMediaMuxerをstartするタイミング制御が必要になります.
(VideoとAudioの遅い方のonOutputFormatChanged()の中でMediaMuxer.start()を呼ぶ,等)

まとめ

通常,AndroidでMP4録画しようとしたらMediaRecorderを使うと思います.
今回,VideoのAspectを1:1にしたかったため,Camera2からMediaRecorderに直接接続することはできませんでした.
(CameraのSupported SizeのAspectでないとMediaRecorderに直接接続できない)

そのため,GL/EGLを使ってCameraのFrameを1:1にCropしてRenderするような実装を作りました.
また,せっかくなのでMediaRecorderでなくMediaCodecを使ってみよう,と思ったのですが,公式サンプルもろくに無く,ぐぐっても情報がほとんど出てこない,という状態で,だいぶ七転八倒することになりました.

---///

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

脱SharedPreferences、DataStoreに移行してみよう

はじめに

Android開発について学んでいる時、SharedPreferencesは序盤に触れるものではないでしょうか?
つい最近その代替となるライブラリがリリースされたので紹介したいと思います。

Jetpack DataStoreとは

Jetpack DataStoreShared Preferencesの代わりに利用できる新しいライブラリです。Kotlin CoroutineとFlowを使っているので、非同期の読み取りや書き込みを簡単に行うことができます。(執筆時点の最新版は1.0.0-alpha02です)

現状まだ解説されているページが少なく、いろいろな情報が断片的に散らばっているような状況なので調べたことを整理しつつ、纏めたいと思います。

Preferences DataStoreとProto DataStore

DataStoreにはPreferences DataStoreとProto DataStoreの2種類が存在します。
すごく簡単に説明するとPreferences DataStoreはSharedPreferencesを置き換えるもの、Proto DataStoreはそれよりもっと自由に、型には厳格に値を保存したい時に使えます。

この記事では主にPreferences DataStoreについて詳しく解説します。

注意点

部分的な更新や参照整合性を必要とするデータを永続化したい場合はDataStoreではなく、Roomを使用してください。
また、SharedPreferencesを利用していた際と同様に大規模なデータを保存したい場合にもRoomを使用しましょう。

比較表

Prefer Storing Data with Jetpack DataStoreより抜粋

Feature SharedPreferences PreferencesDataStore ProtoDataStore
Async API ✅ (only for reading changed values, via listener) ✅ (via Flow) ✅ (via Flow)
Synchronous API ✅ (but not safe to call on UI thread)
Safe to call on UI thread ❌* ✅ (work is moved to Dispatchers.IO under the hood) ✅ (work is moved to Dispatchers.IO under the hood)
Can signal errors
Safe from runtime exceptions ❌**
Has a transactional API with strong consistency guarantees
Handles data migration ✅ (from SharedPreferences) ✅ (from SharedPreferences)
Type safety ✅ with Protocol Buffers

*SharedPreferencesは、UIスレッド上で呼び出しても安全に見える同期APIを持っていますが、実際にはディスクI/O操作を行います。さらに、apply() は fsync() で UI スレッドをブロックします。保留中の fsync() コールは、サービスが開始または停止するたびに、またアプリケーションのどこかでアクティビティが開始または停止するたびにトリガーされます。UI スレッドは apply() によってスケジュールされた保留中の fsync() コールでブロックされ、しばしばANRの原因となります。
**SharedPreferencesは、実行時例外として解析エラーをスローします。
(よく分からなかったので機械翻訳そのままです、おかしな点があったらコメントで教えてください)

簡単な説明

表の中でのSharedPreferencesとの差について簡単に書きます、特に指定がない場合はPreferences DataStore、Proto DataStore共通です。

  • 非同期処理をFlowを用いて行う
  • UIスレッドで呼び出した場合I/O処理用に予約されたスレッド(Dispatchers.IO1)で実行される
  • エラーハンドリングが出来る
  • 実行時例外を安全に防ぐことができる
  • 強力な一貫性が保証されたトランザクションAPIを持っている
  • SharedPreferencesからのデータ移行に対応している
  • 型安全(Proto DataStoreのみ)

というように、SharedPreferencesと比較した際に異なる点が多くあることが分かります。
これらの変更点は、現在のAndroid開発においてはメリットとして働くことが多いと思います。

Preferences DataStore

前述したように、Preferences DataStoreはSharedPreferences を完全に置き換えるものです。
SharedPreferencesと同様にスキーマを事前に定義せずに、キーに基づいてデータにアクセスします。

Preferences DataStoreを使う際の流れを以下で解説します。

保存できる型

Preferences DataStoreで保存できる型はSharedPreferencesと同様で

  • int
  • long
  • float
  • boolean
  • String
  • Set<String>

となります。

依存関係の追加

Preferences DataStoreを利用するため下記を追加します。

build.gradle
implementation "androidx.datastore:datastore-preferences:1.0.0-alpha02"

これだけだとAdding support for Java 8 language features could solve this issue.のエラーが出てしまうため

build.gradle
kotlinOptions {
    jvmTarget = '1.8'
}

も追加します、公式ページには書かれていないので注意が必要です。

 値の保存

SharedPreferencesとは異なり、事前にデータ型とキー名を使用してオブジェクトを作成する必要があります。

Key
private object PreferenceKeys {
    val ID = preferencesKey<Int>("id")
    val NAME = preferencesKey<String>("name")
}

次に、DataStoreオブジェクトを作成します。

DataStoreObject
private  val dataStore : DataStore <Preferences> by lazy {
    createDataStore(name = "example")
}

editを利用して値を書き込みます。

updateName
private suspend fun updateName(name: String) {
    dataStore.edit {
        it[PreferenceKeys.NAME] = name
    }
}

一連の利用方法はたったこれだけです。

内容は/data/app/PACKAGE_NAME/files/datastoreに保存されます。

SharedPreferencesからの移行

現状SharedPreferencesを利用しているアプリからPreferences DataStoreに移行したいという場面は多くあると思います。
その場合も簡単に移行することができるようになっています。

DataStoreObject
private val dataStore: DataStore<Preferences> = 
    createDataStore(
        name = "example",
        migrations = listOf(SharedPreferencesMigration(this, "shared_preferences_name"))
    )

createDataStoreを行う際に移行元のSharedPreferencesをmigrationsに指定するだけで簡単に移行ができるようになっています。
キーは一度だけSharedPreferencesから移行されるので、DataStoreに移行後にharedPreferencesを使用しないよう注意してください。

値の読み出し

値の読み出しは

read
val preferencesFlow: Flow<String> = dataStore.data
    .map { preferences ->
        preferences[PreferenceKeys.NAME] ?: "Not Found"
    }
}

のように行うことができます、しかしこれは例外が発生した際に捕捉することができません。これではせっかくのDataStoreの利点が活かせないので例外を捕捉するように書き換えましょう。

例外処理

SharedPreferencesではファイルの入出力が失敗しても例外を吐いてくれませんでした。
この問題に遭遇した際のトレースには困った方が多いと思いますが、DataStoreではしっかりと解決されています。

Working with Preferences DataStoreによると

As DataStore reads data from a file, IOExceptions are thrown when an error occurs while reading data. We can handle these by using the catch() Flow operator before map() and emitting emptyPreferences() in case the exception thrown was an IOException. If a different type of exception was thrown, prefer re-throwing it.

DataStoreはファイルからデータを読み込みます。そのため、データの読み込み中にエラーが発生するとIOExceptionがスローされます。
投げられた例外がIOExceptionであった場合は、map() の前にcatch()を使用し、emptyPreferences()を発行することで対処できます。別のタイプの例外がスローされた場合は、再スローすることをお勧めします。

とあります。
例外をしっかりと捕捉できるよう改善されていますね。具体的にコードにすると以下のような感じです。

Exception
val preferencesFlow: Flow<String> = dataStore.data
    .catch { exception ->
        if (exception is IOException) {
            emit(emptyPreferences())
        } else {
            throw exception
        }
    }.map { preferences ->
        preferences[PreferenceKeys.NAME] ?: "Not Found"
    }

これでPreferences DataStoreの簡単な使い方は理解できたのではないでしょうか?
DataStoreのもう一つであるProto DataStoreについても簡単に説明したいと思います。

Proto DataStore

Proto DataStoreについては大まかな流れだけを解説します。利用する機会があれば後日まとめて別記事として書きたいと思っています。

Proto DataStoreはprotobufをベースとした型安全で高度なオブジェクトデータストレージを利用するためのスキーマを作成できます。
Working with Proto DataStoreによると

One of the downsides of SharedPreferences and Preferences DataStore is that there is no way to define a schema or to ensure that keys are accessed with the correct type. Proto DataStore addresses this problem by using Protocol buffers to define the schema. Using protos DataStore knows what types are stored and will just provide them, removing the need for using keys.

SharedPreferencesとPreferences DataStoreの欠点の一つは、スキーマを定義したり、キーが正しい型でアクセスされることを保証する方法がないことです。Proto DataStoreは、スキーマを定義するためにプロトコルバッファを使用することで、この問題に対処します。Proto DataStoreを使用すると、どのような型が保存されているかを知っていて、それを提供するだけなので、キーを使用する必要はありません。

とあり、Preferences DataStoreとどちらを利用するかは型安全が必要であるかが選ぶ要素になりそうです。

保存できる型

Proto DataStoreはProtocol Bufferを使っているため、.protoファイルが扱える型 = Proto DataStoreで利用できる型となります。
詳しく書くと非常に長くなるため、詳しくはLanguage Guide (proto3)を確認してください。

使用の流れ

大まかな流れとして

  • プロトファイルの作成
  • シリアライザーの作成
  • データストアの作成
  • データの書き込み、読み取り

となります。
データ型とキー名を使用してオブジェクトを作成する工程が不要になった代わりにプロトファイルの作成、シリアライザーの作成という過程が増えるため、Preferences DataStoreとは前半部分の書き方がかなり変わります。
しかし、達成したい目標は基本的には同じです(Preferences DataStoreのCodelabと比較しながら、Proto DataStoreのCodelabを行うと理解がスムーズに行えると思います)。

まとめ

まだalpha版で今後の大きな変更があり得ることも考え今すぐに急いで移行するべき!というものではないですが、個人的にはエラーハンドリングがしやすい点だけでも移行する価値はあるのかなーと考えています。
質問や間違っている点などは気軽にコメント、編集リクエストでご指摘お願いします。

参考サイト

Jetpack DataStore
Shared Preferences
Prefer Storing Data with Jetpack DataStore
Android 開発の最新情報をご紹介する「Now in Android」#25
Working with Preferences DataStore
Working with Proto DataStore


  1. ディスクおよびネットワークIOに最適化されたスレッド 

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

Android Firebase Cloud Messaging notification

Android Firebase Cloud Messagingで、ノーティフィケーションが表示されるのは、アプリが終了している状態。
アプリが起動中、または、バックグラウンドにある状態では表示されない

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

【備忘録】Androidのステータスバーにグラデーションを指定する

画面一面グラデーションなどをやると結構綺麗でした。

Activity.kt
class Activity : AppCompatActivity() {

   override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        //好きなグラデーションの向き
        val orientation = GradientDrawable.Orientation.BR_TL
        //好きなグラデーションの色
        val color1 = Color.parseColor("#e5a323")
        val color2 = Color.parseColor("#c4972f")
        val color3 = Color.parseColor("#ac6b25")
        //決めたグラデーションを色が変化する順番に並べる(二色でも良い)
        val colors: IntArray = intArrayOf(color1,color2,color3)

        val gradientDrawable = GradientDrawable(orientation,colors)

        //ステータスバーの背景を透明にする
     window?.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
        window?.statusBarColor = Color.TRANSPARENT
        window?.navigationBarColor = Color.TRANSPARENT
        window?.setBackgroundDrawable(gradientDrawable)

        //レイアウトファイルを読み込む
        setContentView(R.layout.activity)
        
        
        

   }


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

Android File Permission

Manifest

<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

<uses-feature android:glEsVersion="0x00020000" android:required="true" />

起動後

    private static final int REQUEST_EXTERNAL_STORAGE = 787;
    private static String[] PERMISSIONS_STORAGE = {
            Manifest.permission.READ_EXTERNAL_STORAGE,
            Manifest.permission.WRITE_EXTERNAL_STORAGE
    };

   void requestPermission() {
        // activityは、これを実行するアクティビティ
        // REQUEST_WRITE_PERMISSIONは、8bit以下の整数定数
 /* Context context = getBaseContext();
 if (ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) !=
 PackageManager.PERMISSION_GRANTED) {
 // 以前に許諾して、今後表示しないとしていた場合は、ここにはこない
 String[] permissions = new String[] {
 Manifest.permission.WRITE_EXTERNAL_STORAGE
 };
 ActivityCompat.requestPermissions(this, permissions, REQUEST_WRITE_PERMISSION);
 } else {
 // 許諾されているので、やりたいことをする
 }*/

        int permission = ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE);
        if (permission != PackageManager.PERMISSION_GRANTED) {
            // We don't have permission so prompt the user
            ActivityCompat.requestPermissions(
                    this,
                    PERMISSIONS_STORAGE,
                    REQUEST_EXTERNAL_STORAGE
            );
        }
    }


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