コンテンツ作成 > アプリ > Android罠

Android罠(トラップ)


コンテンツ


概要

  • Androidのアプリ開発時に引っかかった罠についてのメモ

リソース

文字列リソースにサロゲートペアが必要な文字を使うと実行時エラー

  • 文字列リソースにBMP(基本多言語面)外の文字を\uでサロゲートペアとして記述すると実行時に致命的エラーになる
    • 例えばU+1F601を"\ud83d\ude01"と記述すると以下のエラーが発生する
JNI WARNING: NewStringUTF input is not valid Modified UTF-8: illegal start byte ~
  • ソース中の文字列リテラルとしてString s = "\ud83d\ude01"のように記述するのは問題ない
  • U+10000以降の文字(Unicode絵文字など)をリソースにしたい場合は自前で処理が必要

ウィジェット

ImageViewのsetAlphaが使えない

  • Android 2.1未満ではRemoteViews経由で呼び出すとエラーになる
  • バージョンを調べて投げないようにする制御が必要
  // 2.2以降の場合のみ透明度を設定
  if (Build.VERSION.SDK_INT > Build.VERSION_CODES.ECLAIR_MR1) {
    views.setInt(viewId, "setAlpha", alpha);
  }

差分更新すると画面回転時に表示がおかしくなる

  • 画面回転時などの画面復元は最後に受け取ったRemoteViewsのみ再送する仕様になっている模様
  • そのため常にウィジェット全体を更新するように実装する必要がある
    • API 11以降ならAppWidgetManager#partiallyUpdateAppWidget()が使えるようだが……
    • 差分更新せずに大量の画像を表示する方法は一応存在する(下記参照)

ログに「FAILED BINDER TRANSACTION」と表示されて更新に失敗する

  • RemoteViewsにデータを載せすぎると発生する模様
    • RemoteViews#setTextViewText()だとViewのIDとシリアライズした文字列が載る
    • RemoteViews#setImageViewBitmap()だとViewのIDとシリアライズした画像(!)が載る
  • 解決方法
    • RemoteViewsを使いまわさず毎回生成する(内部をクリアするメソッドが無いため)
    • 設定するデータ量を減らす(文字列なら表示される部分までで切る、画像なら縮小したり品質を下げる)
    • RemoteViews#setImageViewBitmap()を使わないようにする(別の画像設定メソッドを使う)
      • リソースはRemoteViews#setImageViewResource()を使う(画像リソースのIDしかデータが載らない)
      • 動的な画像はRemoteViews#setImageViewUri()を使う(Uriしかデータが載らない)
  • 動的な画像を大量に表示する方法(Yotsubaで使用)
    • あらかじめ画像を端末内にファイルとして保存してからRemoteViews#setImageViewUri()を使う
    • ファイルとして保存する場合はContext#openFileOutput()にContext.MODE_WORLD_READABLEを指定
      • アプリとウィジェットは別プロセスで動くためこの設定をしないと画像が読めない
      • 外部メモリ上ならアクセス権の制約は無いが外部メモリが必ず存在するとは限らないしユーザが画像を勝手に削除することも考えられる
      • Context#getDir()でディレクトリを切って入れるのは不可(上記メソッドはパス区切り文字が使えない)
      • いつ更新が完了するか分からないため画像を削除するタイミングには注意が必要
    • 画像はBitmap.createScaledBitmap()で縮小しておく
      • 上記の通り端末内に保持しておく必要があるためサイズを減らす
    • 不名誉だが「@SuppressLint("WorldReadableFiles")」で警告を抑制できる

SharedPreferences

保存した文字列とgetString()した結果が一致しないことがある

  • Strings with line feeds incorrectly restored from SharedPreferences
  • テキストメモ(ウィジェット)で発生を確認
    • EditViewの内容をそのままSharedPreferencesで読み書きしていた
  • 保存した文字列の末尾が改行だった場合、読み込まれた文字列の末尾に半角スペース4個が追加されることがある
    • 比較的新しい端末で発生する
      • 発生する: Android 4.4.2(Xperia Z3C), Android 5.0.2(Xperia Z3C)
      • 発生しない: Android 2.3.4(Xperia acro), Android 4.2.2(Xperia A)
    • 必ず発生するわけではない(プロセスの生死が影響)
      • 発生したかを知るすべがないので発生した時だけ対応というロジックはできない
    • 文字列以外の読み書きでは発生しない(改行を入れられないため)
  • 対処方法
    • 保存時の文字列の末尾が改行でなければいいので、保存前に改行以外の文字列を連結し、読み込み後にその文字を削除すれば対応可能
      • 連結/削除するのは半角スペースでもOK

ImageView

setScaleType()が機能しない

  • 画像設定系メソッドにはsetBackground*()とsetImage*()の2種類が存在する
    • setScaleType()などImageView固有の機能は後者で設定した画像にのみ有効
    • 前者で設定しておいて何故効果がないのか悩んだ

Notification

ステータスバーを引き下げた時のアイコンがバグる

  • 最後の設定としてsetLatestEventInfo()を呼ばないとおかしくなる模様
  • setLatestEventInfo()の後にアイコンを設定していたのが原因

動的に生成した画像が使用できない

  • アイコンの設定にはリソースIDしか使えないため
  • そのため月日のカレンダーや電池残量表示をしようとすると手間が掛かる
    • 電池残量なら101枚、カレンダーなら366枚(土日で色を変えるなら+366×2)も画像を作る必要がある