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

EMUIで最近のアプリ一覧の表示が消えるバグを対処する(非Root環境)

背景

サードパーティランチャー環境下でマルチウインドウで2つのアプリを表示させる
→マルチウィンドウを解除する
→最近のアプリ(バックグラウンドタスク)を表示しようとしてもアプリ一覧が表示されないバグに遭遇
(これ以外にもたまに表示されなくなることがある)

どうやら公式のコミュニティにもEMUI10にて似たような報告がある模様(微妙に違うけど)

環境

・HUAWEI P20 lite (ANE-LX2J),非Root
→ EMUI自体のバグみたいなのでこの端末に限らないはず

・EMUI 9.1.0.336 (C635E5R1P1) 
→ EMUI 9.0.0以降のバージョンで発生するみたい
ただし自分の環境ではEMUI10以降の検証はできてないのであしからず

・Nova Launcher 6.2.12
→ サードパーティランチャー全般で発生するのであまり関係ない

対処法

XDA-devフォーラムのこここことかRedditでも色々出てくる
サードパーティランチャーがデフォルトに設定されていることが前提

1.PCと端末を接続して,以下のADBコマンドを実行する

adb shell pm uninstall -k --user 0 com.huawei.android.launcher

これにより,デフォルトランチャーを無効にできる&タスク一覧のUIがEMUI8.0.0のものに戻る(縦形のやつから横形のやつになる)

※PCにはADBをインストールして,端末は開発者向けオプションからUSBデバッグをオンにして,adb devicesでオンラインになってることを確認すること.Huwaei端末はここらへんの挙動が特に怪しい

2.(ジェスチャーナビゲーションを使っている場合)Fluid Navigation Gesturesを入れる

Google PlayよりFluid Navigation Gesturesを入れる
(権限とか設定方法とかはアプリに従ってやればいい)

デフォルトランチャーを消すと,ジェスチャーナビゲーションでホーム画面&タスク一覧画面が出せなくなるので別のジェスチャーアプリで代用する必要があるため
デフォルトのジェスチャーもラップしてくれるみたいなので推奨

『3つのキーによるナビゲーション』の場合そのまま動く

補足

adb shellで消したやつは,

adb shell cmd package install-existing com.huawei.android.launcher

で戻すことができる
EMUI10以降ではHuwaeiホームがないと表示されなくなる報告(前述のフォーラム)もあるようなので一応
こっちのやり方でも戻せる

考察とか(読まなくていい)

EMUI8.0.0⇒9.0.0更新時に,バックグラウンドタスク一覧のUIが変化&ジェスチャーナビゲーションに対応したが,Huwaeiホーム以外動作が不安定みたいで,マルチウィンドウ起動時にHuwaeiホームが呼び出される仕様
なので今回みたいにでデフォルトランチャーを変えるとバグるっぽい

なおデフォルトランチャーでもタスク一覧画面を表示させようとしても反応しなくなるバグがあったので,そもそもちゃんとマルチウィンドウとかジェスチャーナビゲーションに対応してない気がする...

というより日本語の情報がなかった時点で日本市場のHuawei端末って絶滅危惧種なのでは...?

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

Status barを透明にする方法

直近の案件でStatus Bar部分にもコンテンツを表示したいという要件が合った際に使用したコードを、自分への備忘録も兼ねて記載します。

実装方法

XMLではなくコードから指定します。

// このコードでStatus Barを非表示にし、レイアウトを全画面に表示する
window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN

// レイアウトの上部にStatus Barの高さの分Paddingを入れることで、Status Barの領域を再確保
findViewById<View>(R.id.root).setPadding(0, getStatusBarHeight(), 0, 0)

// Status Barの部分を透明に塗りつぶす
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
window.statusBarColor = Color.TRANSPARENT

