Android 4.0にしたらモバイルSuicaが使えなくなった [Androidアプリ開発]
本日は、Android 4.0である。珍しく電車に乗って外出した。携帯電話の時代からモバイルSuicaを使ってきた、XperiaにしてからもAndroidアプリのモバイルSuicaを使ってきた。携帯からスマホへの移行時にちょっと戸惑ったが、何回かチャージはしている。
しかし、である。本日、気になって、モバイルSuicaのアプリを立ち上げてみると、トップ画面は表示されるものの、その後の画面が表示されない。
あれー、ちゃんと動いてたけど...
そういえば、Android 4.0にアップデートしてから、チャージしてないかも。Android 4.0じゃあ動かないのか?
困ったときは、検索。
「モバイルSuica Android 4.0」で検索してみる。当然、外からなので、Xperiaを使って検索。
おお、これか?
Android 4.0搭載スマートフォンにおけるモバイルSuica利用時で注意点!HTC J ISW13HTで不具合発生のケースありで回避策が案内
http://s-max.jp/archives/1473428.html
公式ページにも書いてある模様。なに、GPUレンダリング使っちゃいけない。
AREarthroidが速くなるかと思い、有効にした覚えはあるぞ。これか。
効果のほどもいまいちだったので、GPUレンダリングはOFFで使いますか。
GPUレンダリングの影響を調べるために、電車の中でAREarthroidを起動してみた。電車の中だと磁場がおかしくなっているのか、加速度のせいか、地球がぐるぐる回っている。これは...
車に積んだときは、ぐるぐるは回らなかったから、磁場の関係だろうな。電車だからなぁ。
サイト内を検索
しかし、である。本日、気になって、モバイルSuicaのアプリを立ち上げてみると、トップ画面は表示されるものの、その後の画面が表示されない。
あれー、ちゃんと動いてたけど...
そういえば、Android 4.0にアップデートしてから、チャージしてないかも。Android 4.0じゃあ動かないのか?
困ったときは、検索。
「モバイルSuica Android 4.0」で検索してみる。当然、外からなので、Xperiaを使って検索。
おお、これか?
Android 4.0搭載スマートフォンにおけるモバイルSuica利用時で注意点!HTC J ISW13HTで不具合発生のケースありで回避策が案内
http://s-max.jp/archives/1473428.html
公式ページにも書いてある模様。なに、GPUレンダリング使っちゃいけない。
AREarthroidが速くなるかと思い、有効にした覚えはあるぞ。これか。
効果のほどもいまいちだったので、GPUレンダリングはOFFで使いますか。
GPUレンダリングの影響を調べるために、電車の中でAREarthroidを起動してみた。電車の中だと磁場がおかしくなっているのか、加速度のせいか、地球がぐるぐる回っている。これは...
車に積んだときは、ぐるぐるは回らなかったから、磁場の関係だろうな。電車だからなぁ。
サイト内を検索
メニューにアイコン画像を付ける 画像データはシステムリソースを利用 [Androidアプリ開発]
本日は久しぶりにAndroidネタである。
Androidアプリにはメニューを付けることができる。アクティビティのonCreateOptionsMenuをオーバーライドして、メニューアイテムを作成すればよい。
メニューは、リソースファイルで定義してもよい。res/menu/menu.xmlを作ればOK。
onCreateOptionsMenuで、menu.xmlをインフレーターでインフレーションさせるとメニューができる。
メニューが選択されたときは、onOptionsItemSelectedがコールバックされる。メニュー選択後の処理はここで行えばよい。
アイコン画像を付ける
Androidにはシステムリソースとしてアイコン画像がいくつか用意されている。
Android SDKがインストールされているフォルダにリソースが入っている。
platforms\android-x\data\res
メニューにシステムリソースのアイコン画像を付けておけば、他のアプリと統一感が出せるし、言語に対応していなくても、画像ならわかってもらえる可能性が高い。
ファイル名が、ic_menu_xxxxとなっているものが、メニュー用のリソース。
アイコン画像の指定は、MenuItemのsetIconメソッドで指定できる。引数でシステムリソースでもアプリのリソースでもいいので、リソース番号を指定すればOK。
リソースファイルで定義するときは、android:icon属性に、リソース番号を設定してやる。
AREarthroidのメニューにアイコン画像を付けてみる
AREarthroid 1.2.8(現時点での最新版)では、メニューにアイコン画像は付いていない。それらしいアイコンをシステムリソースから探して、設定してみた。
Xperia(Android 2.3)では、このように表示された。
なかなか、いい感じかも。
A100(Android 3.2)では、アクションバーにしているので、こんな感じ。
アイコンだけだとさみしいので、「ifRoom|withText」を指定している。
先日、バグを取ってバージョンアップしたばかりなので、AREarthroidの更新は、もう少し後で。
サイト内を検索
Androidアプリにはメニューを付けることができる。アクティビティのonCreateOptionsMenuをオーバーライドして、メニューアイテムを作成すればよい。
メニューは、リソースファイルで定義してもよい。res/menu/menu.xmlを作ればOK。
onCreateOptionsMenuで、menu.xmlをインフレーターでインフレーションさせるとメニューができる。
@Override public boolean onCreateOptionsMenu(Menu menu){ getMenuInflater().inflate(R.menu.menu,menu); return true; }
メニューが選択されたときは、onOptionsItemSelectedがコールバックされる。メニュー選択後の処理はここで行えばよい。
アイコン画像を付ける
Androidにはシステムリソースとしてアイコン画像がいくつか用意されている。
Android SDKがインストールされているフォルダにリソースが入っている。
platforms\android-x\data\res
メニューにシステムリソースのアイコン画像を付けておけば、他のアプリと統一感が出せるし、言語に対応していなくても、画像ならわかってもらえる可能性が高い。
ファイル名が、ic_menu_xxxxとなっているものが、メニュー用のリソース。
アイコン画像の指定は、MenuItemのsetIconメソッドで指定できる。引数でシステムリソースでもアプリのリソースでもいいので、リソース番号を指定すればOK。
@Override public boolean onCreateOptionsMenu(Menu menu){ getMenuInflater().inflate(R.menu.menu,menu); menu.findItem(R.id.item_delete).setIcon(android.R.drawable.ic_menu_delete); return true; }
リソースファイルで定義するときは、android:icon属性に、リソース番号を設定してやる。
<item android:id="@+id/item_delete" android:title="@string/menu_item_delete" android:icon="@android:drawable/ic_menu_delete"> </item>
AREarthroidのメニューにアイコン画像を付けてみる
AREarthroid 1.2.8(現時点での最新版)では、メニューにアイコン画像は付いていない。それらしいアイコンをシステムリソースから探して、設定してみた。
Xperia(Android 2.3)では、このように表示された。
なかなか、いい感じかも。
A100(Android 3.2)では、アクションバーにしているので、こんな感じ。
アイコンだけだとさみしいので、「ifRoom|withText」を指定している。
先日、バグを取ってバージョンアップしたばかりなので、AREarthroidの更新は、もう少し後で。
サイト内を検索
Androidアプリ開発 SQLiteデータベースを使用する(トランザクション) SQLポケリ [Androidアプリ開発]
前回の記事
SQLite Androidアプリ開発 SQLiteデータベースを使用する
SQLiteは小さいデータベースシステムではあるが、ちゃんとトランザクションをサポートしている。トランザクションをかけておけば、エラーになったときにまとめて「なかったこと」にできるので便利。
また、大量のデータをINSERTする場合は、トランザクションをかけた方が高速。
普通のデータベースでは、BEGIN TRANSACTIONによりトランザクションを開始して、INSERTやDELETEでレコードを追加、削除する。処理に成功したら、COMMIT TRANSACTIONでコミットしてトランザクションを閉じる。途中でエラーになったら、ROLLBACK TRANSACTIONでトランザクションをかける前の状態に戻す。
Javaのコードで書くと以下のような感じになる。
AndroidのSQLiteDatabaseクラスにもトランザクションを制御するメソッドが備わっているが、少々使い方が異なる。
SQLiteDatabase#beginTransactionメソッドを呼び出すことでトランザクションが開始される。SQL命令でいうところのBEGIN TRANSACTIONを実行したことに等しい。
レコードの追加や削除が終了し、それを確定させたい場合、SQLiteDatabase#setTransactionSuccessfulメソッドを呼び出す。これは、SQL命令のCOMMIT TRANSACTIONと同じではない。まだ確定はしていない。上記の例でsuccessのフラグを立てた状態と同じ。
beginTransactionで開始したトランザクションは、endTransactionで終了させる。successフラグが立っていれば(setTransactionSuccessfulが呼び出されていれば)、COMMIT TRANSACTIONと同じ。successフラグが立っていなければ、ROLLBACK TRANSACTIONと同じになる。
つまりですね。BEGIN TRANSACTIONに対応するメソッドはあるが、COMMIT TRANSACTIONとROLLBACK TRANSACTIONに対応するメソッドがないっていうこと。
トランザクションは、endTransactionで終了させるが、そのときにCOMMITされるかROLLBACKされるかは、setTransactionSuccessfullが呼び出されたかどうかで決定される。
えーと、こんな説明で分かって頂けたでしょうか。Javaのコードにしたらもっと簡単です。
processData内で例外が発生すると、setTransactionSuccessfulは実行されないので、endTransactionではロールバックされる。
例外が発生せず、setTransactionSuccessfulが呼び出されれば、endTransactionでコミットされる。
ロック
トランザクションとくれば、次はロックでしょう。Androidはマルチタスクやスレッドが可能なので、データの更新が競合する可能性がある。
SQLiteでのロックの粒度は、データベース単位。Oracleなどでは行単位のロックが可能。SQLiteは小さい組み込み用のデータベースなので、ロックの粒度はおおざっぱでかまわないのであろう。
どういうことかというと、あるコードでデータベースを更新している間は、他のコードからはデータベースを参照することができないようにロックがかかる。Oracleなどの高機能なデータベースは、行単位などの細かい粒度でロックされるが、SQLiteの場合は、データベース単位でロックされる。ということ。
テーブル単位ではなく、データベース単位。
どういうことかというと、どこかのスレッドでトランザクションを開始したら、そのデータベースは、そのスレッドがロック保持者になる。別のスレッドからデータベースにアクセスできない状態になる。
別のテーブルに書き込むからOKでしょ。というわけにはいかない。
トランザクションのネスト
トランザクションをネストさせる場合を考えてみて欲しい。ネストというのは、入れ子にすること。トランザクションの中でまたトランザクションをかけるのである。
transaction1の値を持つレコードはINSERTで作成されるが、transaction2のレコードは追加されない。
もし、外側のトランザクションがロールバックされれば、transaction1のレコードも追加されない。
トランザクションの開始、コミット、ロールバックをリスナーで受ける
トランザクションをbeginTransactionWithListenerで開始するとその状態変化をリスナーで受け取ることができる。ちょとやってみる。
SQLiteTransactionListenerインターフェースを実装したクラスで通知を受けることができる。ロールバックした際にエラーを表示する、といった用途に使用できそう。
お約束の宣伝を... SQLについての詳細は、手前味噌で恐縮ではあるが「SQLポケットリファレンス」を参照されたし。
「改訂第4版SQLポケットリファレンス」は、SQLiteにも対応しています。
関連記事
SQLite Androidアプリ開発 SQLiteデータベースを使用する
「SQLite ODBC」 Excel AccessからSQLiteデータベースを使用する
「SQLite for Excel」 Excel VBAからSQLiteデータベースを使用する
SQLiteでのデータ型
SQLポケットリファレンス 台湾版SQLポケットリファレンス 台湾版の紹介
SQLポケリが第4版になりました
「かんたんAndroidアプリ作成入門」には、sqliteのサンプルもあります。
サイト内を検索
SQLite Androidアプリ開発 SQLiteデータベースを使用する
SQLiteは小さいデータベースシステムではあるが、ちゃんとトランザクションをサポートしている。トランザクションをかけておけば、エラーになったときにまとめて「なかったこと」にできるので便利。
また、大量のデータをINSERTする場合は、トランザクションをかけた方が高速。
普通のデータベースでは、BEGIN TRANSACTIONによりトランザクションを開始して、INSERTやDELETEでレコードを追加、削除する。処理に成功したら、COMMIT TRANSACTIONでコミットしてトランザクションを閉じる。途中でエラーになったら、ROLLBACK TRANSACTIONでトランザクションをかける前の状態に戻す。
Javaのコードで書くと以下のような感じになる。
db.execSQL("BEGIN TRANSACTION"); // トランザクション開始 try { processData(); // データを追加したり削除したり success = true; } catch(Exception e){ // 例外発生 e.printStackTrace(); success = false; } if ( success ){ db.execSQL("COMMIT TRANSACTION"); // コミット } else { db.execSQL("ROLLBACK TRANSACTION"); // ロールバック }
AndroidのSQLiteDatabaseクラスにもトランザクションを制御するメソッドが備わっているが、少々使い方が異なる。
SQLiteDatabase#beginTransactionメソッドを呼び出すことでトランザクションが開始される。SQL命令でいうところのBEGIN TRANSACTIONを実行したことに等しい。
レコードの追加や削除が終了し、それを確定させたい場合、SQLiteDatabase#setTransactionSuccessfulメソッドを呼び出す。これは、SQL命令のCOMMIT TRANSACTIONと同じではない。まだ確定はしていない。上記の例でsuccessのフラグを立てた状態と同じ。
beginTransactionで開始したトランザクションは、endTransactionで終了させる。successフラグが立っていれば(setTransactionSuccessfulが呼び出されていれば)、COMMIT TRANSACTIONと同じ。successフラグが立っていなければ、ROLLBACK TRANSACTIONと同じになる。
つまりですね。BEGIN TRANSACTIONに対応するメソッドはあるが、COMMIT TRANSACTIONとROLLBACK TRANSACTIONに対応するメソッドがないっていうこと。
トランザクションは、endTransactionで終了させるが、そのときにCOMMITされるかROLLBACKされるかは、setTransactionSuccessfullが呼び出されたかどうかで決定される。
えーと、こんな説明で分かって頂けたでしょうか。Javaのコードにしたらもっと簡単です。
db.beginTransaction(); // トランザクション開始 try { processData(); // データを追加したり削除したり db.setTransactionSuccessful(); // 成功 } catch(Exception e){ // 例外発生 e.printStackTrace(); } finally{ db.endTransaction(); // トランザクション終了 }
processData内で例外が発生すると、setTransactionSuccessfulは実行されないので、endTransactionではロールバックされる。
例外が発生せず、setTransactionSuccessfulが呼び出されれば、endTransactionでコミットされる。
ロック
トランザクションとくれば、次はロックでしょう。Androidはマルチタスクやスレッドが可能なので、データの更新が競合する可能性がある。
SQLiteでのロックの粒度は、データベース単位。Oracleなどでは行単位のロックが可能。SQLiteは小さい組み込み用のデータベースなので、ロックの粒度はおおざっぱでかまわないのであろう。
どういうことかというと、あるコードでデータベースを更新している間は、他のコードからはデータベースを参照することができないようにロックがかかる。Oracleなどの高機能なデータベースは、行単位などの細かい粒度でロックされるが、SQLiteの場合は、データベース単位でロックされる。ということ。
テーブル単位ではなく、データベース単位。
どういうことかというと、どこかのスレッドでトランザクションを開始したら、そのデータベースは、そのスレッドがロック保持者になる。別のスレッドからデータベースにアクセスできない状態になる。
別のテーブルに書き込むからOKでしょ。というわけにはいかない。
トランザクションのネスト
トランザクションをネストさせる場合を考えてみて欲しい。ネストというのは、入れ子にすること。トランザクションの中でまたトランザクションをかけるのである。
db.beginTransaction(); // トランザクション開始 try { db.beginTransaction(); // 子トランザクション1開始 try { db.execSQL("INSERT INTO sample VALUES('transaction1')"); db.setTransactionSuccessful(); // 成功 } finally { db.endTransaction(); // 子トランザクション1終了 } db.beginTransaction(); // 子トランザクション2開始 try { db.execSQL("INSERT INTO sample VALUES('transaction2')"); } finally { db.endTransaction(); // 子トランザクション2終了 } db.setTransactionSuccessful(); // 成功 } finally { db.endTransaction(); // トランザクション終了 }
transaction1の値を持つレコードはINSERTで作成されるが、transaction2のレコードは追加されない。
もし、外側のトランザクションがロールバックされれば、transaction1のレコードも追加されない。
トランザクションの開始、コミット、ロールバックをリスナーで受ける
トランザクションをbeginTransactionWithListenerで開始するとその状態変化をリスナーで受け取ることができる。ちょとやってみる。
public class TestSQLiteActivity extends Activity implements SQLiteTransactionListener { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); MySQLiteOpenHelper helper = new MySQLiteOpenHelper(this); SQLiteDatabase db = helper.getWritableDatabase(); db.beginTransactionWithListener(this); try { db.execSQL("INSERT INTO sample(name) VALUES('test data')"); db.setTransactionSuccessful(); } catch(Exception e){ // 例外発生 e.printStackTrace(); } finally { db.endTransaction(); } Cursor c = db.rawQuery("SELECT * FROM sample", null); boolean hasRecord = c.moveToFirst(); while(hasRecord) { android.util.Log.v("database sample", c.getInt(0) + "," + c.getString(1)); hasRecord = c.moveToNext(); } c.close(); db.close(); } @Override public void onBegin() { android.util.Log.i("TestSQLite", "transaction begin"); } @Override public void onCommit() { android.util.Log.i("TestSQLite", "transaction commit"); } @Override public void onRollback() { android.util.Log.i("TestSQLite", "transaction rollback"); } }
SQLiteTransactionListenerインターフェースを実装したクラスで通知を受けることができる。ロールバックした際にエラーを表示する、といった用途に使用できそう。
お約束の宣伝を... SQLについての詳細は、手前味噌で恐縮ではあるが「SQLポケットリファレンス」を参照されたし。
「改訂第4版SQLポケットリファレンス」は、SQLiteにも対応しています。
関連記事
SQLite Androidアプリ開発 SQLiteデータベースを使用する
「SQLite ODBC」 Excel AccessからSQLiteデータベースを使用する
「SQLite for Excel」 Excel VBAからSQLiteデータベースを使用する
SQLiteでのデータ型
SQLポケットリファレンス 台湾版SQLポケットリファレンス 台湾版の紹介
SQLポケリが第4版になりました
「かんたんAndroidアプリ作成入門」には、sqliteのサンプルもあります。
かんたんAndroidアプリ作成入門 (プログラミングの教科書)
- 作者: 朝井 淳
- 出版社/メーカー: 技術評論社
- 発売日: 2013/04/16
- メディア: 単行本(ソフトカバー)
サイト内を検索
Androidアプリ開発 SQLiteデータベースを使用する SQLポケリ [Androidアプリ開発]
Androidには標準でSQLiteが搭載されている。SQLiteはリレーショナルデータベース。リレーショナルデータベースといえば、Oracle、DB2、SQL Serverなどが有名。SQLはリレーショナルデータベースを扱うときの命令群のこと。
SQLについての詳細は、手前味噌で恐縮ではあるが「SQLポケットリファレンス」を参照されたし。
Androidでは、大規模なデータを扱うことはない。せいぜい数メガがよいところであろう(動画はそれなりに大きいが、動画データはデータベースではない)。サーバ用のOracleやDB2ではなく、組み込み用に開発されたSQLiteで十分である。
SQLiteは小さいリレーショナルデータベースシステム。アプリに組み込まれることが前提のシステム。Oracleなどのようにサーバーとしてではなく、アプリに組み込んで使う。
Androidにサーバ的な用途は要求されないでしょう。
Oracleなどのサーバ形式のデータベースシステムでは、クライアントから接続して、SQLを投げ、その結果をレコードセット(カーソル)でもらい、結果を出力する、といったお決まりのパターンになる。
サーバに依存しないように、ある程度の抽象化がされている。依存する部分はプロバイダが吸収する。
サーバとの接続は、主にTCP/IPが使われる。
SQLiteの場合、アプリに組み込みが前提なので、TCP/IPによる通信は行われない。SQLiteのAPI関数を直接呼び出す感じで、SQL命令が投げられる。C言語によるSQLiteの基本的なAPI関数は以下の3つしかない。
sqlite_openでデータベースをオープンして
sqlite_execでSQL命令を実行して
sqlite_closeでデータベースをクローズ
JDBCを使ってSQLiteデータベースにアクセスすることも可能ではあるが、Androidの場合は、SQLiteDatabaseクラスを使ってアクセスを行う方が便利であろう。
SQLiteDatabaseクラスは、上記SQLiteのアクセス用APIをjavaのクラスでラップした感じになる。
では、具体的にはどうすればよいか説明していく。
まずは、データベースに接続を行う必要がある。SQLiteではデータベースはデータベースファイル毎に管理される。データベースファイルをオープンすることにより、そのデータベースに対して、SQL命令を投げることができるようになる。
データベースオープンは、SQLiteOpenHelperの派生クラスを作成し、そのヘルパークラスてオープンするのがAndroidでの作法となっている。
少々、まどろっこしいが、ヘルパークラスを作成することで、初回のアクセスでデータベースを作成されたときにテーブルを作ったり、バージョン管理ができるようになる。
ヘルパークラスの作成
では、SQLiteOpenHelperクラスの派生クラスを作成してみよう。
コンストラクタが必要と言われるので、作成する。
クイックフィックスでコンストラクタを生成すると、データベースファイル名やらの引数が追加されてしまう。データベースファイル名、バージョンは、staticフィールドで指定したいので、引数からは削除。
スーパークラスのコンストラクタには、contextとデータベースファイル名、使用するCursorFactoryクラス、データベースのバージョンを与える必要がある。
コンストラクタは作成できたが、まだエラーになっているはず。
SQLiteOpenHelperクラスは、抽象クラスなので、実装すべきメソッドがいくつかある。
onCreate
onUpgrade
これらもクイックフィックスで作成してしまおう。
onCreateは、データベースファイルが作成された場合に呼び出されるもの。存在しないデータベースファイルにアクセスがあると自動的に作成される。作成された後で、onCreateが呼び出される。
onCreateでは、そのデータベースに必要なテーブルを作成するのに都合がよい。「テーブル」はデータベースの基本となるもので、CREATE TABLE命令で作成できる(SQLの詳細は、SQLポケットリファレンスをみてね)。
sampleという名前のテーブルを作成した。列はふたつ_idとname。_idは自動インクリメントの列でプライマリキーとした。nameはTEXT型。SQLiteの場合、列の型は「指定しなくてもよい」。普通のデータベースは数値型、文字列型の区別があり、数値型の列に文字列データを突っ込むとエラーになったりするのだが、SQLiteでは変換は起こるが、エラーにはならない。なんと寛容なデータベースである。
列名は任意であるが、「_id」という名前にしてプライマリキーにしておくと「よいことがある」ので、Androidでテーブルを作成するときの定石となっている。
サンプルでは、ひとつのテーブルしか作成していないが、データベースに複数のテーブルを作成することができる。ただし、同じ名前で作成することはできないので、複数作成する場合はテーブル名が重ならないようにする。
SQLiteには、ユーザの概念がない。接続したユーザによりスキーマが異なるということはない。名前空間はデータベースファイルでひとつになる。
onUpgradeはデータベースのバージョンが上がったときに呼び出される。アプリのバージョンアップでテーブルに列が追加になったりする。そうなった場合に、列を追加したり属性を変更したり、といったことができるようになっている。とりあえず、今回は実装しないので、スタブのままにしておく。
これで、データベースオープンの準備はOK。完全なソースを載せておく。
データベースのオープン
実際にデータベースをオープンして、使ってみよう。
アクティビティからアクセスすることにする。onCreateでデータベースオープンをする。オープンにはMySQLiteOpenHelperを使う。
ヘルパーのインスタンスを作って、getReadableDatabase()を呼び出すとSQLiteDatabaseオブジェクトが戻ってくる。getReadableDatabaseなので、読み込み可能なデータベースが戻ってくる。SQLのSELECT命令だけを受け付ける。
書き込みを行うためには、getWritableDatabase()でデータベースオブジェクトを取得すればOK。読み込みに加えて、書き込みも行うことができるようになる。
SQL命令の実行
データベースをオープンできたので、SQL命令を発行してみよう。SQL命令は、SQLiteDatabase#execSQLを使うことで実行することができる。以下は、INSERT命令を実行する例。
簡単ですな。
えっINSERT命令のsyntaxがわからない?!、では、SQLポケットリファ...
execSQLは値を戻さない。SELECT命令を実行する際は、SQLiteDatabase#rawQueryを使う。rawQueryはCursorオブジェクトを戻す。
moveToFirstで先頭のレコードに移動。カーソルが空であるとfalseが戻される。カーソルは、一度に1レコードしかアクセスできない。JDBCのレコードセットと同じ。次のレコードを参照するには、moveToNextを呼び出せばよい。最後まで行くとfalseが戻されるのでループを抜ける。
カーソルとデータベース接続は、使い終わったらcloseする。
アクティビティ側の全ソースも載せておく。
基本的には、こんなところを押さえておけばOKでしょう。後は、知っておくと便利なテクニックをいくつか紹介しておこうと思うのであるが、これは次回。
サイト内を検索
SQLについての詳細は、手前味噌で恐縮ではあるが「SQLポケットリファレンス」を参照されたし。
Androidでは、大規模なデータを扱うことはない。せいぜい数メガがよいところであろう(動画はそれなりに大きいが、動画データはデータベースではない)。サーバ用のOracleやDB2ではなく、組み込み用に開発されたSQLiteで十分である。
SQLiteは小さいリレーショナルデータベースシステム。アプリに組み込まれることが前提のシステム。Oracleなどのようにサーバーとしてではなく、アプリに組み込んで使う。
Androidにサーバ的な用途は要求されないでしょう。
SQLite本家のURL
http://www.sqlite.org/
Oracleなどのサーバ形式のデータベースシステムでは、クライアントから接続して、SQLを投げ、その結果をレコードセット(カーソル)でもらい、結果を出力する、といったお決まりのパターンになる。
サーバに依存しないように、ある程度の抽象化がされている。依存する部分はプロバイダが吸収する。
サーバとの接続は、主にTCP/IPが使われる。
SQLiteの場合、アプリに組み込みが前提なので、TCP/IPによる通信は行われない。SQLiteのAPI関数を直接呼び出す感じで、SQL命令が投げられる。C言語によるSQLiteの基本的なAPI関数は以下の3つしかない。
sqlite *sqlite_open(const char *dbname, int mode, char **errmsg); void sqlite_close(sqlite *db); int sqlite_exec( sqlite *db, char *sql, int (*xCallback)(void*,int,char**,char**), void *pArg, char **errmsg );
sqlite_openでデータベースをオープンして
sqlite_execでSQL命令を実行して
sqlite_closeでデータベースをクローズ
JDBCを使ってSQLiteデータベースにアクセスすることも可能ではあるが、Androidの場合は、SQLiteDatabaseクラスを使ってアクセスを行う方が便利であろう。
SQLiteDatabaseクラスは、上記SQLiteのアクセス用APIをjavaのクラスでラップした感じになる。
では、具体的にはどうすればよいか説明していく。
まずは、データベースに接続を行う必要がある。SQLiteではデータベースはデータベースファイル毎に管理される。データベースファイルをオープンすることにより、そのデータベースに対して、SQL命令を投げることができるようになる。
データベースオープンは、SQLiteOpenHelperの派生クラスを作成し、そのヘルパークラスてオープンするのがAndroidでの作法となっている。
少々、まどろっこしいが、ヘルパークラスを作成することで、初回のアクセスでデータベースを作成されたときにテーブルを作ったり、バージョン管理ができるようになる。
ヘルパークラスの作成
では、SQLiteOpenHelperクラスの派生クラスを作成してみよう。
import android.database.sqlite.SQLiteOpenHelper; public class MySQLiteOpenHelper extends SQLiteOpenHelper { }
コンストラクタが必要と言われるので、作成する。
class MySQLiteOpenHelper extends SQLiteOpenHelper { static private String DB_NAME = "sample.db"; static private int DB_VERSION = 1; public MySQLiteOpenHelper(Context context) { super(context, DB_NAME, null, DB_VERSION); } }
クイックフィックスでコンストラクタを生成すると、データベースファイル名やらの引数が追加されてしまう。データベースファイル名、バージョンは、staticフィールドで指定したいので、引数からは削除。
スーパークラスのコンストラクタには、contextとデータベースファイル名、使用するCursorFactoryクラス、データベースのバージョンを与える必要がある。
コンストラクタは作成できたが、まだエラーになっているはず。
SQLiteOpenHelperクラスは、抽象クラスなので、実装すべきメソッドがいくつかある。
onCreate
onUpgrade
これらもクイックフィックスで作成してしまおう。
onCreateは、データベースファイルが作成された場合に呼び出されるもの。存在しないデータベースファイルにアクセスがあると自動的に作成される。作成された後で、onCreateが呼び出される。
onCreateでは、そのデータベースに必要なテーブルを作成するのに都合がよい。「テーブル」はデータベースの基本となるもので、CREATE TABLE命令で作成できる(SQLの詳細は、SQLポケットリファレンスをみてね)。
@Override public void onCreate(SQLiteDatabase db) { db.execSQL("CREATE TABLE IF NOT EXISTS sample(_id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, name TEXT)"); }
sampleという名前のテーブルを作成した。列はふたつ_idとname。_idは自動インクリメントの列でプライマリキーとした。nameはTEXT型。SQLiteの場合、列の型は「指定しなくてもよい」。普通のデータベースは数値型、文字列型の区別があり、数値型の列に文字列データを突っ込むとエラーになったりするのだが、SQLiteでは変換は起こるが、エラーにはならない。なんと寛容なデータベースである。
列名は任意であるが、「_id」という名前にしてプライマリキーにしておくと「よいことがある」ので、Androidでテーブルを作成するときの定石となっている。
サンプルでは、ひとつのテーブルしか作成していないが、データベースに複数のテーブルを作成することができる。ただし、同じ名前で作成することはできないので、複数作成する場合はテーブル名が重ならないようにする。
SQLiteには、ユーザの概念がない。接続したユーザによりスキーマが異なるということはない。名前空間はデータベースファイルでひとつになる。
onUpgradeはデータベースのバージョンが上がったときに呼び出される。アプリのバージョンアップでテーブルに列が追加になったりする。そうなった場合に、列を追加したり属性を変更したり、といったことができるようになっている。とりあえず、今回は実装しないので、スタブのままにしておく。
@Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { }
これで、データベースオープンの準備はOK。完全なソースを載せておく。
package cx.fam.asai.TestSQLite; import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; public class MySQLiteOpenHelper extends SQLiteOpenHelper { static private String DB_NAME = "sample.db"; static private int DB_VERSION = 1; public MySQLiteOpenHelper(Context context) { super(context, DB_NAME, null, DB_VERSION); } @Override public void onCreate(SQLiteDatabase db) { db.execSQL("CREATE TABLE IF NOT EXISTS sample(_id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, name TEXT)"); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { } }
データベースのオープン
実際にデータベースをオープンして、使ってみよう。
アクティビティからアクセスすることにする。onCreateでデータベースオープンをする。オープンにはMySQLiteOpenHelperを使う。
MySQLiteOpenHelper helper = new MySQLiteOpenHelper(this); SQLiteDatabase db = helper.getReadableDatabase();
ヘルパーのインスタンスを作って、getReadableDatabase()を呼び出すとSQLiteDatabaseオブジェクトが戻ってくる。getReadableDatabaseなので、読み込み可能なデータベースが戻ってくる。SQLのSELECT命令だけを受け付ける。
MySQLiteOpenHelper helper = new MySQLiteOpenHelper(this); SQLiteDatabase db = helper.getWritableDatabase();
書き込みを行うためには、getWritableDatabase()でデータベースオブジェクトを取得すればOK。読み込みに加えて、書き込みも行うことができるようになる。
SQL命令の実行
データベースをオープンできたので、SQL命令を発行してみよう。SQL命令は、SQLiteDatabase#execSQLを使うことで実行することができる。以下は、INSERT命令を実行する例。
db.execSQL("INSERT INTO sample(name) VALUES('test')");
簡単ですな。
えっINSERT命令のsyntaxがわからない?!、では、SQLポケットリファ...
execSQLは値を戻さない。SELECT命令を実行する際は、SQLiteDatabase#rawQueryを使う。rawQueryはCursorオブジェクトを戻す。
Cursor c = db.rawQuery("SELECT * FROM sample", null); boolean hasRecord = c.moveToFirst(); while(hasRecord) { android.util.Log.v("database sample", c.getInt(0) + "," + c.getString(1)); hasRecord = c.moveToNext(); }
moveToFirstで先頭のレコードに移動。カーソルが空であるとfalseが戻される。カーソルは、一度に1レコードしかアクセスできない。JDBCのレコードセットと同じ。次のレコードを参照するには、moveToNextを呼び出せばよい。最後まで行くとfalseが戻されるのでループを抜ける。
カーソルとデータベース接続は、使い終わったらcloseする。
c.close(); db.close();
アクティビティ側の全ソースも載せておく。
package cx.fam.asai.TestSQLite; import android.app.Activity; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.os.Bundle; public class TestSQLiteActivity extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); MySQLiteOpenHelper helper = new MySQLiteOpenHelper(this); SQLiteDatabase db = helper.getWritableDatabase(); db.execSQL("INSERT INTO sample(name) VALUES('test')"); Cursor c = db.rawQuery("SELECT * FROM sample", null); boolean hasRecord = c.moveToFirst(); while(hasRecord) { android.util.Log.v("database sample", c.getInt(0) + "," + c.getString(1)); hasRecord = c.moveToNext(); } c.close(); db.close(); } }
基本的には、こんなところを押さえておけばOKでしょう。後は、知っておくと便利なテクニックをいくつか紹介しておこうと思うのであるが、これは次回。
「改訂第4版SQLポケットリファレンス」は、SQLiteにも対応しています。
関連記事
トランザクション SQLiteでトランザクションを使う
SQLポケットリファレンス 台湾版SQLポケットリファレンス 台湾版の紹介
SQLポケットリファレンスが第4版になりました
関連記事
トランザクション SQLiteでトランザクションを使う
SQLポケットリファレンス 台湾版SQLポケットリファレンス 台湾版の紹介
SQLポケットリファレンスが第4版になりました
かんたんAndroidアプリ作成入門 (プログラミングの教科書)
- 作者: 朝井 淳
- 出版社/メーカー: 技術評論社
- 発売日: 2013/04/16
- メディア: 単行本(ソフトカバー)
サイト内を検索
Androidアプリ開発 リリース時にレイアウトを自動選択する adbによるapkのリモートインストール [Androidアプリ開発]
ご存じのように、MapViewを使用するときは、APIキーを取得して、レイアウトファイルで指定しなければならい。
APIキーは、署名時に使用する証明書を使って取得する必要がある。デバッグ時に使用する証明書とリリース時に使用する証明書が異なるため、APKをエクスポートする際に、いちいち切り替えないといけない。
これが、めんどうなので、自動化することにした。
まず、デバッグモードで実行しているのかどうなのかは、BuildConfig.DEBUGをみればわかる。
BuildConfigクラスは、ADTが自動生成する。BuildConfig.javaに存在しているクラス。
レイアウトとして、main_release.xmlとmain_debug.xmlのふたつを用意しておいて、それぞれにデバッグ用と本番用のAPIキーを設定しておく。
アクティビティのsetContentViewでレイアウトを指定する際に、BuildConfig.DEBUGをみて、main_release/main_debugのどちらか選択する。コードにすると以下のような感じ。
もちろん、BuildConfig.DEBUGがtrueなら、main_debug.xmlのレイアウトを使う。
で、まずは普通にデバッグモードで実行。
地図が表示された。OK。OK。
次に、APKをエクスポートして、インストールして実行。
ありゃ、だめじゃん。
BuildConfig.DEBUGはWindows環境だとバグがあるらしい。Eclipseの「プロジェクト」メニューの「自動的にビルド」を外すと正しくAPKを作成できるようになる。
えー、自動的にビルドを外したら、ソース変更したときに、自動でビルドされないんだよね...
うぉー、これじゃあ、便利になったのかどうかわからん。
普段の開発時は、「自動的にビルド」を有効にしておいて、ガンガン実行をかける。
よし、リリースするAPKを作るぞ、といったときに、「自動的にビルドをはずして」、クリーンしてエクスポートすればOK?
クリーンはかけなくてもいいかも?
やっぱり、めんどくさいというか、「自動的にビルドをはずして」というところを忘れそう。
しかし、main.xmlをいちいち編集するよりかはいいか。
adbによるapkファイルのインストール
PCに接続して、デバッグ用アプリを実行させる場合は、Eclipseから操作できる。ボタンひとつでインストールができるので便利である。
署名付きのAPKファイルを作成して、動作確認をしようと思うと、まず、Android端末側で、デバッグ用のアプリを一度アンインストールしないと署名付きのAPKがインストールできない。
次に、PCからAPKをダウンロードする必要がある。
これらの操作もめんどくさいのよねぇ。後ダウンロードとか特に。
Eclipseから操作できないのかなぁ...
専用のアプリを作ってしまうか。
と調べてみると、adbでインストールできることが判明。PC側の操作になる。
まずは、adb.exeにパスを通す。
次に、以下の内容でバッチファイルをAPKファイルのあるフォルダに作成。
パッケージ名、APKのファイル名は適宜変更して欲しい。
コマンドプロンプトから上記のバッチファイルを実行。
おっ、なんかできた。
ついでなので、Pro用のインストールバッチも作成。
こっちもできた。
では、Xperiaにも入れてみよう。A100とXperiaのふたつの端末を同時にUSBで接続。
だめだ。エラーになった。
A100の方にはインストール済なので、A100を外してみる。
おお、できた。ふたつの端末をつなげているとだめなのね。
関連記事
サイト内を検索
APIキーは、署名時に使用する証明書を使って取得する必要がある。デバッグ時に使用する証明書とリリース時に使用する証明書が異なるため、APKをエクスポートする際に、いちいち切り替えないといけない。
これが、めんどうなので、自動化することにした。
まず、デバッグモードで実行しているのかどうなのかは、BuildConfig.DEBUGをみればわかる。
BuildConfigクラスは、ADTが自動生成する。BuildConfig.javaに存在しているクラス。
レイアウトとして、main_release.xmlとmain_debug.xmlのふたつを用意しておいて、それぞれにデバッグ用と本番用のAPIキーを設定しておく。
アクティビティのsetContentViewでレイアウトを指定する際に、BuildConfig.DEBUGをみて、main_release/main_debugのどちらか選択する。コードにすると以下のような感じ。
setContentView((BuildConfig.DEBUG) ? R.layout.main_debug : R.layout.main_release);
もちろん、BuildConfig.DEBUGがtrueなら、main_debug.xmlのレイアウトを使う。
で、まずは普通にデバッグモードで実行。
地図が表示された。OK。OK。
次に、APKをエクスポートして、インストールして実行。
ありゃ、だめじゃん。
BuildConfig.DEBUGはWindows環境だとバグがあるらしい。Eclipseの「プロジェクト」メニューの「自動的にビルド」を外すと正しくAPKを作成できるようになる。
えー、自動的にビルドを外したら、ソース変更したときに、自動でビルドされないんだよね...
うぉー、これじゃあ、便利になったのかどうかわからん。
普段の開発時は、「自動的にビルド」を有効にしておいて、ガンガン実行をかける。
よし、リリースするAPKを作るぞ、といったときに、「自動的にビルドをはずして」、クリーンしてエクスポートすればOK?
クリーンはかけなくてもいいかも?
やっぱり、めんどくさいというか、「自動的にビルドをはずして」というところを忘れそう。
しかし、main.xmlをいちいち編集するよりかはいいか。
adbによるapkファイルのインストール
PCに接続して、デバッグ用アプリを実行させる場合は、Eclipseから操作できる。ボタンひとつでインストールができるので便利である。
署名付きのAPKファイルを作成して、動作確認をしようと思うと、まず、Android端末側で、デバッグ用のアプリを一度アンインストールしないと署名付きのAPKがインストールできない。
次に、PCからAPKをダウンロードする必要がある。
これらの操作もめんどくさいのよねぇ。後ダウンロードとか特に。
Eclipseから操作できないのかなぁ...
専用のアプリを作ってしまうか。
と調べてみると、adbでインストールできることが判明。PC側の操作になる。
まずは、adb.exeにパスを通す。
次に、以下の内容でバッチファイルをAPKファイルのあるフォルダに作成。
adb uninstall cx.fam.asai.AREarthroid adb install AREarthroid.apk
パッケージ名、APKのファイル名は適宜変更して欲しい。
コマンドプロンプトから上記のバッチファイルを実行。
おっ、なんかできた。
ついでなので、Pro用のインストールバッチも作成。
adb uninstall cx.fam.asai.AREarthroidPro adb install AREarthroidPro.apk
こっちもできた。
では、Xperiaにも入れてみよう。A100とXperiaのふたつの端末を同時にUSBで接続。
だめだ。エラーになった。
A100の方にはインストール済なので、A100を外してみる。
おお、できた。ふたつの端末をつなげているとだめなのね。
関連記事
サイト内を検索
Androidアプリ開発 DisplayMetrics.densityで解像度の違いに対応する [Androidアプリ開発]
Android端末のディスプレイは、タブレット型か、スマホ型かで大きく違う。スマホは、5インチ程度のものが多い。片手で持てるくらいの大きさがちょうどいいので、だいたい5インチ=12.7センチくらいがちょうどよいサイズになる。
タブレット型は、7~10インチが多いようである。11インチくらいになるとノートPCの領域に入ってくる。Android搭載のノートPCも存在しなくもないが、Androidとはちょっと違う気がする。
ということで、Android端末には、4~10インチまでの多種なディスプレイが装備されている。
一方、解像度(ドット数)はというと、800x480、1024x600、1280x768とスマホのグレードにより様々。解像度が高ければそれだけ、メモリが必要になる。
これまでも、WindowsなどでGUIを構築する際にも、ディスプレイの解像度というのは、けっこう重要であった。画面に多くの情報を詰め込もうと思ったら、広いディスプレイの方がいいに決まっている。
一枚のダイアログに多くのボタンやリストなどのコントロールを並べたら、低解像度のノートPCでダイアログが表示できなくなってしまった、といったトラブルはしょっちゅうであったと思う。
PCのディスプレイの場合、最初は640x480であった。これが、数年で800x600になり、1024x768になり、ワイドになって、1280x768とかになり、HDとなり、1920x1080とかになっている。さらなる高解像度への道は続くであろう。
解像度とディスプレイの大きさは別。大体、小さいディスプレイはそれなりに解像度も低く、大きいディスプレイほど、解像度は高い。
PCのディスプレイは、解像度が高くなるにつれて、大きさも大きくなる方向に進化してきた。なので、1ドットの大きさはあまり変化がなかった。ドットの大きさを示す単位としてDPIがある。DPIはDot Per Inch。1インチあたりにどれだけドットがあるかっていうこと。1インチは2.3cmくらいなので、ディスプレイの横方向(または縦方向)の2.3cmの直線上にどれだけドットが入っているかっていうこと。
だいたい、72~96といのがPCのディスプレイでのDPI。
これは、補足であるが、プリンタでのDPIは、2400とか9600になる。やけに大きいのは、プリンタの場合、ディザで色を表現するから。ディザの説明は、以下の記事を参照。
写真の印刷 プリンタのしくみについて考える
Android端末の場合、大きさは用途によって決定される。スマホなら4~5インチ。タブレットなら7~10インチ。解像度(ドット数)は、端末の価格によって大方決められる。高い端末なら高解像度、安い端末は低解像度。
結果、大きさと解像度の組み合わせで、多種多様なディスプレイが生み出され、DPIもまちまちになる。
大は小をかねる
大は小をかねる、の逆で、従来のGUI設計では、小さく作っておけば問題が少ない。最低の解像度に合わせて作っておけば、そんなに問題はなかった。決定的にダメなのは、ウインドウがディスプレイに収まりきらず、どうやっても操作できない状態に陥ること。
Androidでも同じではあるが、最低の解像度に合わせてGUIをレイアウトしていくと、広い画面のタブレットに持っていくと、「文字が小さすぎるよー」、とか「なんか無駄な空白があり過ぎー」っていうことになりかねない。
逆に、タブレットでイイ感じに、画面を作ってもスマホに持っていくと、「やっぱり文字が小さい」場合によっては「文字が大き過ぎ」っていうことになる。DPIが異なるのだからこれはしょうがない。
多くのAndroidアプリが、スマホ用に開発されている。なので、そういうアプリをタブレットで動かすと、だいたい文字が小さくて使いずらい。文字が小さいと老眼な目にはちょっとつらいし、ボタンが押しにくい。
設計の段階では、画面の半分はこういうのに使って、半分はこうしよう、といった感じで画面に対する比率で設計していく、と思う。だから、設計段階では、DPIについてはあまり考えていない。
画面のレイアウトをどのDPIのディスプレイでもきれに表示することができるかは、実装の段階で決まってくる。ドット単位に画面を作っていたら、DPIが異なるディスプレイに持っていけば、レイアウトが崩れるのはごく自然なことである。
Androidでは、dipという単位を使うことができる。これなら、端末のディスプレイの解像度、大きさに依存しない単位で画面を設計できる。
XMLでレイアウトを定義するのなら、ドットじゃなくて、dipを使えば端末に依存しない画面を設計できる。
しかし、Canvasを使って描画する際の単位にdipは使用できない。単位はドットになる。
DisplayMetrics
DPIの異なる多くのディスプレイで見た目が同じようにCanvasで描画を行う場合、ディスプレイの解像度(ピクセル密度)を取得して、dipからドットに変換すればOK。
DisplayMetricsは、アクティビティで、getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);とすれば取得できる。getMetricsの引数にDisplayMetricsのインスタンスを渡すので、事前にnewして作成しておく。
displayMetrics.densityがピクセル密度を表すフィールド。
DisplayMetricsは、リソースからも取得できる。
dipにdensityを掛け算すれば、ドット(ピクセル)数が計算できる。
逆に、ドット(ピクセル)数をdensityで割り算すればdipになる。
ということで、onDrawでCanvasを使ってテキストを描画するときに、dipで指定したのであれば、Canvasのscaleでdensityを入れてしまえば、自動的にドットからdipに変換できる。
もちろん、ひとつひとつの数値を計算してもかまわない。
scaleでみな変換してしまった方が簡単。
AREarthroidでは、緑色で緯度経度などが表示される、モニタ表示部分をdensityでスケールして表示している。
写真の左側は、Xperia(スマホ)。右側がA100(タブレット)。画面サイズ、解像度、densityの値はそれぞれ異なるが、見た目はどちらも同じくらいの大きさとなっていることがわかる。
Canvasで、drawTextする際は、DisplayMetricsのdenstityを使おう、という話でした。
AREarthroid ARで地球を表示するアプリ
https://play.google.com/store/apps/details?id=cx.fam.asai.AREarthroid
以下、追記。
Android端末は自由に向きを変えられる。画面の解像度にしても、ランドスケープだと1920x1080、ポートレイトで1080x1920になったりするので、話がややこしくなっている。
しかも、1ドットの形状が、どうも正方形ではないらしい。これは、アスペクト比の計算について調べていたときに気付いた。
アスペクト比についての話は、以下を参照
http://asai-atsushi.blog.so-net.ne.jp/2013-06-12
DisplayMetricsでは、ディスプレイのドット数や解像度などを取得することができた。このdisplayMetricsの中に、dpiがある。さらに、dpiについては、x方向とy方向の両方ある。xdpiが横方向のDPIの値。ydpiが縦方向のDPIの値になる。
densityは、どっちなのよ?という感じだが...
で、このxdpiとydpiを表示させてみると、なんと違った値が出てくるのである。これは、縦横でDPIが違うっていうことになる。そうすると、ドットは正方形ではない。
といっても微妙にしか違わないので、「細かいことは気にしない」というのであれば問題ないか。
関連記事
サイト内を検索
タブレット型は、7~10インチが多いようである。11インチくらいになるとノートPCの領域に入ってくる。Android搭載のノートPCも存在しなくもないが、Androidとはちょっと違う気がする。
ということで、Android端末には、4~10インチまでの多種なディスプレイが装備されている。
acer【アイコニア】 ICONIA タブレットPC ネイビー ICONIATAB A100-07U16C
- 出版社/メーカー: 日本エイサー
- メディア: Personal Computers
SONY XPERIA mini ブラック ST15i SIMフリー PSE認可済み&国内変換アダプタ付
- 出版社/メーカー: Sony
- メディア: エレクトロニクス
一方、解像度(ドット数)はというと、800x480、1024x600、1280x768とスマホのグレードにより様々。解像度が高ければそれだけ、メモリが必要になる。
これまでも、WindowsなどでGUIを構築する際にも、ディスプレイの解像度というのは、けっこう重要であった。画面に多くの情報を詰め込もうと思ったら、広いディスプレイの方がいいに決まっている。
一枚のダイアログに多くのボタンやリストなどのコントロールを並べたら、低解像度のノートPCでダイアログが表示できなくなってしまった、といったトラブルはしょっちゅうであったと思う。
PCのディスプレイの場合、最初は640x480であった。これが、数年で800x600になり、1024x768になり、ワイドになって、1280x768とかになり、HDとなり、1920x1080とかになっている。さらなる高解像度への道は続くであろう。
解像度とディスプレイの大きさは別。大体、小さいディスプレイはそれなりに解像度も低く、大きいディスプレイほど、解像度は高い。
PCのディスプレイは、解像度が高くなるにつれて、大きさも大きくなる方向に進化してきた。なので、1ドットの大きさはあまり変化がなかった。ドットの大きさを示す単位としてDPIがある。DPIはDot Per Inch。1インチあたりにどれだけドットがあるかっていうこと。1インチは2.3cmくらいなので、ディスプレイの横方向(または縦方向)の2.3cmの直線上にどれだけドットが入っているかっていうこと。
だいたい、72~96といのがPCのディスプレイでのDPI。
これは、補足であるが、プリンタでのDPIは、2400とか9600になる。やけに大きいのは、プリンタの場合、ディザで色を表現するから。ディザの説明は、以下の記事を参照。
写真の印刷 プリンタのしくみについて考える
Android端末の場合、大きさは用途によって決定される。スマホなら4~5インチ。タブレットなら7~10インチ。解像度(ドット数)は、端末の価格によって大方決められる。高い端末なら高解像度、安い端末は低解像度。
結果、大きさと解像度の組み合わせで、多種多様なディスプレイが生み出され、DPIもまちまちになる。
大は小をかねる
大は小をかねる、の逆で、従来のGUI設計では、小さく作っておけば問題が少ない。最低の解像度に合わせて作っておけば、そんなに問題はなかった。決定的にダメなのは、ウインドウがディスプレイに収まりきらず、どうやっても操作できない状態に陥ること。
Androidでも同じではあるが、最低の解像度に合わせてGUIをレイアウトしていくと、広い画面のタブレットに持っていくと、「文字が小さすぎるよー」、とか「なんか無駄な空白があり過ぎー」っていうことになりかねない。
逆に、タブレットでイイ感じに、画面を作ってもスマホに持っていくと、「やっぱり文字が小さい」場合によっては「文字が大き過ぎ」っていうことになる。DPIが異なるのだからこれはしょうがない。
多くのAndroidアプリが、スマホ用に開発されている。なので、そういうアプリをタブレットで動かすと、だいたい文字が小さくて使いずらい。文字が小さいと老眼な目にはちょっとつらいし、ボタンが押しにくい。
設計の段階では、画面の半分はこういうのに使って、半分はこうしよう、といった感じで画面に対する比率で設計していく、と思う。だから、設計段階では、DPIについてはあまり考えていない。
画面のレイアウトをどのDPIのディスプレイでもきれに表示することができるかは、実装の段階で決まってくる。ドット単位に画面を作っていたら、DPIが異なるディスプレイに持っていけば、レイアウトが崩れるのはごく自然なことである。
Androidでは、dipという単位を使うことができる。これなら、端末のディスプレイの解像度、大きさに依存しない単位で画面を設計できる。
XMLでレイアウトを定義するのなら、ドットじゃなくて、dipを使えば端末に依存しない画面を設計できる。
しかし、Canvasを使って描画する際の単位にdipは使用できない。単位はドットになる。
DisplayMetrics
DPIの異なる多くのディスプレイで見た目が同じようにCanvasで描画を行う場合、ディスプレイの解像度(ピクセル密度)を取得して、dipからドットに変換すればOK。
DisplayMetricsは、アクティビティで、getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);とすれば取得できる。getMetricsの引数にDisplayMetricsのインスタンスを渡すので、事前にnewして作成しておく。
DisplayMetrics displayMetrics = new DisplayMetrics(); getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
displayMetrics.densityがピクセル密度を表すフィールド。
DisplayMetricsは、リソースからも取得できる。
getResources().getDisplayMetrics().density;
dipにdensityを掛け算すれば、ドット(ピクセル)数が計算できる。
逆に、ドット(ピクセル)数をdensityで割り算すればdipになる。
ということで、onDrawでCanvasを使ってテキストを描画するときに、dipで指定したのであれば、Canvasのscaleでdensityを入れてしまえば、自動的にドットからdipに変換できる。
protected void onDraw(Canvas canvas) { canvas.scale(displayMetrics.density, displayMetrics.density); paint.setTextSize(20); canvas.drawText("sample string", 4, 22, paint); }
もちろん、ひとつひとつの数値を計算してもかまわない。
protected void onDraw(Canvas canvas) { paint.setTextSize(20 * displayMetrics.density); canvas.drawText("sample string", 4 * displayMetrics.density, 22 * displayMetrics.density, paint); }
scaleでみな変換してしまった方が簡単。
AREarthroidでは、緑色で緯度経度などが表示される、モニタ表示部分をdensityでスケールして表示している。
写真の左側は、Xperia(スマホ)。右側がA100(タブレット)。画面サイズ、解像度、densityの値はそれぞれ異なるが、見た目はどちらも同じくらいの大きさとなっていることがわかる。
Canvasで、drawTextする際は、DisplayMetricsのdenstityを使おう、という話でした。
AREarthroid ARで地球を表示するアプリ
https://play.google.com/store/apps/details?id=cx.fam.asai.AREarthroid
以下、追記。
Android端末は自由に向きを変えられる。画面の解像度にしても、ランドスケープだと1920x1080、ポートレイトで1080x1920になったりするので、話がややこしくなっている。
しかも、1ドットの形状が、どうも正方形ではないらしい。これは、アスペクト比の計算について調べていたときに気付いた。
アスペクト比についての話は、以下を参照
http://asai-atsushi.blog.so-net.ne.jp/2013-06-12
DisplayMetricsでは、ディスプレイのドット数や解像度などを取得することができた。このdisplayMetricsの中に、dpiがある。さらに、dpiについては、x方向とy方向の両方ある。xdpiが横方向のDPIの値。ydpiが縦方向のDPIの値になる。
densityは、どっちなのよ?という感じだが...
で、このxdpiとydpiを表示させてみると、なんと違った値が出てくるのである。これは、縦横でDPIが違うっていうことになる。そうすると、ドットは正方形ではない。
といっても微妙にしか違わないので、「細かいことは気にしない」というのであれば問題ないか。
かんたんAndroidアプリ作成入門 (プログラミングの教科書)
- 作者: 朝井 淳
- 出版社/メーカー: 技術評論社
- 発売日: 2013/04/16
- メディア: 単行本(ソフトカバー)
関連記事
サイト内を検索
Androidアプリ開発 インテント タスク アクティビティ・スタック [Androidアプリ開発]
Androidはインテントによって、アプリ内、外のアクティビティを呼び出すことができる。明示的なインテントなら、ある特定のアクティビティを起動する、といった指定が可能だし、暗黙的インテントなら、画像データを表示できるアクティビティを起動というように、ざっくりとしたアクティビティの指定もできる。
呼び出されるアクティビティは、アプリの中にあってもいいし、外でもいい。アプリ内でのアクティビティの呼び出しではプロセス起動は行われない。同じプロセスでアクティビティが起動するだけ。アプリ外のアクティビティを呼び出した場合は、プロセスが起動されアクティビティが起動される。
インテントによりアクティビティの遷移が起こると、アクティビティがスタックに積まれていく。このスタックのことを「アクティビティ・スタック」と呼んでいる。ラウンチャーからアプリを起動すると、AndroidManifest.xmlのMAINアクションとなっているインテントフィルタを持っているアクティビティが最初にスタックに積まれる。
C言語での「プログラムはmain関数から始まります」みたいなものか。
C言語ではmainという名前の関数から始まるが、Androidでは、AndroidManifest.xmlで定義されているアクティビティのうち、MAINアクションのインテントフィルタが定義されているアクティビティから始まる。タスク関係の用語では、「ルート・アクティビティ」と呼ばれている。
ルート・アクティビティはスタックに最初に積まれているアクティビティ。ラウンチャーはスタックには積まれない。
C言語の場合も純粋に最初ではないかも知れないがスタックに積まれるのはmainが最初。
ラウンチャーから起動するときの様子だけなら「ルート・アクティビティ」じゃなくて「メイン・アクティビティ」の方がしっくりくるのかも。
スタックの様子を図にすると以下のような感じ。
ルート・アクティビティが別のアクティビティBを呼び出したとする。アクティビティBの画面が表示され、アクティビティ・スタックに積まれる。
設定画面もアクティビティとして実装されている場合が多いであろう。アプリのルート・アクティビティからプリファレンス・アクティビティをインテントで表示させると、スタックにはルート・アクティビティとプリファレンス・アクティビティのふたつが積まれている状態になる。
プリファレンス・アクティビティを戻るボタンで終了すると、スタックからポップされてなくなる。
もう一度、プリファレンスを表示したら、スタックにプッシュされる。
別のアクティビティをラウンチャーから起動すると、タスクがひとつ作成されて、アクティビティがスタックに積まれていく。
タスクリストというのであろうか。2.3にはないが、3.2にはバックボタンと横並びで、起動中のタスクを表示できるボタンが付いている。これで、バックグラウンドにあるタスクをフォアグラウンドに移動することができる。
2.3だと同様のリストが、ホームボタンの長押しで表示できる。
タスクリストで選択するとスタックごとフォアグラウンドになる。
C言語で例えるなら、「コンテキストスイッチ」または「タスクスイッチ」されるっていうことか。まさにタスクスイッチだもんなぁ...
実際に、切り替えて操作してみるものの、どうも動きが異なる。ホームボタン長押しで表示されるリストは、タスク切り替えのためのリストではないらしい。単なる起動履歴なのか。
これらのことが、android developpersの日本語訳
http://developer.android.com/intl/ja/guide/topics/fundamentals.html#acttask
に書かれているのだが、わかりにくい。訳が悪いのであろうか。
デフォルト設定のインテントでプリファレンス・アクティビティに遷移するだけなら、同じタスクのスタックにプリファレンス・アクティビティが積まれる。しかし、アクティビティの設定とインテントの設定によっては、そうはならず、別のタスクのスタックに積むことも可能なのである。
まず、簡単に理解できるのは、インテントのFLAG_ACTIVITY_NEW_TASKフラグ。このフラグが立っていれば、アクティビティが起動される際に、新しくタスクが起こされる。
と思って読み進めると簡単でもなかった。常に新しいタスクが起こされるか、というとそうでもなく、「同じ親和性」が設定されているアクティビティを検索し、それが存在していると、そのアクティビティを含むタスクのスタックに積まれる。
親和性により、より親和度が高いとみなされるタスクに移動する、ということにもできるらしい。
しかし、それって便利なんだろうか。アプリを使っているうちにいろいろなアクティビティが開いたり、閉じたりしている。それらが開かれた順番にスタックに保持されていて、戻るボタンで順に戻っていけるのは、操作性としてわかりやすいと思う。親和性によるアクティビティの移動をすると、途中でスタックの順番が入れ替わってしまうんでしょ。そうすると、戻るボタンで戻らないことになるじゃない。親が変わってしまうのか。
例えば、「ブラウザ」を開いて、「マップ」を開いて、「電話をかける」を開いて、戻ってもう一度戻るとGoogle Earthに戻った。っていうことにならないか?
親和性が高いから違和感はないのかなぁ... あれ、Google Earth使ってたっけ?ということにならないか。もしかしてバグったかと思ってしまうかも。
allowTaskReparenting属性をfalseにしておけばそういったことは起きないのか。
ラウンチャから起動する場合は、新しいタスクが作成される。しかし、既存のタスクがあれば、そのタスクをフォアグラウンドにするだけ。
えーと、AREarthroidとプレイスを別々にラウンチャーから起動すると以下のようになる。
後から起動したプレイスの方がフォアグラウンドにある。ラウンチャーでAREarthroidを起動(もしくは起動履歴リストから選択)すると、AREarthroidの方がフォアグラウンドになる。
別のアプリからインテントでアクティビティが起動される場合は、インテントとアクティビティの設定によるが、デフォルトだと呼び出す側のタスクのスタックに積まれる。
プレイスからAREarthroidを「この場所を共有」で開いて、設定画面を開くとタスクのスタックは
という感じになる。
戻るボタンでプレイスまで戻っていくことができる。
さらに、設定画面を開いている状態で、ラウンチャーからAREarthroidを起動するとタスクが増えて、以下のようになる。
AREarthroidのインスタンスがふたつできる。
と思ったのであるが、どうも動きが違うような。インスタンスがふたつできるのはあっているぽいが...
プレイスを起動するとスタックがクリアされている感じ。行き場がなくなったAREarthroidがおかしな動きをしている。
このあたりはもう少し調べる必要がありそう。
どうも「ちゃんとしたタスク切り替えができない」っていうところに問題がありそうな気がしている。
サイト内を検索
呼び出されるアクティビティは、アプリの中にあってもいいし、外でもいい。アプリ内でのアクティビティの呼び出しではプロセス起動は行われない。同じプロセスでアクティビティが起動するだけ。アプリ外のアクティビティを呼び出した場合は、プロセスが起動されアクティビティが起動される。
インテントによりアクティビティの遷移が起こると、アクティビティがスタックに積まれていく。このスタックのことを「アクティビティ・スタック」と呼んでいる。ラウンチャーからアプリを起動すると、AndroidManifest.xmlのMAINアクションとなっているインテントフィルタを持っているアクティビティが最初にスタックに積まれる。
C言語での「プログラムはmain関数から始まります」みたいなものか。
C言語ではmainという名前の関数から始まるが、Androidでは、AndroidManifest.xmlで定義されているアクティビティのうち、MAINアクションのインテントフィルタが定義されているアクティビティから始まる。タスク関係の用語では、「ルート・アクティビティ」と呼ばれている。
ルート・アクティビティはスタックに最初に積まれているアクティビティ。ラウンチャーはスタックには積まれない。
C言語の場合も純粋に最初ではないかも知れないがスタックに積まれるのはmainが最初。
ラウンチャーから起動するときの様子だけなら「ルート・アクティビティ」じゃなくて「メイン・アクティビティ」の方がしっくりくるのかも。
スタックの様子を図にすると以下のような感じ。
┃ ┃ ┃ ┃ ┃ ┃ ┃ルート・アクティビティ ┃←最初に積まれる ┗━━━━━━━━━━━━━━━━┛ アクティビティ・スタック
ルート・アクティビティが別のアクティビティBを呼び出したとする。アクティビティBの画面が表示され、アクティビティ・スタックに積まれる。
設定画面もアクティビティとして実装されている場合が多いであろう。アプリのルート・アクティビティからプリファレンス・アクティビティをインテントで表示させると、スタックにはルート・アクティビティとプリファレンス・アクティビティのふたつが積まれている状態になる。
┃ ┃ ┃ ┃ ┃プリファレンス・アクティビティ ┃←ここに積まれる ┃ルート・アクティビティ ┃ ┗━━━━━━━━━━━━━━━━┛ アクティビティ・スタック
プリファレンス・アクティビティを戻るボタンで終了すると、スタックからポップされてなくなる。
┃ ┃ ┃ ┃ ┃ ┃←ポップされてなくなる ┃ルート・アクティビティ ┃ ┗━━━━━━━━━━━━━━━━┛ アクティビティ・スタック
もう一度、プリファレンスを表示したら、スタックにプッシュされる。
┃ ┃ ┃ ┃ ┃プリファレンス・アクティビティ ┃←プッシュして積まれた ┃ルート・アクティビティ ┃ ┗━━━━━━━━━━━━━━━━┛ アクティビティ・スタック
別のアクティビティをラウンチャーから起動すると、タスクがひとつ作成されて、アクティビティがスタックに積まれていく。
┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃プリファレンス・アクティビティ ┃ ┃ ┃ ┃ルート・アクティビティ ┃ ┃ルート・アクティビティ ┃ ┗━━━━━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━━━━━┛ タスク1のアクティビティ・スタック タスク2のアクティビティ・スタック
タスクリストというのであろうか。2.3にはないが、3.2にはバックボタンと横並びで、起動中のタスクを表示できるボタンが付いている。これで、バックグラウンドにあるタスクをフォアグラウンドに移動することができる。
2.3だと同様のリストが、ホームボタンの長押しで表示できる。
タスクリストで選択するとスタックごとフォアグラウンドになる。
C言語で例えるなら、「コンテキストスイッチ」または「タスクスイッチ」されるっていうことか。まさにタスクスイッチだもんなぁ...
┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃プリファレンス・アクティビティ ┃ ┃ ┃ ┃ルート・アクティビティ ┃ ┃ルート・アクティビティ ┃ ┗━━━━━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━━━━━┛ タスク1のアクティビティ・スタック タスク2のアクティビティ・スタック バックグラウンド フォアグラウンド
実際に、切り替えて操作してみるものの、どうも動きが異なる。ホームボタン長押しで表示されるリストは、タスク切り替えのためのリストではないらしい。単なる起動履歴なのか。
これらのことが、android developpersの日本語訳
http://developer.android.com/intl/ja/guide/topics/fundamentals.html#acttask
に書かれているのだが、わかりにくい。訳が悪いのであろうか。
デフォルト設定のインテントでプリファレンス・アクティビティに遷移するだけなら、同じタスクのスタックにプリファレンス・アクティビティが積まれる。しかし、アクティビティの設定とインテントの設定によっては、そうはならず、別のタスクのスタックに積むことも可能なのである。
まず、簡単に理解できるのは、インテントのFLAG_ACTIVITY_NEW_TASKフラグ。このフラグが立っていれば、アクティビティが起動される際に、新しくタスクが起こされる。
と思って読み進めると簡単でもなかった。常に新しいタスクが起こされるか、というとそうでもなく、「同じ親和性」が設定されているアクティビティを検索し、それが存在していると、そのアクティビティを含むタスクのスタックに積まれる。
親和性により、より親和度が高いとみなされるタスクに移動する、ということにもできるらしい。
しかし、それって便利なんだろうか。アプリを使っているうちにいろいろなアクティビティが開いたり、閉じたりしている。それらが開かれた順番にスタックに保持されていて、戻るボタンで順に戻っていけるのは、操作性としてわかりやすいと思う。親和性によるアクティビティの移動をすると、途中でスタックの順番が入れ替わってしまうんでしょ。そうすると、戻るボタンで戻らないことになるじゃない。親が変わってしまうのか。
例えば、「ブラウザ」を開いて、「マップ」を開いて、「電話をかける」を開いて、戻ってもう一度戻るとGoogle Earthに戻った。っていうことにならないか?
親和性が高いから違和感はないのかなぁ... あれ、Google Earth使ってたっけ?ということにならないか。もしかしてバグったかと思ってしまうかも。
allowTaskReparenting属性をfalseにしておけばそういったことは起きないのか。
ラウンチャから起動する場合は、新しいタスクが作成される。しかし、既存のタスクがあれば、そのタスクをフォアグラウンドにするだけ。
えーと、AREarthroidとプレイスを別々にラウンチャーから起動すると以下のようになる。
┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃AREarthroid ┃ ┃プレイス ┃ ┗━━━━━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━━━━━┛ アクティビティ・スタック アクティビティ・スタック バックグラウンド フォアグラウンド
後から起動したプレイスの方がフォアグラウンドにある。ラウンチャーでAREarthroidを起動(もしくは起動履歴リストから選択)すると、AREarthroidの方がフォアグラウンドになる。
別のアプリからインテントでアクティビティが起動される場合は、インテントとアクティビティの設定によるが、デフォルトだと呼び出す側のタスクのスタックに積まれる。
プレイスからAREarthroidを「この場所を共有」で開いて、設定画面を開くとタスクのスタックは
┃ ┃ ┃AREarthroidPreference ┃ ┃AREarthroid ┃ ┃プレイス ┃ ┗━━━━━━━━━━━━━━━━┛ アクティビティ・スタック
という感じになる。
戻るボタンでプレイスまで戻っていくことができる。
さらに、設定画面を開いている状態で、ラウンチャーからAREarthroidを起動するとタスクが増えて、以下のようになる。
┃ ┃ ┃ ┃ ┃AREarthroidPreference ┃ ┃ ┃ ┃AREarthroid ┃ ┃ ┃ ┃プレイス ┃ ┃AREarthroid ┃ ┗━━━━━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━━━━━┛ アクティビティ・スタック アクティビティ・スタック
AREarthroidのインスタンスがふたつできる。
と思ったのであるが、どうも動きが違うような。インスタンスがふたつできるのはあっているぽいが...
プレイスを起動するとスタックがクリアされている感じ。行き場がなくなったAREarthroidがおかしな動きをしている。
このあたりはもう少し調べる必要がありそう。
どうも「ちゃんとしたタスク切り替えができない」っていうところに問題がありそうな気がしている。
サイト内を検索
Androidアプリ開発 ProGuardによる難読化と最適化 [Androidアプリ開発]
昨日のネタ、難読化の続きである。「ProGuardによるログの削除」にも使えることがわかった。通常、android.util.Log.v()で出力するログって、デバッグ時にしか使わなかったりする。Javaは条件コンパイルができないので、C/C++のようにassertで書いておいて、リリース時はデバッグ用のコードをなくすといったことができない。
ProGuardは難読化のために、メソッド名を異なる名前にしてしまうことができる。もちろん、呼び出す側と、呼び出される側の両方で変換が行われる。変更ができないAPIとかライブラリについては、変換されない。
ここは、変換して欲しくないな、ということをproguard-project.txtで指定できる。オプションの書き方で特定のメソッド呼び出しを「無かったこと」にもできるというのである。
どうするかというと、proguard-project.txtに以下のような設定を行うと、Log.vが削除される。
これは、やってみなくては。
APKをエクスポートする際に、Log.vが残っていても自動で消してくれる。
しかし、本来の用途はそういうことじゃないらしい。そんなトリッキーなことしていいのか?
assume no side effectsなんだろうな。ちょっと検索。
http://d.hatena.ne.jp/m12i/20110410/1302406778
から抜粋
-assumenosideeffects class_specification
(値を返す以外の)副作用のないメソッドを指定します。最適化のステップでは、ProGuardは返値が利用されていないと判断できるメソッドの呼び出しを削除しれます。ProGuardは入力クラスファイルのコードを解析し、そのようなメソッドを自動で発見します。しかしこの解析はライブラリコードに対しては実施されません。このため-assumenosideeffects オプションが活きていきます。例えば、無駄なメソッド呼び出しをすこしでも減らすため、System.currentTimeMillis() メソッドをこのオプションを使用して指定するといったことができます。ProGuardはこのオプションを、指定されたメソッドの呼び出し階層のすべてにわたって適用することに注意してください。最適化処理においてのみ有効なオプションです。一般的に、憶測でことをなすのは危険なことです。(訳者:このオプションの使い方を誤ると)容易にコードを破壊できてしまうのです。自身が何をしているかを理解している場合にのみ、このオプションを使用してください。
なるほど。ProGuardは最適化も行ってくれる。最適化で「意味の無いメソッド呼び出しを削除する」というのが本来の目的なのか。
Log.v()は戻り値で何かを返すのか?
intを戻すようである。voidじゃなかったのか。
だとすると、やらないとは思うが、戻り値を受けるような呼び出しのLog.vは削除されないっていうことだよね。
本当にそうか。ちょっと試してみよう。
上記のコードを書いて、APKを署名付きでエクスポート。
proguard/mapping.txtとかを見ても削除されたかどうかわからないなぁ...
APKの逆コンパイル
そこで、リバースエンジニアリングしてみることに。まず、APKファイルは、ZIPで圧縮されているので、拡張子をzipに変えて、展開してしまう。なんかエラーになるが、classes.dexはできた。
classes.dexをdex2jarにかけて、jarファイルに変換。
jarファイルをjar xvfで展開。
jadでclassファイルを逆コンパイル。
ん、a.classとかb.classになっているのは難読化の結果か。a.javaを見ると、フィールドa,b,cが並んでいる。そうか、こんな感じで難読化されるのね。でも、読みにくいけれど、全く読めないわけでもない。
えーと、Log.vはどうなったかな?
あれ、あるじゃん。
最適化オプションが無効になっているのか。
ProGuardの最適化は、Dalvik VMじゃあ通用しないっていうこと?
全体に対して適用するのは危険そうなのでやめた。
できるかどうかのテストのために、proguard-project.txtの方に元のproguard-android.txtの内容をコピーしてきて、-dontoptimizeのところをコメントアウト。
project.propertiesのproguard.configはproguard-project.txtのみに変更。
APKファイルをエクスポートして、zipで展開して、jarに変換して、展開して、jadにかけて、とけっこうやることがいっぱい。で、どうなったかなぁ...
おお、なくなった。けど、引数の部分は残っている。
戻り値を見ている方は削除できない。これは予想通り。2行目は、引数のところは何かしらの演算が行われているので、削除したらだめ、という判断で残されているのか。
他の部分にあった、Log.vは消えている。単純に文字列しか渡していないから。
でも、普通のデバッグ用のログには変数の値をくっつけて出すよなぁ。これじゃあ、中途半端。
最適化のパスを増やせばいいのかも?
よくわからないが、ProGuardでログを消すのは危険っぽいので止めておこう。
関連記事
サイト内を検索
ProGuardは難読化のために、メソッド名を異なる名前にしてしまうことができる。もちろん、呼び出す側と、呼び出される側の両方で変換が行われる。変更ができないAPIとかライブラリについては、変換されない。
ここは、変換して欲しくないな、ということをproguard-project.txtで指定できる。オプションの書き方で特定のメソッド呼び出しを「無かったこと」にもできるというのである。
どうするかというと、proguard-project.txtに以下のような設定を行うと、Log.vが削除される。
-assumenosideeffects class android.util.Log { public static int v(...); }
これは、やってみなくては。
APKをエクスポートする際に、Log.vが残っていても自動で消してくれる。
しかし、本来の用途はそういうことじゃないらしい。そんなトリッキーなことしていいのか?
assume no side effectsなんだろうな。ちょっと検索。
http://d.hatena.ne.jp/m12i/20110410/1302406778
から抜粋
-assumenosideeffects class_specification
(値を返す以外の)副作用のないメソッドを指定します。最適化のステップでは、ProGuardは返値が利用されていないと判断できるメソッドの呼び出しを削除しれます。ProGuardは入力クラスファイルのコードを解析し、そのようなメソッドを自動で発見します。しかしこの解析はライブラリコードに対しては実施されません。このため-assumenosideeffects オプションが活きていきます。例えば、無駄なメソッド呼び出しをすこしでも減らすため、System.currentTimeMillis() メソッドをこのオプションを使用して指定するといったことができます。ProGuardはこのオプションを、指定されたメソッドの呼び出し階層のすべてにわたって適用することに注意してください。最適化処理においてのみ有効なオプションです。一般的に、憶測でことをなすのは危険なことです。(訳者:このオプションの使い方を誤ると)容易にコードを破壊できてしまうのです。自身が何をしているかを理解している場合にのみ、このオプションを使用してください。
なるほど。ProGuardは最適化も行ってくれる。最適化で「意味の無いメソッド呼び出しを削除する」というのが本来の目的なのか。
Log.v()は戻り値で何かを返すのか?
intを戻すようである。voidじゃなかったのか。
だとすると、やらないとは思うが、戻り値を受けるような呼び出しのLog.vは削除されないっていうことだよね。
int rc = android.util.Log.v("tag", "ProGuardで削除されない"); android.util.Log.v("tag", "ProGuardで削除される" + rc);
本当にそうか。ちょっと試してみよう。
上記のコードを書いて、APKを署名付きでエクスポート。
proguard/mapping.txtとかを見ても削除されたかどうかわからないなぁ...
APKの逆コンパイル
そこで、リバースエンジニアリングしてみることに。まず、APKファイルは、ZIPで圧縮されているので、拡張子をzipに変えて、展開してしまう。なんかエラーになるが、classes.dexはできた。
classes.dexをdex2jarにかけて、jarファイルに変換。
jarファイルをjar xvfで展開。
jadでclassファイルを逆コンパイル。
ん、a.classとかb.classになっているのは難読化の結果か。a.javaを見ると、フィールドa,b,cが並んでいる。そうか、こんな感じで難読化されるのね。でも、読みにくいけれど、全く読めないわけでもない。
えーと、Log.vはどうなったかな?
int i = android.util.Log.v("tag", "ProGuardで削除されない"); android.util.Log.v("tag", (new StringBuilder("ProGuardで削除される")).append(i).toString());
あれ、あるじゃん。
最適化オプションが無効になっているのか。
# Optimization is turned off by default. Dex does not like code run # through the ProGuard optimize and preverify steps (and performs some # of these optimizations on its own). -dontoptimize -dontpreverify
ProGuardの最適化は、Dalvik VMじゃあ通用しないっていうこと?
全体に対して適用するのは危険そうなのでやめた。
できるかどうかのテストのために、proguard-project.txtの方に元のproguard-android.txtの内容をコピーしてきて、-dontoptimizeのところをコメントアウト。
# This is a configuration file for ProGuard. # http://proguard.sourceforge.net/index.html#manual/usage.html -dontusemixedcaseclassnames -dontskipnonpubliclibraryclasses -verbose # Optimization is turned off by default. Dex does not like code run # through the ProGuard optimize and preverify steps (and performs some # of these optimizations on its own). #-dontoptimize -dontpreverify # If you want to enable optimization, you should include the # following: # -optimizations !code/simplification/arithmetic,!code/simplification/cast,!field/*,!class/merging/* # -optimizationpasses 5 # -allowaccessmodification # # Note that you cannot just include these flags in your own # configuration file; if you are including this file, optimization # will be turned off. You'll need to either edit this file, or # duplicate the contents of this file and remove the include of this # file from your project's proguard.config path property. 途中省略 # The support library contains references to newer platform versions. # Don't warn about those in case this app is linking against an older # platform version. We know about them, and they are safe. -dontwarn android.support.** -assumenosideeffects class android.util.Log { public static int v(...); }
project.propertiesのproguard.configはproguard-project.txtのみに変更。
# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): proguard.config=proguard-project.txt
APKファイルをエクスポートして、zipで展開して、jarに変換して、展開して、jadにかけて、とけっこうやることがいっぱい。で、どうなったかなぁ...
int i = android.util.Log.v("tag", "ProGuardで削除されない"); (new StringBuilder("ProGuardで削除される")).append(i).toString();
おお、なくなった。けど、引数の部分は残っている。
戻り値を見ている方は削除できない。これは予想通り。2行目は、引数のところは何かしらの演算が行われているので、削除したらだめ、という判断で残されているのか。
他の部分にあった、Log.vは消えている。単純に文字列しか渡していないから。
でも、普通のデバッグ用のログには変数の値をくっつけて出すよなぁ。これじゃあ、中途半端。
最適化のパスを増やせばいいのかも?
よくわからないが、ProGuardでログを消すのは危険っぽいので止めておこう。
関連記事
サイト内を検索
Androidアプリ開発 Android SDK Tools リビジョン19 のエミュレータを試す [Androidアプリ開発]
エミュレータのハードウェアアクセラレーションが可能になったらしいので、リビジョンを上げてみることにした。
Android SDK Managerを起動すると、ARM EABI v7a System Imageのリビジョンが2になっている。Android SDK Toolsの方は19。
アップデート後、念のためEclipseを再起動。
AREarthroidで試してみるか。4.0.3に変更して。エミュレータを起動。ありゃ、2.2のエミュレータが起動してしまったぞ。しかもNullPointerで落ちたし。
ちょっと見なかったことにして、手動で4.0.3のエミュレータを起動。
これで、実行。
あいかわらず、UploadとInstallは遅いなぁ...
あらら、ライブラリのロードに失敗してる。おかしいなぁ。ユーザーID変えたからか...アンインストールしてからもう一回やってみる。
おっ出た。
しかし、なんか変。フラクタルみたいになっている。デプスバッファがおかしいのかなぁ...
確かに、パフォーマンスはよくなっている。アクセラレーションが効いている感じ。でも、前の方がまともに表示できていたような。
エミュレータの設定しないといけないのかなぁ...
GPU Emulation yesっていうのをAVDで追加してみた。
むむ、かなりいい感じになった。ほとんど実機と同じ感覚で動いている。しかし、おしいことになぜか、背景がクリアされない。どうして?
関連記事
サイト内を検索
Android SDK Managerを起動すると、ARM EABI v7a System Imageのリビジョンが2になっている。Android SDK Toolsの方は19。
アップデート後、念のためEclipseを再起動。
AREarthroidで試してみるか。4.0.3に変更して。エミュレータを起動。ありゃ、2.2のエミュレータが起動してしまったぞ。しかもNullPointerで落ちたし。
ちょっと見なかったことにして、手動で4.0.3のエミュレータを起動。
これで、実行。
あいかわらず、UploadとInstallは遅いなぁ...
あらら、ライブラリのロードに失敗してる。おかしいなぁ。ユーザーID変えたからか...アンインストールしてからもう一回やってみる。
おっ出た。
しかし、なんか変。フラクタルみたいになっている。デプスバッファがおかしいのかなぁ...
確かに、パフォーマンスはよくなっている。アクセラレーションが効いている感じ。でも、前の方がまともに表示できていたような。
エミュレータの設定しないといけないのかなぁ...
GPU Emulation yesっていうのをAVDで追加してみた。
むむ、かなりいい感じになった。ほとんど実機と同じ感覚で動いている。しかし、おしいことになぜか、背景がクリアされない。どうして?
関連記事
サイト内を検索
Androidアプリ開発 ProGuardによる難読化 [Androidアプリ開発]
本日は、NDKの話をしようと思って書き始めたのではあるが、難読化についてである。ご存じのように、Androidアプリは基本Javaで開発する。Javaで書いておけば、プラットフォームの違いは、JavaVMが吸収してくれる。
しかし、Javaはどうしても実行速度が遅くなる。機械語に翻訳しながらプログラムを実行させているわけなので。
ガベージコレクションも結構、邪魔者扱いされるときがある。「解放しなくてもいい」っていうのは、プログラマにとっては楽なんだけどねぇ。
ゲームなんかだと、ガベージコレクションで画面の更新が止まってしまうからいや。といった発言があちこちで見られる。
ネイティブな機械語のプログラムを走らせた方が実行速度はぜんぜん速い。ポータビリティを考えなくてよいのであれば、C/C++でアプリを書いた方が速いプログラムを作成できるっていうこと。
難読化
マーケットに出すアプリであると、リバースエンジニアリングされることも考えないといけない。Javaのバイトコードは、機械語ではないので、簡単にソースコードの状態に戻すことができる。もちろん、コメントとかはなくなっているが、変数名とかはそのまま。
バイトコードへのコンパイル時に、最適化が行われるので、逆コンパイルしても全く同じソースコードにはならない。しかし、それでも開発したソースコードをさらしていると思うと気分はよくない。
最適化と同時に、「難読化」といったことも行われているらしい。
AndroidManifest.xmlの下に、proguard.cfgというテキストファイルがある。これが難読化の設定ファイル。
難読化でどういったことが行われるかというと、メソッド名や変数名が意味不明の番号などに置き換えられる。っていうことらしい。
C/C++で作成した機械語のプログラムでも逆コンパイルでアセンブラレベルまでには戻せる。リバースエンジニアリングできなくもないが、けっこう大変。関数や変数がみなアドレスに変換されてしまっているから。
これと同じように、メソッド名、変数名をそのままじゃなくて、アドレスのように人間が見たら何これ?って思うような意味の無い名前に変換してしまう。それが、難読化のうちのひとつで「名前の無意味化」というもの。ProGuardの機能のひとつ。
名前を変換することで、プログラムのファイルサイズが幾分か小さくなる、といった効果もあるとか。
うん?ちょっとまてよ。AREarthroidで例外が報告されていた際に、「難読化されているかも」といった注意書きがあった。メソッド名が無意味なものに変換されていたら、スタックトレースを見ても何がなんだか理解不能なはず。
ちゃんとメソッド名が出ているっていうことは、難読化されてない?
だよねぇ。java.langとか、android.openglとかのソースは変換しちゃだめな気がするが、自分で作成している部分については、難読化してくれていてもよさそうなのだが...
ちゃんと設定していないとだめなのか。
調べ中...
project.propertiesでproguard.config=proguard.cfgっていう設定が必要なのか。がーん。
試しにやってみる。
project.propertiesを編集後、プロジェクトをクリーン。実行。
アプリは、普通に起動されたが、難読化された形跡がないなぁ。
さらに調べ中...
ADTのr17で変更されたのか。proguard-project.txtになってるし。アンコメントすればいいだけなのか。
これでいいのか?
以下の記事がr17での変更について詳しく書かれていた。
http://d.hatena.ne.jp/bs-android/20120325/1332662384
しかし、これでも難読化された形跡がないのだが...
本当に難読化されたのであろうか。
どうも、署名付きのAPKをエクスポートする際に、発動するものらしい。エミュレータで実行している分にはProGuardの出番はない。
じゃあ、APKを作ってみるとしよう。
おお、プロジェクトにproguardのフォルダができた。その中のmapping.txtをみると...
やってるやってる。
最初の行はクラス名の変換だな。
次は、フィールドか。順番にa、b、cと名前を付けていく模様。
これは、AREarthroidでもやらなければ。
しかし、難読化することで問題が発生するかも知れないので十分にテストしないと。
関連記事
サイト内を検索
しかし、Javaはどうしても実行速度が遅くなる。機械語に翻訳しながらプログラムを実行させているわけなので。
ガベージコレクションも結構、邪魔者扱いされるときがある。「解放しなくてもいい」っていうのは、プログラマにとっては楽なんだけどねぇ。
ゲームなんかだと、ガベージコレクションで画面の更新が止まってしまうからいや。といった発言があちこちで見られる。
ネイティブな機械語のプログラムを走らせた方が実行速度はぜんぜん速い。ポータビリティを考えなくてよいのであれば、C/C++でアプリを書いた方が速いプログラムを作成できるっていうこと。
難読化
マーケットに出すアプリであると、リバースエンジニアリングされることも考えないといけない。Javaのバイトコードは、機械語ではないので、簡単にソースコードの状態に戻すことができる。もちろん、コメントとかはなくなっているが、変数名とかはそのまま。
バイトコードへのコンパイル時に、最適化が行われるので、逆コンパイルしても全く同じソースコードにはならない。しかし、それでも開発したソースコードをさらしていると思うと気分はよくない。
最適化と同時に、「難読化」といったことも行われているらしい。
AndroidManifest.xmlの下に、proguard.cfgというテキストファイルがある。これが難読化の設定ファイル。
難読化でどういったことが行われるかというと、メソッド名や変数名が意味不明の番号などに置き換えられる。っていうことらしい。
C/C++で作成した機械語のプログラムでも逆コンパイルでアセンブラレベルまでには戻せる。リバースエンジニアリングできなくもないが、けっこう大変。関数や変数がみなアドレスに変換されてしまっているから。
これと同じように、メソッド名、変数名をそのままじゃなくて、アドレスのように人間が見たら何これ?って思うような意味の無い名前に変換してしまう。それが、難読化のうちのひとつで「名前の無意味化」というもの。ProGuardの機能のひとつ。
名前を変換することで、プログラムのファイルサイズが幾分か小さくなる、といった効果もあるとか。
うん?ちょっとまてよ。AREarthroidで例外が報告されていた際に、「難読化されているかも」といった注意書きがあった。メソッド名が無意味なものに変換されていたら、スタックトレースを見ても何がなんだか理解不能なはず。
ちゃんとメソッド名が出ているっていうことは、難読化されてない?
だよねぇ。java.langとか、android.openglとかのソースは変換しちゃだめな気がするが、自分で作成している部分については、難読化してくれていてもよさそうなのだが...
ちゃんと設定していないとだめなのか。
調べ中...
project.propertiesでproguard.config=proguard.cfgっていう設定が必要なのか。がーん。
試しにやってみる。
project.propertiesを編集後、プロジェクトをクリーン。実行。
アプリは、普通に起動されたが、難読化された形跡がないなぁ。
さらに調べ中...
ADTのr17で変更されたのか。proguard-project.txtになってるし。アンコメントすればいいだけなのか。
# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): proguard.config=${sdk.dir}\tools\proguard\proguard-android.txt:proguard-project.txt
これでいいのか?
以下の記事がr17での変更について詳しく書かれていた。
http://d.hatena.ne.jp/bs-android/20120325/1332662384
しかし、これでも難読化された形跡がないのだが...
本当に難読化されたのであろうか。
どうも、署名付きのAPKをエクスポートする際に、発動するものらしい。エミュレータで実行している分にはProGuardの出番はない。
じゃあ、APKを作ってみるとしよう。
おお、プロジェクトにproguardのフォルダができた。その中のmapping.txtをみると...
やってるやってる。
cx.fam.asai.TestOpenGL.Model -> cx.fam.asai.TestOpenGL.a: java.nio.FloatBuffer buffer -> a java.nio.FloatBuffer normalBuffer -> b java.nio.FloatBuffer textureBuffer -> c
最初の行はクラス名の変換だな。
次は、フィールドか。順番にa、b、cと名前を付けていく模様。
これは、AREarthroidでもやらなければ。
しかし、難読化することで問題が発生するかも知れないので十分にテストしないと。
関連記事
サイト内を検索
Copyright Atsushi Asai Google+朝井淳
[データベースの気持ちがわかる]SQLはじめの一歩 (WEB+DB PRESS plus)
- 作者: 朝井 淳
- 出版社/メーカー: 技術評論社
- 発売日: 2015/03/03
- メディア: 単行本(ソフトカバー)
Access クエリ 徹底活用ガイド ~仕事の現場で即使える
- 作者: 朝井 淳
- 出版社/メーカー: 技術評論社
- 発売日: 2018/05/25
- メディア: 大型本