メモ2ブログ

メモtoウェブログ。旧ブログはこちら。 http://sakebook.blogspot.jp/

MashwmallowでqueryIntentActivitiesの挙動が変わってた

Chrome Custom Tabsを使っていて、公式のサンプルにあるCustomTabsHelperを流用していたのですが、Mashwmallowでうまく動かないことがありました。

CustomTabsHelper内で、PackageManager#queryIntentActivitiesを使っているのですが、その挙動がMashwmallow以前と以降で異なっていたのが原因でした。

どう変わったのかと、回避方法を紹介します。

Mashwmallow以前

次のように呼ぶことで、引数のIntentに対応できるActivityを持つアプリを取得できます。

PackageManager pm = this.getPackageManager();
List<ResolveInfo> resolvedActivityList = pm.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);

queryIntentActivities(Intent intent, int flags)の第二引数のflagsには、PackageManager.MATCH_DEFAULT_ONLYを指定します。こうすることで、 android.intent.category.DEFAULT を持つActivityのみを反応させることができます。

どんなIntentかにもよりますが、次のようにするとブラウザ系のアプリの一覧が表示されると思います。

Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.example.com"));

Mashwmallow以降

Mashwmallow以降で同じように呼ぶと、そのIntentに対応するActivityを「常時」で規定で開く設定にしている場合、結果が変わります。

結果は、 規定で開く設定にしているActivityしか返さない ようになります。

規定で開くの設定を解除すると、Mashwmallow以前と同じになります。

Mashwmallow以前と等しい挙動で取得するためにはqueryIntentActivities(Intent intent, int flags)の第二引数のflagsを、PackageManager.MATCH_ALLを指定します。

PackageManager pm = this.getPackageManager();
List<ResolveInfo> resolvedActivityList = pm.queryIntentActivities(intent, PackageManager.MATCH_ALL);

PackageManager.MATCH_ALLAPI Level 23から追加されたflagです。

Querying flag: if set and if the platform is doing any filtering of the results, then the filtering will not happen.

とあるので、フィルタリングされたくない時に使うようです。

バージョン対応

共存させるには、バージョンでflagsを切り替えれば良いです。

例えば次のような感じです。

int flag;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    flag = PackageManager.MATCH_ALL;
} else {
    flag = PackageManager.MATCH_DEFAULT_ONLY;
}
ResolveInfo info = pm.resolveActivity(intent, flag);

CustomTabsHelperの場合は、公式のサンプルにPullRequestがきてるので、それに習うといいと思います。

まとめ

Mashwmallow以降以前で挙動が変わる場合があるので、バージョンを見てflagsを切り替えましょう。

しかし、API Level 23未満で、PackageManager.MATCH_ALLを指定したところ、クラッシュも発生せず同じように取得できていたので、最悪PackageManager.MATCH_ALLのほうだけ使っておけば良いのかもしれません。。

参考

Chrome Custom Tabs / Google Chrome

GoogleChrome/custom-tabs-client / GitHub

custom-tabs-client/CustomTabsHelper.java / GitHub

Intent Resolving in Android M / Medium

Android:IntentFilterにDEFAULT_CATEGORYが必要な理由 / Yukiの枝折

PackageManager | Android Developers / Android Developers)

Updated CustomTabsHelper#getPackageNameToUse / GitHub

スプラッシュにはActivityはいらない

スプラッシュ

iOSだとLaunchScreenとか言われる、起動時に出てくる画面です。

Androidだと、ユーザに無駄な待ち時間を与えるということで不要だと言われてたのですが、最近はGoogle製のアプリが、軒並みスプラッシュを入れてきています。

Bottom Navigationのことといい、考え方が変わってきたのでしょう。 Android開発を行う以上、プラットフォームが出すガイドラインに合わせるのが、結果的にユーザに良い体験を与えることにつながります。

スプラッシュについては、iOSと同様に、LaunchScreenというPatternで紹介されています。その実装方法を紹介します。

Activityあり

f:id:sakebook:20160601030423g:plain

