Making an AS3 app for android that uses camera roll to load, select and then use an image.
The cameraroll browser works fine, but when the image is selected, the app crashes - almost every time - as it has worked on a handful of occasions (!?!) We assumed a memory issue and attempted to close all other windows / use smaller photos in the camera roll etc. and try different devices - but cannot recreate success consistently. Cannot find another ANE that works either
It fails at the point where the photo has been selected, and RESTARTS the app...
here's the relevant code, any help appreciated.
public function openGallery():void {
var cameraRoll:CameraRoll = new CameraRoll();
if(CameraRoll.supportsBrowseForImage) {
cameraRoll.addEventListener(MediaEvent.SELECT, imageSelected);
cameraRoll.addEventListener(flash.events.Event.CANCEL, browseCanceled);
cameraRoll.addEventListener(flash.events.ErrorEvent.ERROR, mediaError);
cameraRoll.browseForImage();
}
else { trace( "Image browsing is not supported on this device."); }
}
private function imageSelected(event:MediaEvent):void {
trace("Media selected...");
var imagePromise:MediaPromise = event.data as MediaPromise;
_dataSource = imagePromise.open();
if(imagePromise.isAsync) {
trace("Asynchronous media promise.");
var eventSource:IEventDispatcher = _dataSource as IEventDispatcher;
eventSource.addEventListener(flash.events.Event.COMPLETE, onMediaLoaded);
} else {
trace("Synchronous media promise.");
readMediaData();
}
}
private function onMediaLoaded(event:flash.events.Event):void{
trace("Media load complete");
_mediaBytes = new ByteArray();
_dataSource.readBytes(_mediaBytes);
_tempDir = File.createTempDirectory();
var now:Date = new Date();
var filename:String;
filename = now.fullYear + now.month + now.day+now.hours + now.minutes + now.seconds + ".JPG";
_file = _tempDir.resolvePath(filename);
//writing temporal file to display image
_stream = new FileStream();
_stream.open(_file,FileMode.WRITE);
_stream.writeBytes(_mediaBytes);
_stream.close();
if(_file.exists){
_imageLoader = new Loader();
_imageLoader.contentLoaderInfo.addEventListener(Event.COMPLETE,onMediaLoadedBitmapData);
_imageLoader.loadBytes(_mediaBytes);
}
}
private function onMediaLoadedBitmapData(event:Event):void{
trace("onMediaLoadedBitmapData");
var loaderInfo:LoaderInfo = LoaderInfo(event.target);
_bitmapData = new BitmapData(loaderInfo.width,loaderInfo.height,false,0xFFFFFF);
_bitmapData.draw(loaderInfo.loader);
addPictureToScreen();
}
I had a similar thing happen in Air for iOS but it was related to picking large images. I just checked the dimensions of the chosen image and then used a scalar matrix if they were past a certain point. Sounds like you've thought about this but maybe look further into that?
Related
In my app, I need to download few files form server. I used the following code:
function downloadFile(index:int):void
{
var urlLoader:URLLoader = new URLLoader();
urlLoader.dataFormat = URLLoaderDataFormat.BINARY;
urlLoader.addEventListener(Event.COMPLETE, onLoadFileComplete);
urlLoader.addEventListener(ProgressEvent.PROGRESS, onLoadFileProgress);
urlLoader.addEventListener(IOErrorEvent.IO_ERROR, onURLIOError);
urlLoader.load(new URLRequest("some url"));
fileNameToSave = "some name";
trace("file name:" + fileNameToSave);
downloading = true;
}
function onLoadFileProgress(e:ProgressEvent):void
{
var loadedPct:uint = Math.round(100 * (e.bytesLoaded / e.bytesTotal));
}
function onURLIOError(e:IOErrorEvent):void
{
trace("error msg");
e.target.removeEventListener(IOErrorEvent.IO_ERROR, onURLIOError);
}
function onLoadFileComplete(e:Event):void
{
trace("File downloaded");
var file:File;
file = File.documentsDirectory.resolvePath("somelocation/" + fileNameToSave);
if(file != null)
{
var fileStream:FileStream = new FileStream();
fileStream.openAsync(file, FileMode.WRITE);
fileStream.addEventListener(OutputProgressEvent.OUTPUT_PROGRESS, outputProgressHandler);
fileStream.addEventListener(Event.CLOSE, onSaveFile);
fileStream.writeBytes(e.target.data);
fileStream.close();
}
e.currentTarget.removeEventListener(Event.COMPLETE, onLoadFileComplete);
e.currentTarget.removeEventListener(ProgressEvent.PROGRESS, onLoadFileProgress);
e.currentTarget.removeEventListener(IOErrorEvent.IO_ERROR, onURLIOError);
}
function outputProgressHandler(e:OutputProgressEvent):void
{
if (e.bytesPending == 0)
{
trace("File is completely written");
}
}
function onSaveFile(e:Event):void
{
trace("Saved Complete");
loadfiles();
e.currentTarget.removeEventListener(Event.CLOSE, onSaveFile);
}
It works fine. But the problem I'm having is when the internet is slow, sometime it triggers complete event even if the file is not fully downloaded. Is there anyway to prevent this? Any help would be appreciated.
UPDATE: Is it a good practice to use progress event to check if the download is complete rather than using complete event. Like:
if(e.bytesLoaded >= e.bytesTotal)
{
//downloadComplete
}
else
{
//not complete
}
I can not comment because I have insufficient reputation.
I have previously had similar issue where filestream write reports as complete when in fact it has not. This occurs only on older devices and occurs randomly. My testing seemed to confirm that Flash reports file saved before the OS had actually completed. When a read then occurred it would report as file not found.
I solved it rather crudely using setTimeout and repeatedly checked if the data was complete.
During developing application in Xamarin Android we encountered strange error. The pick image/video (whether its from camera or documents UI) application sometimes crashes with no error. Scenario is that we open application, make steps to the activity, where the images are chosen, and then open the camera or document UI by standard way:
public void ChooseMediaAfterTypeChose (bool photo, bool camera) {
try {
string title = "";
int id;
Intent iIntent;
if (camera) {
if (photo) {
iIntent = new Intent("android.media.action.IMAGE_CAPTURE");
id = Const.AND_pickImageID_camera;
App._file = new File (App._dir, "myPhoto.jpg");
iIntent.PutExtra (Android.Provider.MediaStore.ExtraOutput, Uri.FromFile (App._file));
} else {
iIntent = new Intent("android.media.action.VIDEO_CAPTURE");
id = Const.AND_pickVideoID_camera;
App._file = new File (App._dir, "myVideo.mp4");
iIntent.PutExtra (Android.Provider.MediaStore.ExtraOutput, Uri.FromFile (App._file));
}
} else {
iIntent = new Intent ();
iIntent.SetAction (Intent.ActionGetContent);
if (photo) {
iIntent.SetType ("image/jpg");
title = Static.mainData.currentTexts.ChoosePhoto;
id = Const.AND_pickImageID_galery;
} else {
iIntent.SetType ("video/mp4");
title = Static.mainData.currentTexts.ChooseVideo;
id = Const.AND_pickVideoID_galery;
}
}
if (camera) {
StartActivityForResult (iIntent, id);
} else {
StartActivityForResult (Intent.CreateChooser (iIntent, title), id);
}
} catch (Exception ex) {
ShowError (Static.mainData.currentTexts.MediaError);
W.L(ex.Message);
}
}
The chosen application crashes during image/video picking (E.g. during browsing images in gallery wthout picking any). When that happens we never get to any method in our code.
What we have:
When user don't pick photos, the application runs without any memory crash
Sometimes the picking proceedes without error.
We think that the issue is mainly on Android 4.4.2 (It happend to us only on devices with this android - on other android version devices we didn't encounter it)
What we tried:
We put all permissions to manifest (read/write external storage, hardware.camera etc.)
We delete almost all memory stored content to save up memory
All Activities are unload from memory when they leave the screen
Logcat says that every process connected (Application and the active application) just died with no trace to any error
Our question is if anyone stumbled to similar issue and how to handle it. The second question is if there is any way to find out whats happening.
EDIT:
App._dir code added.
App._dir = new Java.IO.File (
Environment.GetExternalStoragePublicDirectory (
Environment.DirectoryPictures), "Camera");
if (!App._dir.Exists ())
{
App._dir.Mkdirs( );
}
I am working on an android app where I wish to download swf files from an external server, save them to sdcard and load them later in the app. Downloading works fine and the swf is saved in the application directory. Here is my code that loads the swf from the sdcard :
var myloader:Loader = new Loader();
var myhomeButton:btnHome = new btnHome();
addChild(myloader);
var swfFilePath:File = File.applicationStorageDirectory.resolvePath("Android/data/myswffile.swf");
var inFileStream:FileStream = new FileStream();
inFileStream.open(swfFilePath, FileMode.READ);
var swfBytes:ByteArray = new ByteArray();
inFileStream.readBytes(swfBytes);
inFileStream.close();
var loaderContext:LoaderContext = new LoaderContext(false, ApplicationDomain.currentDomain);
loaderContext.allowCodeImport = true;
myloader.contentLoaderInfo.addEventListener(Event.COMPLETE, mycompleteHandler);
myloader.loadBytes(swfBytes, loaderContext);
function mycompleteHandler(evt:Event):void
{
myloader.contentLoaderInfo.removeEventListener(Event.COMPLETE, mycompleteHandler);
addChild(myhomeButton);
myhomeButton.height = _height * 0.08;
myhomeButton.width = myhomeButton.height;
myhomeButton.x = 10;
myhomeButton.y = 10;
myhomeButton.addEventListener(MouseEvent.CLICK, myexitfftn);
}
function myexitftn(evt:Event):void
{
myloader.unloadAndStop(true);
removeChild(myhomeButton);
gotoAndStop(1, "SomeOtherFrame");
}
the problem is when I click the exit button the swf unloads but when I reload it, it starts from the second frame of the loaded swf, the third time from the third frame and so on... Where am I going wrong or what is an alternative solution, please guide.
Try, addChild(myloader) in Complete handler and removeChild and null while exiting, so that loaded swf is completely removed from the memory like so:
function mycompleteHandler(evt:Event):void
{
myloader.contentLoaderInfo.removeEventListener(Event.COMPLETE, mycompleteHandler);
addChild(myloader);
//... Rest of code remains same
}
function myexitftn(evt:Event):void
{
myloader.unloadAndStop();
removeChild(myloader);
myloader = null;
removeChild(myhomeButton);
gotoAndStop(1, "SomeOtherFrame");
}
The following code will erase a bitmap (brush akk droplet) from another bitmap (akka
The code works great on PC and pretty ok performacewise.
When i test it on more android devices, it doesn't work. No matter if is a high end device or a slower one. I've made some tests and found out the problem is lock() and unlock() functions from BitmapData. It simply doesn't update the image on device, only once.
I've tried to remove them, but the then it lags alot. Also the performace drop is noticeable on PC too.
Does anyone know a solution, where am I doing wrong?
import flash.display.BitmapData;
import flash.display.Bitmap;
import flash.geom.Point;
import flash.events.MouseEvent;
import flash.geom.ColorTransform;
import flash.geom.Rectangle;
var m:BitmapData = new water_pattern;
var b:BitmapData = new droplet;
var bm:Bitmap = new Bitmap(m);
var bla = new blabla();
addChild(bla);
bla.addChild(bm);
function p($x,$y){
var refPoint = new Point($x-b.width/2,$y-b.height/2);
for(var i=0;i<b.width;i++)
for(var j=0;j<b.height;j++)
{
var a:uint = (b.getPixel32(i,j)>> 24) & 0xFF;
a=0xFF-a;
var tp:uint = m.getPixel32(refPoint.x+i,refPoint.y+j);
var tp_trans:uint = (tp >> 24)&0xFF;
if(tp_trans>a){
tp=(tp&0x00FFFFFF)|(a<<24);
m.setPixel32(refPoint.x+i,refPoint.y+j,tp);
}
}
//for(var k=0;k<10000000;k++){};
}
var k=1;
var md = function(e)
{
m.lock();
p(bm.mouseX,bm.mouseY);
m.unlock();
};
bla.addEventListener(MouseEvent.MOUSE_DOWN,function(e)
{
bla.addEventListener(Event.EXIT_FRAME,md);
});
bla.addEventListener(MouseEvent.MOUSE_UP,function(e)
{
bla.removeEventListener(Event.EXIT_FRAME,md);
});
I've reworked the code :
public function draw($x, $y)
{
var refPoint = new Point($x - brush.width / 2, $y - brush.height / 2);
var r:Rectangle = new Rectangle(refPoint.x, refPoint.y, brush.width, brush.height);
var pv:Vector.<uint> = pattern.getVector(r);
var bv:Vector.<uint> = brush.getVector(brush.rect);
for (var i = 0; i < bv.length; i++)
{
var a:uint = (bv[i]>>24) &0xFF;
a = 0xFF - a;
var tp:uint = pv[i];
var tp_trans:uint = (tp >> 24) & 0xFF;
// trace(a.toString(16) + " vs " + tp_trans.toString(16));
if (tp_trans > a)
{
tp = (tp & 0x00FFFFFF) | (a << 24);
// trace("??>" + tp);
pv[i] = tp;
}
}
pattern.setVector(r, pv);
}
Now it works, but still it is pretty slow on device. That before i saw Jeff Ward's comment, so i changed it to render mode on CPU. It works fast.
The big problem is in CPU mode the game is very slow compared to GPU. Yet this script is fast on CPU but unusable slow on GPU.
So I've tried again the first code and surprise. It works. Jeff Ward, thank you, you're a genius.
Now the question remains is why? Can someone please explain?
For your original question, sometimes GPU mode doesn't pick up changes into the underlying bitmapdata. Try any one of these operations after your unlock() to 'hint' that it should re-upload the bitmap data:
bm.filters = [];
bm.bitmapData = m;
bm.alpha = 0.98+Math.random()*0.02;
But as you found, uploading bitmapdata can be slow. To clarify GPU/direct render modes:
In GPU mode, changing any pixel in a Bitmap requires a re-upload of the full bitmap, so it's the size of Bitmap that's the limiting factor. In direct mode, it blits only the portions of the screen that have been updated. So I'd guess some parts of the game change a lot of the screen at once (slow in direct mode), whereas this effect changes a large bitmap, but only a little bit at a time (slow in GPU mode).
You have to get creative to maximize your performance wrt GPUs:
In GPU mode, split the effect into many bitmaps, and only change as few as possible for any given frame. (medium effort)
Use Starling GPU-accelerated framework and Starling filters (GPU shaders) to achieve your effect (effort depends on how much you have invested in your game already), see a couple of examples
Ok, I have an app I've been writing in Flash builder that makes several HTTPService requests to a server to gather some data about the user. In one view it goes and downloads a string from a server, it then splits the string using delimiter ":" and then adds the components to an array to populate a Spinnerlist. In the simulator this works great, if I package the app for iOS and install it on my iPhone - it works great. But when I try to run it to my Android device, it doesn't work. It acts as though it is working, it loads the view with the SpinnerList on it but the list is empty. I can't seem to figure it out.
Some things I've tried: the XML settings i have enabled the internet access to the android device, in fact, earlier in the app when the user logs in the phone make a very similar server call which works fine on all devices.
This issue has me completely dumbfounded, and help would be greatly appreciated!!
here is the code that makes the request and separates the data.
HttpService Request:
<mx:HTTPService id="CommonHTTP" url="http://dispatch.americantaxi.com:8080/AT/servlet/OnlineOrderServices?command=retrieveCustomerCommonPlaces&customerId={data.ID}" resultFormat="text"/>
Sorting code:
protected function button5_clickHandler(event:MouseEvent):void
{
PickUpType = "Common";
data.PickUpType = PickUpType;
var CommonDataString:String = new String(CommonHTTP.lastResult);
trace("String " + CommonDataString)
var Arr1:Array = [];
Arr1 = CommonDataString.split("|");
trace("arr1 length " + Arr1.length);
var ArrCount:Number = new Number(Arr1.length);
var Arr2:Array = [];
for (var i:Number = 0; i < (ArrCount - 1); i++) {
var currentSelect:String = new String(Arr1[i]);
Arr2 = currentSelect.split(":");
var currentName:String = new String(Arr2[1]);
trace("Add: " + currentName);
CommonPlacesArray.addItem(currentName);
}
data.CommonPlacesArray = CommonPlacesArray;
navigator.pushView(CommonPlaces, data);
}