phicdy devlog

Androidアプリ開発やその他技術系の記事をたまに書きます

AndroidとかiOSとかモバイル多め。その他技術的なことも書いていきます。

【Android】API16のuiautomatorでNoSuchMethodError


uiautomatorでNoSuchMethodErrorが発生して困っていた問題が解決したのでメモ。

発生までの流れ

http://www.atmarkit.co.jp/ait/articles/1410/07/news029.html

に従ってテストプロジェクトを作り、ターゲットは、API16(4.1, 4.1.1)としていた。 そして(Android SDK)/platforms/android-16のuiautomator.jarを使っていた。

このテストプロジェクトでテストを作成し、API 17(4.2)の端末で実行したところ、NoSuchMethodErrorが発生してしまっていた。


原因

UiScrollableのsetAsVerticalList()の定義がAPI16とAPI17で変わっていた。

//4.1.1
public void setAsVerticalList()

//4.2
public UIScrollable setAsVerticalList()

参考:http://stackoverflow.com/questions/15204154/uiautomator-failing-on-4-1-2-device



今回はScrollViewの中身のViewを取得するために、UiScrollableのインスタンスを作り、setAsVerticalList()を設定していた。

UiScrollable scrollableView = new UiScrollable(new UiSelector().className(ScrollView.class.getName()).scrollable(true));
if (!scrollableView.exists()) {
    fail("scrollview does not exist");
}
scrollableView.setAsVerticalList();


メソッド名が同じで返り値を使っていたわけではないので、ビルドは普通に通る。 だがAPI17で実行しようとするとビルド時の定義と実際に呼ばれるメソッドが異なるためNoSuchMethodErrorが出ていたのかと思われる。

おわりに

悩んだ理由として、Android Developerのページには特にAPI Levelが書いていなくて困った。

変更したことを書いていて欲しい。

http://developer.android.com/tools/help/uiautomator/UiScrollable.html#setAsVerticalList%28%29

【Android】ActionBarから検索して別Activityで結果を表示する

検索してもなぜか、同じActivityで受け取る例ばかり出てくるので、自分のメモ用に。

検索を行うActivityをDoSearchActivity、検索結果を受け取るActivityをSearchResultActivityとする。

AndroidManifest

検索を行う側(DoSearchActivity)にandroid.app.default_searchableという名前のmeta-dataを追加する。
そしてvalueに検索結果を受け取るActvityを指定する

検索結果を表示するActivityも同様にmeta-dataを追加するが、こちらはandroid.app.searchableという名前にしてXMLのリソースを指定する。
そして、ActionBarから検索が行われたときにintentを受け取れるようにandroid.intent.action.SEARCHのアクションをintent-filterに追加する

<activity
	android:name="xxx.xxx.xxx.DoSearchActivity"
	android:configChanges="orientation|keyboardHidden|screenSize"
	android:label="@string/app_name" >
	<meta-data
    		android:name="android.app.default_searchable"
     		android:value="xxx.xxx.xxx..SearchResultActivity" />
</activity>
<activity
	android:name="xxx.xxx.xxx.SearchResultActivity"
	android:configChanges="orientation|keyboardHidden|screenSize"
	android:label="@string/app_name" >
	<meta-data
    		android:name="android.app.searchable"
		android:resource="@xml/searchable" />
    	<intent-filter>
		<action android:name="android.intent.action.SEARCH" />
     	</intent-filter>
</activity>

検索結果を表示するActivityのmeta-dataにて指定したXMLは以下のようにする。

<?xml version="1.0" encoding="utf-8"?>
<searchable xmlns:android="http://schemas.android.com/apk/res/android"
        android:label="@string/app_name"
        android:hint="@string/search" />


android:hintを指定することで検索バーをタップした時に表示する文字を指定できる。


http://developer.android.com/images/ui/actionbar-searchview@2x.png

Action Bar | Android Developers