// Status Barに表示されるアイコンが見えるよう調節する
var systemUiVisibility = window.decorView.systemUiVisibilitysystemUiVisibility = systemUiVisibility or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
window.decorView.systemUiVisibility = systemUiVisibility
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Flutter + GitHub Actions + Firebase App Distributionで自動リリースサイクルを作る(Android編)

Flutterアプリ開発を進めるにあたり、masterにマージしたら、誰でもアプリが実機にインストールできるフローを作りたい。

たまにしかやらなくて忘れそうなので備忘。

やりたいこと

  1. ローカルでもCIでもリリースビルドを作れるようにする
  2. GitHubに署名ファイルはあげない
  3. 自動でバージョンをインクリメントして、Firebase App Distributionにuploadする

ローカルでリリースビルド作れるようにする

オフィシャルドキュメントのこの辺にも書いてある。

1. (無ければ)keystoreファイル作る

サンプルはたくさん転がってるので割愛。
git管理からは外しておく。

2. ローカルでのビルド用にkey.properties作る

storePassword=<password from previous step>
keyPassword=<password from previous step>
keyAlias=key
storeFile=<location of the key store file, such as /Users/<user name>/key.jks>

これもgit管理からは外しておく。

3. アプリのbuild.gradleに下記を追加。

def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties')
if (keystorePropertiesFile.exists()) {
    // For Local
    keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
} else {
    // For CI
    keystoreProperties.setProperty('storePassword', System.getenv('KEY_STORE_PASSWORD'))
    keystoreProperties.setProperty('keyPassword', System.getenv('KEY_PASSWORD'))
    keystoreProperties.setProperty('keyAlias', System.getenv('ALIAS'))
    keystoreProperties.setProperty('storeFile', System.getenv('KEY_PATH'))
}

androidブロックに下記を追加(&書換え)

    signingConfigs {
        release {
            keyAlias keystoreProperties['keyAlias']
            keyPassword keystoreProperties['keyPassword']
            storeFile file(keystoreProperties['storeFile'])
            storePassword keystoreProperties['storePassword']
        }
    }

    buildTypes {
        release {
            signingConfig signingConfigs.release
        }
    }

4. 確認

とりあえずAPK作ってみる
flutter build apk --release

GitHub Actionsでリリースビルド作れるようにする

参照:Signing Flutter Android apps for release in GitHub Actions

1. (無ければ)workflow作る

サンプルはたくさん転がってるので割愛。

2. GitHub Secretsにもろもろ入れとく

key.propertiesの代わり。
この後、これをGitHub Actionsで読み取る。
スクリーンショット 2020-08-10 14.16.06.png

  • ANDROID_KEY_STORE_PASSWORD :point_right: storePassword
  • ANDROID_KEY_PASSWORD :point_right: keyPassword
  • ANDROID_KEY_ALIAS :point_right: keyAlias
  • ANDROID_KEY_JKS :point_right: keystoreファイルをbase64エンコードしたもの。 openssl base64 -A -in key.jks

使わなかったけど、こんなactionもあったりする。
https://github.com/marketplace/actions/sign-android-release

3. workflowにリリースビルドを作るステップを追加

前のステップでGitHub Secretsに保存した値を環境変数に保存。

      - name: set up private files
        run: |
          echo $SIGNING_KEY | base64 -di > android/app/key.jks
        env:
          SIGNING_KEY: ${{ secrets.ANDROID_KEY_JKS }}

      - name: build release apk
        run: |
          flutter build apk --release --build-name=0.0.$GITHUB_RUN_NUMBER --build-number=$GITHUB_RUN_NUMBER
        env:
          KEY_STORE_PASSWORD: ${{ secrets.ANDROID_KEY_STORE_PASSWORD }}
          KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
          ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
          KEY_PATH: key.jks

  • keystoreを復元してるところは、iオプションつけないと自分の環境ではこけた。
  • android/appbuild.gradleでビルドするので、android/app/にファイル置いてる。
  • $GITHUB_RUN_NUMBER使って、ビルドごとにパッチバージョンとバージョン番号があがるようにする

