iアプリ・アーカイブス
これを使えばAndroid端末でiアプリを動かすことができるよ。
2Dゲームのみ。音は出ないよ。
諸事情により、このへんのゲームは動くよ。
- もののふの宇宙
- 海中ピラミッド
- 海中ピラミッド2
他もだいたい動くかんじ。
動かないアプリがあったら教えてね。
これを使えばAndroid端末でiアプリを動かすことができるよ。
2Dゲームのみ。音は出ないよ。
諸事情により、このへんのゲームは動くよ。
他もだいたい動くかんじ。
動かないアプリがあったら教えてね。
iアプリをAndrod上で動作させるため、
SJIS(Shift_JS)のテキストファイルを読み込もうとしている。
しかしこれには文字化けの問題がある。
Androidは一旦置いとく。
まず元となるテキストファイルを用意する。
文字コードはutf-8
とした。
hoge.txt
アンドロイド☆イズ☆デッド
このファイルの文字コードをSJIS
に変更する。
% iconv -f utf-8 -t sjis hoge.txt > hoge.sjis.txt
InputStreamReader
に文字コードを指定する。
指定しないとデフォルトの文字コードを使う。
Androidも(キミのmacも)デフォルトはutf-8
だ。
FileInputStream is = new FileInputStream("hoge.sjis.txt");
InputStreamReader isr = new InputStreamReader(is/*, "utf-8"*/);
BufferedReader reader = new BufferedReader(isr);
String line = reader.readLine();
System.out.println("line: "+bytesToHex(line.getBytes(/*"utf-8"*/)));
bytesToHex(byte[]) http://stackoverflow.com/a/9855338
line: EFBFBD41EFBFBDEFBFBDEFBFBD68EFBFBDEFBFBDEFBFBD43EFBFBD68EFBFBDEFBFBDEFBFBD43EFBFBD59EFBFBDEFBFBDEFBFBD66EFBFBD62EFBFBD68
もちろんこんなString line
をそのままUTF-8文字として出力しても
文字化けして読めないんだけど、
そのバイト列としても、なんだかおおよそ文字とは思えないものがでてくる。
なんだこれは。
もとの文字列のバイト列とくらべてみよう。
% hexdump hoge.sjis.txt
の出力は
0000000 83 41 83 93 83 68 83 8d 83 43 83 68 81 99 83 43
0000010 83 59 81 99 83 66 83 62 83 68 0a
000001b
である。やっぱぜんぜん違う感じする。
utf-8
として読み込んだ文字列のバイト列には
EFBFBD
というものが繰り返し出現することがわかる。
これを**
に置き換えて整形すると……
0000000 ** 41 ** ** ** 68 ** ** ** 43 ** 68 ** ** ** 43
0000010 ** 59 ** ** ** 66 ** 62 ** 68
似ている。
InputStreamReader
がhoge.sjis.txt
の内容を読むときに、
utf-8
の文字が来ると期待するため、
その範疇にない文字を「表現できない文字」として
EFBFBD
に置き換えてしまっている。
JavaのString
になった時点で、元の情報を失う。
InputStreamReader
で文字化けしたStringを、元のbyte列に戻すことはできない。
それゆえ、他の文字コードに変換することもできない。
iアプリではデフォルトの文字コードがSJIS
だった。
Androidではutf-8
。
だから、
new InputStreamReader(is);
とか、
"いやまずiアプリって何".getBytes();
とかやったときに使われる文字コードが
iアプリとAndroidで異なる。
Shift_JISとSJISは必ずしも一致しなかった。
文字コードはUTF-8に統一されつつあるが、未だに悩ましい問題の一つだ。
.java
ファイルはコンパイルすると.class
ファイルになる。
AndroidのDalvikVM
ではこのClassファイルをDx
により、
1つのclasses.dex
なるファイルにまとめる。
Javaファイルを書くときはその文字コードを気にすることもあるだろう。
(とはいえUTF-8一択だ。「気にする」というのはUTF-8になっているかどうかを気にするのだ)
しかしClassファイルの文字コードを気にしたとこはあるだろうか?
Shift_JISでエンコードされたJavaファイルをClassファイルにコンパイルしたとしよう。
クラスローダーはこのClassの文字コードをどのように判断しているのだろうか。
答えは簡潔である。
Classファイルにおいて文字列の文字コードは統一されているのだ。
Classファイルのなかで文字列は「UTF-8っぽい」文字コードで表現される。
それゆえ、Javaはコンパイル後に文字化けすることがない。
JavaファイルをClassファイルにコンパイルするときに、Javaファイルの文字コードを指定してあげる。
javac
のオプションに-encoding
がある。
追記:iアプリを動かすAndroidアプリ、公開しました。
http://android.develga.com/2014/07/androidi.html
iアプリは米国Sun Microsystems社(現在は米国Oracle社)により開発された家電製品・組み込みデバイス向けJavaプラットフォームであるJ2ME CLDCで規定されているAPIと、ユーザインターフェースやHTTP通信などiモード対応機種用に作成されたiアプリAPIにより構成されています。
https://www.nttdocomo.co.jp/service/developer/make/content/iappli/architecture/index.html
iアプリはiPhoneで動くアプリのことではない。
iアプリは docomoのガラケーで動くアプリ のこと。
iOSアプリとかAndroidアプリとかのガラケー版な。
(そういう言い方は気に食わないがな)
私が今回の記事で扱うのはゲームのみな。
記事タイトルをみて、
せっかくスマホにしたっていうのに、Androidでiアプリやらないよ。
いまは、パズドラ、モンストだろ。
と思ったかもしれない。
Androidでiアプリはやらない理由として以下を挙げておく。
iアプリしょぼいとかいうキミの記憶は 間違っている 。
確かにファミコン以上、スーファミよりしょぼいクオリティのゲームが多かった。
しかし、短絡的に判断してはならない。
なぜなら、iアプリの全盛期には、スーファミも
いや!プレステも、
いやや!Wiiも
発売された世界で作られたのである。
それゆえアイディアとして尖ったもの、洗練されたものが(なかには)あった。
リッチなゲームが溢れる世界で膨らんだそういったアイディアは、
非常に(いやまじで非常に)限られたリソースをいかに効率的に使うか、
という技術的な問題を乗り越えて作成された。
一方でiアプリ超おもしろいというキミの記憶も 間違っている 。
iアプリはあくまでレトロゲームであり、
個人小規模ゲームが台頭した時代の歴史的資料に過ぎない。
思い出補正というやつだろう。
iアプリは歴史的な資料。アーカイブだ。
遊ぶのではなく、歴史を閲覧するのだ。
ファミコンや初代プレステのゲームは今も社会に残っていると言える。
なぜなら、今も遊べるからだ。
Wiiやプレステで昔の古いゲームが販売され続けている。
カセットをデータ化すれば
ファミコンというハードなしで、
パソコンやスマホ上で動かすことができる。
ゲームは遊べないと社会から消えるのだ。
だから
とかは、まー、その、どうでもいいんだ!!!!!
というわけでAndroidでiアプリを動かすことを考える。
幸運なことにJavaだ。
しかももっと幸運なことにこのブログが対象としているAndroidアプリもJavaだ。
しかしiアプリはDocomo専用のAPIを含んだDoja(ドコモジャバ)だ。
(Dojaをなんて読むのかは知らない。今となっては知る由もない。)
やることは以下だ。
できそうだ。
iアプリをAndroidアプリに変換しようという人はすでにいた。
とか。
どれもアーカイブ目的じゃない。ガチで売る気だ。
なにより私が気になるのは「ソースコード」が必要ってところ。
それじゃあ製作者しかAndroidアプリに変換できない。
iアプリの開発環境を入れたWindowsでは、iアプリを動かすことができる。
(どうやってアプリを入手するかは別の話)
つくります。
音と3Dは諦めそう。。。
Java プログラムでメモリー・リークが発生するのでしょうか?
その通りです。
http://www.ibm.com/developerworks/jp/java/library/j-leaks/
相互参照は問題ない。
問題なのはライフサイクルの不一致。
Javaはすごい。
ガーベッジ・コレクション(GC)という仕組みによって、
参照できなくなったメモリ領域を自動的に開放してくれる。
だからC/C++のようなメモリリークは起こらない。
参照できなくなったメモリ領域とは、どのようなものだろう。
Javaではうまいことなっているのだが、まずはすごーく素朴なものを考えてみよう。
たとえば、そのオブジェクトの参照されている数を記録しておき、0になったら破棄するというのはどうだろう。これをリファレスカウンタ方式という。
すごーく素朴な方式で軽量ではあるが、これには 相互参照 の問題がある。
理解しやすくするため、
3つのオブジェクト「キミ」と「ゴリラ」と「バナナ」を考えよう。
キミはメインクラスなので被参照数が1ある。(キミの被参照数=1)
キミはゴリラを見ている。(ゴリラの被参照数=1)
ゴリラはバナナを見ている。(バナナの被参照数=1)
これですべてのオブジェクトは解放されない。
ここで、ゴリラが要らなくなったとしよう。
キミはゴリラから視線を外す。
すると、誰にも見られていないゴリラが解放される。
続いて、誰にも見られていないバナナが解放される。
ゴリラとバナナは正しく解放できた。
さて、ここで問題だ。
バナナがゴリラを見ていたら、何が起こるだろう。
キミがゴリラへの視線を外しても、
ゴリラとバナナの被参照数は1となり、解放されないのだ!
もうゴリラはいらないのに、ゴリラは居座り続けるし、
なにより、バナナも残ってしまう。
相互参照とは、バナナとゴリラが残ること、である。
さて、JavaのGCはいかにして上記の問題を解決するのだろうか。
それはルートオブジェクトの導入である。
まずキミに色を塗る。
そしてキミの見ているもの、つまりゴリラに色を塗る。
そしてゴリラの見ているバナナに色を塗る。
で、適当なタイミングで色が塗られていないものを解放する。
これならバナナがゴリラを見ていても
ゴリラが二度塗りされるだけだ。いわゆる二度塗りゴリラだ。
キミがゴリラから視線を外せば
ゴリラもバナナも色塗りされずに解放される。
純粋なキミは、「え、じゃあみんなこれやればいいじゃん」と思うだろう。
しかし、リファレスカウンタ方式に比べて処理が重いとか
ダメなところもあるので一概には言えない。
端的に言えば、ライフサイクルの不一致だ。
メンバー間の音楽性の不一致によって解散するバンドも数多い。
キミは80年生きる。
そのなかでもう使わないのに参照し続けているものがあるのではないか。
たとえば、部屋の隅にある小5のときに書いた文集だ。
当時は確かに使った。
友達のやつは読んだし、自分のも読んだし、あの子のも読んだ。
中学の時に読み返して、なんでこんなこと書いたんだと不思議に思った。
しかし小6ならまだしも、小5である。もう読むことはないだろう。
それはいわばゴミなのだ。Garbageなのだ。Collectされるべきなのだ。
しかし、キミから参照されているために
このゴミはお母さんによって片付けられることはない。
(なお、お母さんがマーク・アンド・スイープ方式を採用しているかは議論の余地が残る)
AndroidではAndroidの用意してくれたフレームワークという舞台上で
キミのイカしたActivityやServiceその他を踊らせていくことになる。
このフレームワークの生存期間は(たいてい)キミのアプリが起動している期間と等しい。
やっかいなことにもっと長いものもある。たとえばタイマー処理だ。
たとえばタイマー処理にゴリラを参照させると、そのタイマーが解放されるまでゴリラはメモリ上に残る。
ゴリラだったらいい。
これがもしActivityだとすると、ActivityはViewをたくさん持っているし、画像も持ってるかもしれないしメモリが大きいので問題になる。
あと、タイマーの時間にもよる。タイマーが24時間後だったらやばい。
24時間ゴリラがメモリ上にいるのだ!!!危ない!!!
Androidでは何がどれくらいの長さ保持されているかが不明瞭なため、
こういったメモリリークが起こりやすい。
Activityで使うもの(ゴリラ)はActivityに持たせる。
そうすればActivityが解放されるタイミングで
それ(ゴリラ)も解放される。
ライフサイクル、つまり
いつ作られて、いつ要らなくなるか
という世界で閉じた作りになっていれば、メモリリークは怖くないのだ。
面接で質問された人もいるという
http://stackoverflow.com/questions/6470651/creating-a-memory-leak-with-java
「これがあったら便利だよね」ってのを、どっかのおっさんが用意してくれる。
それがOpenCVだ。
http://gori-naru.blogspot.jp/2012/11/opencv.html
画像をいじりたい人が使うライブラリ。
だいたい無料で使い放題。
Androidで使うのはマジカンタンだからあ、やったほうがよくね?笑
http://opencv.org/downloads.html へ赴く。
OpenCVはいろんなOS用で動くのだ!wa-i
もちろんここではAndroid版をダウンロードする。
でいい感じのトコに解凍する。
ダウンロードディレクトリとかじゃないほうがいい。
Eclipse起動してくれ。
OpenCV-2.4.8-android-sdk/sdk/java
をインポートする。
これAndroidライブラリプロジェクトだよ。
これでおしまい。
せっかくなのでサンプルを動かしてうぇーいしてみる。
OpenCV-2.4.8-android-sdk/samples/tutorial-1-camerapreview
をインポートして。起動する。
うぇーい
うぇーいできない??
なんかダイアログでた??
そしたら、ナウい日本人丸出しでYes!Yes!って答えておけばおk。
OpenCVの本体が、全部のOpenCVを使ったアプリに1つずつ入っていたら、
端末の容量すげー食って嫌だよね。
まるで、別荘にもワイフがいるようなものさ。
(OpenCV開発者談)
OpenCVはアプリの中に組み込むこともできるけど、
マネージャアプリを通して端末にインストールされたOpenCVを
利用することもできるんだ。
このどっちの方法をとるかは、アプリをつくる側が決められる。
サンプルアプリはマネージャアプリを使っている。
とはいっても、キミの超イケてるCoolなアプリを起動させるために、
件のダイアログを出して、この超ださいアイコンのアプリを
インストールさせるのは気がひけるよね。
たいていはJavaでいじっていればいい。
OpenCVの内部はC/C++で書かれているので十分速いためだ。
しかしNDKから使えないと不便なときもある。
たとえば、JNIとJavaの橋渡しは少し時間がかかる。
非常に頻繁に(それがどれくらいかはキミの想像に任せる)
JavaからOpenCVのメソッドを呼び出していると、
やがてその少しの時間がうざったくなる時が来るのだ。
C/C++でOpenCVをいじったことのある人は、
画像配列をJNIでやりとりして、
実装をC++で書いてしまったほうがラクだ。
jni
ディレクトリがあるものが、NDKを使うサンプルだ。
OpenCV-2.4.8-android-sdk/samples/tutorial-2-mixedprocessing
が該当する。
このプロジェクトをインポートすると
以下の様なエラーがコンソールに出力されちゃったりしちゃうのだ。
error: Program “/ndk-build.cmd” is not found in PATH
まず、NDKを使えるようにしよう。
NDKをダウンロードとか、
Eclipseの設定からAndroidの項目を選び、NDKを選択して、
NDKのパスを設定するとか、
NDKのディレクトリにパスを通すとかはやってある?
それだったら、このサンプルプロジェクトの設定を開き、
C/C++ Build > Tool Chain Editor > Current Builder
の項目をAndroid Builder
にする。
そうしたらビルドできて、動くよ。
うぇーい
ブロードキャストとは不特定多数に同じ情報を同時に送ること。
ここでいう「情報」とはなにか?AndroidではIntent
を用いる。
Intent
をAndroid端末内に投げるイメージでよい。
Androidのプロセス間通信に用いることができる。
シャットダウンするよ〜とか
アプリがインストールされたよ〜とか
そういう系のaction
をもつIntent
は
Androidがブロードキャストしている。
そういうのをBroadcastReceiver
で拾うことができる。
いや、拾うだけじゃない。
僕らも自由にブロードキャスっていいんだぜ?
A description of an Intent and target action to perform with it.
android.app.PendingIntent
他のアプリケーションが、あとから実行できるIntent。
他のアプリケーション(VM)に、
あとで呼び出して欲しいIntentを作って渡す。
といってもIntentは自分のアプリ内に対する、明示的なものじゃないとダメな。
Intentを
などに渡すとそのまま実行されるが、
に渡すとそれぞれの保留Intentを取得できる。
これを他のアプリケーションに渡してあげよう。
他のアプリ(VM)からIntent実行できるとかキモいよな。
しかもPendingIntentは親のアプリが死んでも使える。ヤバい。
果たしてその実体は!?という感じだけど、
PendingIntentはただのトークン。
トークンとデータを紐付けているのはAndroidのシステム。
つまり大元のシステム側にデータを保存してるだけ。
もしも同じようなPendingIntent
がget(getActivity
とか)されたら、
使いまわされるような仕組みになっている。これ大事。
同じようなやつかどうかはIntent.filterEquals
によって判定されるんだけど、
これ、extra
をみていない。
(action, data, type, class, categoriesが同じかどうかを判定している)
だからextra
かえたPendingIntent
を2つ作ると、実体は1つだけになる。
この使いまわしの方法はgetするときにflag
で指定できる。
で使いまわされたくないなら、getするときのrequestCode
を別々の値にする。
つまり、この設計思想はアレに似てる。そう、
Context.startActivityForResult()
だ。
他のActivityに対して返り値を1つだけ要求する。
他のアプリに対してIntentを1つだけ要求する。感じ。
まあそれだけだとアレだから、ってのでrequestCode
が用意してある。
requestCode
をアプリ内でユニークにしておけば、上書きされることは無い。
しかし果たしてほんとにぜったいにPendingIntent
が複数必要なのか、
自分の胸に手を当てて確認して欲しい。
When deciding which processes to kill, the Android system weighs their relative importance to the user.
Processes and Threads
Androidは 殺人鬼 である。
生きている価値がないと(一方的に、偏見をもって)みなされたアプリは
片っ端から殺されてしまう。
一方、Androidは O MO TE NA SHI 上手 でもある。
必要なアプリは丁重に扱われ、少ない、そして限られた、
すごーく貴重な リソースを提供し、
たとえ画面から見えなくなっても紅茶飲み放題は当たり前、
マフィンも食べ放題であるかは電脳世界の中に入ったことがないのでわからない。
まず、たいていのアプリは1つのプロセス内で動いている。
Androidはバックグラウンドにまわったプロセスも保持している。
だって一旦別のアプリ使い始めたっていっても、油断ならないじゃん。
またすぐ戻って使うかもしれないじゃん、キミたちは。
でもメモリが少なくなると、んな悠長なこと言っていられないから、
そういうプロセスは終了させてしまう。
これはLow Memory Killer
による挙動だから詳しくはそれでググって。
無差別に殺すわけではなくて、優先順をつけている。
このアンドロイド、一応の理性は有るようだ。
だいたいこれ。
優先順が高いほど、殺されにくい。
殺されたプロセスは復元される。
別にLow Memory Killer
に殺されたときだけではない。
ランタイムエラーで殺されたプロセスも復元されるんだよ。
そのときActivityは1つだけ復元される。
殺される前、最後に開いていた画面だ。
他のActivityはまだCreateされない。
画面を戻ると順々に復元されていく。
ActivityはActivity.onCreate()
の引数として
Activity.onSavedInstanceState()
で保存したデータを取得できる。
だからプロセスごと殺されたときに復元できるよう、
十分なデータを保存しておくべきだ。
けどめんどくさいから重要なやつだけでいいよ。
static
な変数とか。
ブログでは メモリ不足だとActivityは死ぬ! と書かれているのを見る。
たしかにActivityは殺される。
しかし、Activityが殺されるのは プロセス が殺されるからだ。
ずーっと同じアプリを使っている限り、
メモリが足りなくなった時に、1つ前のActivityが死んだりはしない。
もしメモリ不足であるActivityが死んだのなら、
そのアプリの他のActivityも死んでいる。
An intent is an abstract description of an operation to be performed.
android.content.Intent
まずインテントは「意思」という意味だ、念のため。
Intentはaction
とdata
という2つの情報で
やりたい操作を抽象化したもの だといえる。
例えばaction
が「編集する(ACTION_EDIT
)」で、
data
がcontent://contacts/people/1
だったら、
電話帳のIDが1のデータを編集したい!
ってことだろう。
こんな感じの「やりたい!」を、
あるActivityに渡したり、Android端末中に撒き散らしたりできる。
Intentが持つのはaction
、data
だけではない。
category
type
component
extras
この中で重要なのはextras
だ。
基本的には相手に渡したい情報をextras
へ突っ込んでいくことになる。
たとえば「メールを送りたいんです!」というインテントだったら、
extras
にはメールの題名や本文が入っているのだろうね。
渡す方法も大切。
Activityを立ち上げたかったら、
startActivity
を使って渡す。
ブロードキャストりたかったら、
sendBroadcast
を使って渡す。
例えばブロキャスればレシーバが勝手に反応してくれる。
action
の値を見て、自分が処理すべき情報かを見極めている。
「それだったら俺やりまっせー」
みたいなことだ。
action
は文字列なんです。
だから他のプロセス(VM)でも比較できるわけ。
で、Intent
を撒き散らしちゃう時に
action
の値としてACTION_WRITE
とか設定しちゃうと、
他のアプリも同じ値のaction
を投げてきそうじゃん。
そしたら間違って受け取っちゃう。
だから自分の アプリオリジナル☆ なインテントを作るときは、
com.oreno.package.name.ACTION_WRITE
みたいな名前にしようぜ、
ってGoogleは言ってる。
暗黙的なインテント はアニメタイトルっぽいけどアニメタイトルではない(随筆時)
Implicitが暗黙的、Explicitが明示的だ。
ここでは 明示的=相手を指定している と言い換えられる。
中二病的な名前が付いているだけなのでGoogleにも
そっち系の人がいるということだろう。よかったね。
マニフェストに書いとくと、そのIntentを受け取りやがる。
Base class for code that will receive intents sent by sendBroadcast().
android.content.BroadcastReceiver
BroadcastをReceiveする当番。
Android端末内にはブロードキャストがアホみたいに飛び交っている。
で、このレシーバがそれらを受信する。クールでかっこいいですね〜
レシーバ自体はブロードキャストが来た時に起動して、処理が終わったら消える。
ただBroadcastReceiverを作っておけばおkwwwという世界観ではなくて、
AndroidManifest.xml
に登録する必要がある。
Activity
もAndroidManifest.xml
に定義するんだから仕方あるまい。
と思う人はそれでいい。
納得行かない人は動的にも登録できるよ。
何も設定しないと全部受け取る。
他のアプリケーションから受け取らない設定にもできる。exported="false"
な。
ICSからはブロードキャストを送るときにIntent.setPackage
によって、
受信相手のパッケージ名を指定できる。
まあでもアプリケーション間通信とかしたいわけじゃないってときは、
LocalBroadcastReceiver
ってのがあるから。それ使って。
onReceive(Context, intent)
が呼ばれると生き、returnした後に殺されうる。
なので非同期処理はできない。なぜならcallbackを待つ間に死にかねないから。
例えばダイアログは出せない。てゆーかさ、
バックグラウンドで何かが起こったことを知らせるのは、Notificationだろ?
NotificationManager
を使おう。
onReceive(Context, intent)
実行中ならforegroundプロセスとして扱われる。
よっぽどメモリがアレにならない限り死なない。
で、問題はreturnした後。
殺されやすさは、そのプロセス内の他のアプリケーションに依存する。
もしApplicationと同じプロセスで動かしていたら、
そのApplicationの殺されやすさと同じになるし、
専用のプロセスで動いていたら、空のプロセスとみなされて速攻殺される。
だから、長時間の処理ならServiceと一緒に使うといいよ。
サービスを起動して、処理が終わったら自殺すればいい。
そういうわけで、実行するプロセスが選べる。
Applicationと同じプロセスで実行するか、別のプロセスで実行するか。
registerReceiverで別スレッドにしない限り、
onReceiveはそのプロセスのMainThreadで実行される。
10秒間のタイムアウトが存在するので長時間の処理ならServiceと一緒に(略)
<receiver>
タグで指定されたレシーバはreturnしだい死ぬ。
onReceive(Context, intent)
内しか生きられない儚い命だけど、Context
もらえるからね。
Intent
から情報をもらって、Context
に対して処理するって使い方っすね。
すぐ死ぬからメンバー変数とか作るのはやめてね。