AIR for android メモリについての雑感

昨日のfxugでメモリの話があがっていて、個人的に気になっていたので少し調べてみました。

それぞれ空のflex/flashのプロジェクトを用意して使用されているメモリを調べてみました。

  • air.FlexSample.debug ・・・ 空のFlexモバイルプロジェクト
  • air.flash.empty ・・・ 空のActionScriptモバイルプロジェクト
下の画像はadb shellではいってメモリの使用量を調べるprocrankというコマンドを走らせたときの結果です。

※Pssの欄が実際にプロセスが占有しているメモリの量です。

コマンドprocrankを叩くと専有メモリの多い順に上からログが表示されます。

一般的なネイティブのandroidアプリのメモリ使用量は15Mbyte以下と言われているのでflex/flashのメモリ使用が大きいかわかります。ここにコンテンツが乗っかってくるとさらに使用量は大きくなってきます。

androidは起動しているアプリの状態をメモリ上にマップしていて、アプリの切り替えがスムーズになるように設計されています。また、空きメモリの使用量が少なくなると使われていないアプリをkillしてメモリを開放します。

AIR for Androidが大きくメモリを消費するとどうなるか?予測すると大きく消費した分だけ空きメモリが少なくなり、デバイスに搭載されているメモリがデフォルトで少ない端末などはアプリをkillしていきます。killされると今度はkillされたアプリは呼び出されたときに1から起動を行うことになるのでアプリ間の移動がスムーズではなくなってしまいます。他のアプリにも迷惑がかかりそうなんで、やはりメモリ消費は小さいにこしたことはなさそうです。。。

 

 

 

 

 

 

 

 

 

iPhone構成ユーティリティの使い方 for AIR for iOS

AIR for iOSでアプリつくってみたけど実機で確認するときどうするの?を解決する記事です。(2011/7月時点)

AIR for iOSは自動で端末インストールができないので、FlashやFlexBuilderはipaファイルをパッケージングするところまでしかできません。
ここからipaファイルを実機にインストールするにはiTunes経由でインストールをするかiPhone構成ユーティリティというソフトを使うことで可能になります。

ただ、iTunes経由でインストールする場合は端末の動機が必要になってくるのでとても使いにくい。
そこでiPhone構成ユーティリティが浮上してきます。

これちょっと、便利そうなので使えるかどうか試してみました。
結果、

× iPhone構成ユーティリティ3.3(for Windows)
○ iPhone構成ユーティリティ3.3(for Mac)

