CakePHP基礎の基礎
こんにちは。 アスネット開発部の名倉です。
今回は急遽学習することになったCakePHPについて、得た情報を「CakePHP基礎の基礎」という形で記事にしたいと思います。
◆目次
PHP用のWebアプリケーションフレームワークです。MVCモデルを採用しています。 この「Cake」という名前には「a piece of cake」(朝飯前、すごく簡単)という意味が込められているのだそうです。 概要ついでにMVCフレームワークについても少し触れておきましょう。
ビジネスロジックを扱います。
DBへの問い合わせやデータの登録/削除はModelに実装します。
その他にも、複雑な計算などはモデルで実装し、処理結果をControllerへ渡します。 ユーザが見る「見た目」の部分に該当します。
HtmlやJavaScriptはViewに実装します。 ControllerはModelとViewを繋ぎます。
ユーザからの入力を受け、必要なModelに適宜入力値を渡し処理を実行させ、処理結果を受け取ってViewに反映して表示する、言うなれば司令塔の役割を担っています。 実際のソースコードをMVCに分けて見ていきましょう。
サンプルとして、書籍データの一覧表示と新規追加する機能を紹介します。 Controllerは必ずAppControllerを継承する必要があります。
Controllerで扱うモデルは明示する必要があります。
上記の場合は ここでBookモデルを使うことを宣言しています。
この書き方ではController内全メソッドにおいてBookモデルを使用できるようになりますが、
1メソッドでしか扱わないモデルは、メソッドの中に のように書くことでモデルを使用できるようになります。 続いてViewヘルパーです。 このように書くことでFormヘルパーをView側で使用できるようになります。
なお、サンプルなのでこのように書いていますが、デフォルトで
Formヘルパー
Htmlヘルパー
Sessionヘルパー
の3つは使用できるようになっていますので、これらは明示する必要はありません。
自作したヘルパーを使いたいときなどに記述してください。 次にメソッドの説明を少々。
searchメソッドは書籍一覧を取得、表示するメソッドです。
まずはBookモデルのsearchメソッド(処理内容は後述します)を呼び出し、書籍一覧を取得します。 この1行はView側で取得値を利用するための記述です。
これにより、Controller側で$booksに入っている値をView側の変数「$books」に渡すことができます。
第1パラメータ'books'がViewで使用する変数名、第2パラメータ$booksがControllerから渡す値に該当します。 表示するViewのファイル名を指定します。この記述により表示する画面では「apps/View/Samples/search.ctp」を使用します。
因みに、例として挙げるため明記していますが、書かないことも可能です。
書かない場合は「apps/View/(Controller名)/(アクション名).ctp」というルールでファイルを探し、使用します。
searchメソッドであれば「apps/View/Samples/search.ctp」となり、
実は、明記した場合と明記しない場合に使用するファイルは同じだったりします。 addメソッドは書籍登録画面で入力されたデータの登録を行います。
なお、addメソッドの処理が行われるのはPOST通信が発生したときのみ、という条件をつけています。
画面初期表示時は特に処理はありません。
POST通信が発生した時の処理について順番に説明すると、 ①POSTされた入力データを受け取る $dataには['Book'][Title']などのデータが入ります。 keyとなる値はctpファイルのフォームに記述した これの第1パラメータ'Book.Title'をピリオドで区切った値となります。
因みに、$this->request->data;で受け取れるのはPOSTされたデータです。
GET送信の場合は$this->request->query;で取得可能で、他にもJSON等の受け取り方も別に存在しますがここでは割愛します。 ②データの保存 saveメソッドはCakePHPの標準のメソッドです。
挿入も更新も行えるメソッドとなっており、どちらを行うかはテーブルの主キーを登録するデータに持っていれば更新、無ければ挿入、となりますが、
主キーがデータにあってもsaveメソッドで主キーが一致するデータのselectが行われ、存在しなければ挿入が発生します。 戻り値は以下のようになります。
登録成功:配列に登録したデータが格納されている
登録失敗:
true(Modelにセットしたデータが正しい形でない)
false(バリデーションエラー等が原因) そのため、登録成否の条件判定はis_arrayメソッドを用いて配列か否かをチェックしています。
成功したときはsearchメソッドにリダイレクトさせ、画面表示時に登録完了メッセージを表示させます。
失敗する場合には
$this->request->data = $data;
これによりフォームの第1パラメータに一致するkeyを持つデータが入力フォームに入った状態で画面を再表示することが出来ます。 Modelは必ずAppModelを継承する必要があります。 はコメントにも書きましたが、Modelで扱うテーブルと他テーブルの関係を表します。
Books : Manage = 1 : n のため、hasMany(Booksから見て、複数のManageデータを持っている)に設定しています。
1:1、n : 1、n : nは別の書き方で表します。 searchメソッドは書籍一覧で有効なデータを取得します。
第1パラメータ'all'は条件に合致するデータを全件取得します。
他に 'first':条件に合致するデータの上位1件のみ取得 'list':1つのkeyに対して1つの値を持った配列の一覧を取得します。keyと値に対応する項目を明示しない場合、IDとNameまたはTitle等の項目から一覧を作成します。 等があります。 第2パラメータは取得条件を設定します。 'fields' => array('ID', 'Title', 'Writer', 'PublishYear', 'Price', 'Introduction')
取得項目select。明示しない場合はテーブルの全ての項目となります。 'conditions' => array('DeleteFlag' => 0),
取得条件where。条件は配列で指定します。'DeleteFlag' => 0はDeleteFlag = 0という条件になります。 'order' => 'PublishYear DESC'
並び替え条件order by。 'recursive' => -1
テーブルと親子関係にあるテーブルのデータを取得するための項目。-1は親子関係のデータは取得しません。詳細は省略しますが、値が1で直接関係があるテーブル、更に値が大きくなると間接的に関係のあるテーブルからもデータが取得されます。 search.ctp こちらは書籍一覧を表示する画面です。
変数に格納した配列のkey値を指定してkeyに対応した値(Value)を表示しています。
cakephpの機能を利用するときは必ず「echo」を行った上で値の出力やフォームの作成を行います。 このctpファイルだけで表示出来ないよね?と過った方、お察しの通りです。
このsearch.ctpで記述した内容はCakePHPとは?
本題とは関係ありませんが、美味しそうな名前ですよね。そう思ったのは私だけではないはず…。
「MVC」とはプログラムを構成する要素の頭文字をとったものです。
ソースコードを見てみよう
Controller
<?php
public class SamplesController extends AppController{
//使用するモデルを宣言
public $uses = array('Book');
//Viewで使用するヘルパーを宣言
public $helpers = array('Form');
//書籍一覧を取得、表示する
public function search(){
//Bookテーブルのデータを取得する
$books = $this->Book->search();
$this->set('books', $books);
$this->render('Samples/search');
}
//add.ctpからPOSTされたデータをBooksテーブルに登録する
//add.ctpについては後述
public function add(){
//postされた場合のみ処理を行う
if($this->request->is('post')){
//postされたデータを取得する
$data = $this->request->data;
try{
$saveResult = $this->Book->save($data['Book']);
if(is_array($saveresult)){
//登録成功メッセージを設定する
$message = sprintf(' %s を追加しました。',
$data['Book']['Title']);
$this->Session->setFlash($message);
//Searchにリダイレクトする
$this->redirect(array(
));
}
}catch(Exception ex){
//例外処理(サンプルなので省略)
}
//エラーがある場合
//入力内容を復元して画面表示
$this->request->data = $data;
}
}
}
//使用するモデルを宣言
public $uses = array('Book');
$this->loadModel('Book');
//Viewで使用するヘルパーを宣言
public $helpers = array('Form');
//Bookテーブルのデータを取得する
$books = $this->Book->search();
$this->set('books', $books);
$this->render('Samples/search');
$data = $this->request->data;
<?php
echo $this->Form->input('Book.Title', array(
'type' => 'text'
'lavel' => 'タイトル'
);
?>
$saveResult = $this->Book->save($data['Book']);
Model
<?php
public class Book extends AppModel{
//Booksテーブルと他テーブルとの関係性を宣言
public $hasMany = array('Manage');
//削除されていないデータを出版年で並べかえて取得する
public function search(){
$books = $this->find('all',array(
'fields' => array('ID', 'Title', 'Writer', 'PublishYear', 'Price', 'Introduction')
'conditions' => array('DeleteFlag' => 0),
'order' => 'PublishYear DESC'
'recursive' => -1
));
return $books;
}
}
public $hasMany = array('Manage');
View
<h2> 書籍一覧 </h2>
<table>
<tr>
<td>ID</td>
<td>タイトル</td>
<td>著者</td>
<td>紹介文</td>
<td>出版年</td>
<td>金額</td>
</tr>
<?php foreach($books as $book) : ?>
<tr>
<td><?php echo $book['Book']['ID']; ?></td>
<td><?php echo $book['Book']['Title']; ?></td>
<td><?php echo $book['Book']['Writer']; ?></td>
<td><?php echo $book['Book']['PublishYear']; ?></td>
<td><?php echo $book['Book']['Price']; ?></td>
<td><?php echo $book['Book']['Introduction']; ?></td>
</tr>
<?php endforeach; ?>
</table>
続いては、書籍情報を登録する画面です。 こちらもレイアウトにdefault.ctpを利用しています。
add.ctp
<?php echo $this->Form->create('Book', array('type' => 'post')); ?> <?php echo $this->Form->input('Book.Title', array( 'type' => 'text' 'lavel' => 'タイトル' ); ?> <?php echo $this->Form->input('Book.Writer', array( 'type' => 'text' 'lavel' => '著者' ); ?> <?php echo $this->Form->input('Book.Introduction', array( 'type' => 'text' 'lavel' => '紹介文' ); ?> <?php echo $this->Form->input('PublishYear', array( 'type' => 'text' 'lavel' => '出版年' ); ?> <?php echo $this->Form->input('Book.Price', array( 'type' => 'text' 'lavel' => '金額' ); ?> <?php echo $this->Form->end('登録する'); ?>
フォームの開始、終了を指定するための記述は以下の部分です。
<?php echo $this->Form->create('Book', array('type' => 'post')); ?> <?php echo $this->Form->end('登録する'); ?>
createメソッドの第2パラメータ
'type'=>'post'
は送信方式を明示しています。 データ送信が発生したときにPOST送信を行います。明示しない場合はGET送信をおこないます。 第1パラメータは登録の対象となるモデルの設定を行っています。 サンプルの場合は'Book'モデルに対応するデータを扱うため第1パラメータに'Book'を設定しています。 endメソッドにのパラメータに設定した'登録する'という文字列は送信を発生させる(submitする)ボタンに表示するテキストになります。 設定することでフォームの最下部にボタンが作られます。 パラメータを渡さなければボタンは作られず、任意の場所にボタンを作ることも可能です。
次に入力フォームを構成する記述です。
<?php echo $this->Form->input('Book.Title', array( 'type' => 'text' 'lavel' => 'タイトル' ); ?>
終わりに
いかがでしたでしょうか? cakephp、ルールに則ったソースで開発できれば、ソースコードボリュームを減らすことができ、Javaを見慣れた私にとってはなかなかに便利さを感じています。 本当はもっと沢山学習したことがあるのですが、記事の長さが途轍もないことになりそうなので大雑把な紹介とさせて頂きました。 参考までに私の学習に用いた書籍、公式ドキュメントのURLもご紹介します。 学習の第一歩としてご活用いただければ幸いです。
【書籍】 CakePHP2 実践入門 (WEB+DB PRESS plus) 技術評論社,安藤祐介, 岸田健一郎, 新原雅司, 市川快, 渡辺一宏, 鈴木則夫,2012
静的解析ツールを導入しよう!!
静的解析ツールを導入しよう!!
こんにちは。アスネット開発部の梶原です。今回は静的解析ツールの導入に関して紹介したいと思います。
チームでコードを書く場合にはコードに統一感があるほうが見やすく、
コードレビューの時も「スペースが一個あいてないよ」とか「変数名は小文字で…」とかは本来レビューで指摘するべき箇所からずれてしまい、こういった指摘はレビューする方もされる方もうんざりしてしまいますよね。
JavaではFindbugs, CheckStyle,PMD等のコードを解析するためのツールがそろっていますので、Androidの開発でもどんどん使っていきましょう。
という訳で、今回はその中でもFindbugs, CheckStyleの導入手順について書きたいと思います。
Findbugsの導入手順
- 静的解析を行い、バグの可能性があるコードを指摘するプラグイン
コマンドライン上からfindbugsを実行する
下記の記述を適当な場所に追加、もしくは追記してください。 別ファイルとしてfindbugs_plugin.gradleにtaskとして記述することで、設定ファイルの再利用を簡単にしています。
app/build.gradle
apply from: "${project.rootDir}/gradle/findbugs_plugin.gradle"
gradle/findbugs_plugin.gradle
apply plugin: 'findbugs' task findbugs(type: FindBugs) { ignoreFailures = true effort = "max" reportLevel = "low" // 必要であれば静的解析から除外するファイルを指定してください // excludeFilter = new File("${project.rootDir}/config/findbugs/findbugs_filter.xml") classes = files("build/intermediates/classes/") source 'src/main' include '**/*.java' reports { xml { destination "${project.projectDir}/build/reports/findbugs/findbugs_report.xml" } } classpath = files() } check.dependsOn 'findbugs'
config/findbugs/findbugs_filter.xml
<?xml version="1.0" encoding="UTF-8"?> <FindBugsFilter> <Match> <Class name="~.*.R"/> </Match> <Match> <Class name="~.*.R\$.*"/> </Match> </FindBugsFilter>
実行
$ ./gradlew check
Findbugs Plugin
コマンドライン上からの実行のみであれば下記のプラグインのインストールは必要ありませんが、 GUIから実行したい時には下記のプラグインを導入して実行してください。
プラグインの導入手順
- Preferences > Plugins で FindBugs-IDEA を検索し、インストール
- Preferences > FindBugs-IDEA
- Reporting > Minimum confidence to report を Low に設定
- Advanced にチェック
- Analysis effort を Maximal に設定
CheckStyleの導入手順
- コーディング規約をチェックするための静的解析ツール
コマンドライン上からcheckstyleを実行
こちらも同様に、下記の記述を適当な場所に追加もしくは追記してください。
app/build.gradle
apply from: "${project.rootDir}/gradle/checkstyle_plugin.gradle"
gradle/checkstyle_plugin.gradle
apply plugin: 'checkstyle' task checkstyle(type: Checkstyle) { configFile file("${project.rootDir}/config/checkstyle/checkstyle.xml") configProperties.checkstyleConfigDir = file("${project.rootDir}") source 'src/main' include '**/*.java' reports { xml { destination "${project.projectDir}/build/reports/checkstyle/checkstyle.xml" } } classpath = files() } check.dependsOn 'checkstyle'
config/checkstyle/checkstyle.xml
<module name="Checker"> <property name="charset" value="UTF-8"/> ... <!-- 静的解析から除外するファイルがある場合に使用してください --> <module name="SuppressionFilter"> <property name="file" value="${checkstyleConfigDir}/config/checkstyle/checkstyle_suppressions.xml"/> </module> </module>
config/checkstyle/checkstyle_suppressions.xml
<suppressions> <suppress checks=".*" files="src/test/java/.*" /> </suppressions>
実行
$ ./gradlew check
コーディング規約に関しては会社、チームによって差があるかともいますが新しく作成する場合にはGoogleが出しているGoogle Java StyleもしくはCode Style for Contributorsあたりが参考になるかと思います。
参考
上記の内容を反映したテンプレートとなるプロジェクトは下記に置いてあります。ご参考にしてください。
https://github.com/dkajiwara/AndroidTemplate
Google Java Style
http://google.github.io/styleguide/javaguide.html
Code Style for Contributors
http://source.android.com/source/code-style.html
LinearLayoutの使い方
こんにちは。アスネット開発部の名倉です。 今回はAndroidアプリの画面を作成するためのレイアウトの中から、LinearLayoutについてご紹介します。
LinearLayoutとは
LinearLayoutは子要素を縦一列、もしくは横一列に並べて配置するレイアウトです。
子要素を縦配置するには
LinearLayoutの属性 android:orientation="vertical" を設定します。これだけです。
子要素を横配置するには
LinearLayoutの属性 android:orientation="horizontal" を設定します。こちらもこれだけです。
縦配置のレイアウト、横配置のレイアウトにボタンを配置して表示させてみます。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@color/accent_material_dark" android:orientation="vertical"> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="1"/> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="2"/> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="3"/> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@color/accent_material_light" android:orientation="horizontal"> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="4"/> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="5"/> </LinearLayout> </LinearLayout>
表示結果はこのようになります。 ※それぞれのLinearLayoutには背景色をつけています。
位置決め
LinearLayoutに子要素を配置するとデフォルトで左寄せになってしまいますが、 android:layout_gravity="(表示位置 left/center/rightなど)" を設定することで表示位置を変更することが出来ます。 "|"で区切ることで組み合わせて使うことも可能です。(例:"top|right")
先ほどのサンプルに設定してみます。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@color/accent_material_dark" android:orientation="vertical"> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="left" android:text="1"/> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:text="2"/> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="right" android:text="3"/> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@color/accent_material_light" android:orientation="horizontal"> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="4"/> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="5"/> </LinearLayout> </LinearLayout>
表示してみます。
重みづけ
LinearLayoutでは、子要素に重みをつけることが出来ます。 方法は、子要素に android:layout_weight="(数値)" を設定します。 また、重みづけする子要素はサイズ指定を0dpに設定します。 android:orientation="vertical"の子要素であればandroid:layout_height="0dp" android:orientation="horizontal"の子要素であればandroid:layout_width="0dp"
サイズを0dp以外に指定すると、そのサイズを先に確保してしまうので気を付けましょう。 先ほどのサンプルに重みづけを行いました。 ※比率を見やすくするためandroid:paddingも追加しています。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <LinearLayout android:layout_width="match_parent" android:layout_height="0dp" android:padding="10dp" android:orientation="vertical" android:background="@color/accent_material_dark" android:layout_weight="2"> <Button android:layout_width="match_parent" android:layout_height="0dp" android:text="1" android:layout_weight="1"/> <Button android:layout_width="match_parent" android:layout_height="0dp" android:text="2" android:layout_weight="1"/> <Button android:layout_width="match_parent" android:layout_height="0dp" android:text="3" android:layout_weight="1"/> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="0dp" android:padding="10dp" android:orientation="horizontal" android:background="@color/accent_material_light" android:layout_weight="1"> <Button android:layout_width="0dp" android:layout_height="match_parent" android:text="4" android:layout_weight="1"/> <Button android:layout_width="0dp" android:layout_height="match_parent" android:text="5" android:layout_weight="1"/> </LinearLayout> </LinearLayout>
上下のレイアウトを2:1の割合で重みづけ、 それぞれの子要素は等比率(全て1)で重みづけしています。 表示するとこんな具合です。
終わりに
いかがでしたでしょうか? 今回は開発者としては初歩的な内容でしたが、これだけでも分かると画面表示のバリエーションが広がります。 基礎固めの参考にでもしていただければと思います。
参考URL
今回は重みづけを0dpで行うようにしましたが、wrap_contentを使ったやり方もあります。
Androidアプリ開発入門 リニアレイアウト (LinearLayout) の子の部品の配置 (重み付け) http://androidguide.nomaki.jp/html/layout/linearlayout/weight.html
開発中によく使用するadbコマンド
開発部の梶原です。
adbコマンドを使いこなしていますか?
SDKの中にはadb install以外にも開発時に使えるコマンドがたくさんそろっていますので参考にしてみてください。
adbのPathを通す
まずは、ADBを使うためにパスを通しましょう。
Mac
下記のように、.bash_profileを編集してパスを通してください。
$ vi .bash_profile #ANDROIDのパス ANDROID_HOME=/Users/dkajiwara/android-sdk-macosx export PATH=$PATH:${ANDROID_HOME}/platform-tools $ source ~/.bashrc $ adb version Android Debug Bridge version 1.0.32
Windows
windowsを使用している方は下記等を参考にしてパスを通してください。
参考:Windows 8.1でadbコマンドを使えるようにする簡単な方法を紹介
http://gadget-drawer.net/2014/05/windows-8-1-adb-command/
接続している端末を確認する
$ adb devices List of devices attached 192.168.56.101:5555 device YT910ZK7PB device
特定の端末に接続する
-sオプションで一覧で確認した端末にアクセス出来ます。
$ adb -s YT910ZK7PB
APKのインストール・アップデート・アンインストール
これはもう言わずもがなですが、端末にアプリをインストールするときに使用します。
インストール
$ adb install MyApp.apk
アップデート
$ adb install -r MyApp.apk
-rオプションを付けることで、データは消されずに上書きインストール出来ます。
アンインストール
アンインストールする際にはアプリのパッケージ名を指定します。
$ adb uninstall com.sample.myapp
アプリのデータを削除する
$ adb shell pm clear com.sample.myapp
ログの出力
$ adb shell logcat # 日付も表示 $ adb shell logcat -v time # ログのクリア $ adb logcat -c
下記のようにリダイレクトして適当なところに出力することが出来ます。
$ adb shell logcat -v time > ~/Desktop/test.log
ADBの起動・停止する
adbがつながらなくなったり、端末を認識しなくなった場合に、一度adbを再起動させることで復帰出来る場合があります。
# 起動 $ adb start-server # 停止 $ adb kill-server
Intentを投げる
# 明示的Intentを投げる $ adb shell am start -n com.sample.myapp/.MyAppActivity # 暗黙的Intentを投げる $ adb shell am start -a android.intent.action.VIEW -d http://xxxx.xxxx # サービスの起動 (Intentの指定方法はActivityと同じ) $ adb shell am startservice ... # ブロードキャストの送信 (Intentの指定方法はActivityと同じ) $ adb shell am broadcast ...
明示的Intentを投げるときは-n アプリのパッケージ名/Activity 暗黙的Intentを投げるときは-a Action名
インストール済みのapkのversionを確認する
$ adb shell dumpsys package com.android.email ~省略~ Packages: Package [com.android.email] (42721300): versionCode=600010 targetSdk=19 versionName=6.1 applicationInfo=ApplicationInfo{4261a488 com.android.email}
View階層の確認する
アプリが複雑になってくるとコード追うのが大変でこの画面どこのViewだっけ? そんな時にすぐに確認できるので便利です
$ adb shell dumpsys activity top TASK com.google.android.gm id=62 ACTIVITY com.google.android.gm/.ComposeActivityGmail 42011f98 pid=4333 Local FragmentActivity 42f3d0e0 State: mCreated=truemResumed=true mStopped=false mReallyStopped=false mLoadersStarted=true FragmentManager misc state: mActivity=com.google.android.gm.ComposeActivityGmail@42f3d0e0 mContainer=android.support.v4.app.n@436f5cc8 mCurState=5 mStateSaved=false mDestroyed=false View Hierarchy: com.android.internal.policy.impl.PhoneWindow$DecorView{42efe2f0 V.E..... ... 0,0-720,1184} android.widget.LinearLayout{42f16570 V.E..... ... 0,0-720,1184} android.view.ViewStub{42ff0e70 G.E..... ... 0,0-0,0 #1020328} android.widget.FrameLayout{4357c578 V.E..... ... 0,50-720,1184} android.support.v7.internal.widget.ActionBarOverlayLayout{43304068 V.ED.... ... 0,0-720,1134 #7f0f009b app:id/decor_content_parent}
AlarmManagerでセットしたタイマーを確認する
$ adb shell dumpsys alarm RTC_WAKEUP #6: Alarm{44cecb00 type 0 com.google.android.gms} type=0 whenElapsed=2667655216 when=+29d22h22m58s745ms window=0 repeatInterval=0 count=0 operation=PendingIntent{42025980: PendingIntentRecord{4319de80 com.google.android.gms broadcastIntent}}
ContentProviderにqueryを投げる
※Android4.1.1以上で使用可能です
$ adb shell content query --uri content://media/external/images/media Row: 0 _id=1592, _data=/storage/emulated/0/DCIM/100ANDRO/DSC_0001.JPG, _size=4072815, _display_name=DSC_0001.JPG, mime_type=image/jpeg, title=DSC_0001, date_added=1433051343, date_modified=1433051343, description=NULL, picasa_id=NULL, isprivate=NULL, latitude=35.71935, longitude=139.77538, datetaken=-1467734462, orientation=90, mini_thumb_magic=-1370636478, bucket_id=-413971691, bucket_display_name=100ANDRO, width=3840, height=2160
インストールされているパッケージの一覧を表示
$ adb shell pm list package
いかがだったでしょうか、今回はAndroidのADBコマンドについてよく使うものをまとめてみました。 今回紹介したもの以外にもまだあるので使って試してみてください。
参考URL
Android開発コマンド
https://sites.google.com/site/memo73737/android-dev/android-dev-command
Android端末同士でのUDP・TCP通信
こんにちは。アスネット開発部の名倉です。
今回はUDP通信を使って、同一のWi-Fiに接続したホストとゲストが互いのIPアドレスを知らない状態からTCP通信を成立させるまでのプログラムを紹介します。
概要
処理の流れを簡単にまとめるとこんな感じです。
①ホスト:ゲストから発信されるブロードキャストを受信できる(受信待ち受け)状態にする。
②ゲスト:ホスト探索開始。ゲスト端末のIPアドレスを発信する。
③ホスト:ゲストから受信したIPアドレスに対してホスト端末のIPアドレスを送り返す。
④ゲスト:ホストから端末情報を受け取る
⑤ゲスト:ホストのIPアドレスが判明して、TCP通信を開始する。
それでは、ひとつずつ見ていきましょう。
①ホスト:ゲストから発信されるブロードキャストを受信できる(受信待ち受け)状態にする。
ゲストからいくらブロードキャスト送信をしていても、ホスト側で受け取り態勢が整っていなければ受け取ることが出来ません。まずはゲストが送信を開始する前に待ち受け状態にします。
DatagramSocket receiveUdpSocket; boolean waiting; int udpPort = 9999;//ホスト、ゲストで統一 //ブロードキャスト受信用ソケットの生成 //ブロードキャスト受信待ち状態を作る void createReceiveUdpSocket() { waiting = true; new Thread() { @Override public void run(){ String address = null; try { //waiting = trueの間、ブロードキャストを受け取る while(waiting){ //受信用ソケット DatagramSocket receiveUdpSocket = new DatagramSocket(udpPort); byte[] buf = new byte[256]; DatagramPacket packet = new DatagramPacket(buf, buf.length); //ゲスト端末からのブロードキャストを受け取る //受け取るまでは待ち状態になる receiveUdpSocket.receive(packet); //受信バイト数取得 int length = packet.getLength(); //受け取ったパケットを文字列にする address = new String(buf, 0, length); //↓③で使用 returnIpAdress(address); receiveUdpSocket.close(); } } catch (SocketException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }.start(); }
また、UDP通信とは別にTCP通信も待ち受け状態を作っておきます。 これをしないと、④でゲストから通信しようとしても通信先のホストが見つからず、Exceptionが発生しますのでご注意ください。
ServerSocket serverSocket; Socket connectedSocket; int tcpPort = 3333;//ホスト、ゲストで統一 //ゲストからの接続を待つ処理 void connect(){ new Thread(){ @Override public void run(){ try { //ServerSocketを生成する serverSocket = new ServerSocket(tcpPort); //ゲストからの接続が完了するまで待って処理を進める connectedSocket = serverSocket.accept(); //この後はconnectedSocketに対してInputStreamやOutputStreamを用いて入出力を行ったりするが、ここでは割愛 }catch (SocketException e){ e.printStackTrace(); }catch(IOException e){ e.printStackTrace(); } } }.start(); }
②ゲスト:ホスト探索開始。ゲスト端末のIPアドレスを発信する。
ホストが待ち受け態勢を作ったところで、次はゲスト側からブロードキャスト送信を行います。ここでは送信回数を10回、5秒間隔で送るように制限しています。
ブロードキャスト送信を行うとき、Wi-Fi設定が有効で、かつ、ネットワークに接続されていないとExceptionが発生しますので、対策が必要です。
boolean waiting; int udpPort = 9999;//ホスト、ゲストで統一 //同一Wi-fiに接続している全端末に対してブロードキャスト送信を行う void sendBroadcast(){ final String myIpAddress = getIpAddress(); waiting = true; new Thread() { @Override public void run() { int count = 0; //送信回数を10回に制限する while (count < 10) { try { DatagramSocket udpSocket = new DatagramSocket(udpPort); udpSocket.setBroadcast(true); DatagramPacket packet = new DatagramPacket(myIpAddress.getBytes(), myIpAddress.length(), getBroadcastAddress(), udpPort); udpSocket.send(packet); udpSocket.close(); } catch (SocketException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } //5秒待って再送信を行う try { Thread.sleep(5000); count++; } catch (InterruptedException e) { e.printStackTrace(); } } } }.start(); }
getIpAdress()、getBroadcastAdress()は端末のIPアドレスとブロードキャストアドレスを算出するメソッドを下記のように用意しています。
また、これらを取得可能にするために、あらかじめサービスのハンドルを取得しています。
WifiManager wifi; /*コンストラクタ*/ public Sample_WifiConnection(Context context){ wifi = (WifiManager)context.getSystemService(Context.WIFI_SERVICE); } //IPアドレスの取得 String getIpAddress(){ int ipAddress_int = wifi.getConnectionInfo().getIpAddress(); if(ipAddress_int == 0){ ipAddress = null; }else { ipAddress = (ipAddress_int & 0xFF) + "." + (ipAddress_int >> 8 & 0xFF) + "." + (ipAddress_int >> 16 & 0xFF) + "." + (ipAddress_int >> 24 & 0xFF); } return ipAddress; } //ブロードキャストアドレスの取得 InetAddress getBroadcastAddress(){ DhcpInfo dhcpInfo = wifi.getDhcpInfo(); int broadcast = (dhcpInfo.ipAddress & dhcpInfo.netmask) | ~dhcpInfo.netmask; byte[] quads = new byte[4]; for (int i = 0; i < 4; i++){ quads[i] = (byte)((broadcast >> i * 8) & 0xFF); } try { return InetAddress.getByAddress(quads); }catch (IOException e){ e.printStackTrace(); return null; } }
次に、ホストからTCP通信でIPアドレスが返されてくるので、受け取るための待ち受け状態を作っておきます。
//ホストからTCPでIPアドレスが返ってきたときに受け取るメソッド void receivedHostIp(){ new Thread() { @Override public void run() { while (waiting) { try { if(serverSocket == null) { serverSocket = new ServerSocket(tcpPort); } socket = serverSocket.accept(); //↓③で使用 inputDeviceNameAndIp(socket); if (serverSocket != null) { serverSocket.close(); serverSocket = null; } if (socket != null) { socket.close(); socket = null; } } catch (IOException e) { waiting = false; e.printStackTrace(); } } } }.start(); }
③ホスト:ゲストから受信したIPアドレスに対してホスト端末のIPアドレスを送り返す。
①でIPアドレスを受け取ると
receiveUdpSocket.receive(packet);
ここで止まっていた処理が再開します。
受信データ(IPアドレス)を文字列に変換して、
IPアドレスを返すために用意したメソッドに受け取ったアドレスを引数に渡します。
returnIpAdress(address);
処理はこんな感じです。
Socket returnSocket; //ブロードキャスト発信者(ゲスト)にIPアドレスと端末名を返す void returnIpAdress(final String address){ new Thread() { @Override public void run() { try{ if(returnSocket != null){ returnSocket.close(); returnSocket = null; } if(returnSocket == null) { returnSocket = new Socket(address, tcpPort); } //端末情報をゲストに送り返す outputDeviceNameAndIp(returnSocket,getDeviceName(),getIpAddress); }catch(UnknownHostException e){ e.printStackTrace(); }catch (java.net.ConnectException e){ e.printStackTrace(); try{ if(returnSocket != null) { returnSocket.close(); returnSocket = null; } }catch(IOException e1) { e.printStackTrace(); } }catch(IOException e){ e.printStackTrace(); } } }.start(); } //端末名とIPアドレスのセットを送る void outputDeviceNameAndIp(final Socket outputSocket, final String deviceName, final String deviceAddress){ new Thread(){ @Override public void run(){ final BufferedWriter bufferedWriter; try { bufferedWriter = new BufferedWriter( new OutputStreamWriter(outputSocket.getOutputStream()) ); //デバイス名を書き込む bufferedWriter.write(deviceName); bufferedWriter.newLine(); //IPアドレスを書き込む bufferedWriter.write(deviceAddress); bufferedWriter.newLine(); //出力終了の文字列を書き込む bufferedWriter.write("outputFinish"); //出力する bufferedWriter.flush(); } catch (IOException e) { e.printStackTrace(); } } }.start(); }
④ゲスト:ホストから端末情報を受け取る
ゲストからTCP通信が行われると待ち状態になっていた処理が再開し、②で準備していたinputDeviceNameAndIp()まで処理が進みます。
socket = serverSocket.accept();
//↓③で使用
inputDeviceNameAndIp(socket);
このメソッドはIPアドレスと端末名を取得して保持するために用意しています。
//端末名とIPアドレスのセットを受け取る void inputDeviceNameAndIp(Socket socket){ try { BufferedReader bufferedReader = new BufferedReader( new InputStreamReader(socket.getInputStream()) ); int infoCounter = 0; String remoteDeviceInfo; //ホスト端末情報(端末名とIPアドレス)を保持するためのクラスオブジェクト //※このクラスは別途作成しているもの SampleDevice hostDevice = new SampleDevice(); while((remoteDeviceInfo = bufferedReader.readLine()) != null && !remoteDeviceInfo.equals("outputFinish")){ switch(infoCounter){ case 0: //1行目、端末名の格納 hostDevice.setDeviceName(remoteDeviceInfo); infoCounter++; break; case 1: //2行目、IPアドレスの取得 hostDevice.setDeviceIpAddress(remoteDeviceInfo); infoCounter++; return; default: return; } } } catch (IOException e) { e.printStackTrace(); } }
ゲストが端末情報を受け取るとき、本来はreturnしなくていいのですが、どうにも読み込みが完了してくれなかったので無理やり処理を完了させるためreturnさせています。もっとスマートなやり方があるはず…。
⑤ゲスト:ホストのIPアドレスが判明して、TCP通信を開始する。
ゲスト端末でホストのIPアドレスを入手することが出来たので、あとはTCP通信を試みるだけです。
//IPアドレスが判明したホストに対して接続を行う void connect(String remoteIpAddress){ waiting = false; new Thread() { @Override public void run() { try{ if(socket == null) { socket = new Socket(remoteIpAddress, tcpPort); //この後はホストに対してInputStreamやOutputStreamを用いて入出力を行ったりするが、ここでは割愛 } }catch(UnknownHostException e){ e.printStackTrace(); }catch (ConnectException e){ e.printStackTrace(); }catch(IOException e){ e.printStackTrace(); } } }.start(); }
終わりに
いかがでしたでしょうか?
私がアスネット開発のMeetingForceに同一のWi-Fiに接続したホストとゲスト間でBluetoothと同様の通信を可能にするために苦戦した際の経験をもとに作成してみました。BufferdReader、BufferdWriterのサンプルが意外と出てこなかったり、UDP送受信をした後どうすればいい、というのが見つけられなかったり、いろいろ困ったので。UDP・TCP通信のサンプルをお探しの方に、ほんの少しでもお役にたてれば幸いです。
参考URL
MeetingForceって何?と思った方はこちらからどうぞ。
MeetingForce
Bluetoothを利用した複数端末の接続方法 ~MeetingForce~
こんにちは。アスネット開発部の増田です。 アスネットが開発したAndroidアプリの『MeetingForce』では、 Bluetoothを利用して複数端末の接続・データ共有を行っております。 今回は、その複数端末で接続を行うための概要を紹介します。
『MeetingForce』とは?
まず始めに『MeetingForce』の紹介です。 このアプリはJPEGやPNG、PDF等のファイルへメモが出来、 そのファイルを複数の端末でリアルタイムで共有・同期が出来る、コミュニケーションをサポートするアプリです。
上記の画像のように会議や打合せで使用する資料を参加者がアプリで開き、 その資料へ直接文字等を書き込むことで、より簡単にお互いの考えを理解し合えます。
さて、このMeetingForce、BluetoothもしくはWi-Fi接続によってファイルや描画を共有しています。 今回はBluetoothでの接続に注目してみたいと思います。
Bluetoothの利用例
皆さんはどのような場面でBluetoothを利用しているでしょうか? MeetingForceでのBluetoothの活用方法を紹介する前に、利用例を見てみましょう。
PCとマウス 日頃使用しているPCとマウスをBluetoothで繋ぎ、ワイヤレスでマウスを使用します。
スマートフォンとイヤホン スマートフォンとBluetooth対応のイヤホン(もしくはヘッドフォン)を繋いで、音楽やラジオ等を再生します。 通勤やスポーツ時等、幅広い場面で利用されています。
スマートフォン・PCとヘッドセット スマートフォンやPCとヘッドセットを繋いで、スマートフォンでの通話や無料通話アプリを利用します。 端末を持ったり、ケーブルに気を使う必要がなく、運転中なども通話が可能です。
このように様々なシチュエーションで使用されているBluetoothですが、3つの例に共通していることがあります。 その共通点とは「1対1での接続(ペアリング)」であるということです。 基本的にBluetoothは、1対1での接続に利用されることが多いです。 ですが、冒頭で説明した様にMeetingForceでは、複数の端末を繋げてファイルや文字等の描画を相互に共有・同期が可能です。 では、どのように複数接続を行っているのか、活用方法を紹介します。
『MeetingForce』でのBluetoothの活用方法
MeetingForceでは1台の端末を「ホスト」として使用し、 その端末へ「ゲスト」として複数の端末を繋いでいきます。 ソースコードでは、以下のようにホストが他端末からの接続要求を待ち受け、 ゲストがホストを検出して接続要求を送ります。
【サーバ側】
//BluetoothServerSocketの取得 BluetoothServerSocket tmpServerSock = null; myBtAdapter = btAdapter; try{ //自デバイスのBluetoothサーバーソケットの取得 tmpServerSock = myBtAdapter.listenUsingRfcommWithServiceRecord("BlueToothSample", UUID_SAMPLE); }catch(IOException e){ e.printStackTrace(); }
//クライアントからの接続要求待ち → 接続完了 BluetoothSocket receivedSocket = null; try{ //クライアントからの接続要求待ち receivedSocket = servSock.accept(); } catch(IOException e) { break; } if(receivedSocket != null){ //接続完了時の処理 }
【クライアント側】
//クライアントのBluetoothSocket取得 BluetoothSocket clientSocket= null; BluetoothSocket tmpSock = null; BluetoothDevice = device; myBtAdapter = btAdapter; try{ //自デバイスのBluetoothクライアントソケットの取得 tmpSock = device.createRfcommSocketToServiceRecord(UUID_SAMPLE); } catch(IOException e) { e.printStackTrace(); } clientSocket = tmpSock;
//サーバへ接続要求送信 → 接続完了 try{ //サーバー側に接続要求 clientSocket.connect(); } catch(IOException e) { try { clientSocket.close(); } catch (IOException closeException) { e.printStackTrace(); } return; } //以下、接続完了時の処理
Bluetoothで端末を繋ぐアプリを作成する際には、ごく一般的な実装です。 ですが、MeetingForceではホストへ上記のように接続要求を送ってきた複数ゲストの情報(ソケット)を保持し、 管理している点が異なります。 ファイルの共有から描画の送受信までの流れを下記の例で追ってみましょう。
例)ホスト1台に対して2台のゲスト(A、B)が接続された場合
1.ホスト1台に対して2台のゲスト(A、B)の接続前の状態。
2.ホストとそれぞれのゲストでペアリングを行って接続し、 ホストから共有したいファイルをゲストたちへ送信する。
3.ホストが描画を加える。 →ホストが管理しているゲストのソケットを通して描画の情報を送信し、 ゲストは受け取った情報で描画を表示。
4.ゲストAが描画を加える。 →ホストへ描画の情報を送信。 ホストは受信した情報で描画を表示し、さらにゲストBへ描画情報を送信。 ゲストBはホストから受信したゲストAの描画情報を基に描画を表示。
このようにホストが常に仲介役となることで、 全ての端末で同じファイルや描画をリアルタイムで同期することが可能となるのです。
まとめ
Bluetoothの利用例を踏まえて、複数端末接続方法を紹介してきましたが、いかがでしたでしょうか? MeetingForceでは、Bluetoothの基本的な利用方法である「1対1での接続(ペアリング)」を同時に複数使用することで、 複数端末の同時接続を実現しています。 MeetingForceについて知ってもらえただけでなく、Bluetooth1つを取り上げても使い方を工夫するだけで、 可能性が広がるということをお伝え出来ていれば・・と思います。
最後に、MeetingForceはGooglePlayにて無料でダウンロード出来ますので、ぜひ使ってみてください! https://play.google.com/store/apps/details?id=jp.co.asnet.android.mf
参考URL
MeetingForce http://meetingforce.net/ TechBooster Bluetoothで通信を行う(3) http://techbooster.org/android/device/5833/
Android Studioに変えて行った10のこと
こんにちは。アスネット開発部の梶原です。
Android開発でEclipseからAndroid Studioに乗り換えた時に行った10のことを紹介します。
1つ目
ショートカットキーの変更
体がEclipseのショートカットキーを覚えているので、Eclipseと同じショートカットキー設定に変更。
Preferences > Keymap
2つ目
マウスオーバーでドキュメントを表示
Preferences > Editor > Other 「Show quick doc on mouse move 」にチェックを入れる。
3つ目
見た目の変更
- テーマを変更
見た目大事ですよね。やっぱり黒画面のほうが見やすいので
Preferences > Appearance & Behavior > Appearance
- 行番号表示
Preferences > Editor > Appearance
「Show line numbers」にチェックを入れる。
- スペース表示
Preferences > Editor > Appearance
「Show whitespaces」にチェック。
4つ目
Logcatのカラーを変更
EclipseではLogの出力レベルによって色分けがされていましたが、 Android Studioではデフォルトでは色分けされません。色分けされている方が視覚的に見やすいので下記のように変更しています。 Preferences > Editor > Colors & Fonts > Android Logcat
Log level | Color code |
---|---|
Assert | #FF6B68 |
Debug | #1976D2 |
Error | #EF5350 |
Info | #66BB6A |
Verbose | #BDBDBD |
Warning | #FFA726 |
5つ目
コードテンプレートを追加
ファイルテンプレートとはその名の通り、ファイルのテンプレートです。
毎回繰り返しファイル生成するようなものをテンプレート化してして楽しちゃいます。
Preferences > Editor > File and Code Templates
設定後はファイルを作る時に選択するだけです。
6つ目
ライブテンプレートを追加
EclipseであったTemplatesの機能はAndroid Studioでは下記の場所で設定を行います。
Preferences > Editor > Live Templates
追加したのを実際に使うとこんな感じになります。
自分で作るのが面倒って人は、下記のOSSがオススメです。
https://github.com/keyboardsurfer/idea-live-templates
とりあえずこれを導入してから自分の好きなように編集していくほうが楽かと思います。
7つ目
自動インポート
必要/不要なimport文を即座に追加・削除してくれるようになります。
Preferences > Editor > General > Auto Import
「Optimize imports on the fly」と「Add unambiguous imports on the fly」にチェック
8つ目
バージョン管理
Android Studioではバージョン管理システム(Git, Subversion等)との連携がサポートされています。
VCS > Enable Version Control Integration...
連携を有効後、ツールウィンドウに「Version Control」が表示され、 ここからコミットログの確認等様々な処理を行うことが出来ますので色々試して見てください。
Show Diffから差分も確認できます。
9つ目
gradleをオフラインモードに変更
EclipseからAndroid Studioに切り替えた時に特に気になったのはビルドの遅さです。 これは、どうやらGradleのsyncが走っているせいなので、これを止めるようにします。 新たにdependenciesが追加されるとエラーが出てしまうのでそこだけは注意が必要です。
Preferences > Build Tools > Gradle
「Offline work」にチェック
10つ目
各プラグインのインストール
最後にAndroid Studioで使用しているプラグインを羅列しておきます。
Preferences > Plugins から各プラグインを検索しインストールします。
- FindBugs-IDEA
Javaコードにあるバグを解析するための静的解析ツール - CheckStlye-IDEA
コーディング規約をチェックするための静的解析ツール - Genymotion
Genymotionのエミュレータ起動をAndroid Studio上で実行 - ADB Idea
adbコマンドをAndroid Studio上で実行 - Markdown
MarkdownのシンタックスハイライトとPreview表示 - android-parcelable-intellij-plugin
Parcelableを自動生成 - ButterKnifeZelezny
レイアウトファイルからメンバ変数とアノテーションを自動生成
おわり
いかがだったでしょうか?10個以上あるような気もしますが(気のせい...)、 Android Studioに移行したいけどちょっとって思ってた人の参考になれば幸いです。
参考URL
Android Studioのgradleビルドを高速化する
http://qiita.com/konifar/items/d0392b6428a47262ab0b
最近使っているAndroid Studio Plugin
http://qiita.com/hotchemi/items/f87b31d317f1e9b172b8