DoSearchActivityのActionBarに検索バーを設置する

DoSearchActivityのメニューファイルに検索バーを追加することでActionBarに検索バーを表示できる。
Android SDKにSearchViewというクラスが用意されているのでそれを利用する。

<menu xmlns:android="http://schemas.android.com/apk/res/android" >
    <item
        android:id="@+id/search"
        android:actionViewClass="android.widget.SearchView"
        android:icon="@drawable/ic_action_search"
        android:showAsAction="collapseActionView|ifRoom"
        android:title="@string/search"/>
</menu>

よく見る検索のアイコンはGoogleが公式で配布しているので、ダウンロードして利用する。

Iconography | Android Developers


DoSearchActivityの設定

DoSearchActivityでは、メニューの設定でSearchViewを初期化する。
デフォルトの設定では、キーボードを閉じても検索バーが閉じてくれない。

そのため、OnFocusChangeListenerをセットしてフォーカスが他に移ったら検索バーを閉じるという処理を入れている。

参考:
android - Auto Collapse ActionBar SearchView on Soft Keyboard close - Stack Overflow

@Override
public boolean onCreateOptionsMenu(Menu menu) {
	MenuInflater inflater = getMenuInflater();
	inflater.inflate(R.menu.menu_do_search, menu);

	// SearchViewの初期化
	SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
	final MenuItem searchMenuView = menu.findItem(R.id.search);
	searchView = (SearchView)searchMenuView.getActionView();
	searchView.setSearchableInfo(searchManager
			.getSearchableInfo(getComponentName()));

	// 検索バーからフォーカスが移ったら、SearchViewを終了して空の文字列で検索する
	searchView.setOnQueryTextFocusChangeListener(new View.OnFocusChangeListener() {
        	@Override
        	public void onFocusChange(View view, boolean queryTextFocused) {
          		if(!queryTextFocused) {
            			searchMenuView.collapseActionView();
                		searchView.setQuery("", false);
            		}
        	}
    	});
	
	return true;
}



SearchResultActivityで検索結果を表示する

ユーザがキーワードを入れて検索をすると、Intentにそのキーワードが入る。

キーはSearchManager.QUERYなので、Intent#getStringExtra()にそのキーを指定することでユーザが入力したキーワードが取得できる。

あとはそのキーワードから検索を行い結果を表示すればよい。

Intent intent = getIntent();
if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
	String keyword = intent.getStringExtra(SearchManager.QUERY);

	// keyword を使ってデータベースから検索などをして結果を表示
}

SmartWatch 3を1ヶ月使ってみて

f:id:phicdy:20150530154646j:plain

前月ソニーモバイルコミュニケーションズから発売されたAndroid Wear端末のSmartWatch3を買いました。

SmartWatch 3を買いました - pluea engineering blog

SmartWatch3 SWR50 | ソニーモバイルコミュニケーションズ

だいたい1ヶ月くらい使ってみたので感想を書いていきたいと思います。

スマホで通知を見る回数が減った

Android Wearではスマホに来た通知をリアルタイムに受け取ることができます。

今まではスマホが振動する度に、

  1. スマホの電源をつける
  2. ロックを解除
  3. 通知領域を開いてなんの通知が来たのか確認
  4. 通知が必要なければ消す or 中身を見る

という流れでした。

Android Wearでは、スマホに通知が来ると時計が震え盤面に通知を知らせてくれます。そしてロックの解除が必要ないので、

  1. 時計を目の前に持ってくる
  2. 通知が必要なければ消す or 中身を見る

だけで通知を処理できます。

SmartWatch3の独自機能なのかAndroid Wearの機能なのかわかりませんが、腕を目の前に持ってくる動きをすると自動で盤面の電源が入ります。

なので、盤面が消灯していても盤面をタップすることなく盤面を明るくすることができます。この機能も結構便利です。

また、時計は常に腕につけているので、通知に気が付きやすいのも良い点です。