※ウィンドウズはアプリをインストールする際にkAMDReceiveMessageErrorが発生してアプリのインストールが出来ませんでした。(僕の環境だけかもしれません?。windows7  64bit)
解決方法はあるのですが非常にめんどくさそうなのでいまのところiPhone構成ユーティリティはmac専用といった感じです。。。(参考URL

iPhone構成ユーティリティの使い方

1,準備するもの

  • ipaファイル
  • プロビジョニングプロファイル

2,iPhone構成ユーティリティをインストールします。&起動します。

3.iPhoneかiPodTouchをUSBでマックに接続します。

接続すると、上の画像内赤枠のデバイス欄に、接続したデバイスが現れます。

4,プロビジョニングプロファイルのデバイスへのインストール

画面左のライブラリメニューの中のプロビジョニングプロファイルを選択します。そのあと上部にある追加ボタンをクリックします。予め用意しておいたプロビジョニングプロファイルを開きます。
プロビジョニングプロファイル開いたら、これをデバイスにインストールする作業を行います。インストールを行うにはまず、接続してあるデバイスが表示されているのでデバイス名をクリックし、右画面が切り替わるので上部のタブを「プロビジョニングプロファイル」にクリックして切り替えます。そうすると先ほど開いたプロビジョニングプロファイルがリストの中に表示されていますので、そのリストの右にある「インストール」ボタンをクリックします。
これでプロビジョニングプロファイルのインストールは完了です。

5,アプリのインストール

アプリをインストールするにはまず、iPhone構成ユーティリティにアプリを登録します。
ライブラリメニューのアプリケーションを選択し、上部メニューの追加ボタンをクリックして、予め用意しておいたipaファイルを開きます。
ipaファイルを登録できたらデバイスへのインストールが可能になりますので、接続しておいたデバイス名をクリックして切り替わった右画面上部のタブ「アプリケーション」をクリックします。
そうするとデバイス内にインストールされたアプリのリストが表示されます。アプリのリストの中には先ほど開いたipaファイルが必ずどこかにありますので、それを探します。見つけたらそのアプリの右横にある「インストール」ボタンをクリックします。これでエラーが発生しなければ完了です。
※ここでkAMDReceiveMessageErrorが発生する場合はプロビジョニングファイルにデバイスが登録されていない可能性があります。

QRCodeReader for Playbook をリリースしました。(ソース付き)

BlackBerry playbook向けに QR Code Reader for Playbook をリリースしました。

http://appworld.blackberry.com/webstore/content/49210?lang=en

このアプリはver1~10(セルサイズが1~10)までのQRコードを読み取ってくれるものです。

開発してて気がついたことがありました。androidやiOSではCamera.getCamera()を呼び出したとき背面カメラが返されますが、playbookでは前面カメラが呼び出されます。
playbookで背面カメラを参照する場合はCamera.getCamera(“1”)と記述しなければいけません。

ロゴスソフトウェアさんのライブラリがGPLライセンスなのでソースコードとプロジェクトファイルをここからダウンロードできるようにしています。
QRCoderReader.as

package
{

	import com.logosware.event.QRdecoderEvent;
	import com.logosware.event.QRreaderEvent;
	import com.logosware.utils.QRcode.GetQRimage;
	import com.logosware.utils.QRcode.QRdecode;

	import flash.desktop.Clipboard;
	import flash.desktop.ClipboardFormats;
	import flash.desktop.NativeApplication;
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.Loader;
	import flash.display.MovieClip;
	import flash.display.Sprite;
	import flash.display.StageAlign;
	import flash.display.StageScaleMode;
	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.events.TimerEvent;
	import flash.geom.Matrix;
	import flash.geom.Point;
	import flash.geom.Rectangle;
	import flash.media.Camera;
	import flash.media.Sound;
	import flash.media.Video;
	import flash.net.URLRequest;
	import flash.net.navigateToURL;
	import flash.text.TextField;
	import flash.text.TextFormat;
	import flash.utils.Timer;

	import qnx.ui.buttons.LabelButton;

	import caurina.transitions.Tweener;

	[SWF(height="600", width="1024", frameRate="30", backgroundColor="#FFFFFF")]
	public class QRCodeReader extends Sprite
	{

		private var msg:TextField;
		private var btnSendBrowser:LabelButton;
		private var btnCopy:LabelButton;
		private var qrImage:GetQRimage;
		private var qrDecode:QRdecode;
		private var timer:Timer;
		private var bmptemp:BitmapData;
		private var sf:Sound;
		private var qrresult:Object;
		private var video:Video;
		private var bmp:Bitmap;
		private var marker:Sprite;

		public function QRCodeReader()
		{
			super();
			objectInitialize();
		}

		private function objectInitialize():void
		{
			//ステージ設定
			stage.scaleMode = StageScaleMode.NO_SCALE;
			stage.align = StageAlign.TOP_LEFT;

			//background
			var loader:Loader = new Loader();
			loader.load(new URLRequest("assets/bg.png"));
			addChild(loader);

			//サウンド
			sf = new Sound();
			sf.load(new URLRequest("assets/sf.mp3"));

			//camera
			if (Camera.isSupported) {
				//カメラをゲット
				var camera:Camera

				camera = Camera.getCamera("1");
				//if(camera)camera = Camera.getCamera("0");
				//カメラの解像度のキャプチャの更新頻度(fps)を設定(w,h,fps)
				camera.setMode(400, 400, 10);
				//表示用のビデオを作成
				video = new Video(400, 400);
				//ビデオとカメラを関連付ける
				video.attachCamera(camera);
				addChild(video);
				video.x = 50;
				video.y = 130;

				//デコード用ビットマップ(キャプチャしたQR写真)
				bmp = new Bitmap();
				bmp.bitmapData = new BitmapData(200,200);
				bmptemp = new BitmapData(200,200,false,0xFFFFFF);

				//デコード用インスタンス
				qrImage = new GetQRimage(bmp);
				qrImage.addEventListener(QRreaderEvent.QR_IMAGE_READ_COMPLETE,onQRReadComp);

				//スキャン頻度
				timer = new Timer(1500);
				timer.addEventListener(TimerEvent.TIMER,onTimer);
				timer.start();

			}

			//カメラ用マスク
			var camMask:Loader = new Loader();
			camMask.load(new URLRequest("assets/cammask.png"));
			camMask.x = 50;
			camMask.y = 130;
			addChild(camMask);

			//textfield
			var tf:TextFormat = new TextFormat();
			tf.font = "_ゴシック";
			tf.size = 24;

			msg = new TextField();
			msg.defaultTextFormat =tf;
			msg.selectable = true;
			msg.multiline = true;
			msg.wordWrap = true;
			msg.width = 405;
			msg.height = 84;
			msg.x = 560;
			msg.y = 143;
			addChild(msg);

			//btn copy
			btnCopy = new LabelButton();
			btnCopy.label ="Copy Message";
			btnCopy.width = 430;
			btnCopy.x = 545;
			btnCopy.y = 270;
			btnCopy.addEventListener(MouseEvent.CLICK,onbtnCopy);
			addChild(btnCopy);
			btnCopy.enabled = false;

			//btn sendBrowser
			btnSendBrowser = new LabelButton();
			btnSendBrowser.label = "Search in Browser";
			btnSendBrowser.width = 430;
			btnSendBrowser.x = 545;
			btnSendBrowser.y = 340;
			btnSendBrowser.addEventListener(MouseEvent.CLICK,onSendBrowser);
			addChild(btnSendBrowser);
			btnSendBrowser.enabled = false;

			//marker
			marker = new Sprite();
			marker.graphics.beginFill(0xFF0000);
			marker.graphics.drawCircle(5,5,5);
			marker.graphics.endFill();
			marker.x = 52;
			marker.y = 132;
			marker.alpha = 0;
			addChild(marker);

		}

		//タイマー実行時のイベントリスナー
		private function onTimer(e:TimerEvent):void{

			//キャプチャ用のビットマップ生成
			var bmptemp2:BitmapData = new BitmapData(400,400,false,0x000000);
			//カメラから映像キャプチャ
			bmptemp2.draw(video);
			//小さいビットマップに映像キャプチャし直す。
			bmptemp.copyPixels(bmptemp2,new Rectangle(100,100,200,200),new Point(0,0));
			bmp.bitmapData = bmptemp;
			bmptemp2.dispose();

			qrImage.process();

			marker.alpha = 1;
			Tweener.addTween(marker,{alpha:0,time:1});
		}

		//QRコードが認識されたら↓が実行される
		private function onQRReadComp(event:QRreaderEvent):void
		{

			qrDecode = new QRdecode();
			qrDecode.setQR(event.data);
			qrDecode.addEventListener(QRdecoderEvent.QR_DECODE_COMPLETE,onDecodeComp);
			qrDecode.startDecode();

		}
		//デコード完了時に↓が実行される
		private function onDecodeComp(event:QRdecoderEvent):void
		{

			//読み取り効果音再生
			sf.play();
			//読み取りデータの表示
			msg.text = event.data;

			//ボタンのアクティブ化
			btnSendBrowser.enabled = true;
			btnCopy.enabled = true;

			//読み取りデータの中にURLが含まれているか確認する
			if(qrresult) qrresult = null;
			var urlReg:RegExp = /[^"|^>](http:\/\/+[\S]*)/;
			qrresult = urlReg.exec(" "+msg.text);
		}

		//COPYボタンが押されたときのクリップボードに読み取りデータをコピーする
		private function onbtnCopy (e:MouseEvent):void{
			var cb:Clipboard = Clipboard.generalClipboard;
			cb.setData(ClipboardFormats.TEXT_FORMAT,msg.text);
		}

		//ブラウザに送るボタンを押したときURLが含まれているとき、含まれていない時で処理分岐
		//URLが含まれている場合はURL抽出してアクセス、含まれない場合はグーグルで検索
		private function onSendBrowser(e:MouseEvent):void{
			if(qrresult){
				navigateToURL(new URLRequest(qrresult[1]));
			}else{
				var urlstr:String = "http://www.google.com/search?q=" + msg.text;
				navigateToURL(new URLRequest(urlstr));
			}
		}
	}
}

papervision3dをMultitouch対応にさせる方法

スマートフォンやタブレットでもpapervision3dが使えます。モバイル向けではないので、時間が経てばMolehillとかもっと軽い3Dライブラリがでてくるかもしれません。そこまで重くない処理させるのであれば、メジャーなpapervisionでもサクサク動きます。

スマトーフォンの場合、マルチタッチに対応しているので自然とpapervisionで構成された画面作るならこれに対応させたいところです。

ただ、画面が小さいので各オブジェクト毎にイベントリスナーをつけるっていうのはかなり非現実的なのでstageにイベントリスナーを追加して固定オブジェクトだけ操作するのがいいのでは?と思いました。

以下AIR for Android/AIR for iOS用のサンプルです。

package
{
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.TransformGestureEvent;
	import flash.ui.Multitouch;
	import flash.ui.MultitouchInputMode;

	import org.papervision3d.materials.ColorMaterial;
	import org.papervision3d.materials.utils.MaterialsList;
	import org.papervision3d.objects.primitives.Cube;
	import org.papervision3d.view.BasicView;

	public class pv3d_multitouch extends Sprite
	{
		public var stagew:int;
		public var stageh:int;
		private var basicview:BasicView;
		private var cube:Cube;
		private var yaw:Number = 0;

		public function pv3d_multitouch()
		{
			this.addEventListener(Event.ADDED_TO_STAGE,onInit);
		}

		protected function onInit(event:Event):void
		{

			//stage設定
			stagew = stage.stageWidth;
			stageh = stage.stageHeight;

			//basicviewの生成
			basicview = new BasicView(stagew,stageh);
			addChild(basicview);

			//cubeの生成
			var material:ColorMaterial = new ColorMaterial(0x888888);
			cube = new Cube(new MaterialsList({all : material}),300,300,300);
			basicview.scene.addChild(cube);

			if(Multitouch.supportsGestureEvents){
				//マルチタッチの宣言
				Multitouch.inputMode = MultitouchInputMode.GESTURE;
				stage.addEventListener(TransformGestureEvent.GESTURE_ZOOM,onBasicViewZoom);
				stage.addEventListener(TransformGestureEvent.GESTURE_ROTATE,onBasicViewRotate);
			}

			basicview.startRendering();
		}

		protected function onBasicViewZoom(event:TransformGestureEvent):void
		{
			cube.scale *= event.scaleX;

		}
		protected function onBasicViewRotate(event:TransformGestureEvent):void
		{
			yaw += (event.rotation)/20;
			cube.yaw(yaw);
		}
	}
}

2011/7/8 追記

当初マルチタッチのイベントリスナーをbasicviewに登録していましたがiOSで動作しなかったのでstageにイベントを登録しなおしたら動きました。

ActionScript IDE for Android 「IDEaS」

android端末でActionScriptを開発できるエディタがでてきました。

まだ開発中みたいなんですが一通りの機能(コードのハイライト、コードアシスト、undo/redo)が揃っていてすごいです。

機能的にはエンジン部分にminibuilderが使われていて編集したファイルをサーバーにアップしてswfを返してもらうような仕組みです。ちょうどwonderflのようなイメージでブラウザ部分がアプリになっている感じです。

動作確認かねてnexus one(スマホ)とXOOM(タブレット)で見てみましたが、さすがにnexus oneでの編集はきつい感じでした。

XOOMだと快適この上ないですね。下のキャプチャはXOOMの画面を貼りつけています。

※DropBoxなんかにデータをいれておけば、出先でサクっと編集とかもいけそうです。さすがにすごい長いコードになるとコードハイライトがしんどそうです。

[IDEaS]
https://market.android.com/details?id=air.IDEaS&rdid=air.IDEaS&rdot=1

playbook用のアプリ 「Gmap LE」をリリースしました。

FlexBuilder4.5を使って、playbookにフリーのgooglemapがなかったのでgooglemaps API for Flashを使ってアプリを作ってみました。

マルチタッチ対応、GPSセンサで現在地がわかる程度の簡易版Googlemapになっています。

Gmap LE

http://appworld.blackberry.com/webstore/content/45769

FlashBuilder 4.5 のコードアシストをFlashDevelopライクにする設定

いつも使うエディタがFlashDevelopとFDTだったんですが、winもmacもよく使うのでこのさいFlashBuilderに乗り換えました。

コードアシストの方法が分からなかったんで、メモ。

メニューの[ウィンドウ→設定]から環境設定ダイアログを開いて以下のようにするとOK。

AIR for iOS のipaファイルの構造とインストール時のディレクトリ構造について

AIR for iOSでパッケージングしたipaファイルの構造

AIR for iOSでパッケージングしてできたipaファイルはzipファイルになっています。パッケージングしたファイルの中にswfは含まれていません。

ほとんどのファイルがiOS向けにコンバートされているので解凍したところであまり内容はよくわかりません。ipaファイルを直接どうこうするという機会はないと思うので参考程度に御覧ください。

アプリインストール時のデバイス内のディレクトリ構造について

ipaファイルはデバイスにインストールされると、まずアプリ固有の任意のIDが割り振られ、このIDの名前のディレクトリ(サンドボックス)が作成されます。

このサンドボックス内ではアプリの本体ファイルといくつかのディレクトリが作成されています。

  • Documents … ユーザー用のデータの保管場所
  • Library… アプリ用データの保管場所
  • appidディレクトリ…ipaファイルを展開したディレクトリ
  • temp…作業用ディレクトリ(File.createTempDirectory()で参照できます)

ユニークIDがふられたディレクトリは一般的に外からアクセスできないようになっているのでこのユニークIDがふられたディレクトリならアプリで作成したデータは基本的にどこにおいても安全と思われます。ただiTunesのバックアップの対象がDocuments,Libraryの2つのディレクトリが対象になっているのでユーザー用DBなど記憶させておきたいデータはこの2つのディレクトリに置いたほうがよさそうです。ShareObjectもLibrary配下にあるのでバックアップの対象です。

AIR for iOS のFileクラスで参照できる領域

AIR for iOSでもファイルを参照することができます。参照できる場所はアプリケーション固有に割り当てられた領域(サンドボックス)と写真やビデオが保存されているメディアライブラリになります。(AIR2.6での話)

Fileクラスのプロパティに対応したデバイス上のパス

File.applicationDirectory

/var/mobile/Applications/[uniqueID]/[filename].app

アプリ本体のバンドルになっているのでいじらないほうがいい。

File.applicationStorageDirectory

/var/mobile/Applications/[uniqueID]/Library/Application Support/[appID]/Local Store/

Libraryディレクトリがアプリケーションの設定ファイルなど突っ込んでおくディレクトリ。iTunesバックアップ対象ディレクトリ。

File.desktopDirectory

/var/mobile/Applications/[uniqueID]/Desktop/

モバイルではあまり関係ないディレクトリ。

File.documentsDirectory

/var/mobile/Applications/[uniqueID]/Documents/

ドキュメントやアプリケーションデータを保存するディレクトリ。このディレクトリにあるコンテンツは、ユーザーとのファイルシェアが行える。iTunesバックアップ対象ディレクトリ。

File.userDirectory

/var/mobile/Applications/[uniqueID]/

このディレクトリがアプリに用意されたサンドボックスのルートになっている。

File.createTempDirectory()

/var/mobile/Applications/[uniqueID]/tmp/FlashTmp0/

テンポラリディレクトリは複数作ることができ作成するごとにディレクトリ名の最後の数字がインクリメントされていきます。名前のとおり作業用の一時的なディレクトリのため作業が終わったら自分で掃除してあげたほうがよさそうです。長時間放置された場合はOSが消してくれるそうです。iTunesバックアップ対象外です。

AIR for Androidのメモリ使用量確認方法

※この記事は参考程度におねがいします。自分もいまいち理解できてません。詳しい方の説明or参考URL希望。。。。。。

写真や画像などを含むアプリを作った場合のメモリの使用量がとても気になるところです。特にAIR Runtimeを使ったアプリを作る場合はそれだけで多くのメモリを消費します。さらに画像や動画を読み込むと使用するメモリは増大するのでモバイル開発においてはこの部分が肝になるかと思います。

メモリの使用量の確認方法は幾つかあって、

  • ActionScriptでSystem.privateMemoryを参照する方法
  • メモリ監視アプリによる方法
  • コマンドライン($procrank)から確認する方法

SDKに含まれているコマンド$procrankで参照するメモリが一番確実なのではないかと個人的に思ったりしています。。。。
時間経過でみるならメモリ使用量の増減をみるならStatsライブラリを少し改造してSystem.privateMemoryを見ればいいのかなと。。

ActionScriptでSystem.privateMemoryを参照する方法

flash.systemパッケージにあるSystemクラスのプロパティでメモリの使用量を確認することができます。AIR for Androidでメモリの使用量を確認したい場合はSystem.privateMemoryを参照します。

System.totalMemory ・・・AIR Runtimeがアプリに割り当てているメモリ
System.privateMemory・・・アプリが固有に占有するメモリ領域
System.freeMemory・・・totalMemoryのうち使われていないメモリ領域

import flash.events.Event;
import flash.system.System;

addEventListener(Event.ENTER_FRAME,enterFrameHandler);

function enterFrameHandler(e:Event){
	trace("MEMORY STATE (Mbyte)\n")
	//trace("System.totalMemory",System.totalMemory/1024/1024+"\n");
	trace("System.privateMemory",System.privateMemory/1024/1024+"\n");
	//trace("System.freeMemory",System.freeMemory/1024/1024+"\n");
}

ここで取得できるメモリの使用量は実際にAndroidSDKから見るメモリの使用量や、メモリ監視アプリとは一致していません。メモリ監視アプリによっても定義しているメモリの種類やキャプチャタイミングによって値が異なる場合があるため、目安として参照する必要があると思います。

StatsライブラリはSystem.totalMemoryを見ています。

メモリ監視アプリによる方法

メモリ監視用のアプリはたくさんリリースされていて代表的なアプリとしては

さくっと確認する分にはお手軽でいいかと思います。

コマンドライン($procrank)から確認する方法

USB接続した端末のメモリ使用量をDOSコマンドで確認する方法もあります。

windowsの場合はコマンドプロンプトを起動して、

adb shell と入力します。プロンプトが#に変わるのでそこで

procrank と入力します。

VSS  (virtual set size)…… 仮想メモリ
RSS  (Resident set size)……物理メモリの消費量
PSS  (proportional set size)…… プロセスが実質的に所有しているメモリ
USS  (unique set size) ……ひとつのプロセスが占有しているメモリ

プロセスに割り当てられているメモリはPSSを参照すればいいようです。

雑感

メモリの使用量の確認方法をリストアップしてみましたが、仮に内容が空のアプリをインストールして上記のそれぞれの方法でメモリを確認したところ、下のように全く異なる結果が返ってきます。今のところAIR runtimeがどういう仕組みで動いているかよくわからないのでとりあえず目安程度に参考にしていただければ幸いです。

System.privateMemory 11.1Mbyete
watchdog 18.0Mbyte
procrank 12.2Mbyte