Firebase App Distributionへリリース

既存のGitHub Actionの恩恵にあずかって楽する

(App Distributionのセットアップも忘れずに)

      - name: upload artifact to Firebase App Distribution
        uses: wzieba/Firebase-Distribution-Github-Action@v1
        with:
          appId: ${{secrets.FIREBASE_ANDROID_APP_ID}}
          token: ${{secrets.FIREBASE_TOKEN}}
          groups: Developer
          file: build/app/outputs/flutter-apk/app-release.apk

もう一声

  • メジャー・マイナーバージョン名は、workflowの外で定義したい
  • やっぱり署名も既存のactionを使った方が楽かもしれん
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

FlutterでAndroidビルドのみ失敗するようになる場合の対処法

現象

Androidでデバッグビルドしてデバッグ実行していたのに、突然ビルドが出来なくなることがある。
エラーは私の場合、firebase_analytics:webの依存関係が解決できない的なメッセージ。

FAILURE: Build failed with an exception.

* What went wrong:
Could not determine the dependencies of task ':firebase_analytics:compileDebugAidl'.
> Could not resolve all task dependencies for configuration ':firebase_analytics:debugCompileClasspath'.
   > Could not resolve project :firebase_analytics_web.
     Required by:
         project :firebase_analytics
      > Unable to find a matching configuration of project :firebase_analytics_web:
          - None of the consumable configurations have attributes.

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.

* Get more help at https://help.gradle.org

BUILD FAILED in 25s
Finished with error: Gradle task assembleDebug failed with exit code 1

環境など

ツールなど バージョンなど
MacBook Air Early2015 macOS Mojave 10.14.6
Android Studio 3.6.1
Java 1.8.0_131
Flutter 1.12.13+hotfix.9
Dart 2.7.2
Xcode 11.3.1

対処法

https://github.com/FirebaseExtended/flutterfire/issues/1615
こちらにある、flutter pub cache repairを実行して解消しました。

必ず解決できるとは限らないようですが、今のところ私は現象が発生した2回中2回とも、この方法で解消しました。

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

Ghidra ARM32 全関数がARMモードかThumbモードかを出力する

ARM32のアプリの解析を行っているとき、正確に逆アセンブルした状態で各関数がARMモードかThumbモードなのかを判定したいという状況はあると思います。
そんな時のための備忘録的メモ。

設定

コンソールの出力文字制限を50000->1000000程度にします。
Edit->Tool Options...->Console->Character Limitの値を1000000に変更。

options.png

Script

thumb_or_arm.py
def isThumbFunction(func):
    r = currentProgram.getRegister("TMode")
    value = currentProgram.programContext.getRegisterValue(r, func.entryPoint)
    return value.unsignedValueIgnoreMask == 1

func = getFirstFunction()
while func is not None:
    if(isThumbFunction(func)):
        print("Function: {} @ 0x{}".format(func.getName(), func.getEntryPoint()),"Thumb")
        func = getFunctionAfter(func)
    else:
        print("Function: {} @ 0x{}".format(func.getName(), func.getEntryPoint()),"ARM")
        func = getFunctionAfter(func)


出力

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

ConstraintLayout入門その5 - 「制約に合致」 (0dp) とは

ConstraintLayoutを使用するための設定については、ConstraintLayout入門その1をご覧ください。

制約に合致 (MATCH_CONSTRAINT)

以下の2つのレイアウトXML記述例では、 <ChildView> の垂直方向の長さはともに「親Viewと同じ」になります。

linear_layout_1.xml
<ParentLayout
    ...
    >
    <ChildView
        ...
        android:layout_height="match_parent"
        ...
    />
</ParentLayout>
constraint_layout_1.xml
<androidx.constraintlayout.widget.ConstraintLayout
    ...
    >
    <ChildView
        ...
        android:layout_height="0dp"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        ...
    />
