tag:blogger.com,1999:blog-34408387842711008502024-03-05T23:47:28.761+09:00Out of DimensionJavaプログラミング、Androidプログラミング、サイエンス、その他何でも気の向くままに。Masamichi Yoshiihttp://www.blogger.com/profile/10172931203881042645noreply@blogger.comBlogger53125tag:blogger.com,1999:blog-3440838784271100850.post-21283137710348744002016-12-02T20:28:00.000+09:002016-12-02T22:50:41.748+09:00続・ howm + ripgrep のちょっとした問題前回 howm + ripgrep を使う上で発生する問題を考えました。<br />
<a href="http://extra-vision.blogspot.jp/2016/11/howm-ripgrep.html">http://extra-vision.blogspot.jp/2016/11/howm-ripgrep.html</a><br />
<br />
要約すると、ripgrep はマルチスレッドで並列検索を行なうため、howm-menu の「最近のメモ」の順番が狂ってしまう。-j1オプションを付けるととりあえずは回避できるが、シングルスレッド検索になるので、ripgrep の高速性を活かせない、というものでした。<br />
<br />
まあ致命的な問題でもないし、それ以外の検索では ripgrep は十分速いので、最初は無視していたのですが、しかしどうにも気持ち悪いので何とかしてみました。<br />
<br />
<a name='more'></a><br />
根本的な対策ではありませんが、howm-menu の時だけ、-j1 オプションを使うようにしてみます。<br />
<br />
その前に、howm-menu の動作について少し説明しておきます。今回この問題を調べるために howm の動きをトレースして色々分かりました。<br />
<br />
howm-menu は「最近のメモ」を検索するためにまず、タイムスタンプの新しい順にメモファイルN個を選び出します。ここでN個は howm-menu-recent-num に設定されている値です(デフォルトは20)。この選び出されたファイルを一気に grep に渡し、一回の grep 呼び出しで検索します。<br />
<br />
その後で「ランダムセレクト」のための検索を行ないますが、この時は一ファイルにつき一 grep 呼び出しで検索しています。<br />
<br />
なので、-j1 オプションで影響を受ける(つまりマルチスレッド性が活かせない)のは前半の「最近のメモ」の部分だけです。この位であればまあ許容範囲かなという気がします。<br />
<br />
本題を外れますが、後半のランダムセレクトの検索の方こそ ripgrep の並列性を使いたい所です。こちらは順番が狂おうが一行に構わないので。<br />
<br />
さて最終的に辿り付いたコードは以下の通りです。Emacs の advice 機能を使って、関数の実行前、実行後に変数 howm-view-grep-option を強制的に設定します。howm-menu 関数に設定するとメニュー更新時に有効にならないので、howm-menu-refresh 関数に設定しているところがポイントです。<br />
<br />
<pre><code>(setq howm-view-use-grep t)
(setq howm-view-grep-command "rg")
(setq howm-view-grep-option "-nH --no-heading --color never")
(setq howm-view-grep-extended-option nil)
(setq howm-view-grep-fixed-option "-F")
(setq howm-view-grep-expr-option nil)
(setq howm-view-grep-file-stdin-option nil)
;; howm-menu で -j1 オプションを使う
(defun howm-menu-with-j1 (orig-fun &rest args)
(setq howm-view-grep-option "-nH --no-heading -j1 --color never")
(apply orig-fun args)
(setq howm-view-grep-option "-nH --no-heading --color never"))
(advice-add 'howm-menu-refresh :around #'howm-menu-with-j1)
</code></pre>
<br />Masamichi Yoshiihttp://www.blogger.com/profile/10172931203881042645noreply@blogger.com0tag:blogger.com,1999:blog-3440838784271100850.post-5158595262143266552016-11-30T23:12:00.000+09:002016-12-02T01:02:25.500+09:00howm + ripgrep のちょっとした問題少し前に howm で ripgrep を使う記事を書きました。<br />
<a href="http://extra-vision.blogspot.jp/2016/10/ripgrep-howm.html">http://extra-vision.blogspot.jp/2016/10/ripgrep-howm.html</a><br />
<br />
しかし使い初めてすぐに気付いたのですが、howm-menu に表示されるメモの順番がおかしくなるのです。<br />
<br />
通常 howm-menu はメモファイルのタイムスタンプでソートし、最後に更新したメモ(の見出し)を一番上に表示してくれます。これがどうにもちゃんと順番が揃いません。更に howm-menu を表示する毎に、メモの順番が少しずつ変わます。<br />
<br />
最初は何が起こっているのかさっぱり分かりませんでした。そこで読めない elisp を苦労してトレースしてみると、howm 自身はファイルのソートを正しく行ない、そのファイルリストを ripgrep に引数として渡しています。しかし ripgrep の出力結果が引数の順番通りになっていないのです。<br />
<br />
<a name='more'></a><br />
そこでハタと気がつきました。ripgrep の高速化の理由は並列処理に依るところが大きい、ということを。そうです、ripgrep は多数のファイルを検索する時、ファイル毎にスレッドを立ち上げ、複数ファイルを同時検索しているのです。そして検索が終わったところから順に出力します。つまり引数のファイルの順番が出力では保たれていないのです。<br />
<br />
howm でメモを書いていると、メモファイルのサイズが小さいうちは最初の方に表示され、大きくなってくるとだんだん順番が下ってくる傾向がある事を何となく感じていまいしたが、これも説明がつきます。<br />
<br />
ということで調べてみると、世界にはやはり同じことに気付いていた人がいました。<br />
<a href="https://github.com/BurntSushi/ripgrep/issues/152">https://github.com/BurntSushi/ripgrep/issues/152</a><br />
<br />
これを読むと、とりあえず -j1 オプションを付けることで問題を回避することはできます。しかしこの -j オプションは単に起動するスレッド数を制限するだけなので、これでは並列処理による高速化の恩恵を受けられません。<br />
<br />
根本的には、ripgrep の作者に対策をしてもらわないと解決できない問題です。結果の取り出しにスレッドの同期が必要になります。マルチスレッドの練習問題としてちょっと面白そうな課題です。しかし上のリンクのやり取りを読むと、作者は今すぐには対応する予定はないようです。<br />
<br />
howm で利用するにはちょっと不便かも知れません。howm-menu で表示順が不定になるのはちょっと気持の悪いものです。しかし howm-menu 以外のワード検索であれば ripgrep の高速性は十分有用です。なので私はまだ howm + ripgrep を使っています。Masamichi Yoshiihttp://www.blogger.com/profile/10172931203881042645noreply@blogger.com0tag:blogger.com,1999:blog-3440838784271100850.post-1788809672181990132016-11-27T23:08:00.001+09:002016-11-30T23:23:54.839+09:00Kotlin の正規表現を使うKotlin を使い始めても色々な点で未だ Java 的なコーディングから抜けきれません。正規表現もそんな一つです。こんなことじゃいけないと思い、Kotlin の正規表現検索をちゃんと勉強すべく、色々情報をを探し回ったのですが、意外なことにまとまった情報が見つかりません。断片的な解説ページはいくつか有りますが、ちゃんと体系立って説明したものが見つからないのです。日本語、英語どちらにもです。<br />
<br />
なので試行錯誤的に使い込んで分かってきたことを整理しておきます。<br />
<br />
<h4>
<a name='more'></a><a href="https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.text/-regex/">Regexクラス</a></h4>
正規表現検索を行なうにはまず正規表現文字列を定義しなくてはなりません。この時トリプルクオート("""〜""")を使うと便利です。特殊文字をエスケープする必要がなくなります。例えば通常の文字列表現では <code>"\\d+"</code> と書くところを <code>"""\d+"""</code> と書けます。複雑な正規表現を書く場合のあの悪夢のような苦痛が少しは軽減できます。<br />
<br />
正規表現文字列が定義できたら、それを基に Regex オブジェクトを作成します。これには二通りの方法があります。Regex のコンストラクタを使う方法と String#toRegex() を使う方法です。<br />
<pre class="prettyprint"><code>val regex = Regex("""\d+""")</code></pre>
<pre class="prettyprint"><code>val regex = """\d+""".toRegex()</code></pre>
<br />
<h4>
Regex クラスの検索メソッド</h4>
Regex クラスには様々な正規表現検索メソッドがあります。それらを一つずつ説明していきます。<br />
<h5>
containsMatchIn() メソッド</h5>
containsMatchIn() は文字列を引数にとり、正規表現が部分一致するかどうかをチェックします。戻り値は Boolean です。文字列の中に正規表現に一致する部分が一つでもあると true を返し、無ければ false を返します。<br />
<br />
<pre class="prettyprint"><code>val regex = Regex("Kotlin")
regex.containsMatchIn("Hello Kotlin!") ===> true
val regex = Regex("^Kotlin")
regex.containsMatchIn("Hello Kotlin!") ===> false
</code></pre>
<br />
<h5>
matches() メソッド</h5>
matches() は全文一致チェックを行ない Boolean を返します。文字列全体が正規表現に一致すれば true、それ意外は false を返します。なので一致する部分以外に一文字でも余計な文字があると false になります。<br />
<br />
<pre class="prettyprint"><code>val regex = Regex("^Kotlin")
regex.matches("Kotlin") ===> true
regex.matches("Kotlin ") ===> false
</code></pre>
<br />
<h5>
find() メソッド</h5>
find() は部分一致検索を行ない、一致した文字列に対応する MatchedResult オブジェクトを返します。マッチが無い場合は null を返します。引数は文字列と何番目のマッチから検索を行なうかを示すインデックスを渡します。インデックスを省略した場合は先頭からの検索となります。検索開始した位置から最初に一致した文字列に対応する MatchedResult オブジェクトを返します。<br />
<h5>
matchEntire() メソッド</h5>
matchEntire() は全文一致検索を行ない、MatchedResult オブジェクトを返します。一致が無い場合は null を返します。<br />
<h5>
findAll() メソッド</h5>
findAll() は部分一致で検索で、一致した複数の文字列を Sequence<MatchedResult> オブジェクトの形で返します。一致が無かった場合は null を返します。引数は文字列と検索開始の位置のインデックスを渡し、インデックス省略時は先頭からになります。検索開始位置以降の全ての一致を返します。<br />
<br />
さて、ここまで Regex の検索メソッドを見てきましたが、contains, find, match と様々な動詞を使っていて,どれがどれだかすぐ分からなくなります。名前のセンスがあまりよくないような気がします。<br />
<br />
<h4>
<a href="https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.text/-match-result/index.html">MatchedResult クラス</a></h4>
検索一致した文字列から様々なデータを取り出す場合、上の各メソッドが返す MatchedResult オブジェクトを使うことになります。MatchedResult オブジェクトは一つの一致を表現し、その中にはグループ化された文字列や、繰り返し回数等様々な情報が含まれています。<br />
<br />
MatchedResult は以下のプロパティを持ちます。<br />
<ul>
<li>value: String</li>
<li>groupValue: List<String></li>
<li>range: IntRange</li>
<li>groups: MatchedGroupCollection</li>
<li>destructed: Destructed</li>
</ul>
<div>
<br /></div>
<h5>
value: String</h5>
value プロパティは一致した文字列全体を表わします。<br />
<br />
<pre class="prettyprint"><code>val userData = "123,Taro Yamada,1980-11-20"
val userRegex = """\d+,[a-zA-Z ]+,[0-9-]+""".toRegex()
val matchResult = userRegex.matchEntire(userData)
println(matchResult?.value)
123,Taro Yamada,1980-11-20</code></pre>
<br />
<h5>
groupValue: List<String></h5>
groupValue プロパティはグループ化された文字列のリストです。<br />
<br />
<pre class="prettyprint"><code>val userData = "123,Taro Yamada,1980-11-20"
val userRegex = """(\d+),([a-zA-Z ]+),([0-9-]+)""".toRegex()
val matchResult = userRegex.matchEntire(userData)
println(matchResult?.groupValues)
[123,Taro Yamada,1980-11-20, 123, Taro Yamada, 1980-11-20]
</code></pre>
<br />
正規表現をグループ化して検索し、得られた groupValue を表示してみました。カンマが被って見にくいのですが、最初の要素は "123,Taro Yamada,1980-11-20" です。これは一致文字列全体に相当します。以降、"123", "Taro Yamada", "1980-11-20" とグループ化された文字列要素が順に格納されています。<br />
<h5>
range: IntRange</h5>
<div>
range プロパティは正規表現の繰り返しが発生した位置を表わします。</div>
<br />
<pre class="prettyprint"><code>val regex = Regex("""\d+""")
val matchedResult = regex.find("12345")
println("range=" + matchedResult?.range)
range=0..4</code></pre>
<br />
検索文字列を少し変えてみると、<br />
<br />
<pre class="prettyprint"><code>val regex = Regex("""\d+""")
val matchedResult = regex.find(" 12345")
println("range=" + matchedResult?.range)
range=1..5</code></pre>
<br />
レンジが変わりました。<br />
<h5>
groups: MatchGroupCollection</h5>
group プロパティは MatchedGroupCollection 型のオブジェクトです。このクラスはグループ化された文字列とその繰り返し range を一つのクラスにしただけのものです。Pair クラスを使って表わしてもいいような気がしますが、わざわざ専用のクラスを定義したのですね。<br />
<h5>
destructured: Destructured</h5>
<div>
厄介なのがこの destructured プロパティです。多分目的は以下の様に、Destructuring Declarations (分解宣言)として使いたいのだと思います。</div>
<br />
<pre class="prettyprint"><code>val userData = "123,Taro Yamada,1980-11-20"
val userRegex = """(\d+),([a-zA-Z ]+),([0-9-]+)""".toRegex()
val matchResult = userRegex.matchEntire(userData)
val (id, name, birthday) = matchResult?.destructured
</code></pre>
<br />
しかしこの最後ステートメントが何故かエラーになってしまいます。もしこの部分のコードが正しければ、以下のコードに落とし込まれる筈です。<br />
<br />
<pre class="prettyprint"><code>val id = matchResult?.destructured?.component1()
val name = matchResult?.destructured?.component2()
val birthday = matchResult?.destructured?.component3()
</code></pre>
<br />
直接こう書くと完全に正しく動きます。ひょっとして Kotlin のバグかもしれません。<br />
<br />
この destructured プロパティの使い方が一つでも見つかればいいのですが、Google様を使っても世界中どこにも見つかりませんでした。誰も使っている人いないのかなあ?これ、ちゃんと動けばこれ結構便利だと思うのですが。<br />
<br />
<a href="https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.text/-regex/index.html">Regex クラス</a> にはまだ置換機能とか色々あるのですが、今回はここまでとします。Masamichi Yoshiihttp://www.blogger.com/profile/10172931203881042645noreply@blogger.com0tag:blogger.com,1999:blog-3440838784271100850.post-61371302282576402202016-11-25T09:22:00.000+09:002016-11-28T00:31:52.872+09:00Emacs のマニア度を判定する方法Emacs で C-t を使うかどうかで習熟度が分かると言われています。キーストロークを節約するために C-t を使っている人は相当なハードユーザーです。<br />
<br />
通常 C-t は transpose-chars というコマンドにバインドされていて、これは前後の二文字を入れ替えます。文字を入れ替えるタイプミスをしてしまった場合、C-t で修正する人は相当 Emacs を使い込んでいる人です。<br />
<br />
<a name='more'></a><br />
一連の transpose コマンドには以下のものがあります。<br />
<ul>
<li>C-t (transpose-chars)</li>
<li>M-t (transpose-words)</li>
<li>C-x C-t (transpose-lines)</li>
<li>M-x transpose-sexps</li>
<li>M-x transpose-sentences</li>
<li>M-x transpose-paragraphs</li>
</ul>
<br />
更に org-mode を使っていれば<br />
<ul>
<li>C-M-t (org-transpose-element)</li>
</ul>
というのもあります。これだけ element が複数形になっていません。共通化し忘れたのでしょう。<br />
<br />
コマンド名を見ればだいたい意味は分かると思われますが、いくつか注意点があります。<br />
<br />
Emacs ではカーソル位置の事を<b>ポイント</b>と呼びます。大抵の場合、一般的なカーソル位置と同じ意味に捉えて問題ありませんが、厳密に言うとポイントは文字の上ではなく、<b>文字と文字の間</b>にあると考えます。つまりカーソル位置の文字とその前の文字との間にポイントがあります。<br />
<br />
C-t (transpose-chars) は「ポイント位置の前後の文字を入れ替える」と定義されています。カーソル位置と考えると訳が分からなくなります。一般的な概念で言うと、カーソル位置とその前の文字を入れ替える、という意味になります。<br />
<br />
M-t (transpose-words) はワードを入れ替えるコマンドですが、注意が必要です。以下の例を見て下さい。<br />
<br />
<pre>one two three ===> one と two を入れ替え
^
one two three ===> two と three を入れ替え
^
</pre>
カーソル位置がワードの先頭にあるかそれ以外の所にあるかで動作が異なります。<br />
<br />
上の点さえ抑えておけば、それ以外はだいたい想像通りに動くと思います。<br />
<br />
transpose-sexps は s-式(Symbolic Expression)を入れ替えるコマンドです。s-式は Elisp のコードを書く人にはお馴染みですが、それ以外のプログラミング言語にも適用される概念です。<br />
<br />
transpose-sentences はセンテンス(文)を入れ替ええます。日本語の文章でもちゃんと機能します。(但し、句点で区切られたまともな日本語を書いていればですが。)<br />
<br />
上に紹介した最初の三つ以外はキーバインドが無いので、長いコマンド名を入力しなくてなりません。(helmを使えばキータイプ数はかなり節約できますが。) そのせいもあり、使っている人はほとんどいないでしょう。存在すら知らない人が多いと思います。人前でこれらのコマンドを使えば、驚かれること間違いなしでしょう。<br />
<div>
<br /></div>
また、これらのコマンドは日本語の文章にはなかなか馴染まない点もあります。特にワードの入れ替えは日本語で使われることはまず無いでしょう。しかし長い文章を何度も推敲したりする人は、センテンスやパラグラフの入れ替えは結構便利に使えるかもしれません。<br />
<br />
他のエディタの事はよく知りませんが、こんなコマンドが存在するのは Emacs ならではだと思います。これらのコマンドを使いこなして Emacs のマニア度を高めましょう。Masamichi Yoshiihttp://www.blogger.com/profile/10172931203881042645noreply@blogger.com0tag:blogger.com,1999:blog-3440838784271100850.post-47779974383166950762016-11-24T17:15:00.000+09:002016-11-25T22:58:38.273+09:00Android でテキストをブリンクさせる意外なことに Android にはテキストをブリンクさせる機能がありません。<br />
<br />
なのでアニメーションを使ってブリンクさせる例をよく見かけます。例えばこんな感じ。<br />
<br />
<pre class="prettyprint"><code> Animation animation = new AlphaAnimation(0.0f, 1.0f);
animation.setDuration(1000L);
animation.setRepeatMode(Animation.REVERSE);
animation.setRepeatCount(Animation.INFINITE);
View textView = findViewById(R.id.text_view);
textView.startAnimation(animation);
</code></pre>
<br />
Android 3.0 以降であれば ObjectAnimator を使って、以下でもよいでしょう。<br />
<br />
<pre class="prettyprint"><code> View textView = findViewById(R.id.text_view);
ObjectAnimator animator = ObjectAnimator.ofFloat(textView, "alpha", 0.0f, 1.0f);
animator.setDuration(1000L);
animator.setRepeatCount(ValueAnimator.INFINITE);
animator.setRepeatMode(ValueAnimator.REVERSE);
animator.start();
</code></pre>
<br />
しかしアニメーションを使う方法は値を連続的に変化させるのでCPUに負荷がかかり、ブリンクさせている間中ずっとこの負荷がかかり続けます。この点がどうも気になります。<br />
<br />
<a name='more'></a><br />
<br />
パソコン端末でサポートしているANSIエスケープシーケンスのブリンク機能は、多くの場合、単にオン/オフを切り替えているだけです。なのでこれと同じ仕組みを作ってみました。<br />
<br />
せっかく作るのであれば全てをカプセル化し、専用の BlinkTextView にしてみました。Handler の遅延実行機能を使って定期的にオン/オフを繰り返すだけです。これであれば負荷は最小限で済みます。<br />
<br />
<pre class="prettyprint"><code>public class BlinkTextView extends TextView {
private static final int MESSAGE_CODE = 100;
private final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg.what == MESSAGE_CODE) {
mVisible = !mVisible;
if (mVisible) {
setAlpha(0.0f);
} else {
setAlpha(1.0f);
}
if (mBlinking) {
sendEmptyMessageDelayed(MESSAGE_CODE, mInterval);
}
}
}
};
private boolean mBlinking = false;
private boolean mVisible;
private long mInterval = 500L;
public BlinkTextView(Context context) {
super(context);
}
public BlinkTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public BlinkTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public void startBlinking(long intervalMillis) {
// Do nothing if already blinking
if (mHandler.hasMessages(MESSAGE_CODE)) {
return;
}
mInterval = intervalMillis;
mBlinking = true;
mHandler.sendEmptyMessageDelayed(MESSAGE_CODE, mInterval);
}
public void stopBlinking() {
mBlinking = false;
mHandler.removeMessages(MESSAGE_CODE);
}
}
</code></pre>
<br />
ブリンクをスタート、ストップするには startBlinking(), stopBlinking() を呼び出します。二重起動しないようにとか、多少の工夫をしています。<br />
<br />
使い方は、例えば Activity からであれば以下の様に呼び出します。Fragment でもほぼ同じです。<br />
<br />
<pre class="prettyprint"><code>public class MainActivity extends AppCompatActivity {
private BlinkTextView mBlinkTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mBlinkTextView = (BlinkTextView) findViewById(R.id.blink_text);
}
@Override
protected void onResume() {
super.onResume();
mBlinkTextView.startBlinking(700L);
}
@Override
protected void onPause() {
super.onPause();
mBlinkTextView.stopBlinking();
}
}
</code></pre>
<br />
忘れがちなのが、View が非表示中にブリンクを止めることです。非表示中に虚しい作業を繰り返しても意味がありません。onPause() でブリンクを停止、onResume() で開始しています。この部分もできればカプセル化したいところですが、View の中からは表示/非表示のタイミングが分からないので、こればかりは外部から呼んでもらうしかありません。<br />
<br />
今回の例はアルファ値を 0 と 1.0 に切り替えて、View 全体を表示、非表示させていますが、例えば背景色だけをブリンクさせるとか色々な応用ができます。<br />
<br />
ソースコードはここに。<br />
<a href="https://github.com/masamichi441/AndroidBlinkText">https://github.com/masamichi441/AndroidBlinkText</a>Masamichi Yoshiihttp://www.blogger.com/profile/10172931203881042645noreply@blogger.com1tag:blogger.com,1999:blog-3440838784271100850.post-68799824477339421262016-11-18T23:37:00.000+09:002016-11-21T23:04:12.860+09:00Kotlin の let(), apply(), run(), with() を使いこなす<h4>
始めに</h4>
Kotlin の標準ライブラリの中に let(), apply(), run(), with() という一連の関数があります。これらは Standard.kt という数十行の短かいソースコードの中で、それぞれ一行で定義されています。<br />
<br />
<pre class="prettyprint"><code>public inline fun <T, R> T.let(block: (T) -> R): R = block(this)
public inline fun <T, R> with(receiver: T, block: T.() -> R): R = receiver.block()
public inline fun <T> T.apply(block: T.() -> Unit): T { block(); return this }
public inline fun <R> run(block: () -> R): R = block()
</code></pre>
<br />
これを一目見て理解できる人は相当な強者です。初心者にとっては宇宙語を読んでいるようで、まず理解できないでしょう。<br />
<br />
これらの関数はその定義のシンプルさとは裏腹に強力なパワーを秘めています。これらが使いこなせるようになると、Kotlin の表現力がぐっと増します。<br />
<br />
どの関数も引数に関数オブジェクトをとり、あるクラスインスタンスに対してその関数オブジェクトを適用する、という基本機能を持っています。しかしそれぞれ実装が違うため、使い方が微妙に異なります。<br />
<br />
<a name='more'></a><br />
<h4>
レシーバと拡張関数</h4>
各関数の説明に入る前に基礎知識としてレシーバ機能と拡張関数を説明しておきます。<br />
<br />
Kotlin にはレシーバと呼ばれる機能があります。レシーバは、他のクラスのメンバを、あたかも自分のクラスのメンバであるかのように、this 識別子を使ってアクセスできるようにする機能です。このレシーバ機能を巧みに使ったのが拡張関数です。<br />
<br />
拡張関数を使うとクラスを継承せずに、メソッドやプロパティを拡張することができます。例えば次の例を考えます。<br />
<br />
<pre class="prettyprint"><code>fun String.lastChar(): Char {
return this.get(this.length - 1)
}
</code></pre>
<br />
これは省略すれば以下のように書けます。<br />
<br />
<pre class="prettyprint"><code>fun String.lastChar() = this.get(this.length - 1)
</code></pre>
<br />
更に省略するとこうにも書けます。<br />
<br />
<pre class="prettyprint"><code>fun String.lastChar() = this[length - 1]
</code></pre>
<br />
もう何がなんだか。<br />
<br />
とにかく、これは String に lastChar() メソッドを追加(拡張)している例です。これをトップレベルで宣言しておくと、 "abcdefg".lastChar() みたいに、どんな String からでもこの拡張メソッドを呼ぶことができるようになります。<br />
<br />
Java の枠組みの中でどうやって実現しているのかというと、仕組みは簡単で、元のクラスに本当にメソッドを追加するのではなく、別のクラスを作成してその中に static なメソッドを作成します。そして元クラスからのドット表記でそのメソッドを呼び出せるようにしているだけです。<br />
<br />
拡張関数の中では this キーワードで呼び出し元のクラスメンバ(上の例では String のメンバ)にアクセスできるため、あたかも元のクラスのメンバであるかのように記述できます。ここでレシーバ機能が使われています。<br />
<br />
<h4>
let()</h4>
let() はどんなオブジェクトからも呼び出せる拡張関数です。なので任意のオブジェクトから<br />
<br />
<pre class="prettyprint"><code>foo.let( ... )</code></pre>
<br />
といった風に呼び出すことができます。<br />
<br />
let() の引数は関数オブジェクト一つです。通常はラムダ式で記述し、引数の最後のラムダ式は括弧の外に出すことができるルールがあるので、以下の様に記述します。<br />
<br />
<pre class="prettyprint"><code>foo.let {
...
}
</code></pre>
<br />
let() の戻り値はラムダ式の最後に実行されたコードになります。<br />
<br />
<h5>
let()によるスコープ限定</h5>
さて、let() を使ったイディオム一つに変数のスコープを限定する、というものがあります。以下の例を見てください。<br />
<br />
<pre class="prettyprint"><code>File("foo").let {
it.mkDirs() // Fileインスタンスは it で参照できる
println(it.name)
...
}</code></pre>
<br />
Fileインスタンスを作成して、そこから let() を呼び出しています。ラムダ式の中からは、it を使って呼び出し元のインスタンス(この場合は File インスタンス)を参照することができます。it は、ラムダ式の省略時のデフォルト引数名なので、気に入らなければ明示的に他の名前を使うこともできます。<br />
<br />
<pre class="prettyprint"><code>File("foo.txt").let { file ->
file.mkDirs() // Fileインスタンスは file で参照できる
println(file.name)
...
}</code></pre>
<br />
ここで重要なのは File インスタンスへのアクセスがラムダ式の中に限定され、外部からは一切参照できないことです。つまりスコープがラムダ式の内部に限定されているのです。また一時的なワーク変数などもこのラムダ式の中で定義して使えば、他と衝突する心配がありません。<br />
<br />
<h5>
let()による null-safe アクセス</h5>
let() のもう一つのイディオムとして、nullable な変数を安全に参照する方法があります。<br />
<br />
<pre class="prettyprint"><code>val file: File? = ...
file?.let {
it.mkDirs()
println(it.name)
....
}</code></pre>
<br />
<br />
.? 演算子(safe call operator) を使って let() を呼び出します。こうすると変数 file が null でない場合のみ let() が実行されるので、ラムダ式の中では it が null でないことが保証されます。<br />
<br />
これは if (file != null) { ... } の代替え手段として使えます。しかし可読性の点で優れているかというと、ちょっと微妙な気がします。また、let() の方法は else 節が記述できないので、null で何らかの処理が必要な場合は使えません。<br />
<br />
<h4>
apply()</h4>
<div>
apply() も任意のオブジェクトから呼び出せる拡張関数として機能します。使い方も let() とほぼ同じですが、呼び出し元オブジェクトはレシーバとして参照されるので(it ではなく) this を使います。更に this は省略可能なのでより記述が簡潔になります。</div>
<div>
<br /></div>
<div>
上の null-safe の例を apply() を使って書くと以下の様になります。<br />
<br />
<pre class="prettyprint"><code>val file: File? = ...
file?.apply {
mkdirs()
println(name)
...
}
</code></pre>
<br />
(let() と違い)何の識別子も無しに呼び出し元オブジェクトのメンバにアクセスしています。実際は、省略された this を通して、拡張関数のレシーバオブジェクトにアクセスしていることに注意して下さい。</div>
<br />
apply() 関数の戻り値はレシーバオブジェクト本体、つまり呼び出したインスタンスそのものになります。これも let() との違いです。<br />
<br />
<h4>
run()</h4>
run() は普通の関数として直に呼び出すものと、let(), apply() の様に、任意のオブジェクトの拡張関数として呼び出すものの二種類が定義されています。<br />
<br />
まず普通の関数として実行するものから。これは非常に簡単です。単純に関数オブジェクトを実行する方法はいくつかありますが、その一つと考えるとよいでしょう。<br />
<br />
<pre class="prettyprint"><code>val sum1 = { 1 + 2 }()
val sum2 = { 1 + 2 }.invoke()
val sum3 = run { 1 + 2 }
</code></pre>
<br />
こうして眺めると、記述方法として run() を使うのが一番自然な感じがします。しかしこれ意味があるのかというと、よく分かりません。単に val sum = 1 + 2 と書くのと変わりませんから。まあ、ラムダ式を使って書くから、こういう変なことになるのであって、変数で渡された関数オブジェクトを実行するような場合は意味があるのかも知れません。<br />
<br />
拡張関数としての run() は apply() と似ています。呼び出し元オブジェクトは、レシーバとして this で参照されます(そして省略可能です)。唯一の違いは、戻り値がレシーバ本体ではなく、ラムダ式の最後の実行コードになる事です。(これは let() と同じ)<br />
<br />
null-safe の例を run() で書くと以下の様になります。<br />
<br />
<pre class="prettyprint"><code>val file: File? = ...
file?.run {
mkdirs()
println(name)
...
}
</code></pre>
<br />
見た目は関数名が違うだけで、apply() と全く同じです。<br />
<br />
<h4>
with()</h4>
他の三つと違い with() は拡張関数ではありません。第一引数に操作対象のクラスインスタンス、第二引数に関数オブジェクトを取る普通の関数です。<br />
<br />
操作対象(第一引数)のクラスはレシーバとして登録されるので、ラムダ式の中からは this でアクセス可能です。つまり省略して直接メンバにアクセスできます。<br />
<br />
width() の戻り値はラムダ式の最後の実行コードとなります。<br />
<br />
null-safe の例を with() で書くと以下の様になります。<br />
<br />
<pre class="prettyprint"><code>val file: File? = ...
with(file!!) {
mkdirs()
println(name)
...
}
</code></pre>
<br />
ただし、これは他と同じ意味での null-safe ではありません。!! 演算子なので、null だった場合には例外が発生します。<br />
<br />
<h4>
整理</h4>
<div>
さて頭が混乱してきたと思うので、それぞれの特徴を整理しておきます。<br />
<br />
<div class="table-responsive">
<table class="table table-bordered table-striped">
<colgroup>
<col class="col-xs-2"></col>
<col class="col-xs-2"></col>
<col class="col-xs-2"></col>
<col class="col-xs-2"></col>
</colgroup>
<tbody>
<tr>
<td></td>
<td>形態</td>
<td>オブジェクト参照</td>
<td>戻り値</td>
</tr>
<tr>
<td>let()</td>
<td>拡張関数</td>
<td>it</td>
<td>最後の実行コード</td>
</tr>
<tr>
<td>apply()</td>
<td>拡張関数</td>
<td>this</td>
<td>呼び出し元オブジェクト</td>
</tr>
<tr>
<td>run()</td>
<td>拡張関数/通常関数</td>
<td>this</td>
<td>最後の実行コード</td>
</tr>
<tr>
<td>with()</td>
<td>通常関数</td>
<td>this</td>
<td>最後の実行コード</td>
</tr>
</tbody>
</table>
</div>
<br />
<h4>
実行例</h4>
Android の Paint オブジェクトをそれぞれの関数を使って初期化する例を以下に示します。<br />
<br /></div>
<pre class="prettyprint"><code> val paintByLet = Paint()
val paintByApply = Paint()
val paintByRun = Paint()
val paintByWidth = Paint()
init {
paintByLet.let {
it.color = Color.GREEN
it.style = Paint.Style.STROKE
it.isAntiAlias = true
it.textSize = 30.0f
}
paintByApply.apply {
color = Color.MAGENTA
style = Paint.Style.STROKE
isAntiAlias = true
textSize = 50.0f
}
paintByRun.run {
color = Color.CYAN
style = Paint.Style.STROKE
isAntiAlias = true
textSize = 60.0f
}
with(paintByWidth) {
color = Color.RED
style = Paint.Style.STROKE
isAntiAlias = true
textSize = 40.0f
}
}
</code></pre>
<div>
<br />
もう一つ例を示します。例えば以下のような Java の関数があったとします。<br />
<br />
<pre class="prettyprint"><code> @Nullable
String findUserName(int userId) {
String userName;
User user = findUser(userId);
if (user != null) {
userName = user.getUserName();
} else {
userName = null;
}
return userName;
}
</code></pre>
<br />
これを let() や run() を使うと、それぞれ以下の様に一行で書くことができます。<br />
<br />
<pre class="prettyprint"><code>fun findUserNameByLet(userId: Int) : String? = findUser(userId)?.let { it.userName }
fun findUserNameByRun(userId: Int) : String? = findUser(userId)?.run { userName }
</code></pre>
<br />
確かに短かくはなりますが、読み易さの点ではどうかな?という気がします。Java 頭から完全に抜けきれていないと、ちょっと抵抗があります。しかしこういうのに慣れないとKotlinプログラマーとして一流でないのかも知れません。<br />
<br />
<h4>
まとめ</h4>
let(), apply(), run(), with() は Kotlin の標準ライブラリ関数でありながら、公式ドキュメントでもまり詳しく説明されていません。目的はほぼ同じですが、微妙に異なる実装してみたら色々なものが出来てしまった、といったような印象を受けます。使い分けがよく分からなくて、皆さん戸惑っているようです。<br />
<div>
<br /></div>
これらを使いこなせるとKotlinの楽しさがぐっと増します。しかし状況に応じて使い分ける必要がある程違いがあるとは思えません。どれか一つお気に入りのものを常に使って、他は全く使わないというので良いかと思います。<br />
<br />
それにしても冒頭で示したように、これらの関数が全てワンライナーだということは驚きです。こういったコードを自由に「読める」ではなく「書ける」ようになりたいものです。</div>
<!-- Latest compiled and minified CSS -->
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css" rel="stylesheet"></link>
<!-- Optional theme -->
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap-theme.min.css" rel="stylesheet"></link>
Masamichi Yoshiihttp://www.blogger.com/profile/10172931203881042645noreply@blogger.com0tag:blogger.com,1999:blog-3440838784271100850.post-30843600368152662302016-10-31T02:39:00.000+09:002016-11-28T15:39:56.590+09:00Emacs 25.1 の isearch のちょっとした改良点Emacs 25.1 もだんだん手に馴染んできました。24からあまり大きな変化は見られないというのが正直な感想ですが、細かいこところで地味な改良があるのを時々発見します。<br />
<br />
isearch にもちょっとした改良が行なわれていました。以下の設定を init.el に追加します。<br />
<br />
<pre><code>(setq search-default-mode #'char-fold-to-regexp)</code></pre>
<br />
こうしておくと、例えば isearch (C-s) で "(ダブルクォーテーション文字)を検索した場合、«» や ❝❞ 等の、非ASCIIで何となく同じ仲間の文字もヒットするようになります。<br />
<br />
<a name='more'></a><br /><br />
もう一つ例を挙げると、ASCII 文字の a を検索した場合、ã, á, ⓐ, 𝒶 といったような、非ASCII の対応する文字もヒットするようになります。<br />
<br />
ウムラウトやダイアクリティカル文字を普通に使っている人達にとっては、キーボードから直接入力できないような文字も簡単に isearch できるので便利かもしれません。<br />
<br />
isearch だけでなく、query-replace コマンドでもこの機能を使う場合は以下を追加します。<br />
<br />
<pre><code>(setq replace-char-fold t)</code></pre>
<br />
日本語環境ではあまり有難味は無いと思います。まあ日本語には Migemo があるので、必要ないか?Masamichi Yoshiihttp://www.blogger.com/profile/10172931203881042645noreply@blogger.com0tag:blogger.com,1999:blog-3440838784271100850.post-59237201712872637862016-10-13T22:04:00.000+09:002016-10-31T02:39:30.915+09:00Emacs25 の package-selected-packages を何とかするEmacs25 になって、インストールされたパッケージが package-selected-package 変数に保持されるようになりました。これはパッケージを削除する時などに依存性に矛盾が発生しないようにするために使われるようです。<br />
<div>
<br /></div>
<div>
パッケージを保持するだけならばいいのですが、問題は、M-x list-packages などを実行した時に、この値が init.el (まはた .emacs)の末尾に勝手に書き込まれてしまうことです。<br />
<div>
<br />
<a name='more'></a></div>
</div>
<div>
init.el を常にクリーンに保っている身からするとこれは堪まりません。同じように思っている人が多いようで、この問題に対する対策が <a href="https://www.reddit.com/r/emacs/comments/53zpv9/how_do_i_get_emacs_to_stop_adding_custom_fields/?st=iu8biz0j&sh=ca350a4d">reddit Emacs</a> で議論されています。</div>
<div>
<br />
変数 custom-file を設定するとカスタム内容が書き込まれるファイルを指定することができるので、これを利用して、custom.el に書き込むようにします。そして起動時にこのファイルが存在すれば、それを読み込むことにします。<br />
<br />
コードは以下になります。これを init.el に追加します。init.el に既に追加されてしまった変数は削除しておきましょう。<br />
<br />
<pre><code>(setq custom-file (expand-file-name "custom.el" user-emacs-directory))
(when (file-exists-p custom-file)
(load custom-file))
</code></pre>
<br />
<br /></div>
Masamichi Yoshiihttp://www.blogger.com/profile/10172931203881042645noreply@blogger.com0tag:blogger.com,1999:blog-3440838784271100850.post-84963847576580328832016-10-13T00:04:00.000+09:002016-10-13T08:09:53.313+09:00超新星とは何か<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj6C6EWYRLtzu_cKRQMDkKUwyexHXPD0MgSL6DiEBzM2_DMHwlrfAL6CHOtFWJcZsFrvfprJ4RFyKYFahfSxAeSdEOUK57JTL2XOileXazmrcK2-dweBq12Neq6gKxjTXCYugaJgD6NkHXf/s1600/NGC6751.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj6C6EWYRLtzu_cKRQMDkKUwyexHXPD0MgSL6DiEBzM2_DMHwlrfAL6CHOtFWJcZsFrvfprJ4RFyKYFahfSxAeSdEOUK57JTL2XOileXazmrcK2-dweBq12Neq6gKxjTXCYugaJgD6NkHXf/s320/NGC6751.jpg" width="267" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">NGC6751<br />
NASA/The Hubble Herritage Team(STScI/AURA)</td></tr>
</tbody></table>
<br />
<h4>
誤解されている用語</h4>
作家の星新一氏は、よく人から「星新一って逆から読むと一新星となり、いい名前ですね」と言われ、その都度、新星ってのは星が死んでいく時の最後の姿なんだけどなとボヤいている、とエッセイに書いておられました。因みに「星新一」はペンネームではなく、星氏の本名です。<br />
<br />
最近でもよく「野球界に新星誕生」みたいな使い方をされます。そう言えば「超新星」なんていう音楽グループもいましたっけ。新星は英語で言うと Nova です(超新星は Supernova)。これもよく使われる名前です。死ぬゆく星の名前だということ認識してるのでしょうか。<br />
<br />
新星/超新星は最も誤解されている天文用語です。<br />
<br />
<a name='more'></a><br />
<h4>
超新星爆発</h4>
新星(超新星)は星が燃料を燃やし尽くした後に起こる最後の大爆発です。超新星爆発とも呼ばれます。普段観測すらできないような遠方の星が突然夜空に光輝くため、新しい星が生まれたかの如く見えることから「新星」と呼ばれます。通常数週間から数ヶ月かけてゆっくり暗くなっていきます。<br />
<br />
超新星爆発は宇宙の中でも最も激しい現象です。恒星が一生かけて放出するようなエネルギーを一瞬で放出してしまいます。どれだけ凄まじいかというと、例えば半径50光年くらいの距離にある星の生命は壊滅的な打撃を受けるとすら言われています。こんなのが太陽系の近くで起こったらそれこそシャレになりません。<br />
<br />
超新星爆発は単一の星の現象でありながら、他の銀河で発生しても観測可能なものが多くあります。稀にですが肉眼で見えることすらあります。逆に銀河系の中で発生したものでも、星間物質や天体に遮られて観測できないものも多くありますが。<br />
<br />
観測技術の向上により、ここ十年くらいは驚くべきことに毎年数千個もの超新星が発見されています。これらは全て他所の銀河で発生したものです。我々の銀河系の中でも100年に2〜3個の超新星爆発が発生していると考えられていますが、1604年にヨハネス・ケプラーが観測したものを最後に、銀河系の中の超新星は観測されていません。<br />
<br />
有名なところで超新星1987Aというのを憶えている人がいるかもしれません。1987年に発見された最初の超新星ということで1987Aという名前が付いています。当時、カミオカンデでこの超新星爆発で発生したニュートリノを観測し、小柴先生のノーベル賞に繋がりました。これは銀河系に寄り沿うように存在する大マゼラン雲の中で発生したもので、ギリギリ銀河系の外側です。<br />
<br />
<h4>
超新星爆発が発生する原理</h4>
昔からこの超新星爆発に非常に興味を引かれてきました。そもそも燃料を燃やし尽くした星が何でこんな大爆発を起こすのかということに疑問を持っていました。いろいろな科学解説書を読んでもこの点に関して詳しく説明しているものはあまりありません。少しずつこの超新星爆発のことが分かってくると、だんだんその理由が分かってきました。星の最後は膨張や収縮を繰り返し、あまりにも複雑な現象なので簡単には説明できないのです。細かい所は端折ってごく簡単に説明すると、以下の様になります。<br />
<br />
太陽のような恒星が一定の大きさを保っていられるのは、重力で収縮しようとする力と核融合反応で燃料が燃えることによって発生する内圧が釣り合っているからです。燃料が燃え尽きてこの内圧が無くなると、星は重力で潰れてしまいます。この収縮現象(重力崩壊)は徐々に起こるのではなく、ある一線を越えた時、瞬間的に発生します。太陽の質量を越えるような物質が一瞬にして半径10Km程度の空間に収縮してしまいます。収縮すると物質間に反発力が働き、自由落下で恐ろしいほどの運動エネルギーを得た物質は反動で外側に向かって激しく弾き飛ばされます。弾き飛ばされる物質の速度は光速の10%にも達します。<br />
<br />
これが超新星爆発の基本的な原理です。それにしても収縮する力で爆発が発生し、更にそれが宇宙最大規模の爆発になるというのは、どうにもイメージが捉みにくいものです。<br />
<br />
超新星爆発で星を構成していた物質の大部分は飛散してしまいますが、中心に近い部分はより強く圧縮され、中性子星やブラックホールが残ります。飛び散った物質が明るく光輝き、星雲が残ることもあります。<br />
<br />
上で毎年数千個もの超新星が発見されていると述べましたが、死にゆく星の中で超新星爆発を起こすのはごく一部に過ぎません。更の我々に観測できるのはその中のごく一部です。それでもこれだけの数の超新星が発見されているのは、宇宙全体を考えると、超新星爆発というのはごくありふれた現象であるとも言えます。<br />
<br />Masamichi Yoshiihttp://www.blogger.com/profile/10172931203881042645noreply@blogger.com0tag:blogger.com,1999:blog-3440838784271100850.post-3107649935066431572016-10-12T17:40:00.000+09:002016-12-02T20:36:56.210+09:00次世代の高速検索ツール ripgrep を howm で使う<h4>
高速検索ツール ripgrep</h4>
以前、<a href="http://extra-vision.blogspot.jp/2016/02/emacs-howm-ag-silver-searcher.html">howm で ag (The Silver Searcher) を使う</a> という記事を書きました。しかし ag を使っていても、howm のメモが数千を越えるとそれなりに検索に時間がかかるものです。それに ag はバグが多いという問題もあります。<br />
<br />
そんな時、<a href="http://emacs.rubikitch.com/ripgrep/">るびきちさんのブログ</a> 見て ripgrep なるツールがあることを知りました。<br />
<br />
初めて ag の検索を見た時、その速度に驚いたものですが、ripgrep は更にその数倍の速度を叩き出しています。ag でもうこれ以上劇的なパフォーマンスの向上はないだろうと思い込んでいましたが、まだこれだけ改善の余地があった事には驚きです。実際に使ってみても、おっ!と思うくらい速いのが実感できます。<br />
<br />
<div>
<a name='more'></a></div>
ripgrep の高速化の一番の理由は Rust の正規表現ライブラリによるもののようです。Rust は安全性、速度、並列性に特化したプログラミング言語です。<br />
<br />
Linux, Mac, Windows 用のバイナリイメージ及びソースコードが以下のサイトでリリースされています。Windows 用はMinGW版とMSVC版があり、それぞれに32bit、64bit版が用意されています。<br />
<a href="https://github.com/BurntSushi/ripgrep/releases">https://github.com/BurntSushi/ripgrep/releases</a><br />
<br />
また <a href="http://blog.burntsushi.net/ripgrep/">作者のサイト</a> でベンチマークや高速化のためのテクニックが紹介されてます。<br />
<br />
<h4>
howm で使ってみる</h4>
さて、この ripgrep を、Emacs のメモ書きツール howm で使ってみようと思いました。ripgrep には豊富なオプションがあり、grep との互換性も高いので、ag の時のような苦労はありません。常にファイル名を表示するという -H オプションも用意されています。(ag の時、一番苦労した点です。)<br />
<br />
実際やってみるとほとんど苦労なく動かすことができました。既に howm が動いている環境があれば、以下の設定を追加するだけで ripgrep で検索できます。<br />
<br />
<pre><code>(setq howm-view-use-grep t)
(setq howm-view-grep-command "rg")
(setq howm-view-grep-option "-nH --no-heading --color never")
(setq howm-view-grep-extended-option nil)
(setq howm-view-grep-fixed-option "-F")
(setq howm-view-grep-expr-option nil)
(setq howm-view-grep-file-stdin-option nil)
</code></pre>
<br />
rg (rg.exe) は予め PATH の通ったディレクトリに配置するか、howm-view-grep-command にフルパス名を設定してください。<br />
<br />
以下の環境で動作確認しました。<br />
<ul>
<li>Windows10 Pro 64bit</li>
<li>NTEmacs 25.1 64bit (IMEパッチなし)</li>
<li>howm 1.4.4-snapshot</li>
<li>ripgrep 0.2.3 64bit (MinGW版、MSVC版両方で確認)</li>
</ul>
<br />
使ってみると実に快適です。howm は Emacs の中でも最も grep を酷使するパッケージです。なのでこれ、絶対に使ってみるべきです。<br />
<br />
<h4>
次世代の検索ツール</h4>
この ripgep、リリースされたばかりとういうこともあり、まだあまり知られていません。最初のリリースが2016年9月、つい最近です。その後怒涛のようにアップデートが繰り返されています。この記事を書いている最中にも新しいバージョン(0.2.3)がリリースされました。<br />
<br />
Emacs インタフェースもまだ ripgrep.el という、とりあえず検索ができるというものが、Melpa にある程度です。そのうち helm インタフェースなども出てくることでしょう。<br />
<br />
こんなのが出てくると、もはや ack や ag は出番が無くなるような気がします。ripgrep は次の世代の検索ツールとしてこれからどんどん活躍することでしょう。Masamichi Yoshiihttp://www.blogger.com/profile/10172931203881042645noreply@blogger.com1tag:blogger.com,1999:blog-3440838784271100850.post-68014473871956336282016-10-07T23:19:00.000+09:002016-10-13T23:33:11.105+09:00Emacs 25.1 で起動時に警告が<h4>
Emacs 25.1 を使ってみた</h4>
Emacs 25.1 がリリースされたので、早速使ってみました。24.5 の時の設定は、何も変更せずにほぼそのまま使えました。<br />
<br />
一点だけ問題があり、起動時に以下の警告が出るようになってしまいました。<br />
<br />
<pre class="prettyprint"><code>Warning (bytecomp): (lambda (arg) ...) quoted with ' rather than with #'
</code></pre>
<br />
警告なので内容からしても無視してして構わないと思いますが、起動する度にこれが出るのは精神衛生上よくありません。<br />
<br />
lambda 関数のクォートに #' ではなくて、' が使われていることに対して警告を出しているようです。<br />
<br />
<a name='more'></a><br />
<h4>
変数、関数のクォート</h4>
elisp のクォートに関して少しだけ説明をしておきます。elisp では変数や関数を参照する時に式を評価させないようにする場合クォートを使います。<br />
<br />
変数をクォートするには quote 関数を使います。例えば変数 foo をクォートするには (quote foo) としますが、これは省略形が使え、通常は 'foo と書きます。<br />
<br />
また関数をクォートする場合は fuction 関数を使います。例えば my-func という関数をクォートするには (function my-func) と書きますが、これも省略形で #'my-func と書くことができます。<br />
<br />
実は quote と function は全く同じ実装で、実行時には何の違いもありません。なので関数をクォートするのに 'my-func と書いても何の問題もありません。実際多くのコードでそのように書いています。<br />
<br />
ではこれらの違いは何なのでしょう?それは lambda 関数(匿名関数)をバイトコンパイルする場合に違いが出ます。<br />
<br />
'(lambda ...) と書いた場合、バイトコンパイラはその中身が単なるリストなのか関数なのか判断できません。関数であれば更にその中身もバイトコンパイルした方が高速になります。(コンパイルしなくても実行は可能です。) なので、明示的に #'(lambda ...) と書くことにより、バイトコンパイラにこれは関数だと伝えることができます。<br />
<br />
更に lambda 関数の場合、#'(lambda ...) の #' を省略することができ、単に (lambda ...) と書いてもまったく同じことになります。つまり結局、lambda 関数はクォートする必要が無いのです。あー、ややこし。<br />
<br />
<h4>
問題点を調査</h4>
さて、件の Emacs 25.1 で警告が出る問題の原因を調査したところ、警告が出るのは usage-memo.el パッケージの umemo-initialize() 関数の中でした。<br />
<pre class="prettyprint"><code>(defun umemo-initialize ()
"A bunch of `define-usage-memo' definitions. Feel free to redefine!"
(define-usage-memo ri "ruby" 0 "ri `%s'")
(define-usage-memo lh-refe "ruby" 0 "refe \"%s\"")
;; slime-describe-symbol, slime-describe-function, and slime-documentation
;; calls slime-show-description.
(define-usage-memo slime-show-description "cl" 0 "*SLIME Description*" umemo-make-entry-name:slime)
(define-usage-memo describe-function "elisp" 0 "*Help*")
(define-usage-memo describe-variable "elisp" 0 "*Help*")
(define-usage-memo describe-mode "elisp" 0 "*Help*"
(lambda (arg) major-mode)))
</code></pre>
<br />
この中の一番最後の (define-usage-memo describe-mode ...) の部分で警告が出ます。これだけ見ると、一見 lambda 式は問題無いように見えます。<br />
<br />
しかしよく調べてみると、define-usage-memo は関数ではなく、マクロであることに気が付きました。なのでマクロ展開したコードに問題がある筈です。define-usage-memo マクロの中を見ると以下のコードが見つかります。<br />
<pre class="prettyprint"><code> (let ((converter (or name-converter-function 'identity)))
`(defadvice ,command (around usage-memo activate)
ad-do-it
(let* ((buffer-fmt ,buffer-fmt)
(entry-name (funcall ',converter (format "%s" (ad-get-arg ,nth-arg))))
(buf (with-no-warnings (format ,buffer-fmt entry-name))))
(umemo-setup ,category entry-name buf)))))
</code></pre>
<br />
上の lambda 関数は、このコードの中で converter という変数に代入され、最終的に以下のコードで実行されます。<br />
<pre class="prettyprint"><code> (funcall ',converter (format "%s" (ad-get-arg ,nth-arg)))</code></pre>
<br />
ありました、シングルクォートによる関数のクォート。これを以下の様に変更します。<br />
<pre class="prettyprint"><code> (funcall #',converter (format "%s" (ad-get-arg ,nth-arg)))</code></pre>
<br />
これで警告が出なくなりました。<br />
<br />
Emacs 25 になって文法チェックがより厳密になったようです。今のところこれ以外で問題は出ていません。なのでこのまま 25.1 を使っていこうと思います。お疲れ様でした 24.5。Masamichi Yoshiihttp://www.blogger.com/profile/10172931203881042645noreply@blogger.com0tag:blogger.com,1999:blog-3440838784271100850.post-82881626469988327432016-09-30T01:13:00.000+09:002016-09-30T01:13:00.126+09:00Android AppCompatActivity で openOptionsMenu() が機能しないAppCompatActivity を使っていて openOptionsMenu() が機能しないことに気づきました。普通の Activity であれば以下のコードでメオプションニューを起動できます。<br />
<br />
<pre class="prettyprint"><code> View button = findViewById(R.id.menu_button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
openOptionsMenu();
}
});
</code></pre>
<div>
<code><br /></code></div>
これが AppCompatActivity では機能しません。Google はどうもアクションバー以外からオプションメニューを起動させたくないようです。アクションバーを使用しない NoActionBar という選択肢もあるのに、それでも openOptionsMenu() は機能しません。<br />
<br />
Activity や Fragment 上に配置したボタンからオプションメニューを起動したい場合もある筈です。なのでその方法を探してみました。<br />
<a name='more'></a>色々試してみました。結局メニューボタンをエミュレートする方法で何とかすることができました。他にも方法はあるかも知れません。<br />
<br />
<pre class="prettyprint"><code> View button = findViewById(R.id.menu_button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Runnable runnable = new Runnable() {
@Override
public void run() {
// Menu ボタンのエミュレーション
Instrumentation instrumentation = new Instrumentation();
instrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_MENU);
}
};
Thread thread = new Thread(runnable);
thread.start();
}
});
</code></pre>
<br />
Instrumentation クラスからキーコードを送信します。GUI をテストするユニットテストで使われているクラスです。この処理は UI スレッドで実行すると例外が発生するので、スレッドを起動します。<br />
<br />
とにかくこれで一応オプションメニューを起動できるようになりました。アクションバーから起動したメニューは上から出てきますが、ボタンエミュレーションでは下から出てきます。<br />
<br />
<table cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: left; margin-right: 1em; text-align: left;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiUGE5QYKFPO_pGHRps3WwREaCbeu3w_jylCXA0cZgtj-TKUMt5x8cX_CO9MhoFGjDnI8WylLlf4yJ_zcmixAnCpep12Cvo_7tbUL8Cu_YcvcI-CUYbFrz3rT7avpfSNIJlhXaTFBDOJqy-/s1600/device-2016-09-29-220339.png" imageanchor="1" style="clear: left; margin-bottom: 1em; margin-left: auto; margin-right: auto;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiUGE5QYKFPO_pGHRps3WwREaCbeu3w_jylCXA0cZgtj-TKUMt5x8cX_CO9MhoFGjDnI8WylLlf4yJ_zcmixAnCpep12Cvo_7tbUL8Cu_YcvcI-CUYbFrz3rT7avpfSNIJlhXaTFBDOJqy-/s320/device-2016-09-29-220339.png" width="180" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">アクションバーで起動</td></tr>
</tbody></table>
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi6PWqMQm_Mjn8cdTL0AIYUjPIQGTc3tn9v1XYJOoqjPee_LMr7V2QFhnI59xyByN8CE6Yi2L9o6mkv1IQH-OQ9GctuRj-hNeZGQmWVOJyYl8GlE3Nrd9dNPEKOcMKvuotclvA7bGqsAioY/s1600/device-2016-09-29-220454.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi6PWqMQm_Mjn8cdTL0AIYUjPIQGTc3tn9v1XYJOoqjPee_LMr7V2QFhnI59xyByN8CE6Yi2L9o6mkv1IQH-OQ9GctuRj-hNeZGQmWVOJyYl8GlE3Nrd9dNPEKOcMKvuotclvA7bGqsAioY/s320/device-2016-09-29-220454.png" width="180" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">ボタンエミュレーションで起動</td></tr>
</tbody></table>
<br />
<br />
しかし一つだけ問題があります。ボタンエミュレーションで起動したメニューにテーマが適用されないのです。上の例で見ても分かる通り、Light 系のテーマを使っているにも係わらず、ダーク系のメニューが表示されています。<br />
<br />
これは原因を調べるのがちょっと厄介でした。結論から言うと、AppCompat 系の多くのテーマで actionBarPopupTheme が null に設定されているのが原因でした。これを適切に設定してやれば何とかなりそうです。例えば Light 系であれば以下のように、アプリケーションのテーマに一行追加します。<br />
<br />
<pre class="prettyprint"><code><style name="AppTheme" parent="Theme.AppCompat.Light">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="actionBarPopupTheme">@style/ThemeOverlay.AppCompat.Light</item>
</style>
</code></pre>
<br />
ダーク系であれば以下の一行を追加します。<br />
<br />
<pre class="prettyprint"><code> <item name="actionBarPopupTheme">@style/ThemeOverlay.AppCompat</item>
</code></pre>
<br />
これでボタンエミュレーションからのメニューでもテーマに沿った色になりました。<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhrnBL6y-ypYYP9G1hgwwnUdOsyd7k8_vzjO6fmUKv6uoL_HUusNjPKk2GAtx2m4oasT2Mm87EkXMGyAm_Ujc2L9vqA0xY7O0h3qWBgMqa33xCvYBI0is7Y9oHrDfUL0mLWtOIJd4qANBos/s1600/device-2016-09-29-222113.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhrnBL6y-ypYYP9G1hgwwnUdOsyd7k8_vzjO6fmUKv6uoL_HUusNjPKk2GAtx2m4oasT2Mm87EkXMGyAm_Ujc2L9vqA0xY7O0h3qWBgMqa33xCvYBI0is7Y9oHrDfUL0mLWtOIJd4qANBos/s320/device-2016-09-29-222113.png" width="180" /></a></div>
<br />
<br />
<br />Masamichi Yoshiihttp://www.blogger.com/profile/10172931203881042645noreply@blogger.com0tag:blogger.com,1999:blog-3440838784271100850.post-35895875511250626532016-09-22T23:23:00.000+09:002016-09-30T12:34:25.729+09:00Android アプリの後方互換性と前方互換性<h4>
Android アプリの互換性</h4>
<div>
Android アプリには後方互換性(backward compatibility)と前方互換性(forward compatibility)が保証されています。<br />
<br />
これらの互換性は build.gradle の中の以下のパラメータで制御されます。<br />
<br />
<ul>
<li>compileSdkVersion</li>
<li>minSdkVersion</li>
<li>targetSdkVersion</li>
</ul>
<br />
これらは使用可能な API レベルや互換性をコントロールする重要なパラメータですが、意外にも正確なところがあまり理解されていないようです。 なのでこれらのパラメータを整理してみました。</div>
<div>
<br />
<a name='more'></a><br /></div>
<h4>
compileSdkVersion</h4>
<div>
compileSdkVersion は Gradle に対し、どの SDK を使うかを指示します。ビルド時のみ参照されるパラメータです。<br />
<br />
SDK のアップデートで追加された新しい API を使いたい場合、compileSdkVersion を更新します。<br />
<br />
意外と知られていない様ですが、compileSdkVersion を変更してもアプリの動作は一切変化しません。これは重要な点です。<br />
<br />
但し SDK のアップデートによりエラーチェックが厳密になり、今まで何も無かったところに警告やエラーが出たり、非推奨になった API の使用に警告が出ることはあります。当然これらは何らかの対策をすべきです。<br />
<br />
compileSdkVersion は名前からして、変更するとアプリの動作が変わってしまうのではないかと、アップデートすることに以前は強い抵抗感がありましたが、その心配はありません。基本的には常に最新 SDK バージョンを指すようにすることが推奨されています。<br />
<br />
また、サポートライブラリを使う場合は、compileSdkVersion とメジャーバージョンが同じライブラリを使う必要があります。そうしないと以下のような警告が出ます。<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEghuGgBUzbhgcb47ixkix1F9WKRiL2aCIFquNddQpzK4OE_p9n1rK_kyEi-zMsMq9VGNJeoiL-zAIsAZyHKAQ6AGl0DYrzGRGi9w_N3peECzsrLxRRqVIiRv4TqKXl-b-Bx5QsrfWrHegyD/s1600/support-library-version-err.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="46" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEghuGgBUzbhgcb47ixkix1F9WKRiL2aCIFquNddQpzK4OE_p9n1rK_kyEi-zMsMq9VGNJeoiL-zAIsAZyHKAQ6AGl0DYrzGRGi9w_N3peECzsrLxRRqVIiRv4TqKXl-b-Bx5QsrfWrHegyD/s400/support-library-version-err.png" width="400" /></a></div>
<br /></div>
<h4>
minSdkVersion</h4>
<div>
minSdkVersion は後方互換性(backword compatibility)の限界値を示すパラメータです。警告等を適切に処理したアプリであれば、この値以上の Android バージョンのデバイスで正しく実行することができます。<br />
<br />
minSdkVersion は Google Play Store に対して、どのデバイスでインストール可能にするかを伝える役割も果たします。<br />
<br />
また開発時に、Lint もこのパラメータを使用します。minSdkVersion で指定したデバイスに存在しない API を使おうとした場合エラー出してくれます。これにより、実行時に存在しない API を呼び出してクラッシュしてしまうことを回避できます。<br />
<br />
注意点は、サポートライブラリやその他のライブラリを使う場合、それぞれのライブラリが独自に minSdkVersion を持っていることがあります。なので、アプリで指定するバージョンは、依存するライブラリの minSdkVersion と同じかそれ以上でなくてはなりません。<br />
<br />
どうしてもそれより低い minSdkVersion を使いたい場合は AndroidManifest.xml の中で tools:overrideLibrary アトリビュートを使えばできないことはありません。その場合、Java コードの中でバージョンによって処理を切り替える実装が必要になるでしょう。<br />
<br /></div>
<h4>
targetSdkVersion</h4>
<div>
一番分かりにくいのが targetSdkVersion です。どうも、動作が保証される Android バージョンの上限値と思い込んでいる人が多いようですが、これは間違いです。<br />
<br />
上で compileSdkVersion は変更しても動作が変わらないと述べました。それに対し、この targetSdkVersion は、変更するとアプリの動作が変わる可能性があります。例えば、targetSdkVersion を23以上にすると、<a href="http://android-developers.blogspot.jp/2015/08/building-better-apps-with-runtime.html?utm_campaign=adp_series_sdkversion_010616&utm_source=medium&utm_medium=blog">runtime permission model</a> が有効になり、Android 6.0 以上で実行した場合に実行時パーミッションの処理が必要になります。<br />
<br />
しかし逆に言うと、targetSdkVersion を変えない限りアプリの動作は変化しないので、compilseSdkVersion だけをアップデートすることにより、アプリに影響せずに新しい API を使用できます。<br />
<br />
また、targetSdkVersion よりも高い Android バージョンのデバイスで実行しても、動作の互換性が保証されます。これが前方互換性(forward compatibility)です。<br />
<br />
targetSdkVersion は原則的には自分の目的に合ったものを選択する必要があります。そのためにはどのバージョンがどんな特徴を持つかを把握しなくてはなりません。基本情報は <a href="https://developer.android.com/reference/android/os/Build.VERSION_CODES.html?utm_campaign=adp_series_sdkversion_010616&utm_source=medium&utm_medium=blog">ここ</a> にありますが、こんなもの見てもさっぱり分からないでしょう。なので例えば Android 6.0 であれば、<a href="https://developer.android.com/about/versions/marshmallow/android-6.0-changes.html?utm_campaign=adp_series_sdkversion_010616&utm_source=medium&utm_medium=blog">ここ</a> を見たりして詳細を知る必要がります。しかし実際には、とりあえずはその時点での最新の API レベルを指すようにして、問題があれば調整する、というのが現実的ではないでしょうか。<br />
<br />
<a href="https://developer.android.com/guide/topics/manifest/uses-sdk-element.html">公式ドキュメント</a> でも、Android のバージョンアップに合わせてアプリを成長させるには、targetSdkVersion も最新の API レベルに合わせて更新することを推奨しています。そのためには、当然ながら常にターゲットバージョンで慎重に、かつ十分にテストする必要があります。<br />
<br />
サポートライブラリを使う場合、サポートライブラリのバージョンは targetSdkVersion 以上である必要があります。<br />
<br />
<h4>
各SDKバージョンの関係</h4>
compileSdkVersion はコンパイル時のみに参照されるパラメータです。それに対し、minSdkVersion と targetSdkVersion は apk の中に埋め込まれます。<br />
<br />
build.gradle の記述方法を見てみましょう。<br />
<br />
<pre class="prettyprint"><code>android {
compileSdkVersion 23
buildToolsVersion "23.0.1"
defaultConfig {
applicationId "com.android.example.testapp"
minSdkVersion 9
targetSdkVersion 23
versionCode 1
versionName "1.0"
}
}
</code></pre>
<br />
compileSdkVersion は、buildToolsVersion と共に、 android { ... } の直下に記述します。それに対し、minSdkVersion と targetSdkVersion は defaultConfig { ... } の中に記述します。defaultConfig は build variant の一種ですが、全ての build variant のベースとなるものです。<br />
<br />
三つのパラメータの関係は、<br />
<br />
<div style="text-align: center;">
minSdkVersion <= targetSdkVersion <= compileSdkVersion</div>
<br />
となります。<br />
<br />
理想的には、minSdkVersion は可能な限り低いバージョンを指定し、compileSdkVersion と targetSdkVersion はどちらもその時点での SDK の最新を設定するのがよいでしょう。<br />
<br />
<br />
参照ページ<br />
<a href="https://medium.com/google-developers/picking-your-compilesdkversion-minsdkversion-targetsdkversion-a098a0341ebd#.9igya6j4r">https://medium.com/google-developers/picking-your-compilesdkversion-minsdkversion-targetsdkversion-a098a0341ebd#.9igya6j4r</a><br />
<a href="https://developer.android.com/guide/topics/manifest/uses-sdk-element.html">https://developer.android.com/guide/topics/manifest/uses-sdk-element.html</a></div>
Masamichi Yoshiihttp://www.blogger.com/profile/10172931203881042645noreply@blogger.com0tag:blogger.com,1999:blog-3440838784271100850.post-50022149013848302122016-09-21T20:03:00.002+09:002016-09-29T09:44:40.284+09:00Windows10 Anniversary Update で Genymotion が起動せず自動更新で Windows10 Anniversary Update (Version 1607)がなかなか降ってこないので、手動で更新チェックを行なったらあっさりとダウンロードが開始されました。<br />
<br />
更新してみて、少し触ってみた限りでは、ここ何年も迷走しているスタートメニューが少し変わったくらいで、どこが使い易くなったのかさっぱり分かりません。Edge や Cortana がアップデートされたようですが、最初から使っていないし、事前のアナウンスの割にはどうでもいい更新でした。<br />
<br />
まあそれはそれとして、困ったことに Genymotion がまったく起動しなくなってしまいました。起動しないだけならまだいいのですが、Windows そのものがクラッシュしてしまいます。久し振りに見ました、ブルースクリーン。今こんなんなってんのね。<br />
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhd2GicdEjMW9Ns5KaiCmX3xFyXSr8ovyEwT3obY6bISVXqPTuOsYHKjjmNefReI_EoAaRiMFn4oM_ujnNFqUl4hNnb5TVFgwq0V7mQRGWCsCOUXa0QPmKjGmDop4qKT65FQ2kigOShFEHK/s1600/DSC_0886.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="180" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhd2GicdEjMW9Ns5KaiCmX3xFyXSr8ovyEwT3obY6bISVXqPTuOsYHKjjmNefReI_EoAaRiMFn4oM_ujnNFqUl4hNnb5TVFgwq0V7mQRGWCsCOUXa0QPmKjGmDop4qKT65FQ2kigOShFEHK/s320/DSC_0886.JPG" width="320" /></a></div>
<br />
何か情報を収集いていますが、こんなもので物事が解決した事はありません。<br />
<br />
<a name='more'></a><br />
ここまでヒドいと、多分 Genymotion というよりも、VirtualBox 絡みではないかと思います。そこで試しに Google の Android エミュレータ(AVD)は起動するか試してみました。すると Hyper-V が有効になっているので無効にするようにと警告が出ます。<br />
<br />
なるほどそういうことか。ということで Hyper-V を無効化しました。やり方は、コントロールパネルから「プログラムと機能」を開き、左ペインの「Windows の機能の有効化または無効化」を開きます。Hyper-V にしっかりチェックマークが付いていたので、全てチェックを外します。OK ボタンを押して Windows を再起動します。<br />
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiRxrUGnWMVP5wO32aqmYSD56BIOejLYkgpqMNwmwN5nP6IPGPTrIHnF6k1Cmc1M60rcq8KxaiwvJL55G589IZByKy4qvvPSlDJFPl_Ffb5fDs7TfJl08gxRQB8q6bRyzOQ022vu1T12RFd/s1600/hyper-v.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="286" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiRxrUGnWMVP5wO32aqmYSD56BIOejLYkgpqMNwmwN5nP6IPGPTrIHnF6k1Cmc1M60rcq8KxaiwvJL55G589IZByKy4qvvPSlDJFPl_Ffb5fDs7TfJl08gxRQB8q6bRyzOQ022vu1T12RFd/s320/hyper-v.png" width="320" /></a></div>
<br />
これで Genymotion も AVD も起動できるようになりました。<br />
<br />
それにしても Windows って何でこういう余計なことをするのでしょう?人が苦労して調整した設定内容を、アップデートの度にいともあっさりと全クリアしてくれたり。何をどうアップデートしても構いませんが、人に迷惑だけはかけないで欲しいものです。Masamichi Yoshiihttp://www.blogger.com/profile/10172931203881042645noreply@blogger.com0tag:blogger.com,1999:blog-3440838784271100850.post-30354889555984138142016-09-10T21:48:00.000+09:002016-09-29T23:54:26.828+09:00Android AppCompat の闇 - カスタムスタイルの落とし穴<h4>
テーマ、スタイル、アトリビュート...</h4>
Android のテーマとスタイルは謎に包まれています。Android の闇と言っていいかも知れません。<br />
<br />
スタイルは個々の View に適用するもので、スタイルを集めたものがテーマです。テーマは Application や Activity に適用するものです。ここまでの考え方は非常にシンプルです。<br />
しかしソースコードの中に入るとシンプルとは程遠いものです。まずややこしいのが、このテーマとスタイル、XML 的にはどちらも同じ <style> タグで定義していること。そしてシステムの themes.xml や styles.xml、attrs.xml あたりをトレースしたことがある人なら分かると思いますが、これらのファイル、定義が定義を呼びあって、肥大化し、依存関係がスパゲッティの様に絡まりあっています。悪いデザインの見本みたいなものです。まあよくこれで秩序を保っていられるなあと逆に感心してしまいます。<br />
<br />
ここに AppCompat 系のテーマが入ってくると更に話はややこしくなります。この AppCompat が引き起こす問題について説明したいと思います。<br />
<a name='more'></a>例としてスタイルとテーマを使って Button をカスタマイズする方法を考えてみます。画面上にある全ての Button の文字を赤くする必要があるとします。<br />
<br />
<h4>
テーマに直接アトリビュートを設定する</h4>
最初の試みとして、まず Application に設定してあるメインのテーマに直接アトリビュートを設定してみます。<br />
<pre class="prettyprint"><code> <style name="AppTheme" parent="Theme.AppCompat.NoActionBar">
<item name="android:textColor">@color/red</item>
</style>
</code></pre>
color リソースは予め設定してあるものとします。これで一応ボタン文字を赤くすることはできます。しかしこの方法のダメなところは、Button 以外の例えば TextView 等の文字も全て赤くしてしまうことです。なのでもう少し気の効いた方法を考えてみます。<br />
<br />
<h4>
カスタムスタイルを作る</h4>
そこで次にボタン専用のカスタムスタイルを作ることを考えます。以下の様なカスタムスタイルを定義します。<br />
<pre class="prettyprint"><code> <style name="MyButtonStyle" parent="Widget.AppCompat.Button">
<item name="android:textColor">@color/red</item>
</style>
</code></pre>
スタイル名は任意の名前で構いませんが、parent= で指定する親スタイルには注意が必要です。使用するテーマと View の種類によって適切なものを選択しなくてはなりません。これがまた厄介です。ドキュメントのどこにも書かれていません。正確に知るには Java のソースコードと XML 定義を見て探し出す必要があります。慣れてくると代表的な View であればだいたい把握できるようになります。例えば Theme.Holo を使った TextView では Widget.Holo.TextView みたいに。<br />
<br />
作成したカスタムスタイルをテーマに適用します。<br />
<pre class="prettyprint"><code> <style name="AppTheme" parent="Theme.AppCompat.NoActionBar">
<item name="android:buttonStyle">@style/MyButtonStyle</item>
</style>
</code></pre>
ここでまた android:buttonStyle という訳の分からないものが出てきました。これはデフォルトでボタンに適用されるスタイルのアトリビュート id (リソース id)です。これもソースコードを見ないと分からないパラメータです。Button のコンストラクタのコードを見ると以下のようなっています。<br />
<pre class="prettyprint"><code> public Button(Context context, AttributeSet attrs) {
this(context, attrs, com.android.internal.R.attr.buttonStyle);
}
</code></pre>
スタイルを指定しなかった場合、デフォルトで com.android.internal.R.attr.buttonStyle が適用されるようになっています。これはアプリケーションの Java コードドからは android.R.attr.buttonStyle として参照できるものです。これが XML 的には android:buttonStyle となります。<br />
<br />
テーマとスタイルのコードをまとめると以下の様になります。<br />
<pre class="prettyprint"><code> <style name="AppTheme" parent="Theme.AppCompat.NoActionBar">
<item name="android:buttonStyle">@style/MyButtonStyle</item>
</style>
<style name="MyButtonStyle" parent="Widget.AppCompat.Button">
<item name="android:textColor">@color/red</item>
</style>
</code></pre>
これでボタンだけを赤くすることが出来ました。しかしこのコード、一つだけ間違いがあります。どこだか分かりますか?<br />
実はこのコード Android5 以降でないと機能しません。Button と TextView を並べただけのレイアウトでこのスタイルを使い、Android4.x と Android5.x で実行してみると以下の様になります。<br />
<br />
<table cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: left; margin-right: 1em; text-align: left;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi-Uk3CgfwZsodBC3GThWYoagjVnv1FpZAh4_6P4i5MapnDqldGmyEiPZtzPIBbFmcTa4mdXYzvzKrSE1L6Bgqnb9hF6oZQOAEZ7O2GD27u8Ef_LMWG4jx-mKZC0HBIhI9HiVgK9ymQAb9n/s1600/Screenshot_20160910-021720.png" imageanchor="1" style="clear: left; margin-bottom: 1em; margin-left: auto; margin-right: auto;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi-Uk3CgfwZsodBC3GThWYoagjVnv1FpZAh4_6P4i5MapnDqldGmyEiPZtzPIBbFmcTa4mdXYzvzKrSE1L6Bgqnb9hF6oZQOAEZ7O2GD27u8Ef_LMWG4jx-mKZC0HBIhI9HiVgK9ymQAb9n/s320/Screenshot_20160910-021720.png" width="180" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Android 4.3</td></tr>
</tbody></table>
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgup1uaqAJBX7Bq2yNzSAETLyA8rUgL7Mz_SHA2NMDLWBZB2-R2rBusI-FrbOJ0VpSEoXLo3L38Sma3w2y4LbwUppUzQvlpeK2ZnDrP3KFG5RmY9xSAugrdUQG1f-EWOcwUWshHD5sp28LN/s1600/Screenshot_20160910-021833.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgup1uaqAJBX7Bq2yNzSAETLyA8rUgL7Mz_SHA2NMDLWBZB2-R2rBusI-FrbOJ0VpSEoXLo3L38Sma3w2y4LbwUppUzQvlpeK2ZnDrP3KFG5RmY9xSAugrdUQG1f-EWOcwUWshHD5sp28LN/s320/Screenshot_20160910-021833.png" width="180" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Android 5.1</td></tr>
</tbody></table>
<br />
Android4.x ではボタン文字が赤くなっていません。ではどうすればいいのでしょう?<br />
答えを言ってしまうと、テーマを以下の様に変更する必要があります。<br />
<pre class="prettyprint"><code> <style name="AppTheme" parent="Theme.AppCompat.NoActionBar">
<item name="buttonStyle">@style/MyButtonStyle</item>
</style>
</code></pre>
name= で指定するアトリビュートを android:buttonStyle から buttonStyle に変更しました。こうすると全てのバージョンでカスタムスタイルが有効になります。<br />
<br />
ここでは例として Button を使いましたが、AppCompat 系テーマを使った場合、どの View でもこの問題は発生します。AppCompat では android: プレフィックス無しのアトリビュートを使うようにしましょう。<br />
<br />
<h4>
android: プレフィックスの例</h4>
ではこの android: プレフィックスが付く、付かないの違いは何なのでしょう?答えを出す前にもう一つ android: プレフィックスの例を揚げておきます。<br />
<br />
Application や Activity に適用するテーマを作る時、例えば Holo 系や Material 系のテーマの場合、正式には以下の様に書きます。<br />
<pre class="prettyprint"><code> <style name="AppTheme" parent="@android:style/Theme.Holo">
....
</style>
</code></pre>
<pre class="prettyprint"><code> <style name="AppTheme" parent="@android:style/Theme.Material">
....
</style>
</code></pre>
これらは省略形が使え、一般には以下の様に記述します。<br />
<pre class="prettyprint"><code> <style name="AppTheme" parent="android:Theme.Holo">
....
</style>
</code></pre>
<pre class="prettyprint"><code> <style name="AppTheme" parent="android:Theme.Material">
....
</style>
</code></pre>
<br />
これに対し AppCompat 系のテーマを使う場合は以下の様に書きます。<br />
<pre class="prettyprint"><code> <style name="AppTheme" parent="Theme.AppCompat">
....
</style>
</code></pre>
<br />
ここでも android: プレフィックスが付く、付かない違いがあります。<br />
<br />
<h4>
android: プレフィックスの意味</h4>
<div>
プレフィックスの意味を理解するには、Holo や Material 等の Android 標準テーマと、AppCompat 系テーマの違いを理解しなくてはなりません。<br />
<br /></div>
<div>
Holo や Material 等の標準テーマの場合、それらを定義するリソースは Android デバイスにファームウェアの形で組込まれています。それに対し AppCompat 等のテーマはライブラリにより提供されており、各アプリがそれぞれローカルにリソースを保持しています。</div>
<div>
<br /></div>
<div>
この違いが分かると android: プレフィックスの意味も理解できると思います。デバイス組込みのリソースを参照する場合は android: プレフィックスを付け、アプリのローカルリソース参照には付けない、ということです。</div>
<div>
<br /></div>
<div>
上で挙げた例は見ると分かる通り Theme.AppCompat 系のデザインを使っているので、android: プレフィックスを付けないローカルリソースの buttonStyle アトリビュートを指定するのが正解です。<br />
<br />
更に説明すると、AppCompt 系テーマを使った場合、レイアウト XML の中で Button を指定しても、実際に使われるのは AppCompatButton という Button を継承した別クラスなのです。これについては別の記事に詳しく書きました。<br />
<a href="http://extra-vision.blogspot.jp/2016/09/android-appcompat.html">http://extra-vision.blogspot.jp/2016/09/android-appcompat.html</a><br />
<br />
この AppCompatButton のソースコードを見ると、コンストラクタは以下の様になっています。<br />
<pre class="prettyprint"><code> public AppCompatButton(Context context, AttributeSet attrs) {
this(context, attrs, R.attr.buttonStyle);
}
</code></pre>
デフォルトで適用されているスタイルは R.attr.buttonStyle になっています(android.R でない点に注意)。つまりアプリケーションのローカルリソースを参照しています。このことからも android: プレフィックス無しの buttonStyle を使う必要があることが分かります。</div>
<br />
<h4>
まとめ</h4>
この android: プレフィックス問題、相当危険なトラップです。バージョンによって動く場合もあるし、動かない場合もある。なまじ Android5 以降ではちゃんと動いてしまい、しかもクラッシュするような致命的な問題でもないので、そのまま見過されてしてしまうかもしれません。更にタチが悪いのは、上の例で buttonStyle と入力すると Android Studio の補完機能が勝手に android: プレフィックスを付けてしまうことです。まさにトラップに誘導しているようにしか見えません。<br />
<br />
しかしこの件についてちゃんと説明している解説を見たことがありません。僅かにここで EditText の問題として取り上げていますが、理由までは述べられていません。(EditText だけの問題じゃないんだけどな)<br />
<div>
<a href="http://y-anz-m.blogspot.jp/2015/06/appcompat-edittext-androidedittextstyle.html">http://y-anz-m.blogspot.jp/2015/06/appcompat-edittext-androidedittextstyle.html</a></div>
<div>
<br />
また AppCompat でカスタムスタイルを使う例として、この android: プレフィックスを付けたまま解説している例が多く見られます。まんまとトラップに嵌まっています。<br />
<br /></div>
<div>
Stackoverflow などでも時々、android: プレフィックスを外したら動くようになったという報告がありますが、その意味まではあまり理解されていないようです。</div>
<br />
それにしてもテーマとスタイルの複雑さ、更に AppCompat が加わることによる訳の分からなさは頭がくらくらしてきます。テーマとスタイルについてはドキュメントが整備されていないことは Google も認めています。<br />
<a href="https://developer.android.com/guide/topics/ui/themes.html#PlatformStyles">https://developer.android.com/guide/topics/ui/themes.html#PlatformStyles</a><br />
<br />
なので、あの悪夢のような XMLファイルを見て、少しずつ体を慣らしていくしかないようです。Masamichi Yoshiihttp://www.blogger.com/profile/10172931203881042645noreply@blogger.com0tag:blogger.com,1999:blog-3440838784271100850.post-77102030772653528302016-09-05T23:50:00.000+09:002016-10-11T23:45:41.881+09:00helm のアップデートで Emacs の起動エラーが<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh_Sw31AFkjQD5FI96KKWJWSu7YTXpqJCfDtAb96q2hfukt6XO0xon1OriuoeQNnU92mLdwvCEaiMjnzcmBEZfzJ9HvmvFHh7772j8YSy98cisOLdF5FOX7-ED-JDQ24ReVqbOtOijxBMWX/s1600/emacs.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh_Sw31AFkjQD5FI96KKWJWSu7YTXpqJCfDtAb96q2hfukt6XO0xon1OriuoeQNnU92mLdwvCEaiMjnzcmBEZfzJ9HvmvFHh7772j8YSy98cisOLdF5FOX7-ED-JDQ24ReVqbOtOijxBMWX/s1600/emacs.png" /></a></div>
<br />
helm は Anything の時代から、Emacs のユーザインタフェースに革新を持たらしました。今や Emacs と helm は切っても切れない関係です。しかしそれにしても helm のアップデートの激しさは異常です。Melpa の最新版を追い掛けていると、ほぼ毎日アップデートされています。もう何が何だか、更新内容なんて追跡する気にもなれません。アップデートの激しさだけからすると、よっぽどデザインが悪いんだろうなという気がします。<br />
<br />
これだけ頻繁にアップデートされるとやはり問題が起こるものです。最近(2016年8月のいつか)のアップデートで Emacs の起動エラーが出るようになってしまいました。原因を切り分けていくと、やはり helm でした。その時は時間もなかったこともあり、helm を少し前のバージョンに戻し、そのままにしておきました。<br />
<br />
しかしいつまでもこのままにしておくわけにもいかなので、ちゃんと調べてみました。<br />
<br />
<a name='more'></a>さて、helm と helm-core をアップデートすると Emacs の起動時に以下のエラーが出ます。<br />
<pre class="prettyprint">Warning (initialization): An error occurred while loading `d:/home/.emacs.d/init.elc':
Symbol's value as variable is void: helm-compile-source-functions
To ensure normal operation, you should investigate and remove the
cause of the error in your initialization file. Start Emacs with
the `--debug-init' option to view a complete error backtrace.
</pre>
<br />
<code>helm-compile-source-functions</code> という変数が見つからないようです。helm のソースを新旧見比べてみると、バージョンアップでこの変数が削除されていました。無くなったものは仕方ありません。しかし誰かこの変数を参照している者が他にいる筈です。<br />
<br />
エラー箇所をデバッガで止めてみるとすぐに分かりました。helm-migemo です。helm-migemo.el の中に以下のコードがありました。<br />
<br />
<pre class="prettyprint">(add-to-list 'helm-compile-source-functions 'helm-compile-source--migemo t)
</pre>
<br />
考えてみると helm-mogemo ってほんとんど使っていなかったので、とりあえず (reauire 'helm-migemo) をコメントアウトしておきました。自分的にはちっとも困りません。そのうち helm-migemo がアップデートされたら復活させましょう。<br />
<br />
と思っていたのですが、よくよく調べてみたら helm-migemo は今や helm 本体に取り込まれていて、わざわざ helm-migemo を使う必要はなくなったようです。初めて知りました。helm をロードした後、(helm-migemo-mode 1) とするだけで使うことができます。なんだ、そういう事だったのね。Masamichi Yoshiihttp://www.blogger.com/profile/10172931203881042645noreply@blogger.com0tag:blogger.com,1999:blog-3440838784271100850.post-47953521011904716632016-09-05T19:04:00.000+09:002016-09-30T00:48:37.275+09:00Android AppCompat系テーマでのカスタム View の落とし穴現在の Android Studio で普通にプロジェクトを作成すると、テーマとして Theme.AppCompat.Light.DarkActionBar 等が使われています。いわゆる AppCompat 系のテーマです。<br />
<br />
Android で普通にマテリアルデザインを使おうとすると Android5.0 以上が必要ですが、さすがに Android4 以下を切り捨ててしまうのは現時点ではあまり現実的ではありません。そこで AppCompat 系のテーマが使われる訳です。AppCompat 系テーマは appcompat-v7 ライブラリに含まれていることからも分かるように、APIレベル7(Android2.1) まで遡ってマテリアルデザインを使用することができます。<br />
<br />
しかしこの AppCompat テーマには意外な落とし穴があることを発見しました。というか自分が落とし穴に嵌まっただけですが。<br />
<a name='more'></a>Android Studio でプロジェクトを作成して、以下の様な Button のサブクラスを作ります。<br />
<pre class="prettyprint"><code>public class MyButton extends Button {
public MyButton(Context context, AttributeSet attrs) {
super(context, attrs);
}
}</code></pre>
<br />
単純に継承しただけで、何も動作は何も変えていません。これを標準の Button と並べて配置してみます。レイアウト XML はこんな感じです。<br />
<pre class="prettyprint"><code><?xml version="1.0" encoding="utf-8"?>
<LinearLayout
android:id="@+id/main_layout"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
tools:context="com.mytest.android.myapplication.MainActivity">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"/>
<com.mytest.android.myapplication.MyButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"/>
</LinearLayout>
</code></pre>
<br />
Android 4.x と Android 5.x のデバイスでこれを実行すると以下の様になります。<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: left; margin-right: 1em; text-align: left;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjiGynGXBvcukbr5rDH0hn3ognUPGk3mCSknbGoln2odDOneV-IyOq1o3Gp6D4HXRV9OJY00hYvUIsmYxtVHRKlmAjcyEI-yOsELBWzzVmeEJgjwXJjapIpKKcDMRzM7tqg3MiQms2BM7bn/s1600/Screenshot_20160905-093024.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjiGynGXBvcukbr5rDH0hn3ognUPGk3mCSknbGoln2odDOneV-IyOq1o3Gp6D4HXRV9OJY00hYvUIsmYxtVHRKlmAjcyEI-yOsELBWzzVmeEJgjwXJjapIpKKcDMRzM7tqg3MiQms2BM7bn/s320/Screenshot_20160905-093024.png" width="180" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Android 4.3</td></tr>
</tbody></table>
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiHokiHMoz3iWK8oMCWJO84THO2aQRPozhWfXSOmUWhNkfHTCiTJtZMLud1InP8_-1gv14JmuDa3fGV6On4u3u0cKyPHePl_AOiUHbUdnLxiva0APbH6DKqwZxOOHbNi6ergRdqF3sK6Gc0/s1600/Screenshot_20160905-170555.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiHokiHMoz3iWK8oMCWJO84THO2aQRPozhWfXSOmUWhNkfHTCiTJtZMLud1InP8_-1gv14JmuDa3fGV6On4u3u0cKyPHePl_AOiUHbUdnLxiva0APbH6DKqwZxOOHbNi6ergRdqF3sK6Gc0/s320/Screenshot_20160905-170555.png" width="180" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Android 5.1</td></tr>
</tbody></table>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
Android 4.x ではボタンのデザインが異なっていて、サイズも微妙に違っています。オリジナルボタンの方はマテリアルデザイン特徴であるボタン文字が大文字になっていますが、カスタムボタンにはそれが適用されていません。更にカスタムボタンの方は押してみると青く反転するので、Holo 的なデザインになっているのが分かります(デバイスによって見え方は変わると思いますが)。</div>
<div class="separator" style="clear: both; text-align: left;">
Android 5.x では両ボタンともまったく同じ、マテリアル的なデザインになっています。</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
この違いは何なのでしょう?</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
ここでもう一つテストしてみます。ここまでは Application に適用されているテーマ(res/values/styles.xml)はウィザードが生成したままですが、これに一行追加して以下のようにしてみます。テキストカラーを赤くしてみます。</div>
<pre class="prettyprint"><code><resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="android:textColor">#ff0000</item>
</style>
</resources>
</code></pre>
<br />
これを Android4.x で実行すると以下のようになりました。<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjdoKkSqVQpipswaZCDcz5boTA36yO6QxVoBezwa68EoIovHsjrIa_ISTLdLPgPuJxl0QrdjfGLU49GdQJbRQeCdt1hwrlF_BFPKyT9I-d3EeLYz01fhKuZjjCuuBfWUFMhc9GzMK7pbrhB/s1600/Screenshot_20160905-163129.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjdoKkSqVQpipswaZCDcz5boTA36yO6QxVoBezwa68EoIovHsjrIa_ISTLdLPgPuJxl0QrdjfGLU49GdQJbRQeCdt1hwrlF_BFPKyT9I-d3EeLYz01fhKuZjjCuuBfWUFMhc9GzMK7pbrhB/s320/Screenshot_20160905-163129.png" width="180" /></a></div>
<br />
赤くなったのはオリジナルボタンの方だけです。つまりカスタムボタンの方にはテーマが適用されていないのです。<br />
<br />
何が起こっているのかさっぱり分かりません。ということで Google グループの日本Androidの会で聞いてみたところ、速攻で返事が返ってきました。<br />
<br />
結論から言うと、AppCompat 系のテーマを使う場合は Button クラスではなくて、AppCompat 用の専用クラス <a href="https://developer.android.com/reference/android/support/v7/widget/AppCompatButton.html">AppCompatButton</a> クラスを使わなくてはならないのです。確かに以下の様に、MyButton の親クラスを AppCompatButton にしてみたところ、Android4.x でもマテリアルデザインになりました。<br />
<pre class="prettyprint"><code>public class MyButton extends AppCompatButton {
public MyButton(Context context, AttributeSet attrs) {
super(context, attrs);
}
}</code></pre>
<br />
support-v7 ライブラリには、Button だけでなく、ありとあらゆるウィジェットの AppCompat 版が用意されています。<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhFW-HO29dQqgXUZ1uZP3FuD9rdJbIdG4hd7ecZtV6E_H2pAe1a09E92Bju65LpjiXcPG7iWQdC5pd2Z_oBQ30qOlW6_6ROZlKjCCaz5mw_d75LzKZKEjcGkYswt1r9rqFk279_-EIj5Tbs/s1600/AppCompatWodgets.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhFW-HO29dQqgXUZ1uZP3FuD9rdJbIdG4hd7ecZtV6E_H2pAe1a09E92Bju65LpjiXcPG7iWQdC5pd2Z_oBQ30qOlW6_6ROZlKjCCaz5mw_d75LzKZKEjcGkYswt1r9rqFk279_-EIj5Tbs/s400/AppCompatWodgets.png" width="265" /></a></div>
<br />
<br />
つまり、TextView をカスタム化する時は AppCompatTextView を、CheckBox をカスタム化する時は AppCompatCheckBox を、というようにそれぞれの AppCompat 版を使わなくてならないのです。<br />
<br />
これ結構重要なことだと思うのですが、ちゃんと解説している記事はほんとんど見あたりません。それぞれの Javadoc の中で<br />
<blockquote class="tr_bq">
You should only need to manually use this class when writing custom views. (カスタム View を作るときのみマニュアルでこのクラスを使わなくてはならない)</blockquote>
と軽く説明されているだけです。実際これを守らなかった時に何が起こるかとか、これだけではまったく分からないと思います。<br />
<br />
Android Lint や Inspection で警告を出してくれてもいいと思うのですが、今のところそれも無いようです。<br />
<br />
更にもう一点説明しておくと、レイアウトXML の中で Button を指定すると、(AppCompat 系テーマを使用した場合、) 自動的に AppCompoatButton に置き換えられています。つまり開発者の意図しないところで勝手にクラスが置き換えられているのです。かなり強引なことをやっているなという印象を受けます。<br />
<br />
<h4>
まとめ</h4>
AppCompat 系テーマを使う場合は、Button や TextView ではなく、AppCompatButton や AppCompatTextView 等の AppCompat 専用の View を使いましょう。これを怠ると Android4 以前のデバイスでマテリアルデザインにならなかったり、カスタム定義したテーマが適用されないといった問題が発生します。<br />
<br />
それにしてもこの件についてあまりにも情報が無いことが不思議でなりません。AppCompat 系テーマってそれほど使われていないのでしょうか?それともあまりにも当たり前過ぎて、説明するまでもない事なのでしょうか?Masamichi Yoshiihttp://www.blogger.com/profile/10172931203881042645noreply@blogger.com0tag:blogger.com,1999:blog-3440838784271100850.post-36583340177129268562016-09-02T19:56:00.000+09:002016-09-02T21:13:29.363+09:00Android - カスタムViewにカスタムレイアウトを適用する<h4>
ViewGroup をカスタマイズする</h4>
内部に複雑なレイアウト構造を持つ ViewGroup をカスタムクラス化すると、内部が隠蔽され、レイアウトを単純な View パーツとして扱うことができます。<br />
<br />
TextView や Button 等の単純な View をカスタム化する方法はあらゆるところで解説されていますが、LinearLayout や FrameLayout 等の ViewGroup をカスタム化する方法はあまり見あたりません。今まで自分で試行錯誤しながらやってきましたが、自分の辿ってきた道を基に、ViewGroup をカスタマイズする方法を解説したいと思います。<br />
<br />
<a name='more'></a><br />
<h4>
LinearLayout をカスタム化する</h4>
まず LinearLayout のサブクラスを作ります。<br />
<br />
<code>CustomView.java</code><br />
<pre class="prettyprint"><code>public class CustomView1 extends LinearLayout {
public CustomView1(Context context, AttributeSet attrs) {
super(context, attrs);
}
}
</code></pre>
<br />
このクラスはまだ LinearLayout とまったく同じなので、例えば以下の様に内部に View を配置することができます。<br />
<br />
<code>activity_main.xml</code><br />
<pre class="prettyprint"><code><?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.android.test.customviewtest.CustomView1
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</com.android.test.customviewtest.CustomView1>
</FrameLayout>
</code></pre>
<br />
内部に配置した子 View に対する操作を CustomView1 の内部に実装しましょう。<br />
<br />
CustomView1.java<br />
<pre class="prettyprint"><code>public class CustomView1 extends LinearLayout {
private TextView mTextView;
private Button mButton;
public CustomView1(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mTextView = (TextView) findViewById(R.id.text);
mTextView.setText("CustomView 1");
mButton = (Button) findViewById(R.id.button);
mButton.setText("Button 1");
}
}
</code></pre>
<br />
一応これで LinearLayout をカスタム化することができました。しかしこの方法は色々問題点があります。<br />
<br />
まずカスタム View の内部構造であるところのレイアウトを常にメインレイアウトにベタに記述しなくてはなりません。他で使いまわそうとした場合、Java コードとレイアウト XML を常にペアで移動させなくてはなりません。Javaコードがレイアウトに依存し、レイアウトもJavaコードに依存しています。つまり依存性の循環が発生し、Java コードとレイアウトが非常に強く結合しています。<br />
<br />
更にコンストラクタの中ではまだ子 View の検索ができません。子 View が検索できるのは <code>onFinishInflate()</code> が実行されたでからです。なので子 View をフィールドメンバーとして保持する場合、final にすることができません。つまり子 View が必ず存在することを Java コードとして保証することができないのです。<br />
<br />
<h4>
レイアウトを独立化する</h4>
<br />
では上の問題を解決するために以下の様な独立したレイアウトXMLを作成します。<br />
<br />
custom_view_layout_2.xml<br />
<pre class="prettyprint"><code><?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
</code></pre>
<br />
このレイアウトを使用するカスタム View を作成します。<br />
<br />
CustomView2.java<br />
<pre class="prettyprint"><code>public class CustomView2 extends FrameLayout {
private final TextView mTextView;
private final Button mButton;
public CustomView2(Context context, AttributeSet attrs) {
super(context, attrs);
// XMLレイアウトをインフレートしてアタッチ
LayoutInflater inflater = LayoutInflater.from(context);
inflater.inflate(R.layout.custom_view_layout_2, this, true);
mTextView = (TextView) findViewById(R.id.text);
mTextView.setText("CustomView 2");
mButton = (Button) findViewById(R.id.button);
mButton.setText("Button 2");
}
}
</code></pre>
<br />
コンストラクタの中で、レイアウトをインフレートし、自分自身にアタッチしています。この場合コンストラクタの中で子 View を検索でき、フィールドを final 化することもできます。<br />
これであれば、見て分かる通り、レイアウトから Java コードへの依存は一切ありません。<br />
<br />
また親クラスに FrameLayout を指定している点も注意してください。内部にレイアウトを配置するだけの入れ物なので何でもいいのですが、比較的処理が軽いと思われる FrameLayout を使っています。<br />
<br />
このカスタム View をメインレイアウトに配置するには以下の様にします。<br />
<br />
activity_main.xml<br />
<pre class="prettyprint"><code><?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.android.test.customviewtest.CustomView2
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
</code></pre>
<br />
一般の View と全く同じ感覚で使うことができます。<br />
<br />
<h4>
更に軽量化する</h4>
上の方法で一つだけ気になる点があります。それは View の階層が一つ深くなってしまうことです。親クラスとして使った FrameLayout は単なる入れ物で、ほとんど仕事をしていません。<br />
そこでレイアウトXML を以下の様に変更します。<br />
<br />
custom_view_layout_3.xml<br />
<pre class="prettyprint"><code><?xml version="1.0" encoding="utf-8"?>
<merge
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</merge>
</code></pre>
<br />
LinearLayout を merge タグに変えただけです。<br />
更にこのレイアウトを使うカスタム View を以下の様に変更します。<br />
<br />
CustomView3.java<br />
<pre class="prettyprint"><code>public class CustomView3 extends LinearLayout {
private final TextView mTextView;
private final Button mButton;
public CustomView3(Context context, AttributeSet attrs) {
super(context, attrs);
// XMLレイアウトをインフレートしてアタッチ
LayoutInflater inflater = LayoutInflater.from(context);
inflater.inflate(R.layout.custom_view_layout_3, this, true);
mTextView = (TextView) findViewById(R.id.text);
mTextView.setText("CustomView 3");
mButton = (Button) findViewById(R.id.button);
mButton.setText("Button 3");
}
}
</code></pre>
<br />
継承する親クラスを LinearLayout に変えただけです。元々のレイアウトで使う一番外側のクラスをJavaコードに移動したことになります。これであれあば View 階層が深くならずにカスタム View を使うことができます。<br />
<br />
但しこの場合、レイアウトが(Javaコードの) LinearLayout にアタッチされることを暗黙的に仮定しているので、レイアウトから Java コードへの非常に弱いかたちでの依存が発生することになります。これは効率化とのトレードオフと言えるでしょう。<br />
<br />
最後にメインレイアウト上に、上の全てのカスタム View を配置したコードを示します。<br />
<br />
activity_main.xml<br />
<pre class="prettyprint"><code><?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.android.test.customviewtest.CustomView1
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</com.android.test.customviewtest.CustomView1>
<com.android.test.customviewtest.CustomView2
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<com.android.test.customviewtest.CustomView3
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
</code></pre>
<br />
実行したイメージはこんなかんじになります。どの方法を使っても外見は全く同じです。<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgfxxFyx7pnsuh4D35mNnKKV2jM2gXHP1UY3Vd98u71UHAc_2ZqwUMt7aI9whX0GNkJvDfOTPKInFyHNESi2eIVgljTaT8r9G1cT7ak-ZzRBJ6xYaONwz1jHZbF7yMT-H8lM2Z9VQSfsoh0/s1600/device-2016-09-02-185919.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgfxxFyx7pnsuh4D35mNnKKV2jM2gXHP1UY3Vd98u71UHAc_2ZqwUMt7aI9whX0GNkJvDfOTPKInFyHNESi2eIVgljTaT8r9G1cT7ak-ZzRBJ6xYaONwz1jHZbF7yMT-H8lM2Z9VQSfsoh0/s320/device-2016-09-02-185919.png" width="180" /></a></div>
<br />
テストに使ったコードを以下に置きました。<br />
<a href="https://github.com/masamichi441/CustomViewCustomLayout">https://github.com/masamichi441/CustomViewCustomLayout</a>Masamichi Yoshiihttp://www.blogger.com/profile/10172931203881042645noreply@blogger.com0tag:blogger.com,1999:blog-3440838784271100850.post-19265494318191451452016-08-28T12:14:00.002+09:002016-09-23T09:39:40.936+09:00小学生でも分かる相対性理論<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg_G5fOn2aCITowO8IVgVnilYvWEJDkmxjIHN4j_H4AgDbloCYBwhr_CQWYMgRho3HcaW1gWHbYSEz0uCdHmmwqeLZfyHwh2CLPiv0ltOuFV1TNPs8qWugSRWbwe51ZhqpDfk2R0QLw-jIH/s1600/banner-982162_1920.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="125" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg_G5fOn2aCITowO8IVgVnilYvWEJDkmxjIHN4j_H4AgDbloCYBwhr_CQWYMgRho3HcaW1gWHbYSEz0uCdHmmwqeLZfyHwh2CLPiv0ltOuFV1TNPs8qWugSRWbwe51ZhqpDfk2R0QLw-jIH/s400/banner-982162_1920.jpg" width="400" /></a></div>
<br />
<h4>
相対性理論は難解?</h4>
20世紀の物理学の最大の成果は相対性理論と量子力学です。特にアインシュタインの相対性理論は時間の進み方が変化したり、ブラックホールという変なものを生み出してしまったりと、それまでの常識を引っくり返すような結論を導き出し、世間をあっと言わせました。難解な理論の代表とでも言える相対性理論は、専門的な勉強をした人でなければ理解できないと思っている人は多いのではないでしょうか?しかしその根底にあるアイデアは驚くほど単純なものです。<br />
<a name='more'></a><br />
<h4>
光の速度を測定する</h4>
<br />
光の速度が秒速(約)30万キロというのはよく知られた話です。1秒間に地球7周半という例えもよく耳にします。この光の速度、19世紀末には既にかなりの精度で測定できるようになっていました。光の速度が正確に測定できるようになると、次は静止した物体と動いている物体から発した光で速度の違いが見られる筈だと考え、この違いを測定しようとする動きが始まりました。19世紀末から20世紀初頭にかけて様々な実験が行なわれました。<br />
<br />
一番有名なところではマイケルソン&モーリーの実験があります。1887年のことです。これは当時人類が観測できる最高の速度、つまり地球が太陽の回りを公転する運動を利用たものです。地球の公転運動方向に発した光とその垂直方向に発した光を、非常に巧妙な方法で比較し、少しでも速度に違いがあればたちどころに検出できる装置を開発しました。これは近年今話題になっている重力波観測装置の原型とでもいうべきものです。地球の公転速度は秒速約30Kmです。理論的には当時の実験装置でも十分検出可能な筈でした。<br />
<br />
しかし結論から言うとこの実験は失敗に終わります。いくら実験を繰り返しても速度の違いが検出できないのです。マイケルソン&モーリーに限らず、当時行なわれた他の同様な実験もことごとく失敗に終わりました。<br />
因みに、マイケルソンはこの実験により後年ノーベル賞を受賞します。失敗したにも係わらずです。<br />
<br />
<h4>
ボールの落下実験</h4>
少し話を変えてボールの落下を考えます。<br />
<br />
静止している電車の中でボールを落とすと、ボールは真っすぐ地面に向かって落ちていきます。(図のA-B)<br />
それに対し、動いている電車の中でボールを落とすと、電車の中の人にとっては真っすぐの落ちているように見えますが、外部で静止している人から見ると、ボールは斜めに落ちていくように見えます。図の A-B' という距離を移動したことになります。移動している物体から発した物質には、電車の移動速度が加わって速度が増し、より長い距離を移動したことになります。<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgYe0_8aNL5bd2i_VwiXus5da7mQTsdFP42VYLTIPtc9u26yQODW7JutF13w8H9NUBdxybUBtc_IiNDNgW1iHRWU96HtwU3Sdx38zGYtTqyD3OFIVu0laDu5BCGFfBBV4hZ3hoF7HX4RzAL/s1600/%25E3%2583%259C%25E3%2583%25BC%25E3%2583%25AB%25E3%2581%25AE%25E8%2590%25BD%25E4%25B8%258B.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="296" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgYe0_8aNL5bd2i_VwiXus5da7mQTsdFP42VYLTIPtc9u26yQODW7JutF13w8H9NUBdxybUBtc_IiNDNgW1iHRWU96HtwU3Sdx38zGYtTqyD3OFIVu0laDu5BCGFfBBV4hZ3hoF7HX4RzAL/s320/%25E3%2583%259C%25E3%2583%25BC%25E3%2583%25AB%25E3%2581%25AE%25E8%2590%25BD%25E4%25B8%258B.png" width="320" /></a></div>
<br />
<br />
<h4>
整合性をとるために</h4>
<br />
ところで、速度の公式は以下で表わされます。小学生でも分かる公式です。<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhptmEDfxxUteAQloZFiPWQaj7mqy5iLi9IFl6z0gycAxFBMdgNmA6uuXHqLt0-cf7ta4ef0TmDStV07qOlEISWq-DCml_zpDTPgkarYVKEgLs68lqjmGoBBCIYV96KH9sPAiweEQ4Q9wU7/s1600/%25E5%2585%25AC%25E5%25BC%258F.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="59" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhptmEDfxxUteAQloZFiPWQaj7mqy5iLi9IFl6z0gycAxFBMdgNmA6uuXHqLt0-cf7ta4ef0TmDStV07qOlEISWq-DCml_zpDTPgkarYVKEgLs68lqjmGoBBCIYV96KH9sPAiweEQ4Q9wU7/s200/%25E5%2585%25AC%25E5%25BC%258F.png" width="200" /></a></div>
<br />
20世紀初頭、どんな実験をやっても光の速度に違いが見られないということは、上の公式で V が変化しないということです。しかし電車の例で見たように、静止している物体と移動している物体では移動距離 L が変化します。そうなると上の公式を保つには時間 T が変化しなくてはなりません。<br />
<br />
どんな観測者に対しても光の速度の変化が見られないという当時の実験結果を受け入れるとすると、その皺寄せは時間が引き受けなくてはなりません。つまり時間が伸びたり縮んだりしなくてはならないのです。<br />
<br />
これが相対性理論の根底にある考え方です。どうです、非常に簡単でしょ?難しいところどこにも有りませんよね。<br />
<br />
<h4>
それでもアインシュタインは天才的</h4>
時間というのは観測者に係わらず、どんな人にとっても同じように流れていくものと信じられていた当時の常識からすると、これは驚くべき結論です。常識というのが如何にいい加減なものかを物語るいい例です。地動説や進化論など、盲目的に信じられている従来の常識を打ち破ることから科学は大きく進歩します。<br />
<br />
当時、光速に違いが見られない実験結果をどう解釈していいか頭を悩ませていた科学者達は、何とかしてそれを理屈付けしようと苦しんでいました。今から思うと何故こんな単純なことに気が付かなかったのかと不思議に思います。それ程常識の束縛というのは強いものなのです。<br />
<br />
アインシュタインが特殊相対性理論を発表したのが1905年。20世紀初頭、時は満ちていました。もしアインシュタインが発見していなくても、それほど時を待たずに他の誰かが発見していたことでしょう。それでも時間が変化するという着想を最初に得たアインシュタインは天才的だと思います。更にアインシュタインの凄いところは、この理論を更に展開し、重力の理論(一般相対性理論)にまで発展させたことです。これはアインシュタインの天才性を持ってして成しえた業績だと思います。Masamichi Yoshiihttp://www.blogger.com/profile/10172931203881042645noreply@blogger.com0tag:blogger.com,1999:blog-3440838784271100850.post-38220022431772043022016-08-26T00:39:00.000+09:002016-09-01T18:51:58.245+09:00Android Studio で依存性循環に立ち向かう<h4>
依存性循環とは</h4>
漫然とプログランミングをやっていると、ついクラス間の参照(依存性)に循環が発生してしまいます。オブジェクト指向プログラミングの原則では依存性の向きは一方向であるべきであり、循環は悪しき存在とされています。循環があるとクラス間の結合が密になり、改造に対して脆弱になります。<br />
<br />
例えば ClassA が ClassB を参照し、ClassB も ClassA を参照するとクラス間の依存性に循環が発生します。ここで両クラスが別のパッケージに属しているとパッケージ間の依存性循環にもなります。<br />
<div>
<br />
このように二つのクラス間で相互参照が発生している場合はまだ分かり易いのですが、これが A → B → C → A の様に三つ、またはそれ以上のクラスを介して循環していると直感ではなかなか判別できません。<br />
<br />
<div>
Android Studio にはこの依存性の循環を検出する仕組みが備わっています。この記事は IntelliJ IDEA にもそのまま適用できる内容ですが、自分のフィールドが Android Studio なので、Android Studio をベースに説明していきます。</div>
<div>
<a name='more'></a><br />
<h4>
依存性循環を作成する</h4>
Android Studio の依存性検出機能を見るために、以下の三つのクラスを作成します。</div>
</div>
<pre class="prettyprint"><code>public class ClassA {
private static final String TAG = ClassA.class.getSimpleName();
ClassA() {
Log.d(TAG, "Reference to " + ClassB.class.getSimpleName());
}
}
</code></pre>
<div>
<pre class="prettyprint"><code>public class ClassB {
private static final String TAG = ClassB.class.getSimpleName();
ClassB() {
Log.d(TAG, "Reference to " + ClassC.class.getSimpleName());
}
}
</code></pre>
<pre class="prettyprint"><code>public class ClassC {
private static final String TAG = ClassC.class.getSimpleName();
ClassC() {
Log.d(TAG, "Reference to " + ClassA.class.getSimpleName());
}
}
</code></pre>
<br />
ほんとんど中身の無いクラスです。コンストラクタの中で無理矢理他のクラスを参照して、 A → B → C → A の依存性循環を作り出しています。<br />
<br />
これらのクラスを以下の様に別々のパッケージに配置します。<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjTwtbIQ6KZHsqK2kr8AahsLpBjHCjiJtfkx6XUqpGFKRkdtvvnfC0qEj5h9ADAe3mrKJn07K_lnTnni8o2teWupZNkyGL3gLfHdzboemVGOfk60LV6Ag-Jveioi9iMOqFgVlVmETBJihYo/s1600/cyclic_dependency-1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjTwtbIQ6KZHsqK2kr8AahsLpBjHCjiJtfkx6XUqpGFKRkdtvvnfC0qEj5h9ADAe3mrKJn07K_lnTnni8o2teWupZNkyGL3gLfHdzboemVGOfk60LV6Ag-Jveioi9iMOqFgVlVmETBJihYo/s1600/cyclic_dependency-1.png" /></a></div>
<br />
<br />
<h4>
Inspection で検出する</h4>
この状態で Inspection を実施すると、Dependency Issues の下に、以下の様に依存性の循環が検出されます。<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg0cMYce-baA1ub6XPtV7lEQF7uL2kjofOavbCFqKu-GpiQlaBhyPB0daVG2z533QNwXlyUEZR7SjI3Lfd6Fqh8BaflIGq05Z3FvC02Xtm-gHHzxF6XVd11ApMp7_zGgKFZiHOUJw7O-XB_/s1600/cyclic_dependency-2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="246" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg0cMYce-baA1ub6XPtV7lEQF7uL2kjofOavbCFqKu-GpiQlaBhyPB0daVG2z533QNwXlyUEZR7SjI3Lfd6Fqh8BaflIGq05Z3FvC02Xtm-gHHzxF6XVd11ApMp7_zGgKFZiHOUJw7O-XB_/s400/cyclic_dependency-2.png" width="400" /></a></div>
<br />
<br />
これを見ると分かるように、クラス間の循環(Cyclic class dependencies)が三つ、パッケージ間の循環(Cyclic package dependencies)が三カウントされています。<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhuJAqCPC6W7eOMdfBYGIvq2waWB55HELvnKXtLkZuynMK2p-yJLEyrhYjhM9ttFmDg4y1Uge56lz5809qSRFSNiLcnjwa2hb3URUieozYkAJ20fi4spV5PnmmPTyWibcBEHFFIhiVzLsLR/s1600/cyclic_dependency-6.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="259" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhuJAqCPC6W7eOMdfBYGIvq2waWB55HELvnKXtLkZuynMK2p-yJLEyrhYjhM9ttFmDg4y1Uge56lz5809qSRFSNiLcnjwa2hb3URUieozYkAJ20fi4spV5PnmmPTyWibcBEHFFIhiVzLsLR/s640/cyclic_dependency-6.png" width="640" /></a></div>
<br />
検出された項目を選択すると、どのクラス、パッケージに対して循環が発生しているかが分かります。しかしここからはコードのどの部分なのかというところまでは分かりません。<br />
<br />
<h4>
Analyze ツールで検出する</h4>
Inspection で調べる方法とは別にもう一つ別の方法があります。メニューの Analyze → Analyze Cyclic Dependencies... から依存性循環検出ツールを使う方法です。<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj-hM2epA8vA9S0nzvOzhXPDpi9UFCmKLZCWfsXNmot3PHN6vmE3JGS50KaJfpE9_cHpYM_x51HeV06dRO9hI03d58VRdO9zVk8pbKvwDxnDFrTDl3TM4gaYg98Fhlc12yqC3dtVY_STRcm/s1600/cyclic_dependency-3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="293" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj-hM2epA8vA9S0nzvOzhXPDpi9UFCmKLZCWfsXNmot3PHN6vmE3JGS50KaJfpE9_cHpYM_x51HeV06dRO9hI03d58VRdO9zVk8pbKvwDxnDFrTDl3TM4gaYg98Fhlc12yqC3dtVY_STRcm/s320/cyclic_dependency-3.png" width="320" /></a></div>
<br />
<br />
この方法はパッケージ間の循環を検出します。なのでクラス間に循環はあるが、パッケージ間にはないような場合には検出できません。しかし結果を見るとどのクラスで循環が発生しているかまで知ることができます。実行すると以下のような結果を得ます。<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgGf7EDK8HNeiQCrbBY-_vkUjeKy0aBj7Lzc053MuEDulMpOYK2qSZKvSei_vyPv-Qo9MRCVNjLQXDw-VEuUJYfkAG77tgrgL0jbjKvmHTRZ8DAweTE2rNKs8nsvgXoXJgSQ969lTUXPt7o/s1600/cyclic_dependency-4.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="272" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgGf7EDK8HNeiQCrbBY-_vkUjeKy0aBj7Lzc053MuEDulMpOYK2qSZKvSei_vyPv-Qo9MRCVNjLQXDw-VEuUJYfkAG77tgrgL0jbjKvmHTRZ8DAweTE2rNKs8nsvgXoXJgSQ969lTUXPt7o/s640/cyclic_dependency-4.png" width="640" /></a></div>
<br />
<br />
三つのペインに分かれています。上左ペインには循環が発生しているパッケージが全て表示されます。ここで一つのパッケージを選択すると、上右ペインにその依存先のパッケージやクラスが表示されます。少し分かりにくいのは、左に表示されるのは直接の依存先だけでなく、間接的な依存先も全て表示されていることです。更に自分自身も表示されています。右ペインでパッケージやクラスを選択すると、下ペインに循環に関わるコードが表示されます。<br />
また左ペインで三つのパッケージが表示されていますが、この例の場合、これらどれも同じ循環に関わるものです。この検出結果は慣れるまで、なかなか意味が分かりにくいかもしれません。もう少し工夫して、依存性グラフが視覚的に分かるような表示になるといいのですが。<br />
<br />
<h4>
パッケージ間の依存性循環を解決する</h4>
パッケージ間の循環ついては背景にクラス間の循環があるわけですが、まずはパッケージ間のみで考えます。パッケージ循環はクラスをパッケージ移動するだけで消えてしまうことがあります。極端な話、全てのクラスを同じパッケージに収めてしまえば、パッケージ間の循環は無くなります。これ結構見落しがちな点かもしれません。そこまで極端でなくても、パッケージデザインが適切でなく、パッケージ分割を見直すことで解決できることがあります。<br />
<br />
教科書やウェブページには詳細なプログラミングテクニックを教えてくれる膨大な情報がありますが、ことパッケージデザインに関しては、指針を与えてくれる情報はほとんどありません。パッケージデザインというのは Java プログラミングにおいて結構重要な要素であるにも係わらず、意外と軽視されている気がします。この点に昔から不満を抱えていました。自分なりに工夫してきましたが、まだこれであれば鉄板という域に達していません。どうすれば良いか模索している段階です。<br />
<br />
<h4>
クラス間の依存性循環を解決する</h4>
クラス間の依存性循環を解決するのは結構厄介です。まずオブジェクト指向プログラミングには <a href="http://d.hatena.ne.jp/asakichy/20090128/1233144989">DIP (Dependency Inversion Principle, 依存性逆転の法則)</a> というテクニックあります。これは依存性の矢の向きを反転させてしまう魔法のようなテクニックです。一種のデザインパターンと言ってもいいかもしれません。ここでは詳細は書きません。各自調べてください。<br />
この DIP を依存性のどこかに適用すると循環を断ち切ることができます。しかしこの方法の欠点は、あまり多用し過ぎるとコードの見通しが悪くなってしまうことです。<br />
<br />
もう一つの方法はクラスのデザインを見直すことです。循環が発生しているということは何かしらデザインに問題があるとも言えます。循環を解決するには結構大胆にクラスを作り変える必要があるかもしれません。しかしそうすることで、クラスの構造が見違えるようにスッキリするのはよくあることです。<br />
<br />
<h4>
まとめ</h4>
パッケージにしろクラスにしろ依存性循環を解決するのは結構大変な作業です。そのせいか世の中には循環に対してあまり注意を払わないコードが数多く見られます。<br />
Android Studio/IntelliJ IDEA には、あまり洗練されているとは言えませんが、循環を検出する機能が標準で搭載されています。これらを駆使して依存性循環を駆逐しましょう。</div>
Masamichi Yoshiihttp://www.blogger.com/profile/10172931203881042645noreply@blogger.com0tag:blogger.com,1999:blog-3440838784271100850.post-47898343718564493792016-08-24T19:43:00.000+09:002016-08-28T20:48:06.489+09:00Inkscape で正確な U字 を描くInkscape で正確なU字図形を描こうと思ったところ、はてどうすればいいのかと迷ってしまいました。分かってしまえばどうってことないのですが、ネット上でもなかなか情報が見つけられなかったので、以下に手順を示します。<br />
<br />
<a name='more'></a><br />
まず円/弧ツールで正円を描きます。このときグリップを表示し、頂点がグリップにスナップするように描いてください。<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjJtSs6gswbARwb1E591wckiENi73e0QLYdyT-smAw2_hiInStDA2vPNwrgX3opV1OlutHHLx6x76wk11GA3LRYfJLWh3p1rf4lvr6_58lWNNmHSjjg8piXNBiPczTQXr_8vUvHgLm3lGfO/s1600/step-1.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="318" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjJtSs6gswbARwb1E591wckiENi73e0QLYdyT-smAw2_hiInStDA2vPNwrgX3opV1OlutHHLx6x76wk11GA3LRYfJLWh3p1rf4lvr6_58lWNNmHSjjg8piXNBiPczTQXr_8vUvHgLm3lGfO/s320/step-1.PNG" width="320" /></a></div>
描いた円オブジェクトをパスへ変換します。<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj6bHfjCsJc9x5cEdcNgOeX9j61JJd6QRj-twMqDftjevFRemAq4mTbHv5AM1dHNrnfZU7JQkOe5pO6jPAjogFQF8Vh-VNkThpV4dGuwaevmJrM3MFiUitObsDKcMSYXbqSbK4SRz6mLKgA/s1600/step-2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj6bHfjCsJc9x5cEdcNgOeX9j61JJd6QRj-twMqDftjevFRemAq4mTbHv5AM1dHNrnfZU7JQkOe5pO6jPAjogFQF8Vh-VNkThpV4dGuwaevmJrM3MFiUitObsDKcMSYXbqSbK4SRz6mLKgA/s1600/step-2.png" /></a></div>
ノードツール<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEien0PVOqtPrf3XscJEA5X0oyzHTkeNIE4aNAR2Peo7t0N6ChcVv6ymMWhnr5sOmJz_Y8mohb-c3PzhpfqH6TYnz_UZaiOc2PuamL3ww_lrndmZRkmY_VzJ4d3Bc52qXby9S74f6WJy6lr0/s1600/step-3.png" imageanchor="1" style="clear: left; display: inline !important; margin-bottom: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEien0PVOqtPrf3XscJEA5X0oyzHTkeNIE4aNAR2Peo7t0N6ChcVv6ymMWhnr5sOmJz_Y8mohb-c3PzhpfqH6TYnz_UZaiOc2PuamL3ww_lrndmZRkmY_VzJ4d3Bc52qXby9S74f6WJy6lr0/s1600/step-3.png" /></a>を選択し、オブジェクトを選択すると以下のようになります。<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg3rGxD_S_PTQNSXn-P3ml10b2LzpB-Alp8nR5DmgHFjE8dgqoAFKd1m6Jqi_1mX-TCCVQMeNI1b6liZtTB9Vr05b-3jQ20u26gMH0z_IHZpQH4gAnj6NMVhzp3IoblLyCJg7kdijaG0X1i/s1600/step-4.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="319" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg3rGxD_S_PTQNSXn-P3ml10b2LzpB-Alp8nR5DmgHFjE8dgqoAFKd1m6Jqi_1mX-TCCVQMeNI1b6liZtTB9Vr05b-3jQ20u26gMH0z_IHZpQH4gAnj6NMVhzp3IoblLyCJg7kdijaG0X1i/s320/step-4.png" width="320" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
一番上のノードを選択し、ここでパスを切断します。</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgoFGbl-v6TOPK2xpb3G76KsKLhI0HeSRY_6b9XHUIKOmrdET9rUtmxQgmMn9ByQrjDMx2Jfrnxlov3wJCosEN_dqrjX2ro7elE30pRB0szW_aTZytLbtp4awyoNQBt6D1AhztDJB0a6w4-/s1600/step-5.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgoFGbl-v6TOPK2xpb3G76KsKLhI0HeSRY_6b9XHUIKOmrdET9rUtmxQgmMn9ByQrjDMx2Jfrnxlov3wJCosEN_dqrjX2ro7elE30pRB0szW_aTZytLbtp4awyoNQBt6D1AhztDJB0a6w4-/s1600/step-5.png" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
切断したノードの片方を端のノードの真上に移動します。この時もグリッドにスナップするようにします。飛び出ているハンドルはノードと重ねてしまいます。<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjbcuqOKPoYZtnH-2yekr7kNLs5mSg2GQX-t1t7CQHM-rZGWHAY_CXTUobXdf7e2l8Ec6DUm3Dy1nn3Bktbqrl0DnYWgtaBiUuq9i7h1gLHGQqxFscHL4lK1_U4U3CYt-13B5lVplSS6AFD/s1600/step-6.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjbcuqOKPoYZtnH-2yekr7kNLs5mSg2GQX-t1t7CQHM-rZGWHAY_CXTUobXdf7e2l8Ec6DUm3Dy1nn3Bktbqrl0DnYWgtaBiUuq9i7h1gLHGQqxFscHL4lK1_U4U3CYt-13B5lVplSS6AFD/s320/step-6.png" width="319" /></a></div>
<br />
反対側も同じにします。<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjHrCdZfciRzFNBVlcsdQbcwRTEGIP5NoMCeTwHiKcdZ-xFC5cH3z-LR6HJPTxOX5OrDYCgaVok6r65wkyzWt0PtMHLecXoMRctX44MWZlrRIxq0uUTZ2zbLo29YRKm9MkxS9eICpFH2a6M/s1600/step-7.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjHrCdZfciRzFNBVlcsdQbcwRTEGIP5NoMCeTwHiKcdZ-xFC5cH3z-LR6HJPTxOX5OrDYCgaVok6r65wkyzWt0PtMHLecXoMRctX44MWZlrRIxq0uUTZ2zbLo29YRKm9MkxS9eICpFH2a6M/s320/step-7.png" width="318" /></a></div>
<br />
後は線の太さを調整し、必要に応じて端の形を選択します。<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjEMHMpWW6EDk6wOzM0voduXV-EppK7RwE1ugLiv3R__jzA3mLdhttSirD6YCjVb2OwNTT_Hv62OA6Mtldv9QyuxO6rOeVAGDaD5Kktwsi27I5LLdLdLQdu3HukTPc2pWNz68Lutpx9crID/s1600/step-8.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjEMHMpWW6EDk6wOzM0voduXV-EppK7RwE1ugLiv3R__jzA3mLdhttSirD6YCjVb2OwNTT_Hv62OA6Mtldv9QyuxO6rOeVAGDaD5Kktwsi27I5LLdLdLQdu3HukTPc2pWNz68Lutpx9crID/s320/step-8.png" width="232" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
はい、出来上がり。</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjyWZm56e7-tdGUcwz1FbAwsZphikG-gplOaOPzWNfDA-XF8p9cy82BHr6swpEKZIgMBCmYQdZ-2E1tDxRM_YrztUuTe7ssaLKoS1MoHMQyobCUakLglqEjQe8HNqOismd1HrhiI4t5eUsY/s1600/step-9.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjyWZm56e7-tdGUcwz1FbAwsZphikG-gplOaOPzWNfDA-XF8p9cy82BHr6swpEKZIgMBCmYQdZ-2E1tDxRM_YrztUuTe7ssaLKoS1MoHMQyobCUakLglqEjQe8HNqOismd1HrhiI4t5eUsY/s320/step-9.png" width="319" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
こんなんのもお手軽に描けます。</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjiKgzo6Q0RhB3pw11XWZ17vrN2eOlfuFht-MvDTJ8yVQYJA6ytcNkxfLyU3PrV3lv3aMJK1_VuRBuiKTeDvpH7eaV6HSFjriomrlJsEetTAcNGw4XtFbdgboPftYYB1iLPeiY3Z92C6ckJ/s1600/step-10.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjiKgzo6Q0RhB3pw11XWZ17vrN2eOlfuFht-MvDTJ8yVQYJA6ytcNkxfLyU3PrV3lv3aMJK1_VuRBuiKTeDvpH7eaV6HSFjriomrlJsEetTAcNGw4XtFbdgboPftYYB1iLPeiY3Z92C6ckJ/s320/step-10.png" width="245" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
因みにマイクの部分は極太の直線の両端を丸めたものです。</div>
<br />Masamichi Yoshiihttp://www.blogger.com/profile/10172931203881042645noreply@blogger.com0tag:blogger.com,1999:blog-3440838784271100850.post-77995829505141991462016-08-24T10:02:00.001+09:002016-09-06T00:10:32.080+09:00Android Activity の onNewIntent() とはGoogle のあるサンプルコードを見ていたら Activity の <code>onNewIntent()</code> なるメソッドを使っていました。<code>onCreate()</code> や <code>onPause()</code>, <code>onResume()</code> 等はライフサイクルメソッドとしてお馴染みですが、<code>onNewIntent()</code> はあまり見たことがありません。この <code>onNewIntent()</code> が何者なのか調べてみました。<br />
<br />
<a name='more'></a><br />
以下は Android プログラマーにとってお馴染みの Activity のライフサイクルです。すっかり体に染み込んでいることでしょう。この中に <code>onNewIntent()</code> は存在しません。<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiQJdi561nPggpiGAyC5dVDk1jtChAdS-cT8myMdeDH4p16CW_USBfjyHbmvRuP6wbrcQ1nArdDMrg67seSHVBjsDjUyCmWT9dwdN3Vx3No_6qp25tYACRbo3-Xx12TrQST4D7Oct5IpBtU/s1600/activity_lifecycle.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiQJdi561nPggpiGAyC5dVDk1jtChAdS-cT8myMdeDH4p16CW_USBfjyHbmvRuP6wbrcQ1nArdDMrg67seSHVBjsDjUyCmWT9dwdN3Vx3No_6qp25tYACRbo3-Xx12TrQST4D7Oct5IpBtU/s640/activity_lifecycle.png" width="492" /></a></div>
<br />
<br />
<br />
<code>onNewIntent()</code> を理解する前に、まず Activity の起動方法を理解しなくてはなりません。<code>Activity#startActivity()</code> で特に何も指定せず、普通に Activity を起動すると、目的の Activity のインスタンスが新規作成されます。<br />
<br />
しかし AndroidManifest.xml で <code>android:launchMode="singltTop"</code> 属性が指定されている Activity を起動したり、または startActivity() を呼び出す時に、<code>FLAG_ACTIVITY_SINGLE_TOP</code> フラグを指定すると、目的の Activity が既に存在し、且つそれが Activity スタックの最上位にある場合、新しい Activity インスタンスが生成されるのではなく、既存のインスタンスが再利用されます。なので Activity の二重起動を防ぐことができます。この時に onNewIntent() が呼び出されます。どのタイミングで呼び出されるかというと以下の赤枠の部分です。<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhlnQTN1nD29CtXmFPcP_IARmoqeid307d6jyyLHVmWtG8LdiZJ8XYHScFytJLfv-9_x030jsGJbj89erWGaVYe-2qhItbUZpUC_XdKr0uoRilAd9Iu_maDPq7nIzZpldldCm3DPuR9CGK_/s1600/activity_lifecycle-1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhlnQTN1nD29CtXmFPcP_IARmoqeid307d6jyyLHVmWtG8LdiZJ8XYHScFytJLfv-9_x030jsGJbj89erWGaVYe-2qhItbUZpUC_XdKr0uoRilAd9Iu_maDPq7nIzZpldldCm3DPuR9CGK_/s640/activity_lifecycle-1.png" width="492" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
これを見ると分かるように、<code>onNewIntent()</code> が呼び出される場合 <code>onCreate()</code> は実行されません。逆に <code>onCreate()</code> が実行される場合は <code>onNewIntent()</code> は実行されません。</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both;">
<code>onNewIntent()</code> へは、Activity を呼び出すのに使われた Intent が引数として渡されます。Intent には Activity へ渡す引数が含まれています。通常のシーケンスであれば <code>onCreate()</code> で Intent を処理しますが、singleTop の場合、<code>onCreate()</code> が実行されないので、それに代わる何らかのタイミングが必要になります。それが <code>onNewIntent()</code> です。</div>
<div class="separator" style="clear: both;">
<br /></div>
<div class="separator" style="clear: both;">
<a href="https://firebase.google.com/docs/app-indexing/android/app?hl=ja#handle-incoming-urls">Firebase HTTP URLs 実装のサンプルコード</a> を見ると以下コードが見られます。</div>
<pre><code> @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_schedule);
onNewIntent(getIntent());
}</code></pre>
<div class="separator" style="clear: both;">
<code>onCreate()</code> の中で <code>onNewIntent()</code> を直接呼び出しています。<code>onCreate()</code> と <code>onNewIntent()</code> どちらでも同じ処理をやりたいということなのでしょうが、コールバックを直接呼び出すというのはちょっと強引な気がします。ここはメソッドを一つ用意して、両者から呼び出す方が自然ではないでしょうか。</div>
Masamichi Yoshiihttp://www.blogger.com/profile/10172931203881042645noreply@blogger.com0tag:blogger.com,1999:blog-3440838784271100850.post-56449370081600244452016-08-23T09:12:00.000+09:002016-08-23T22:45:47.593+09:00Firebase Analytics が使用するAndroidアプリのパーミッションGoogle には現在、「Google アナリティクス」と「Firebase Analytics」の二つの Analytics サービスがあります。Google アナリティクスは昔からあるサービスで、Firebase Analytics は Google が2014年に買収した Firebase 社の流れを汲むサービスです。どちらも似たようなサービスなので、今後両者の関係はどうなっていくのかと思っていましたが、とりあえずはウェブ関係は Google アナリティクス、モバイルアプリは Firebase Analytics という棲み分けになるようです。<br />
<br />
<a name='more'></a><br /><br />
以前、Android アプリに Google アナリティクスを導入した際に、勝手に Wake Lock パーミッションが付いてしまうという記事を書きました。<br />
<a href="http://extra-vision.blogspot.jp/2016/07/android-analytics-wake-lock_22.html">http://extra-vision.blogspot.jp/2016/07/android-analytics-wake-lock_22.html</a><br />
<br />
Androidアプリは現在、Firebase Analytics への移行が強く推奨されています。なので自分のアプリも少しずつ Firebase へ移行させています。半ば予想はしていましたが、Firebase の方も Android アプリに実装すると、勝手にパーミッションが追加されてしまいました。<br />
<br />
勝手に追加されてしまうのは以下の三つのパーミッションです。<br />
<ul>
<li> android.permission.WAKE_LOCK</li>
<li> com.google.android.c2dm.permission.RECEIVE</li>
<li> ???.package.permission.C2D_MESSAGE</li>
</ul>
<br />
<div>
パーミッションは Android アプリの生命線です。必要無いものは極力付けないというのが鉄則です。問題はこれらが本当に必要なのか?ということです。<br />
<br />
Google アナリティクスの時に問題になった Wake Lock はドキュメントに明確に、非Google Playアプリで必要になるものであり、オプションであるとも書かれていました。しかし Firebase Analytics ではこれらのパーミッションについて公式に書かれているものは見あたりません。</div>
<div>
<br />
WALE_LOCK は、想像するに、バックウラウンドでデータを送信するためのものだと思います。<br />
<br />
残りの二つはクラウドメッセージ(Cloud To Device Message)関連のパーミッションです。Firebaseには膨大な機能があり、Analytics はその中の一機能に過ぎません。これらのパーミッションが Firebase Cloud Message (FCM)で必要なことは容易に想像できます。しかし Analytics でも本当にこれらが使われているのでしょうか?<br />
<br />
この件についてはあまり情報がありません。わずかに Firebase の Google Group で議論されていました。<br />
<a href="https://groups.google.com/forum/?hl=ja#!topic/firebase-talk/CXgecSxgsRE">https://groups.google.com/forum/?hl=ja#!topic/firebase-talk/CXgecSxgsRE</a><br />
<br />
中で色々議論されていますが、結論から言うと、確定的な情報ではありませんが、これらのパーミッションは Firebase Analytics が裏で使っている、もしくは将来使うかもしれないものなので、無効にしない方がよいということです。これらを無効にしても Analytics は機能しているという報告もありますが、データを取りこぼすことがあるかもしれないそうです。<br />
<br />
上のも書いたように、Firebase フレームワークには様々な機能があります。クラッシュレポートやクラウドメッセージ、AdMob 等、既存の機能もどんどん Firebase に取り込まれています。これらのどれを使っても上の三つのパーミッションは鉄板で付加されてしまいます。Firebase を使う上でデフォルトで必要となるパーミッションのようです。ドキュメントにの一言書いておいてくれれば悩む必要もないのにな。</div>
Masamichi Yoshiihttp://www.blogger.com/profile/10172931203881042645noreply@blogger.com0tag:blogger.com,1999:blog-3440838784271100850.post-391947911768633212016-08-21T16:57:00.000+09:002016-12-03T13:41:27.749+09:00Blogger の AdSense 404ページ対策ブログの404ページにAdSense広告を表示するとポリシー違反になるのはよく知らています。つまらない事でアカウント停止をくらう前に、出来るだけのことはやっておきたいものです。<br />
<br />
とは言いつつ、自分も最近まで放っておいて、ようやく重い腰を上げ対策を行ないました。やってみると結構苦労したので、備忘録として記録を残しておきます。<br />
<br />
<br />
まずBloggerにはAdSense広告を配置するにも様々な方法があります。テンプレートに直接貼り付ける方法やガジェットを使う方法があり、ガジェットを使うにしても、「Google AdSenseガジェット」を使う方法や「HTML/JavaScriptガジェット」を使う方法などがあります。<br />
<br />
ここでは比較的よく使われる「HTML/JavaScriptガジェット」で AdSense を貼り付けた場合に適用できる方法を説明します。「Google AdSenseガジェット」にも適用できるかもしれませんが、確認はしていません。<br />
<br />
まず404ページで広告を非表示にする方法を調べていると、ざっと以下のページが見つかります。<br />
<br />
<a href="https://ikisakianco.com/blogger404-adsense">Bloggerの404ページでAdSense(アドセンス)を非表示にする</a><br />
<a href="http://blog2.k05.biz/2013/03/blogger404.html">Bloggerの「404 ページ」をカスタマイズする方法</a><br />
<br />
これらのページで解説しているのは、基本的には以下の様な方法です。<br />
<br />
<pre><code><b:if cond='data:blog.pageType != &quot;error_page&quot;'>
AdSenseウィジェットのコード
</b:if></code></pre>
<br />
<code><b:if ..> .. </b:if></code> タグでAdSenseウィジェットのコードを囲むと説明されています。(Bloggerの管理画面ではガジェットとウィジェットという言葉が混在して使われています。どちらも同じ意味なのであまり気にしないでください。)<br />
<br />
Blogger ではレイアウトを定義するのに、HTMLを独自に拡張した構文が使えます。公式な解説は <a href="https://support.google.com/blogger/answer/46995?hl=ja&ref_topic=6321969">ここ</a> にあります。上のやり方はこれを利用したものです。<br />
<br />
たったこれだけの事なので簡単にできると思っていましたが、やってみるとこれがなかなかうまく行きません。<br />
<br />
まず Blogger の管理画面から テンプレート → HTMLの編集 を選択し、テンプレートの編集画面に入ります。「ウィジェットへの移動」ボタンからAdSenseを貼り付けたウィジェットを探します。(ここでは「ウィジェット」という言葉が使われています。)「HTML/JavaScriptガジェット」を使った場合は HTML1 とか HTML2 といった id が付いていると思います。<br />
<br />
ジャンプすると以下の様なコードが見つかると思います。これがテンプレートに貼り付けられたガジェット(ウィジェット)コードです。コードが折り畳まれているかもしれませんが、行番号横の三角マークをクリックして展開したものです。ここには広告ユニットコードは含まれていないので、ユニットidで検索しても見つかりません。<br />
<br />
<pre class="prettyprint"><code><b:section class='tabs' id='crosscol' maxwidgets='1' name='Cross-Column' showaddelement='yes'>
<b:widget id='HTML3' locked='false' title='スポンサーリンク' type='HTML' version='1' visible='true'>
<b:includable id='main'>
<!-- only display title if it's non-empty -->
<b:if cond='data:title != &quot;&quot;'>
<h2 class='title'>
<data:title/>
</h2>
</b:if>
<div class='widget-content'>
<data:content/>
</div>
<b:include name='quickedit'/>
</b:includable>
</b:widget>
</b:section>
</code></pre>
<br />
さて、このコードの中のどの部分を <code><b:if> .. </b:if></code> で囲めば良いのでしょうか?<br />
<br />
「ウィジェットコードを囲め」ということなので、まずは <code><b:widget ...> ... </b:widget></code> の部分を囲んでみました。しかしこれはうまくいきません。というのは、「テンプレートを保存」ボタンを押してもコードを保存してくれないのです。<br />
Bloggerのテンプレートエディタは保存時にかなり色々な事を厳密にチェックしているらしく、少し不備があると全く保存してくません。かと言って、エラーや警告を出してくれる訳でもなく、まったく不親切なエディタです。<br />
<br />
次に一つ外側の <code><b:section ...> ... </b:section></code> の部分まで囲んでみました。結論から言うとこれは、うまく行くものと行かないものがありました。自分のブログで言うと、ヘッダに配置したものについてはうまく行きましたが、それ意外の部分に配置したものについては、これはまたエディタが保存してくれないのです。これについては理由がさっぱり分かりません。<br />
<br />
さてどうしたものかと、更に調べてみると、以下のページが見つかりました。英語版のAdSenseヘルプフォーラムの記事です。<br />
<a href="https://productforums.google.com/forum/#!topic/adsense/58zUGFAFh_I">https://productforums.google.com/forum/#!topic/adsense/58zUGFAFh_I</a><br />
<br />
ここに書いてある方法を適用してみたのが以下のコードです。<br />
<br />
<pre class="prettyprint"><code><b:section class='tabs' id='crosscol' maxwidgets='1' name='Cross-Column' showaddelement='yes'>
<b:widget id='HTML3' locked='false' title='スポンサーリンク' type='HTML' version='1' visible='true'>
<b:includable id='main'>
<span style="background-color: #666666;"> <!-- Don't show Ads on error page -->
<b:if cond='data:blog.pageType != &quot;error_page&quot;'></span><span style="background-color: lime;">
</span> <!-- only display title if it's non-empty -->
<b:if cond='data:title != &quot;&quot;'>
<h2 class='title'>
<data:title/>
</h2>
</b:if>
<div class='widget-content'>
<data:content/>
</div>
<span style="background-color: #666666;"> </b:if></span>
<b:include name='quickedit'/>
</b:includable>
</b:widget>
</b:section>
</code></pre>
<code><b:if ..></code> のコードをウィジェットの内部の追加します。これであれば保存もでき、うまく動きました。自分のブログに貼り付けてある三つの広告全てに適用しました。<br />
<br />
AdSenseの404ページ対策は既にほとんどの人がやっていると思ったていたのですが、具体的なコードまで示して説明しているページが見つからず、結構苦労しました。まさかウィジェットの内部に記述するなんて想像もつきませんでした。しかしみんなどうしているのでしょう?ひょっとして対策をしている人あまりいないのかな?Masamichi Yoshiihttp://www.blogger.com/profile/10172931203881042645noreply@blogger.com0tag:blogger.com,1999:blog-3440838784271100850.post-45018519250080748292016-08-19T19:11:00.000+09:002016-08-25T09:58:16.099+09:00Androidアプリのパーミッションを確認する方法Android アプリの生命線とも言えるパーミッション。このパーミッションを確認しようと思ったら、意外にあちこちに情報が分散していて、まとまったものがありません。エンドユーザーの立場で確認する場合と、アプリ開発者の立場で確認する場合ではやり方が全く違います。色々なパーミッションの確認方法を自分なりに整理してみました。<br />
<br />
<a name='more'></a><br />
<br />
<h4>
Androidデバイス上で確認する</h4>
そのアプリが既にAndroidデバイスにインストールされていれば、設定メニューの「アプリ」から対象のアプリを選択すると、一番下に「許可」として、使用しているパーミッションが表示されます。これはお手軽ですが、プログラマーからするとほとんど役に立ちません。表示内容があまりにもエンドユーザー向き過ぎて、実際、AndroidManifest.xml の中で指定したどのパーミッションに該当するのかよく分からないからです。更に一般ユーザから見てもこれは中途半端なものです。これを見て何をどう判断しろと言うのでしょうか?<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgiRMZunWIlKBpf024gNbj0JnN1aWRN0H_hGi5CNESz_In-Gu8lFS0-y80X4hh8SWwTF9dt09GUlWqMuHOrmGWWgyJItAOuj-LQyCmMmUP6V83J4PZ_bZ4owNx6pw9cc146hoVtXv2NHSCx/s1600/device-2016-08-19-184730.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgiRMZunWIlKBpf024gNbj0JnN1aWRN0H_hGi5CNESz_In-Gu8lFS0-y80X4hh8SWwTF9dt09GUlWqMuHOrmGWWgyJItAOuj-LQyCmMmUP6V83J4PZ_bZ4owNx6pw9cc146hoVtXv2NHSCx/s320/device-2016-08-19-184730.png" width="180" /></a></div>
<br />
<h4>
Developer Console 上で確認する</h4>
アプリが Developer Console 上にアップロードされていて、あなたがその Developer Console にアクセスする権限を持っていれば、そこでパーミッションを確認することができます。Developer Consoleで対象アプリを選択し、APKの項目を開きます。更に目的のバージョンを選択すると「必要な権限」を確認することができます。<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhANClvwYxDEI_PyBVan4Q7P5yp2AjOfh7tp-J1nBoo0ti2psHtIMePdEnD5vsmB9r3Su2__h6LAtb6WcKdIvve2krUSfSv08wqukMkYMnIwp_tvMD63rejyjqOEO7d1l3tjML3pmnPFeAf/s1600/DevConsole-permissions.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhANClvwYxDEI_PyBVan4Q7P5yp2AjOfh7tp-J1nBoo0ti2psHtIMePdEnD5vsmB9r3Su2__h6LAtb6WcKdIvve2krUSfSv08wqukMkYMnIwp_tvMD63rejyjqOEO7d1l3tjML3pmnPFeAf/s320/DevConsole-permissions.png" width="318" /></a></div>
<br />
<h4>
aapt コマンドで確認する</h4>
apk が手元にあれば、aapt コマンドで確認することができます。aapt コマンドは Android SDK の Build Tools に含まれています。aapt を使った方法は色々なやり方があります。いくつか方法を示します。<br />
<pre>aapt l -a hoge.apk | grep -i permission
aapt d xmltree hoge.apk AndroidManifest.xml | grep -i permission
aapt d permisssions hoge.apk
</pre>
<div>
多分最後のやり方が一番確実ではないでしょうか。<br />
<br /></div>
<h4>
Android Studio上で確認する</h4>
ソースコードが手元にあれば、Android Studio 上で確認することができます。ソースがあれば単にAndroidManifest.xml を見ればいいだけだと思うかもしれませんが、話はそんな単純ではありません。基本的にはパーミッションは、開発者が AndroidManifest.xml の中で明示的に指定しますが、それ以外にもライブラリが勝手に引き込んでしまうものがあったりします。なので最終的な apk が使用するパーミッションを知るには、マージされた後の AndroidManifest.xml を見なくてななりません。アプリをビルドすると、<br />
<br />
app/build/intermediates/manifests/<br />
<br />
の下に Flavor や Build Variants (debug/release) に対応するディレクトリができて、その下に最終的なマージされた AndroidManifest.xml が生成されます。これを見るとアプリのパーミッションが確認できます。<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj7xN2FdHgxUlpH95lM2kHRAsCKKj10z6mfTCBcx115Gx_18roCWty-76HyWNvrw9fbm6alsrD_M3rZlSUwAGsUjSugEqzACvtJ3nhfbIKcKMRJwLFT_lvqa7ELlTvaVjDnh2RHbR-tChNW/s1600/android-permissions.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="166" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj7xN2FdHgxUlpH95lM2kHRAsCKKj10z6mfTCBcx115Gx_18roCWty-76HyWNvrw9fbm6alsrD_M3rZlSUwAGsUjSugEqzACvtJ3nhfbIKcKMRJwLFT_lvqa7ELlTvaVjDnh2RHbR-tChNW/s320/android-permissions.png" width="320" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<h4>
追記</h4>
Android Studio 2.2 では更に簡単に確認できるようになりました。プロジェクトの AndroidManifest.xml を開くと下部に Merged Manifest というタブがあり、これを選択するとマージされた Manifest とどのライブラリ由来のものかが確認できます。<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjnWHReh4cLxiF6LeOTUCBLZShOMY94WyuErMNz7cgZsy1v_r5C2LtRb_jlQ9ua-QDrjtRIUWjs0w8IYyb32WBXfr7q3_wtfjIoCvVir0bYL92KFzTQe9EcvxZAirRDeg9WyNpYdFz17sB7/s1600/merged_manifest.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="284" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjnWHReh4cLxiF6LeOTUCBLZShOMY94WyuErMNz7cgZsy1v_r5C2LtRb_jlQ9ua-QDrjtRIUWjs0w8IYyb32WBXfr7q3_wtfjIoCvVir0bYL92KFzTQe9EcvxZAirRDeg9WyNpYdFz17sB7/s320/merged_manifest.png" width="320" /></a></div>
<br />
更に、Build → Analyze APK... というメニューが追加され、任意の apk も解析できるようになりました。これを使っても確認できます。便利になったものです。<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjMJs9bPEYN81IqcDM6xYK2V5MMni2v_hNT3vXcg0_N4lFuM1tidjVM367yVy15PlAs3SnT-R_YIAv2PkC3W6Avjt0FZm7Y03Mui27jgzOb7jn-t1AvCrUHk4Cd_rdxDE4R5uLL3GkcKaZF/s1600/analyze_apk.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="274" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjMJs9bPEYN81IqcDM6xYK2V5MMni2v_hNT3vXcg0_N4lFuM1tidjVM367yVy15PlAs3SnT-R_YIAv2PkC3W6Avjt0FZm7Y03Mui27jgzOb7jn-t1AvCrUHk4Cd_rdxDE4R5uLL3GkcKaZF/s320/analyze_apk.png" width="320" /></a></div>
<br />
<br />
<br />Masamichi Yoshiihttp://www.blogger.com/profile/10172931203881042645noreply@blogger.com0