SplashActivityなどを用意して、起動時に呼び出します。 なんらかの処理や、一定時間を経過した後にメインのActivityを起動させます。

  • DelaySplashActivity.java
public class DelaySplashActivity extends AppCompatActivity {

    private final static int SPLASH_TIME = 1500;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_delay_splash);
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                Intent intent = new Intent(DelaySplashActivity.this, MainActivity.class);
                startActivity(intent);
                finish();
            }
        }, SPLASH_TIME);
    }

    /**
     * バックキー無効。
     * */
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_BACK) {
            return false;
        }
        return super.onKeyDown(keyCode, event);
    }
}

この方法だと、次のような特徴があります。

長所

  • DelaySplashActivityを起動させている間に処理を実行することができる
  • 複雑なアニメーションなどを表現できる

短所

  • 起動時間が若干遅くなる
  • LaunchModeをうまく指定しないとメインのActivityが重複する
  • 起動時に数瞬ブランクが表示される

Activityなし

f:id:sakebook:20160601030502g:plain

新たにActivityは追加しません。 Themeを指定します。

    <style name="SplashTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <item name="android:windowBackground">@drawable/background_splash</item>
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>

親となるテーマのActionBarの有無は用途によって変えてください。
重要なのは android:windowBackground属性にdrawableを指定していることです。

background_splashは次のように設定しています。

  • background_splash.xml
<?xml version="1.0" encoding="utf-8"?>
<layer-list
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:opacity="opaque">

    <item
        android:drawable="@color/colorPrimaryDark"/>

    <item
        android:gravity="center">
        <bitmap
            android:gravity="center"
            android:src="@mipmap/ic_launcher"/>
    </item>
    <item
        android:gravity="bottom"
        android:bottom="@dimen/large_margin">
        <bitmap
            android:gravity="center"
            android:src="@mipmap/ic_launcher"/>
    </item>

</layer-list>

layer-listで作成します。

文字は追加できないのですが、Verctor画像で文字を作れば設定できます
その場合はstyleをv21以降と以前で分ける必要があります。

起動するActivityに作成したテーマを設定します。

  • AndroidManifest.xml
        ...
        <activity
            android:name=".SplashActivity"
            android:label="SplashCold"
            android:theme="@style/SplashTheme">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>

                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
        ...

Splash専用のActivityを追加しなくても、スプラッシュが追加できています!

この方法だと次のような特徴があります。

長所

  • 起動時間が通常とほぼ変わらない
  • 既存のアプリに追加しやすい
  • 起動時に数瞬ブランクが表示されない

短所

  • 事前の処理が行えない
  • 表現できることに制約がある

まとめ

スプラッシュの目的はplaceholder UIBranded launch screensです。

特に理由が無ければ、Acitvityを追加しないSplashの実装で上記の目的を達成するのが良いと思います。
ただし、事前の処理を行っておくことで結果的にユーザ体験を良くできるならば、Activityを追加する実装でも良いと思います。
その場合はbackgroundの要素を指定しておきましょう。

サンプルです。

github.com

参考

Android UI/UX アンチパターン / nein37’s diary

Launch screens / Google design guidelines

プレースホルダ 【 placeholder 】 / IT用語辞典 e-Words

Splash Screens the Right Way / Big Nerd Ranch

Avoiding cold starts on Android / Saúl Molinero

GenymotionにGooglePlayを入れようとするとAn error occured while deploying the fileが発生する

Genymotionについては以前触れたのでこちらで。

sakebook.hatenablog.com

An error occured while deploying the file.

GooglePlayを入れるための準備をしていると次のエラーが発生しました。

f:id:sakebook:20160525022243p:plain

Genymotionのバージョンは2.6.0でした。

エミュレータを作り直したりカスタムしてSDカードを生成したりしてもうまくいかなかったのですが、次のようにすることで解決しました。

ADB tool connection settings

おそらく、Genymotionの設定画面を開いて、User custom Android SDK toolsにチェックが入っているので、 defaultの方へ変更します。

これで解決しました。以上。

参考

Genymotion Android 6.0 に Play Store をインストールして Google Map を利用する / Qiita

wbroek/genymotionwithplay.txt / gitHub gist