</androidx.constraintlayout.widget.ConstraintLayout>

<ChildView> の記述は、2つとも「上端と下端が親Viewに一致する」ことを表現していますが、ConstraintLayoutの子Viewでは app:layout_constraintTop_toTopOf などの値に "parent" 以外に他の子ViewのView IDを指定することができ、他のViewよりも多様な設定を可能にしています。
ConstraintLayoutの子Viewの android:layout_width および android:layout_height の値に "0dp" が設定されている場合、それは文字通りの「長さゼロ」の意味にはならず、「制約に合致する」という意味になります。

余白を埋める

以下の2つのレイアウトXML記述例では(親Viewの垂直方向の長さが300dp以上ある場合)ともに、親Viewの上部に <TopView>, 下部に <BottomView>, 上下に挟まれる形で <MidView> が上30dpと下50dpを除いた部分に表示されます。

linear_layout_2.xml
<LinearLayout
    ...
    android:orientation="vertical"
    ...
    >
    <TopView
        ...
        android:layout_height="30dp"
        ...
    />
    <MidView
        ...
        android:layout_height="0dp"
        android:layout_weight="1"
        ...
    />
    <BottomView
        ...
        android:layout_height="50dp"
        ...
    />
</LinearLayout>
constraint_layout_2.xml
<androidx.constraintlayout.widget.ConstraintLayout
    ...
    >
    <TopView
        android:id="@+id/topView"
        ...
        android:layout_height="30dp"
        app:layout_constraintTop_toTopOf="parent"
        ...
    />
    <MidView
        ...
        android:layout_height="0dp"
        app:layout_constraintTop_toBottomOf="@id/topView"
        app:layout_constraintBottom_toTopOf="@id/bottomView"
        ...
    />
    <BottomView
        android:id="@+id/bottomView"
        ...
        android:layout_height="50dp"
        app:layout_constraintBottom_toBottomOf="parent"
        ...
    />
</androidx.constraintlayout.widget.ConstraintLayout>

LinearLayoutにおける android:layout_weight は、ConstraintLayout入門その3で紹介したような、子View間の長さの比を決める用途だけでなく、単純に「長さが決まっている子Viewで埋めた後に残った余白を埋める」という目的で単独でもよく用いられてきました。linear_layout_2.xmlでは android:layout_weight が設定されている子Viewは1つだけなので、weightの値が "2" でも "3" でも表示結果は同じになります。weightに正の数が設定されていれば、その子Viewは「伸びようとする」性質を帯びます(<TopView><BottomView> のweightにはデフォルト値として0が与えられていて、伸びようとはしません)。
ConstraintLayoutの場合は、 <MidView>android:layout_height="0dp" を設定して「制約に合致」とし、 app:layout_constraintTop_toBottomOfapp:layout_constraintBottom_toTopOf を設定して上下の位置を明示することで「余白を埋め」ます。

とはいえ、これだけならRelativeLayoutでも従来から似た記述で同じレイアウトを表現できました。ConstraintLayoutの「制約に合致」はこの他にもいくつかレイアウト表現を提供します。

片端+長さの制約

上端の位置、下端の位置、垂直方向の長さ、の3者の間には、
(上端の座標)+(垂直方向の長さ)=(下端の座標)
という関係が成り立ちます。
水平方向の場合は、
(左端の座標)+(水平方向の長さ)=(右端の座標)
が成り立ちます。
したがって、子Viewの長さが制約で決まれば、子Viewの位置の制約は片端だけあれば足ります。
以下の2つのレイアウトXML記述例はともに、垂直方向に1:2:3の比で3つの子Viewを縦に並べています。

linear_layout_3.xml
<LinearLayout
    ...
    android:orientation="vertical"
    ...
    >

    <TopView
        ...
        android:layout_height="0dp"
        android:layout_weight="1"
    />

    <MidView
        ...
        android:layout_height="0dp"
        android:layout_weight="2"
    />

    <BottomView
        ...
        android:layout_height="0dp"
        android:layout_weight="3"
    />