通知が必要なければスワイプで簡単に消すことが出来ます。もちろんAndroid Wear側で通知を消せば、スマホ側でも消えています。

通知を見る場合はアプリにもよりますが、対応していればAndroid Wear側で見ることができます。例えばGmailはタップすることでメールの中身を見れます。また地味に便利なのが、スマホでは2件以上メールが来ると通知がまとめられてしまって、Gmailアプリを開くしかないのですが、Android Wearではメール1つ1つを見ることができます。

対応していない場合はスワイプして「端末で開く」を選択してスマホ側で見る流れになります。

バッテリーについて

SmartWatch 3はサイズの小ささもあってバッテリー容量がそれほどあるわけではありません。

公式では約2日となっています。

また、連携するスマホ側では、Android Wearの機能をフルで使うためにGPSBluetoothを常にオンにしています。

1ヶ月使ってみたのですがスマホ・SmartWatch共にバッテリーで困ったことは特にありませんでした。

まずSmart Watch 3ですが、使う時間によるとは思うのですが、帰宅して外したときにバッテリー残量を見てみるとだいたい60~70%くらい残っています。

もしバッテリーが切れたとしても1時間でフル充電できるので、いざという時はモバイルバッテリー等で充電すればそれほど困りません。

ただ、ほぼ毎日充電する手間があるのと、充電の端子が刺しづらいという問題もあります。

f:id:phicdy:20150530154658j:plain

次にスマホ側ですが、Bluetoothの新規格であるBluetooth Low Energyが優秀なのかさほどバッテリー消費量は増えませんでした。

体感ですが、10%程度の増加でしょうか。

GPSに関しても常にGPSを使った処理をしているわけではないので、こちらもあまり影響はありませんでした。

ただ、常に位置情報を取得できる状態にあるので、その点は注意しないと余計な位置情報を漏らす可能性があります。

アプリについて

Android Wearに対応しているアプリがどの程度揃っているかによって使い心地が変わってくると思うのですが、 現状では概ね満足しています。

私が特に使っているのは

Wear Mini Launcher - Google Play の Android アプリ

=> Launcherを追加する

Yahoo!乗換案内 無料の時刻表、運行情報、乗り換え検索 - Google Play の Android アプリ

=> 電車の検索

Wear for Swarm - Google Play の Android アプリ

=> Foursquareのチェックイン

Wearable Widgets - Google Play の Android アプリ

=> Androidウィジェットを表示

です。

特にWear Mini Launcherは便利で、左からスワイプすることでアプリ一覧画面を開くことができるので、アプリへのアクセスが格段によくなります。

そもそもAndroid Wearのアプリ一覧へのアクセスがかなり悪いです。

アプリ一覧にアクセスするには、盤面をタップして一番下までスクロールし、「開始」を押さなければなりません。出てくるアプリ一覧も一行に一つなので非常に見難いです。

Wear Mini Launcherを入れることで、この辺りがかなり改善されました。

Yahoo!乗換案内は、予め経路を登録しておくことで現在時刻の経路をすぐに検索することができます。

急いでいるときなど、スマホを開いてアプリを開いて・・・というよりもかなり早く電車時刻の検索ができます。

最後に

Smart Watch3には概ね満足しています。これからも使い続けていこうと思います。

先週辺りにアップデートがきて、盤面のカスタマイズのAPIが使えるようになったので、ますます対応アプリが増えてくるかと思います。

まだまだAndroid Wearを使ってできることがあると思うので、新しい使い方を模索していこうと思います。

SmartWatch 3を買いました

f:id:phicdy:20150530185308j:plain

11/28にソニーからSmartWatch 3が発売されたので早速買ってきました。

SmartWatch3 SWR50 | ソニーモバイルコミュニケーションズ

値段はビックカメラで26870円でした。

予約なしで買えるのかなと少し思いましたが、普通に買えました。 ただ、あまり広告にやる気がなく、ビックカメラではモックしかなかったのでヨドバシで実機を見てから買いました。

