Learn to Live and Live to Learn

IT(たまにビジネス)に関する記事を読んで、考えて、使ってみたことをまとめる場。

this version of Android Studio is incompatible with the Gradle Plugin used. Try disabling Instant Run

エラー内容

git cloneしてきたアプリをrunしようとしたら,こんなメッセージが出てヒーってなったんですが,あっさり解決できたんで方法を記したいと思います.

this version of Android Studio is incompatible with the Gradle Plugin used. Try disabling Instant Run
(Android Studioのこのバージョンは使われているGradle Pluginと互換性がないよ.Instant Runを無効にしてみてね)

解決方法

Android Studioのバージョンによって設定方法が微妙に異なると思いますが,私の使っているAndroid Studio2.1系のやり方です.

①Instant Runの設定変更

Android Studio>Preferences>Build, Execution, Deployment>Instant Runで

  • Enable Instant Run to hot swap code/resource change on deploy (default enabled)←Instant Runを有効にするか
  • Show Instant Run status notifications←Instant Rundに関する通知を表示するか(こちらはチェックつけてもつけなくても変わらない気がする)

のチェックを外す.

②Clean Project

Build>Clean Projectをする.

③いつも通りrun!

参考

stackoverflow.com

App ShortcutsのStatic Shortcutsを実装してみる

App Shortcutsとは

Android 7.1(SDK 25)で追加されたショートカットを作成する機能です.

アプリを長押しすると,ショートカットの機能が出てきます.

吹き出しをタップすることで指定したintentを起動できます.

f:id:A_01:20170401200738p:plain

図1: ショートカット(左上)

ショートカットは先ほどの吹き出しを長押しすることで,ホーム画面にピン留めできます.

f:id:A_01:20170401192347p:plain

図2: ピン留めされたショートカット(左上)

App Shortcutsには二種類あります.

  • Static Shortcuts:xmlで定義する静的なショートカット
  • Dynamic Shortcuts:Shortcut Managerを利用する動的なショートカット

です.

実装

今回はStatic Shortcutsを作ります.

①AndroidManifest.xmlを定義する

ルートActivityにmeta-dataを追加します.@xml/shortcutはショートカットを設定したxmlです.コードは②に載せています.

(略)
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
            <meta-data android:name="android.app.shortcuts"
                android:resource="@xml/shortcuts" />
        </activity>
        <activity android:name=".ComposeActivity" />
    </application>
(略)

②shortcuts.xmlを定義する

<?xml version="1.0" encoding="utf-8"?>
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
    <shortcut
        android:shortcutId="compose"
        android:enabled="true" <!-- falseにするとこのショートカットは無効化される -->
        android:icon="@drawable/icon"
        android:shortcutShortLabel="@string/compose_shortcut_short_label1" <!-- アプリ長押し時に出るラベル名 -->
        android:shortcutLongLabel="@string/compose_shortcut_long_label1" <!-- ピン留めしたショートカットのラベル名 -->
        android:shortcutDisabledMessage="@string/compose_disabled_message1"> <!-- 無効化されたショートカットをタップした時に出る文言 -->
        <intent
            android:action="android.intent.action.VIEW"
            android:targetPackage="com.example.a01.appshortcut"
            android:targetClass="com.example.a01.appshortcut.ComposeActivity" />
        <categories android:name="android.shortcut.conversation" />
    </shortcut>
</shortcuts>

参考

EventBusを使って簡単にCallbackを実現する

EventBusとは

あるClassでのイベントを検知して,別のClassで何かしたい時ありませんか?

Androidには簡単にそんなCallbackを実現する仕組みがあります.それがEventBusです.

例えば,ショッピングアプリの商品ページでお気に入りボタンが押された時,未ログインならログインページに飛ばし,ログイン完了後,再び商品ページに戻してお気に入り登録処理をしたい,なんて時に使えます(^^)

今回はgreenrobotのEventBusを使いますが,EventBusの提供元は一つではないです.

こちらにはsquere社のEventBusも紹介されています. qiita.com

実装

以下のgithubに載っていますが,EventBusは3stepで実現できちゃいます. github.com

今回は「ボタンがクリックされるとMessageEventというイベントが投げられ,MainActivityにToastを表示する」アプリを実装します.