</LinearLayout>
constraint_layout_3.xml
<androidx.constraintlayout.widget.ConstraintLayout
    ...
    >

    <TopView
        android:id="@+id/view1"
        ...
        android:layout_height="0dp"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintHeight_default="percent"
        app:layout_constraintHeight_percent="0.167"
        ...
    />

    <MidView
        android:id="@+id/view2"
        ...
        android:layout_height="0dp"
        app:layout_constraintTop_toBottomOf="@id/view1"
        app:layout_constraintHeight_default="percent"
        app:layout_constraintHeight_percent="0.333"
        ...
    />

    <BottomView
        ...
        android:layout_height="0dp"
        app:layout_constraintTop_toBottomOf="@id/view2"
        app:layout_constraintHeight_default="percent"
        app:layout_constraintHeight_percent="0.5"
        ...
    />

</androidx.constraintlayout.widget.ConstraintLayout>

constraint_layout_3.xmlでは、 <TopView>, <MidView> の垂直方向の位置制約は上端のみ、 <BottomView> の垂直方向の位置制約は下端のみです。しかし、いずれも app:layout_constraintHeight_percent の指定によって垂直方向の長さが決まっているので、もう一方の端の位置も決まってレイアウトのすべての情報が決まります。このような使い方は、RelativeLayoutではサポートされていませんでした。

ConstraintLayout入門その3で紹介した通り、percentは親View(すなわちConstraintLayout)に対する比を指定します。値は0〜1の小数です。

両端+長さの制約

上記の通り、子Viewの両端の位置と長さの間には等式の関係が成り立つので、両端の位置と長さの3つの制約のうち2つが与えられていればもう1つの制約が自動的に決まり、したがって3つの制約を与える必要はなく2つで足ります。
では、2つの制約で足りるところへあえて3つの制約が与えられ、かつ、それらが「(上端の座標)+(垂直方向の長さ)=(下端の座標)」という関係を満たしていなかったらどうなるでしょうか?
このようなとき、ConstraintLayoutでは3つの制約がバランスするようにレイアウトの微調整が行われます(相矛盾するパラメータのうちいずれかを無視する、といった消極的な調整ではなく、3つの制約のすべてが意味をもつような積極的な調整が行われます)。この性質が、他のレイアウトクラスには見られないConstraintLayoutならではの主要な特徴です。

linear_layout_4.xml
<LinearLayout
    ...
    android:orientation="vertical"
    ...
    >

    <TopView
        ...
        android:layout_height="0dp"
        android:layout_weight="10"
        ...
    />

    <Space
        ...
        android:layout_height="0dp"
        android:layout_weight="16" />

    <MidView
        ...
        android:layout_height="0dp"
        android:layout_weight="20"
        ...
    />

    <Space
        ...
        android:layout_height="0dp"
        android:layout_weight="24" />

    <BottomView
        ...
        android:layout_height="0dp"
        android:layout_weight="30"
        ...
    />

</LinearLayout>
constraint_layout_4.xml
<androidx.constraintlayout.widget.ConstraintLayout
    ...
    >

    <TextView
        android:id="@+id/view421"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintHeight_default="percent"
        app:layout_constraintHeight_percent="0.1"
        ...
    />

    <MidView
        android:id="@+id/view422"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        app:layout_constraintTop_toBottomOf="@id/view421"
        app:layout_constraintBottom_toTopOf="@id/view423"
        app:layout_constraintHeight_default="percent"
        app:layout_constraintHeight_percent="0.2"
        app:layout_constraintVertical_bias="0.4"
        ...
    />

    <BottomView
        android:id="@+id/view423"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintHeight_default="percent"
        app:layout_constraintHeight_percent="0.3"
        ...
    />

</androidx.constraintlayout.widget.ConstraintLayout>