外観と付属品

ディスプレイは1.6インチで小さめです。

時計なのでこのくらいのサイズでちょうどいいかと思います。

小さすぎてタッチがしにくいといったことはなかったです。

f:id:phicdy:20150530185325j:plain

ディスプレイの裏側です。

こちらに充電を行うmicroUSBポートがあります。

f:id:phicdy:20150530185341j:plain

バンドはゴム製になっています。

留め具のところは金属になっていてパチっとはめるだけで簡単に取り外しできるようになっています。

f:id:phicdy:20150530185351j:plain

付属品は以下の通りです。

  • SMART STYLEキャンペーンの申し込み用コード
  • 説明書
  • 保証書
  • microUSBケーブル(短め)

f:id:phicdy:20150530185401j:plain

SMART STYLEキャンペーン

私は店頭で知ったのですが、2014年11月28日(金)10:00 から 2015年1月14日(水)23:59 まで対象のXperiaを持っているとEdy5000円分がもらえるキャンペーンを行っています。

SMART STYLEキャンペーン | Xperia™ Store

Xperia Zシリーズを持っている方であれば、誰でももらえるようなので持っている方は期間内に買うと非常にお得かと思います。

設定

SmartWatch 3を起動するとまず言語設定を聞かれます。

日本語もあるので、日本語にしておきます。

f:id:phicdy:20150530185417j:plain

次にAndroid Wearをペアリングする端末にインストールするように表示されます。

f:id:phicdy:20150530185430j:plain

Google PlayAndroid Wearというアプリがあるのでそれをペアリングする端末にインストールして開きます。

f:id:phicdy:20150530185442p:plain

f:id:phicdy:20150530185454p:plain

あとはアプリの指示に従っていけば設定が完了します。

特に戸惑うことはなくスムーズにペアリングを行うことができました。

Bluetoothや位置情報設定などをオンにしておくと楽かと思います。

チュートリアル

設定が終わるとSmartWatch 3側でチュートリアルが始まります。

正直使い方が全然わからなかったので非常に助かりました。

f:id:phicdy:20150530185506j:plain

まず上にスワイプするように表示されます。

スワイプすると設定が完了した旨が表示されます。

f:id:phicdy:20150530185521j:plain

もう一度上にスワイプします。

f:id:phicdy:20150530185536j:plain

どうやらAndroid Wearではカードという単位で天気や通知などが表示されるようです。

f:id:phicdy:20150530185546j:plain

不要なカードは右にスワイプすることで削除できます。

f:id:phicdy:20150530185555j:plain

画面を暗くしたいときには手で覆えばいいようです。

MarkdownをPandocとブラウザを使ってPDFに変換する

GithubのREADMEなどで使われるMarkdownの形式で文章を書いたり、メモを取ることが最近多くなってきた。
ただ、Markdownの形式で書いていると、人に渡すときに結構困る。
というのもファイルを渡す人がMarkdownに慣れているとは限らないし、そもそもMarkdownエディタ自体をインストールしていなくてファイルを見られない可能性がある。

そこで今回はMarkdownのファイルをPDFに変換してみた。
変換方法を調べるとMarkdown→Tex→PDFというやり方が多く出てくるが、Texの環境を入れるのがなかなか大変(時間がかかる)ので、ただMarkdownを変換したいだけの今回はパスする(綺麗に変換をしたいならこのやり方の方がよい)。
今回の変換方法としては、MarkdownをHTMLファイルに変換し、それをブラウザで開いてPDFで保存する。

今回はMarkdownをHTMLに変換するツールとしてPandocを使う。
PandocのインストールはMacのhomebrewを使って行った。

参考: Haskell製のMarkdownをHTMLやLaTeXに変換できるPandocのインストールとサンプル - 三等兵

※結構時間がかかるので注意

