読者です 読者をやめる 読者になる 読者になる

メモ2ブログ

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

Shinjuku.aar第1回に参加した

android

Shinjuku.aarとは

Shinjuku.aarは、新宿界隈のAndroid技術者たちが 技術ネタで気軽にワイガヤできる コミュニティです。

発表ごとにディスカッションタイムを設けており、発表中の質問やツッコミも随時ウェルカム! さらに、ディスカッションが盛り上がるよう、発表者には参加者に向けた 問いかけを一つ入れるようお願いしています 。

もちろんその後は懇親会も用意しておりますので、思う存分ワイガヤしましょう!

http://shinjuku-aar.connpass.com/event/35434/

第1回で、新宿ということもあり参加してみました。

Few command line tools for help Android development

@tomorrowkeyさんの発表です。

www.slideshare.net

adbのコマンドと自作コマンドラインツールの紹介でした。 自分はapkの生成・配布以外はAndroid Studioでだいたい作業していたのですが、ビルドを途中で停止したり、アプリの設定を初期化したりなど、確かにAndroid Stuidoではなかなかやりにくいこともできるので、徐々に比率を移行していきたいなと思いました。

恥ずかしながらiTermのHotKeyを知らなかったのですが、設定すると切り替えが非常に楽で、移行に前向きになりました。

Chrome Custom Tabsを使いこなそう

自分の発表です。

speakerdeck.com

会場ではChrome Custom Tabsを使っている人が思ったより少なかったです。 24.0.0からの機能の紹介で、知らなかったという声も上がっていたので一安心しました。

AutoValue for Easier Life

@Shaunkawano さんの発表です。

speakerdeck.com

ボイラープレートを無くしていこう、ボイラープレートを書かないでいこう、という、決め事を掲げてチームで取り組むという姿勢が良いなと思いました。

AutoValueは知らなかったのですが、目的に即していると思いました。

DownloadProviderを改めて読む

@YusukeIwaki さん

資料は見つかりませんでした。。

Android標準のコンポーネントであるDownloadProviderの設計を、別の所でも使おうというお話。

雑感

会場の雰囲気作りとして、はじめに皆が自己紹介するという形式で、人数もそこまで多くなかったのでやりやすさはありました。 また10分の発表は初めてだったのですが、質問とかしながらしていく雰囲気もあり、時間があまるという感じにはなりませんでした。

他の発表者の方たちのスライドが皆英語のスライドで、デザイン性も高かったので次発表する機会があれば、英語でスライドを作ってみようと思いました。 英語で作ると、日本人以外でも見れるようになるし、スライドをただ読み上げることもなくなる気がします。

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

android Chrome Custom Tabs Mashwmallow

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はいらない

android design

スプラッシュ

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