イベントを投げるActivityも受けるActivityも同じなんで有難味がないかもしれませんが,簡略化のためそうします.

①イベント定義 (MessageEvent.java)

まず,ボタンがクリックされたことを通知するイベントClassを作ります.

MessageEvent.java

public class MessageEvent {
}

②イベントを監視する準備 (MainActivity.java)

次にイベントの監視を設定します.

通知を受け取りたいクラスのonStartにEventBus.getDefault().register(this);と書き

立つ鳥跡を濁さず,onStopではEventBus.getDefault().unregister(this);をして登録解除します.

③イベントを投げる (MainActivity.java)

最後に,イベントを発火したいタイミングでEventBus#getDefault#postします.

引数は発火したいイベントクラスです.

EventBus.getDefault().post(new MessageEvent());

MainActivity.java

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button button = (Button) findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                EventBus.getDefault().post(new MessageEvent());
            }
        });
    }

    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onMessageEvent(MessageEvent event) {
        Toast.makeText(this, "イベント発火", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onStart() {
        super.onStart();
        EventBus.getDefault().register(this);
    }

    @Override
    public void onStop() {
        super.onStop();
        EventBus.getDefault().unregister(this);
    }
}

build.gradleはこちら

compile 'org.greenrobot:eventbus:3.0.0'

簡単で便利なのでよかったら使ってみてくださーいノシ

Android N マルチウィンドウ非対応

Android O Developer Previewも出てきていますが,Android Nのマルチウィンドウを非対応にする方法を記したいと思います.

 

マルチウィンドウとは

Android 7.0 Nougatで提供されている機能です.

複数のアプリが画面を同時に共有できます。たとえば、ユーザーは画面を分割し、ウェブページを左側の画面に表示しながら、右側の画面でメールを作成することができます 

Multi-Window Support | Android Developersより

f:id:A_01:20170330000959p:plain

 

 非対応にする方法

デフォルト対応になっています.(しかし,正常動作しないことがある)

非対応にするときは

 

targetSdkVersionが24以上の場合

AndroidManifest.xmlのapplication要素,もしくはActiovity要素

android:resizeableActivity="false"と記載する.

ただし,Android Nのマルチウインドウ対応について調べて驚いたところ - Qiitaによると

他のアプリからstartActivityForResultで起動された場合,容赦無くマルチウィンドウの状態を引き継ぐので注意が必要である.

 

targetSdkVersionが24未満の場合

AndroidManifestのActivityの要素でいずれかを設定する.(ことで非対応になるらしい)

 

  • android:screenOrientation="portrait"などで画面の方向を固定する
  • android:immersive="true"で他のアクティビティや通知によって中断されないimmersiveタイプとする

D3.jsでヒートマップっぽいものを作ってみた

概要

地域データを扱うことが多いので,可視化の方法としてD3.jsを勉強しました. (可視化は手段なんで,RでもTablueauでも状況に合わせて使い分ければいいと思います)

D3.js(Data Driven Documents)は

ウェブブラウザで動的コンテンツを描画するJavaScriptライブラリ(Wikipediaより)

です.

dotinstallをざっと見とくと理解が早いと思います.

今回は以下の記事をベースに地図に「都道府県別 100万人あたりの常設映画館数」をプロットしてみました. qiita.com

完成図はこちら. f:id:A_01:20170219212803p:plain 情報源:政府統計の総合窓口 GL01010101 (東京が一番かと思いきや福岡が多いんですね.スクリーンの数とか,映画館の来場回数とか,DVDやテレビなど配給方法の割合とか色々気になりますが,今回は関係ないので触れません)

準備

ツール

# ShapeかGeoJSONへの変換に利用する
$ brew install gdal

# GeoJSONからTopoJSONへの変換に利用する
$ npm install -g topojson

# 最新版のV2系では,Qiitaで使用されているtopojsonというコマンドが4つのコマンドに分割されている.
# V2ではgeo2topoが該当するが,今回はV1を指定してインストールする
$ npm install -g topojson@1.6.27

# kokoじゃなくてもいいんだけど
# 今回,外部ファイルからデータを読み込むんで,直接htmlファイルを開くだけだと外部ファイルを読み込めなず
# サーバにファイルを置いてアクセスする必要がある.kokoはそのための手段.
$ brew install node
$ npm install -g koko

データ

Natural Earthから都道府県レベルの地図データをダウンロードします.Shapeファイルなんで,GeoJSON -> TopoJSONと変換して使います.

http://www.naturalearthdata.com/http//www.naturalearthdata.com/download/10m/cultural/ne_10m_admin_1_states_provinces.zip

# ne_10m_admin_1_states_provinces.shpというShapeファイルが入っているので,pref.jsonというGeoJSONに変換する
$ ogr2ogr -f GeoJSON -where "adm0_a3 = 'JPN'" pref.json ne_10m_admin_1_states_provinces.shp

# GeoJSONからjapan.jsonというTopoJSONに変換する
$ topojson -p name -p name_local -p latitude -p longitude -o japan.json pref.json

実装

こんな感じです. ちなみに2017/02/19時点でD3.jsの最新版はv4なんですが,アニメーションを設定しようとするとエラーになったのでhttps://d3js.org/d3.v3.min.jsを指定しています.

<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="utr-8">
        <title></title>
       <style type="text/css">
        .pref {
            fill: #fff;
            stroke: #aaa;
        }
        </style>
    </head>
    <body>
        <script src="https://d3js.org/d3.v3.min.js"></script>
        <script src="http://d3js.org/topojson.v0.min.js"></script>
        <script>
            var w = 1300;
            var h = 960;

            var svg = d3.select("body")
                .append("svg")
                .attr("width", w)
                .attr("height", h);

            // 地図の投影図法を設定する.
            var projection = d3.geo.mercator()
                .center([136, 35.5])
                .scale(2000)
                .translate([w / 2, h / 2]);

            // GeoJSONからpath要素を作る.
            var path = d3.geo.path()
                .projection(projection);

            var pref = {
                    "1":"北海道",
                    "2":"青森県",
                    "3":"岩手県",
                    "4":"宮城県",
                    "5":"秋田県",
                    "6":"山形県",
                    "7":"福島県",
                    "8":"茨城県",
                    "9":"栃木県",
                    "10":"群馬県",
                    "11":"埼玉県",
                    "12":"千葉県",
                    "13":"東京都",
                    "14":"神奈川県",
                    "15":"新潟県",
                    "16":"富山県",
                    "17":"石川県",
                    "18":"福井県",
                    "19":"山梨県",
                    "20":"長野県",
                    "21":"岐阜県",
                    "22":"静岡県",
                    "23":"愛知県",
                    "24":"三重県",
                    "25":"滋賀県",
                    "26":"京都府",
                    "27":"大阪府",
                    "28":"兵庫県",
                    "29":"奈良県",
                    "30":"和歌山県",
                    "31":"鳥取県",
                    "32":"島根県",
                    "33":"岡山県",
                    "34":"広島県",
                    "35":"山口県",
                    "36":"徳島県",
                    "37":"香川県",
                    "38":"愛媛県",
                    "39":"高知県",
                    "40":"福岡県",
                    "41":"佐賀県",
                    "42":"長崎県",
                    "43":"熊本県",
                    "44":"大分県",
                    "45":"宮崎県",
                    "46":"鹿児島県",
                    "47":"沖縄県"
                    };

            // TopoJSONを読み込む.
            d3.json("japan.json", function(error, japan) {
                console.log(japan);
                var topo = topojson.object(japan, japan.objects.pref).geometries;
                svg.selectAll(".pref")
                    .data(topo)
                    .enter()
                    .append("path")
                    // キーとバリューを逆にしてしまったので回避策.
                    .attr("class", function(d) {
                        var id =  Object.keys(pref).filter((key) => {
                            return pref[key] === d.properties.name_local
                            });
                        return "pref pref" + id;
                    })
                    .attr("d", path);
            });    
    
            var category = [{"name":"常設映画館数(人口100万人当たり)(館)",
                "data":{"1":10.4,
                    "2":13.6,
                    "3":14.0,
                    "4":6.0,
                    "5":13.5,
                    "6":9.7,
                    "7":7.2,
                    "8":10.3,
                    "9":8.1,
                    "10":10.6,
                    "11":4.1,
                    "12":5.6,
                    "13":21.4,
                    "14":6.5,
                    "15":5.6,
                    "16":4.7,
                    "17":6.9,
                    "18":16.5,
                    "19":5.9,
                    "20":13.3,
                    "21":6.4,
                    "22":10.8,
                    "23":7.5,
                    "24":13.7,
                    "25":9.2,
                    "26":7.7,
                    "27":6.6,
                    "28":13.7,
                    "29":3.6,
                    "30":8.2,
                    "31":24.4,
                    "32":5.7,
                    "33":5.2,
                    "34":20.1,
                    "35":13.5,
                    "36":6.5,
                    "37":18.3,
                    "38":15.1,
                    "39":13.6,
                    "40":36.7,
                    "41":6.0,
                    "42":20.2,
                    "43":31.2,
                    "44":12.0,
                    "45":12.6,
                    "46":7.8,
                    "47":9.1}}];
            
            var max = 40;
            var hoge = Math.floor(255 / 40);

            // 右上にあった常設映画館数というテキストをマウスオーバーすると
            // 映画館数によって都道府県の色が濃くなるよう施す.
            svg.selectAll("text")
                .data(category)
                .enter()
                .append("text")
                .attr("class", "category-label")
                .attr("x", w - 360)
                .attr("y", function(d, i) {
                    return i * 20 + 200;
                })
                .text(function(d) {
                    return d.name;
                })
                .on("mouseover", function(d) {
                    for (var key in d.data) {
                        var color = 255 - hoge * d.data[key];
                        if (color < 0) {
                            color = 0;
                        }
                        d3.select(".pref" + key)
                            .transition()
                            .style("fill", "rgb(" + color + ", " + color + " ,255)");
                    }
                    d3.select(this).style("fill", "#66f");
                })
                .on("mouseout", function(d) {
                    for (var i = 1; i < 48; i++) {
                        d3.select(".pref" + i)
                            .transition()
                            .style("fill", "#fff");
                    }
                d3.select(this).style("fill", "gray");
                });
        </script>
    </body>
</html>

動作確認

$ koko -o

参考URL

PHPで実践する動的計画法

※Qiitaに”動的計画法とメモ化”を説明する記事を書きましたので,こちらにも記載したいと思います.内容は全く同じです(>_<) qiita.com

動的計画法とメモ化

動的計画法(Dynamic Programming)とメモ化(Memorization)は,競技プログラミングなんかでよく使われるアルゴリズムの一緒です. 両者とも計算結果を保存し再利用することで,重複した計算による時間ロスを防ぐものです.

例を示しながら説明したいと思います.

動的計画法 or メモ化を使う動機

まず,フィボナッチ数列とは

n 番目のフィボナッチ数を Fn で表すと、Fn は再帰的に F0 = 0, F1 = 1, F(n + 2) = Fn + (Fn + 1) (n ≧ 0)

で定義される数列です.(Wikipediaより)

標準入力から入力される数字を n とし n 番目(0番目スタート)のフィボナッチ数を求めたい場合どう実装するでしょうか.

(色々な方法はあると思いますが)数学的に一般項を求めようとせず,定義をそのまま使うと,こんな感じの再帰的な処理が書けると思います.

<?php
fscanf(STDIN, "%d", $n);
echo fib($n) . "\n";
function fib($n) {
    if ($n == 0) return 0;
    if ($n == 1) return 1;
    return fib($n - 1) + fib($n - 2);
}

しかし,これだと尋常じゃない時間がかかります. n=4でもこれだけ再帰的にfib関数が呼ばれるんです↓

fib(4) = fib(3) + fib(2)
 = (fib(2) + fib(1)) + (fib(1) + fib(0))

この時間を短縮しくてれるのが動的計画法 or メモ化です! 使いたくなりませんか?w では,実際にコードへ適用していきます.

メモ化でフィボナッチ数を求める

ポイントは$memoListという配列に計算結果を保存しておき,余計な計算を減らすことです. n 番目から計算するトップダウンなイメージです. コードにするとこんな感じです.

<?php
fscanf(STDIN, "%d", $n);
echo fib($n) . "\n";

$memoList;

function fib($n) {
    global $memoList;
    if (isset($memoList[$n])) return $memoList[$n]; // 保存済みならその結果を使う
    if ($n == 0) return 0;
    if ($n == 1) return 1;
    $memoList[$n] = fib($n - 1) + fib($n - 2); // 新たに計算した結果を保存する
    return fib($n - 1) + fib($n - 2);
}

動的計画法でフィボナッチ数を求める

ポイントは$dpという配列で,計算結果を保存するという点ではメモ化と一緒です. ただ,こちらはメモ化とは逆向きに,0番目から計算するボトムアップなイメージです. だから,関数は一度呼ぶだけです.

<?php
fscanf(STDIN, "%d", $n);
echo fib($n) . "\n";

function fib($n) {
    $dp[0] = 0;
    $dp[1] = 1;

    for ($i = 2; $i <= $n; $i++) { // ひたすら計算し,結果を配列に保存していく
        $dp[$i] = $dp[$i - 1] + $dp[$i - 2];
    }

    return $dp[$n];
}

使い分け

• 計算の重複が起きる心配がないか、気にならないほど頻度が低い場 合は、素朴な再帰呼び出しで十分。

• 計算に必要な値がとびとびの場合は、メモ化が計算時間を節約でき て良い。

• 計算に必要な値がぎちぎちの場合は、動的計画法が計算時間を節約 できて良い。

「アルゴリズム」資料 18. 動的計画法とメモ化より) とのことです.