$ brew install haskell-platform
$ cabal update
$ cabal install cabal-install    // cabalのアップデート
$ cabal install pandoc

私はbashを使っているので~/.bash_profileに以下を追加する。

export PATH=$PATH:${HOME}/.cabal/bin

パスが通っているかを確認

$ source ~/.bash_profile     // bash_profileを再読み込み
$ echo $PATH                 // パスを確認
$ pandoc -v 

現在(2014/11/5)時点でバージョン1.13.1がインストールされた。

$ pandoc -v
pandoc 1.13.1
Compiled with texmath 0.8, highlighting-kate 0.5.9.
Syntax highlighting is supported for the following languages:
    abc, actionscript, ada, apache, asn1, asp, awk, bash, bibtex, boo, c,
    changelog, clojure, cmake, coffee, coldfusion, commonlisp, cpp, cs, css,
    curry, d, diff, djangotemplate, dot, doxygen, doxygenlua, dtd, eiffel,
    email, erlang, fasm, fortran, fsharp, gcc, glsl, gnuassembler, go, haskell,
    haxe, html, ini, isocpp, java, javadoc, javascript, json, jsp, julia, latex,
    lex, lilypond, literatecurry, literatehaskell, lua, m4, makefile, mandoc,
    markdown, mathematica, matlab, maxima, mediawiki, metafont, mips, modelines,
    modula2, modula3, monobasic, nasm, noweb, objectivec, objectivecpp, ocaml,
    octave, opencl, pascal, perl, php, pike, postscript, prolog, pure, python,
    r, relaxng, relaxngcompact, rest, rhtml, roff, ruby, rust, scala, scheme,
    sci, sed, sgml, sql, sqlmysql, sqlpostgresql, tcl, tcsh, texinfo, verilog,
    vhdl, xml, xorg, xslt, xul, yacc, yaml, zsh
Default user data directory: /Users/kyamaguchi/.pandoc
Copyright (C) 2006-2014 John MacFarlane
Web:  http://johnmacfarlane.net/pandoc
This is free software; see the source for copying conditions.
There is no warranty, not even for merchantability or fitness
for a particular purpose.

そしてMarkdownをHTMLファイルに変換する。

$ pandoc -s hoge.md -o hoge.html

最後に変換されたHTMLをブラウザで開いて印刷メニューからPDFで保存する

f:id:phicdy:20150530185822p:plain

【iOS, Objective-C】 NSUserDefaultsを使って簡単なデータを保存する

iOSではデータを保存する方法が何種類かある。
・ファイル
・データベース(SQLite)
・Core Data
・NSUserDefaults
その中で一番簡単にデータを保存できるのがNSUserDefaultsである。
NSUserDefaultsではキーと値のセットで値が保存できる。
そのため、複雑な構造のデータの保存にはあまり向いていないが、オプションやフラグの値の保存程度ならば簡単に実装できる。

[[NSUserDefaults standardUserDefaults]setBool:YES forKey:@"key"];
Bool value = [[NSUserDefaults standardUserDefaults]boolForKey:@"key"] // YES

setBoolではBoolの値が保存できるが、それ以外の値用のメソッドも用意されている。

メソッド保存できる値
setObject id
setInteger NSInteger
setURL NSURL
setFloat float
setDouble double
setBool Bool

例えばスイッチが1つあってそのオン/オフの状態を保存する場合、以下のようになる。

#import "ViewController.h"

@interface ViewController ()

@property (weak, nonatomic) IBOutlet UISwitch *switch;
@property (weak, nonatomic) NSString *switchKey;

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    _switchKey = @"switchKey";

    _switch.on = NO;
    // 保存した値を読み込み
    if ([[NSUserDefaults standardUserDefaults]boolForKey:_switchKey]) {
        _switch.on = YES;
    }
   
    // スイッチの状態が変わったときに呼ばれる先を指定
    [_switch addTarget:self action:@selector(switchDidChange) forControlEvents:UIControlEventValueChanged];
}