具体的には、制約が3つだったとき、子Viewの長さを制約の長さに合致させつつ、子Viewの位置が両端の中間に位置するように調整されます。
このとき、 app:layout_constraintVertical_bias の値を設定することで、垂直方向の「中間」の位置を比率で設定することができます。
上のConstraintLayout内の <MidView> には3つの制約とbiasが与えられており、この調整によって表示が決定されます。

app:layout_constraintVertical_bias が0のときは上端の制約に張りつきます(下端の制約を省略したのと同じ結果になります)。app:layout_constraintVertical_bias が1のときは下端の制約に張りつきます(上端の制約を省略したのと同じ結果になります)。

水平方向の場合は app:layout_constraintHorizontal_bias で同様の設定ができます。
app:layout_constraintHorizontal_bias が0のときは 'Left' もしくは 'Start' に設定した制約に張りつき、1のときは 'Right' もしくは 'End' に設定した制約に張りつきます。
'Start' 〜 'End' で制約を設定したとき、right to leftレイアウトか否かで app:layout_constraintHorizontal_bias の偏りの方向が反転します。

app:layout_constraintVertical_bias, app:layout_constraintHorizontal_bias のどちらも、0と1の間の値を設定することで中間の位置取りを行えます。biasの値を省略したときのデフォルト値は0.5、すなわち子Viewの位置は両端の真ん中になります。
上記の2つのレイアウトXML記述例では、ConstraintLayoutがbiasの設定で <MidView> の位置を調整しているのに対し、LinearLayoutでは <Space> を挟んで空白を作り出して調整しています。

サンプルコード

今回のサンプルコードは以下のリポジトリにあります。
https://github.com/csayamada/ConstraintLayout5

画面例

LinearLayoutとConstraintLayoutの結果を並べた表示例は下のようになります。

表示結果

最下段(biasを使って表現した部分)をよく見ると、LinearLayoutの表示とConstraintLayoutの表示との間にわずかなズレがありますが、これはLinearLayoutとConstraintLayoutの実装の違いによる誤差のよう(Android OSのバージョンによって変化するかもしれません)で、計算原理は両者同じのようです。

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

ImageViewにDataBindingをする方法

はじめに

今回はImageViewにDataBindingをする方法を解説していきます。

やりたかったこと

<ImageView
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:src="@{iconId}"/>

こんな感じでImageViewにリソースIDをDataBindingをしたかったのですが、なぜか上手くいきませんでした。

改善

<ImageView
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  app:imageResource="@{iconId}"/>

これでできました!

なんでできないんだろうって考えていたんですが、公式のBindingAdapterを見て、そりゃできないなって思いました。
ちなみにこちらが既に用意されているBindingAdapterです。

Java
@BindingAdapter("android:src")
public static void setImageUri(ImageView view, String imageUri) { 
    if (imageUri == null) { 
      view.setImageURI(null); 
    } else { 
             view.setImageURI(Uri.parse(imageUri)); |
    } 
} 

@BindingAdapter("android:src")
public static void setImageUri(ImageView view, Uri imageUri) {           
    view.setImageURI(imageUri);
} 

@BindingAdapter("android:src")
public static void setImageDrawable(ImageView view, Drawable drawable) { |
    view.setImageDrawable(drawable);
} 

setImageResourceをしてくれるBindingAdapterはなかったんですね(笑)
そりゃできないわけだ、、、

ちなみに自作でBindingAdapterを作っても良さそうですね
作るとしたらこんな感じです

kotlin
@BindingAdapter("android:src")
fun ImageView.setImageResource(resourceId: Int) {
    setImageResource(resourceId)
}

というかResourceIdをセットすることを最初から想定しておいてほしいです、Googleさん、、、、

最後に

ImageViewがバグって表示されていて、なんでだろうなぁと小1時間くらい消費してしまいました。
想定通りの挙動をしない場合は、裏側で何をやっているかちゃんと確認した方がいいということを学べたのでよかったです(笑)

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