参考文献

動的計画法(京大 マイコンクラブ)

プログラミングコンテストでの動的計画法( Takuya Akibaさん)

いきなりRxAndroid

RxAndroidを使う機会があったので,調べたことをまとめました.
※理解が浅いので間違っている箇所はご指摘いただけると嬉しいです><

RxAndroidとは

Reactive Extentions Androidの略で,RxJavaを内包しAndroidで利用するための機能を追加してあります.

ソースコードはこちらです.

github.com

非同期処理が簡単に実装できます.
AsyncTaskに比べ実装が簡単で,ModelとViewで役割分担しやすいです.

使い方

Observavle:処理を実行しObserverに結果を伝える
Observer:Observavleから結果を受け取り処理する

build.gradle

compile 'io.reactivex:rxjava:1.1.5'
compile 'io.reactivex:rxandroid:1.2.0'

MainActivity.java

package himitsu;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;

import rx.Observable;
import rx.Observer;
import rx.Subscriber;
import rx.schedulers.Schedulers;
import rx.android.schedulers.AndroidSchedulers;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Observable
            .create(new Observable.OnSubscribe<Integer>() {
                @Override
                public void call(Subscriber<? super Integer> subscriber) {
                    // 重い処理
                    int i = 0;
                    Log.d("debug", "Observable:onNext1");
                    subscriber.onNext(i);
                    Log.d("debug", "Observable:onNext2");
                    Log.d("debug", "Observable:onCompleted1");
                    subscriber.onCompleted();
                    Log.d("debug", "Observable:onCompleted2");
                }
            })
            .subscribeOn(Schedulers.newThread()) // どのスレッドでObservableを走らせるか
            .observeOn(AndroidSchedulers.mainThread()) // どのスレッドでObserverを走らせるか
            .subscribe(new Observer<Integer>() { // Observable.subscribe(observer)すると処理が始まる
                @Override
                public void onCompleted() {
                    Log.d("debug", "Observer::onCompleted");
                }

                @Override
                public void onError(Throwable e) {
                    Log.d("debug", "Observer::onError");
                }

                @Override
                public void onNext(Integer i) {
                    Log.d("debug", "Observer::onNext");
                }
        });
    }
}

Android Monitor

debug: Observable:onNext1
debug: Observable:onNext2
debug: Observable:onCompleted1
debug: Observable:onCompleted2
debug: Observer::onNext
debug: Observer::onCompleted

参考

kirimin.hatenablog.com
使い方やメソッドを丁寧に解説されていて,初心者の私にも分かりやすかったです.

qiita.com
AsyncTaskとの比較が簡潔に記されていました.