- (void)switchDidChange {
	// スイッチの状態が変わったら現在のスイッチの状態をNSUserDefaultsに保存
    if (_switch.on == YES) {
        [[NSUserDefaults standardUserDefaults]setBool:YES forKey:_switchKey];
    }else {
        [[NSUserDefaults standardUserDefaults]setBool:NO forKey:_switchKey];
    }
}


スクリーンショット

f:id:phicdy:20150530185951p:plain

実際にスイッチを切り替えてからアプリを開き直すと、スイッチの状態が保存されていることが確認できる。

【Android】HanlderとMessageを使ってマルチスレッドの処理結果を受け取る

Androidアプリ内で重たい処理を行う場合、処理をメインスレッドで行うと処理が終わるまで画面が止まってしまう。
そのため、スレッドを分けて処理を行わせる。
その際に、処理結果を別スレッドから受け取るときの一つの方法としてHandlerとMessageを使う方法がある。

Handler handler = new Handler() {
	
    @override
    public void handleMessage(Message msg) {
        switch(msg.what) {
	    case 1:
	        // msg.objはObject型なのでキャストする必要がある
	        Log.d("Handler", (String)msg.obj);
	        break;
     	    default : 
    	        break;
        }
    }
}

class TaskTread extends Thread {
	private Handler handler;
	
	public TaskThread(Handler handler) {
		this.handler = handler;
	}
	
	@override
	public void run() {
		//重たい処理
		// ・・・
		
		Message msg = Message.obtain();
		// 実際にはマジックナンバーではなく値を決めておく
		msg.what = 1;
		
		// 結果がString型のresultにあると仮定
		msg.obj = result;
		handler.sendMessage(msg);
	}
}


まず、Handlerをメインスレッド内で宣言する。
Handlerはスレッドを超えてMessageクラスのオブジェクトを送受信することができる。
送信のメソッドは変更の必要がないので、受信を行うメソッドであるhandlerMessage(Message msg)のみoverrideする。

Handler handler = new Handler() {
	
    @override
    public void handleMessage(Message msg) {
        switch(msg.what) {
	    case 1:
	        // msg.objはObject型なのでキャストする必要がある
	        Log.d("Handler", (String)msg.obj);
	        break;
     	    default : 
    	        break;
    }
}

Messageはwhatというintのpublic変数を持っている。
送信の際にこのwhatを設定してあげることで、switch文などで送信先によって処理を分けることができる。
実際に受け取るデータはmsg.objに入っており、キャストしてデータを受け取る。

次に処理を行わせるスレッド(TaskTread)を宣言し、コンストラクタでHandlerを渡しておく。
このHandlerを利用して後にMessageをTaskThreadからメインスレッドに送信する。

class TaskTread extends Thread {
	private Handler handler;
	
	public TaskThread(Handler handler) {
		this.handler = handler;
	}
	
	@override
	public void run() {
		//重たい処理
		// ・・・
		
		Message msg = Message.obtain();
		// 実際にはマジックナンバーではなく値を決めておく
                msg.what = 1;
		
		// 結果がString型のresultにあると仮定
		msg.obj = result;
		handler.sendMessage(msg);
	}
}


TaskThreadで重たい処理を行わせた後、Messageの設定を行う。
MesageのインスタンスはMessage.obtain()で取得できる。
newでインスタンスを作ることもできるが、obtain()で行うことが推奨されており、効率がよい。

While the constructor of Message is public, the best way to get one of these is to call Message.obtain() or one of the Handler.obtainMessage() methods, which will pull them from a pool of recycled objects.

Message | Android Developers

次にwhatに送信先を識別するintを設定し、objに送信するデータを設定する。
そして最後にsendMessage(msg)でMessageを送信する。

sendMessage()を呼ぶとHabdlerのhanldeMessage()がメインスレッドで呼ばれ、処理結果を受け取ることができる。