I seem to have a memory leak and I'm not sure how to fix. I've read all of the ImageSwitcher tutorials and examples on android.com, but those all seem to deal with drawables that are already in the drawables folder. My code allows the user to take one or two photos with their camera, store the images to SD, and use an imageswitcher to "flip" the images over.
public class CardViewImageActivity extends Activity implements ViewFactory {
private CardDBAdapter mDbHelper;
private String _cardImgGuidFront;
private String _cardImgGuidBack;
private Boolean frontShowing = false;
private Boolean hasFront = false;
private Boolean hasBack = false;
private Uri uriFront;
private Uri uriBack;
private int cardId;
private ImageSwitcher iSwitcher;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.card_view_image);
iSwitcher = (ImageSwitcher)findViewById(R.id.imageSwitcher);
iSwitcher.setFactory(this);
iSwitcher.setOnClickListener(SwitcherOnClick);
this.cardId = Integer.parseInt(getIntent().getExtras().getString(
"cardId")); //$NON-NLS-1$
getCardImageGuids(this.cardId);
if(_cardImgGuidFront != null)
{
hasFront = true;
uriFront = Uri.parse(Environment.getExternalStorageDirectory().toString() + "/" + _cardImgGuidFront + ".jpg");
}
if(_cardImgGuidBack != null)
{
hasBack = true;
uriBack = Uri.parse(Environment.getExternalStorageDirectory().toString() + "/" + _cardImgGuidBack + ".jpg");
}
if(hasFront && hasBack)
Toast.makeText(this, R.string.card_view_touch, Toast.LENGTH_SHORT).show();
if(hasFront)
{
iSwitcher.setImageURI(uriFront);
frontShowing = true;
}
else if(hasBack)
{
iSwitcher.setImageURI(uriBack);
frontShowing = false;
}
else
{
Toast.makeText(this, R.string.card_no_image, Toast.LENGTH_SHORT).show();
}
}
#Override
public void onDestroy()
{
iSwitcher.setImageURI(null);
super.onDestroy();
}
public View makeView() {
ImageView iView = new ImageView(this);
iView.setScaleType(ImageView.ScaleType.FIT_CENTER);
iView.setLayoutParams(new ImageSwitcher.LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));
return iView;
}
protected OnClickListener SwitcherOnClick = new OnClickListener()
{
#Override
public void onClick(View v)
{
if(frontShowing && hasBack)
{
iSwitcher.destroyDrawingCache();
iSwitcher.setImageURI(uriBack);
frontShowing = false;
}
else if(!frontShowing && hasFront)
{
iSwitcher.destroyDrawingCache();
iSwitcher.setImageURI(uriFront);
frontShowing = true;
}
else
{
}
}
};
private void getCardImageGuids(int cardId)
{
try
{
this.mDbHelper = new CardDBAdapter(this);
this.mDbHelper.open();
Cursor c = this.mDbHelper.fetchCard(this.cardId);
_cardImgGuidFront = c.getString(c
.getColumnIndex(CardDBAdapter.CARD_IMG_GUID_FRONT));
_cardImgGuidBack = c.getString(c
.getColumnIndex(CardDBAdapter.CARD_IMG_GUID_BACK));
}
catch(SQLiteException ex)
{
Toast.makeText(CardViewImageActivity.this, ex.toString(), Toast.LENGTH_LONG).show();
}
finally
{
this.mDbHelper.close();
this.mDbHelper = null;
}
}
}
The above code seems to work sometimes quite well. However, I'm occasionally getting an OutOfMemory error. Now, from my understanding through debugging, makeView() seems to be getting called twice when .setFactory(this) is called and this seems to be fine since ImageSwitcher's purpose is to switch between two images. I'm wondering if there is a better way to switch the images besides SetImageUri(). I don't see anywhere I might be leaking or what might be causing the issue. I don't see anywhere that convertView might even be utilized. Is there a way to cache the images from the Uri? Are the images being reloaded each time .setImageUri() is called? Is there a way to dump that memory(or reuse)? Is this what's eating up my memory?
Not to sound disrespectful or rude, but I would really prefer help without having someone referencing the Avoiding Memory Leaks article or links to the javadocs for imageswitcher? The Avoid Memory Leaks article shows a couple of "you shouldn't do this" but never shows what you "should" do instead. I already have links to the javadocs. I'm looking for someone that can actually explain what I'm doing incorrectly and point me in a better direction with code(I learn best seeing code rather than vague abstract academic theories) rather than just regurgitating the top 3 links from a Google search. :)
Thank you for any help! :)
EDIT:10Feb2012
So I tried to load the Drawables and IMMEDIATELY received an out of memory error which is WORSE than getting the error occasionally with .setImageUri(). The following contains the mods:
private Drawable front;
private Drawable back;
#Override
public void onCreate(Bundle savedInstanceState) {
...
Resources res = getResources();
...
if(_cardImgGuidFront != null)
{
hasFront = true;
String frontPath = Environment.getExternalStorageDirectory().toString() + "/" + _cardImgGuidFront + ".jpg";
front = new BitmapDrawable(res, frontPath);
}
if(_cardImgGuidBack != null)
{
hasBack = true;
String backPath = Environment.getExternalStorageDirectory().toString() + "/" + _cardImgGuidBack + ".jpg";
back = new BitmapDrawable(res, backPath);
}
Looking at the SoftReference, usage requires the creation of the SoftReference using another drawable. I don't see how using SoftReference could possibly help since I am now crashing on initial load.
ImageView v = (ImageView)imageSwitcher.getNextView();
BitmapDrawable bd = (BitmapDrawable) v.getDrawable();
if (bd != null)
{
Bitmap b = bd.getBitmap();
b.recycle();
}
I'm wondering if there is a better way to switch the images besides SetImageUri().
Call setImageDrawable() using a cached BitmapDrawable.
I don't see anywhere I might be leaking or what might be causing the issue.
Use DDMS and MAT to see where your leaks are.
I don't see anywhere that convertView might even be utilized.
Considering that there is nothing named convertView in your source code, this is not surprising.
Is there a way to cache the images from the Uri?
Yes. Use BitmapFactory, load the images yourself. Cache the results, preferably using SoftReferences. Call recycle() on the Bitmap objects when you no longer need them.
Are the images being reloaded each time .setImageUri() is called?
Yes.
Is there a way to dump that memory(or reuse)?
Not if you have Android create the Bitmap for you. ImageSwitcher seems to be a one-way API, where you can set images but not retrieve them.
Ok, so this seems to work and I will provide the code to save Java frustration for anyone else flustered by this. It's probably not the prettiest, but so far(knock wood) I have not seen any further Out of memory errors.
import android.app.Activity;
import android.content.res.Resources;
import android.database.Cursor;
import android.database.sqlite.SQLiteException;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Environment;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup.LayoutParams;
import android.widget.ImageSwitcher;
import android.widget.ImageView;
import android.widget.Toast;
import android.widget.ViewSwitcher.ViewFactory;
public class CardViewImageActivity extends Activity implements ViewFactory {
private CardDBAdapter mDbHelper;
private String _cardImgGuidFront;
private String _cardImgGuidBack;
private Boolean frontShowing = false;
private Boolean hasFront = false;
private Boolean hasBack = false;
private Drawable front;
private Drawable back;
private int cardId;
private ImageSwitcher iSwitcher;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.cert_view_image);
iSwitcher = (ImageSwitcher)findViewById(R.id.imageSwitcher);
iSwitcher.setFactory(this);
iSwitcher.setOnClickListener(SwitcherOnClick);
Resources res = getResources();
BitmapFactory.Options o = new BitmapFactory.Options();
o.inSampleSize = 2;
this.cardId = Integer.parseInt(getIntent().getExtras().getString(
"cardId")); //$NON-NLS-1$
getCardImageGuids(this.cardId);
if(_cardImgGuidFront != null)
{
hasFront = true;
String frontPath = Environment.getExternalStorageDirectory().toString() + "/" + _cardImgGuidFront + ".jpg";
front = new BitmapDrawable(res, BitmapFactory.decodeFile(frontPath, o));
}
if(_cardImgGuidBack != null)
{
hasBack = true;
String backPath = Environment.getExternalStorageDirectory().toString() + "/" + _cardImgGuidBack + ".jpg";
back = new BitmapDrawable(res, BitmapFactory.decodeFile(backPath, o));
}
if(hasFront && hasBack)
Toast.makeText(this, R.string.card_view_touch, Toast.LENGTH_SHORT).show();
if(hasFront)
{
iSwitcher.setImageDrawable(front);
frontShowing = true;
}
else if(hasBack)
{
iSwitcher.setImageDrawable(back);
frontShowing = false; }
else
{
Toast.makeText(this, R.string.card_no_image, Toast.LENGTH_SHORT).show();
}
res = null;
}
#Override
public void onPause()
{
super.onPause();
}
#Override
public void onDestroy()
{
front = null;
back = null;
super.onDestroy();
}
public View makeView() {
ImageView iView = new ImageView(this);
iView.setScaleType(ImageView.ScaleType.FIT_CENTER);
iView.setLayoutParams(new ImageSwitcher.LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));
return iView;
}
protected OnClickListener SwitcherOnClick = new OnClickListener()
{
#Override
public void onClick(View v)
{
if(frontShowing && hasBack)
{
iSwitcher.setImageDrawable(back);
frontShowing = false;
}
else if(!frontShowing && hasFront)
{
iSwitcher.setImageDrawable(front);
frontShowing = true;
}
else
{
}
}
};
private void getCardImageGuids(int cardId)
{
...
// Put your db logic retrieval for the img id here
}
}
I hope this solution (and ACTUAL code) helps someone else.
Related
I'm trying to create an app with a OCR Scanner by using the tessract/tess-two library, I've successful access to the Phone camera, I can do the manual focus but when I take a picture the following error:
07-18 19:07:06.335 2585-2585/com.fastnetserv.myapp D/DBG_com.fastnetserv.myapp.MainActivity: Picture taken
07-18 19:07:06.335 2585-2585/com.fastnetserv.myapp D/DBG_com.fastnetserv.myapp.MainActivity: Got null data
07-18 19:07:06.405 2585-2585/com.fastnetserv.myapp D/DBG_com.fastnetserv.myapp.MainActivity: Picture taken
07-18 19:07:06.426 2585-2585/com.fastnetserv.myapp D/DBG_com.fastnetserv.myapp.MainActivity: Got bitmap
07-18 19:07:06.427 2585-11599/com.fastnetserv.myapp E/DBG_com.fastnetserv.myapp.TessAsyncEngine: Error passing parameter to execute(context, bitmap)
07-18 19:07:14.111 2585-2585/com.fastnetserv.myapp D/DBG_com.fastnetserv.myapp.CameraUtils: CameraEngine Stopped
Here the CameraFragment code:
package com.fastnetserv.myapp;
import android.content.Context;
import android.graphics.Bitmap;
import android.hardware.Camera;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import com.googlecode.tesseract.android.TessBaseAPI;
/**
* A simple {#link Fragment} subclass.
* Activities that contain this fragment must implement the
* {#link //CameraFragment.//OnFragmentInteractionListener} interface
* to handle interaction events.
* Use the {#link CameraFragment#newInstance} factory method to
* create an instance of this fragment.
*/
public class CameraFragment extends Fragment implements SurfaceHolder.Callback, View.OnClickListener,
Camera.PictureCallback, Camera.ShutterCallback {
static final String TAG = "DBG_" + MainActivity.class.getName();
Button shutterButton;
Button focusButton;
FocusBoxView focusBox;
SurfaceView cameraFrame;
CameraEngine cameraEngine;
// TODO: Rename parameter arguments, choose names that match
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
private static final String ARG_PARAM1 = "param1";
private static final String ARG_PARAM2 = "param2";
// TODO: Rename and change types of parameters
private String mParam1;
private String mParam2;
private OnFragmentInteractionListener mListener;
public CameraFragment() {
// Required empty public constructor
}
/**
* Use this factory method to create a new instance of
* this fragment using the provided parameters.
*
* #param param1 Parameter 1.
* #param param2 Parameter 2.
* #return A new instance of fragment CameraFragment.
*/
// TODO: Rename and change types and number of parameters
public static CameraFragment newInstance(String param1, String param2) {
CameraFragment fragment = new CameraFragment();
Bundle args = new Bundle();
args.putString(ARG_PARAM1, param1);
args.putString(ARG_PARAM2, param2);
fragment.setArguments(args);
return fragment;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
mParam1 = getArguments().getString(ARG_PARAM1);
mParam2 = getArguments().getString(ARG_PARAM2);
}
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_camera, container, false);
}
// TODO: Rename method, update argument and hook method into UI event
public void onButtonPressed(Uri uri) {
if (mListener != null) {
mListener.onFragmentInteraction(uri);
}
}
#Override
public void onAttach(Context context) {
super.onAttach(context);
try {
mListener = (OnFragmentInteractionListener) context;
} catch (ClassCastException e) {
throw new ClassCastException(context.toString()
+ " must implement OnFragmentInteractionListener");
}
}
#Override
public void onDetach() {
super.onDetach();
mListener = null;
}
// Camera Code
public String detectText(Bitmap bitmap) {
TessDataManager.initTessTrainedData(getActivity());
TessBaseAPI tessBaseAPI = new TessBaseAPI();
String path = "/mnt/sdcard/com.fastnetserv.myapp/tessdata/ita.traineddata";
Log.d(TAG, "Check data path: " + path);
tessBaseAPI.setDebug(true);
tessBaseAPI.init(path, "ita"); //Init the Tess with the trained data file, with english language
//For example if we want to only detect numbers
tessBaseAPI.setVariable(TessBaseAPI.VAR_CHAR_WHITELIST, "1234567890");
tessBaseAPI.setVariable(TessBaseAPI.VAR_CHAR_BLACKLIST, "!##$%^&*()_+=-qwertyuiop[]}{POIU" +
"YTREWQasdASDfghFGHjklJKLl;L:'\"\\|~`xcvXCVbnmBNM,./<>?");
tessBaseAPI.setImage(bitmap);
String text = tessBaseAPI.getUTF8Text();
//Log.d(TAG, "Got data: " + result);
tessBaseAPI.end();
return text;
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
Log.d(TAG, "Surface Created - starting camera");
if (cameraEngine != null && !cameraEngine.isOn()) {
cameraEngine.start();
}
if (cameraEngine != null && cameraEngine.isOn()) {
Log.d(TAG, "Camera engine already on");
return;
}
cameraEngine = CameraEngine.New(holder);
cameraEngine.start();
Log.d(TAG, "Camera engine started");
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
#Override
public void onResume() {
super.onResume();
cameraFrame = (SurfaceView) getActivity().findViewById(R.id.camera_frame);
shutterButton = (Button) getActivity().findViewById(R.id.shutter_button);
focusBox = (FocusBoxView) getActivity().findViewById(R.id.focus_box);
focusButton = (Button) getActivity().findViewById(R.id.focus_button);
shutterButton.setOnClickListener(this);
focusButton.setOnClickListener(this);
SurfaceHolder surfaceHolder = cameraFrame.getHolder();
surfaceHolder.addCallback(this);
surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
cameraFrame.setOnClickListener(this);
}
#Override
public void onPause() {
super.onPause();
if (cameraEngine != null && cameraEngine.isOn()) {
cameraEngine.stop();
}
SurfaceHolder surfaceHolder = cameraFrame.getHolder();
surfaceHolder.removeCallback(this);
}
#Override
public void onClick(View v) {
if(v == shutterButton){
if(cameraEngine != null && cameraEngine.isOn()){
cameraEngine.takeShot(this, this, this);
}
}
if(v == focusButton){
if(cameraEngine!=null && cameraEngine.isOn()){
cameraEngine.requestFocus();
}
}
}
#Override
public void onPictureTaken(byte[] data, Camera camera) {
Log.d(TAG, "Picture taken");
if (data == null) {
Log.d(TAG, "Got null data");
return;
}
Bitmap bmp = Tools.getFocusedBitmap(getActivity(), camera, data, focusBox.getBox());
Log.d(TAG, "Got bitmap");
new TessAsyncEngine().executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, this, bmp);
}
#Override
public void onShutter() {
}
}
And here the TessAsyncEngine:
package com.fastnetserv.myapp;
import android.app.Activity;
import android.graphics.Bitmap;
import android.os.AsyncTask;
import android.util.Log;
import com.fastnetserv.myapp.ImageDialog;
import com.fastnetserv.myapp.Tools;
/**
* Created by Fadi on 6/11/2014.
*/
public class TessAsyncEngine extends AsyncTask<Object, Void, String> {
static final String TAG = "DBG_" + TessAsyncEngine.class.getName();
private Bitmap bmp;
private Activity context;
#Override
protected String doInBackground(Object... params) {
try {
if(params.length < 2) {
Log.e(TAG, "Error passing parameter to execute - missing params");
return null;
}
if(!(params[0] instanceof Activity) || !(params[1] instanceof Bitmap)) {
Log.e(TAG, "Error passing parameter to execute(context, bitmap)");
return null;
}
context = (Activity)params[0];
bmp = (Bitmap)params[1];
if(context == null || bmp == null) {
Log.e(TAG, "Error passed null parameter to execute(context, bitmap)");
return null;
}
int rotate = 0;
if(params.length == 3 && params[2]!= null && params[2] instanceof Integer){
rotate = (Integer) params[2];
}
if(rotate >= -180 && rotate <= 180 && rotate != 0)
{
bmp = Tools.preRotateBitmap(bmp, rotate);
Log.d(TAG, "Rotated OCR bitmap " + rotate + " degrees");
}
TessEngine tessEngine = TessEngine.Generate(context);
bmp = bmp.copy(Bitmap.Config.ARGB_8888, true);
String result = tessEngine.detectText(bmp);
Log.d(TAG, result);
return result;
} catch (Exception ex) {
Log.d(TAG, "Error: " + ex + "\n" + ex.getMessage());
}
return null;
}
#Override
protected void onPostExecute(String s) {
if(s == null || bmp == null || context == null)
return;
ImageDialog.New()
.addBitmap(bmp)
.addTitle(s)
.show(context.getFragmentManager(), TAG);
super.onPostExecute(s);
}
}
I have followed this tutorial (http://www.codeproject.com/Tips/840623/Android-Character-Recognition) but probably I forgot something due to the lack of my knowledge with Android
That if(context == null || bmp == null) is not needed, as you already tested those values with instanceof.
But I'm guessing your main problem is passing this from Fragment as Activity parameter, which is not.
To fix.. I overall would try to not toss Activity pointer around wildly, as those have quite limited life cycle on android. I have an app with tess-two and I don't recall ever needing Activity to init it (although usually I init it from native C++, so YMMV).
Isn't just the Context needed for that call? If yes, I would suggest to move to getApplicationContext() value instead. I think this is directly or indirectly accessible from Fragment too.
Sorry for not trying your code, but this is something you can debug quite easily.
One more note to android and tesseract usage. What is Tools.getFocusedBitmap? Will it cut down the pic reasonably? If it keeps full size, and your Camera is set to full size, you are tossing around 5-10+MP Bitmaps around, which in Android means to hit Out-Of-Memory (OOM) almost instantly. Either set Camera to reasonably low resolution, or cut-out designed part of photo ASAP and drop the whole Image, ideally as first step of processing.
Also you may want to reconsider whole tess-two thing, and try the official Google Text API from Google play services.
https://developers.google.com/android/reference/com/google/android/gms/vision/text/Text
It's brand new addition, inside I guess it will use the second generation of Tesseract engine with latest improvements, so very likely to get better results and better speed, than from tess-two.
I think it's accessible only from Android 4.4 and only on devices with Google Play Services, and cross-platform sucks, so I'm staying with tess-two in my projects - as I have to support also iOS and Windows Phone.
And generally I don't believe things which don't come with source along, SW without source is zombie, already dead while you are using it (will take at most 30-50y to die), and it's a waste of time and skill of those programmers.
I'm trying to make an app that will react to screenshots folder and do a toast for beginning,
I'm a new developer and this is my first time using file observer so I can only guess I've made a lot of mistakes.
The problem is there is no toast or log upon taking a screenshot.
Here is the code in my observer class:
public class listeningInit extends FileObserver {
private static final String TAG = "File listener";
public String absolutePath;
public listeningInit(String path) {
super(path, FileObserver.ALL_EVENTS);
absolutePath = path;
}
#Override
public void onEvent(int event, String path) {
if ((FileObserver.CREATE & event)!=0) {
Log.v(TAG, absolutePath + "/" + path + " is created\n");
Context context = getContext();
int duration = Toast.LENGTH_SHORT;
Toast toast = Toast.makeText(context, "Folder action!", duration);
toast.show();
}
}
private Context getContext() {
// TODO Auto-generated method stub
return null;
}
and here's the code on activity that does .startWatching
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
listeningInit startObs = new listeningInit("/Pictures/Screenshots/");
startObs.startWatching();
//Checking if this is a first run
Boolean firstRun = false;
SharedPreferences run = getSharedPreferences("MYPREFS", 0);
firstRun = run.getBoolean("fr", true);
//if true launch tutorial activity
if(firstRun == true){
Intent k = new Intent(MainActivity.this, Tutorial.class);
startActivity(k);
}
}
There's no errors thrown by the code, it's just not responding and I don't have a slight clue on why that might be.
I am facing a problem with an expansion file process, I do not know what the problem is. I hope if someone could help.
I have followed the steps by Google; yet I cannot assure I have done it correctly 100%.
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*/
import com.android.vending.expansion.zipfile.ZipResourceFile;
import com.android.vending.expansion.zipfile.ZipResourceFile.ZipEntryRO;
import com.google.android.vending.expansion.downloader.Constants;
import com.google.android.vending.expansion.downloader.DownloadProgressInfo;
import com.google.android.vending.expansion.downloader.DownloaderClientMarshaller;
import com.google.android.vending.expansion.downloader.DownloaderServiceMarshaller;
import com.google.android.vending.expansion.downloader.Helpers;
import com.google.android.vending.expansion.downloader.IDownloaderClient;
import com.google.android.vending.expansion.downloader.IDownloaderService;
import com.google.android.vending.expansion.downloader.IStub;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Messenger;
import android.os.SystemClock;
import android.provider.Settings;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;
import java.io.DataInputStream;
import java.io.IOException;
import java.util.zip.CRC32;
/**
* This is sample code for a project built against the downloader library. It
* implements the IDownloaderClient that the client marshaler will talk to as
* messages are delivered from the DownloaderService.
*/
public class ExpansionFileDownloaderActivity extends Activity implements IDownloaderClient {
private static final String LOG_TAG = "LVLDownloader";
private ProgressBar mPB;
private TextView mStatusText;
private TextView mProgressFraction;
private TextView mProgressPercent;
private TextView mAverageSpeed;
private TextView mTimeRemaining;
private View mDashboard;
private View mCellMessage;
private Button mPauseButton;
private Button mWiFiSettingsButton;
private boolean mStatePaused;
private int mState;
private IDownloaderService mRemoteService;
private IStub mDownloaderClientStub;
private void setState(int newState) {
if (mState != newState) {
mState = newState;
mStatusText.setText(Helpers.getDownloaderStringResourceIDFromState(newState));
}
}
private void setButtonPausedState(boolean paused) {
mStatePaused = paused;
int stringResourceID = paused ? R.string.text_button_resume :
R.string.text_button_pause;
mPauseButton.setText(stringResourceID);
}
/**
* This is a little helper class that demonstrates simple testing of an
* Expansion APK file delivered by Market. You may not wish to hard-code
* things such as file lengths into your executable... and you may wish to
* turn this code off during application development.
*/
private static class XAPKFile {
public final boolean mIsMain;
public final int mFileVersion;
public final long mFileSize;
XAPKFile(boolean isMain, int fileVersion, long fileSize) {
mIsMain = isMain;
mFileVersion = fileVersion;
mFileSize = fileSize;
}
}
/**
* Here is where you place the data that the validator will use to determine
* if the file was delivered correctly. This is encoded in the source code
* so the application can easily determine whether the file has been
* properly delivered without having to talk to the server. If the
* application is using LVL for licensing, it may make sense to eliminate
* these checks and to just rely on the server.
*/
private static final XAPKFile[] xAPKS = {
new XAPKFile(
true, // true signifies a main file
1, // the version of the APK that the file was uploaded
// against
102459993L // the length of the file in bytes
)
};
/**
* Go through each of the APK Expansion files defined in the structure above
* and determine if the files are present and match the required size. Free
* applications should definitely consider doing this, as this allows the
* application to be launched for the first time without having a network
* connection present. Paid applications that use LVL should probably do at
* least one LVL check that requires the network to be present, so this is
* not as necessary.
*
* #return true if they are present.
*/
boolean expansionFilesDelivered() {
for (XAPKFile xf : xAPKS) {
String fileName = Helpers.getExpansionAPKFileName(this, xf.mIsMain, xf.mFileVersion);
if (!Helpers.doesFileExist(this, fileName, xf.mFileSize, false))
return false;
}
return true;
}
public static boolean expansionFilesDelivered(Context ctx) {
for (XAPKFile xf : xAPKS) {
String fileName = Helpers.getExpansionAPKFileName(ctx, xf.mIsMain, xf.mFileVersion);
if (!Helpers.doesFileExist(ctx, fileName, xf.mFileSize, false))
return false;
}
return true;
}
/**
* Calculating a moving average for the validation speed so we don't get
* jumpy calculations for time etc.
*/
static private final float SMOOTHING_FACTOR = 0.005f;
/**
* Used by the async task
*/
private boolean mCancelValidation;
/**
* Go through each of the Expansion APK files and open each as a zip file.
* Calculate the CRC for each file and return false if any fail to match.
*
* #return true if XAPKZipFile is successful
*/
void validateXAPKZipFiles() {
AsyncTask<Object, DownloadProgressInfo, Boolean> validationTask = new AsyncTask<Object, DownloadProgressInfo, Boolean>() {
#Override
protected void onPreExecute() {
mDashboard.setVisibility(View.VISIBLE);
mCellMessage.setVisibility(View.GONE);
mStatusText.setText(R.string.text_verifying_download);
mPauseButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
mCancelValidation = true;
}
});
mPauseButton.setText(R.string.text_button_cancel_verify);
super.onPreExecute();
}
#Override
protected Boolean doInBackground(Object... params) {
for (XAPKFile xf : xAPKS) {
String fileName = Helpers.getExpansionAPKFileName(
ExpansionFileDownloaderActivity.this,
xf.mIsMain, xf.mFileVersion);
if (!Helpers.doesFileExist(ExpansionFileDownloaderActivity.this, fileName,
xf.mFileSize, false))
return false;
fileName = Helpers
.generateSaveFileName(ExpansionFileDownloaderActivity.this, fileName);
ZipResourceFile zrf;
byte[] buf = new byte[1024 * 256];
try {
zrf = new ZipResourceFile(fileName);
ZipEntryRO[] entries = zrf.getAllEntries();
/**
* First calculate the total compressed length
*/
long totalCompressedLength = 0;
for (ZipEntryRO entry : entries) {
totalCompressedLength += entry.mCompressedLength;
}
float averageVerifySpeed = 0;
long totalBytesRemaining = totalCompressedLength;
long timeRemaining;
/**
* Then calculate a CRC for every file in the Zip file,
* comparing it to what is stored in the Zip directory.
* Note that for compressed Zip files we must extract
* the contents to do this comparison.
*/
for (ZipEntryRO entry : entries) {
if (-1 != entry.mCRC32) {
long length = entry.mUncompressedLength;
CRC32 crc = new CRC32();
DataInputStream dis = null;
try {
dis = new DataInputStream(
zrf.getInputStream(entry.mFileName));
long startTime = SystemClock.uptimeMillis();
while (length > 0) {
int seek = (int) (length > buf.length ? buf.length
: length);
dis.readFully(buf, 0, seek);
crc.update(buf, 0, seek);
length -= seek;
long currentTime = SystemClock.uptimeMillis();
long timePassed = currentTime - startTime;
if (timePassed > 0) {
float currentSpeedSample = (float) seek
/ (float) timePassed;
if (0 != averageVerifySpeed) {
averageVerifySpeed = SMOOTHING_FACTOR
* currentSpeedSample
+ (1 - SMOOTHING_FACTOR)
* averageVerifySpeed;
} else {
averageVerifySpeed = currentSpeedSample;
}
totalBytesRemaining -= seek;
timeRemaining = (long) (totalBytesRemaining / averageVerifySpeed);
this.publishProgress(
new DownloadProgressInfo(
totalCompressedLength,
totalCompressedLength
- totalBytesRemaining,
timeRemaining,
averageVerifySpeed
)
);
}
startTime = currentTime;
if (mCancelValidation)
return true;
}
if (crc.getValue() != entry.mCRC32) {
Log.e(Constants.TAG,
"CRC does not match for entry: "
+ entry.mFileName
);
Log.e(Constants.TAG,
"In file: " + entry.getZipFileName());
return false;
}
} finally {
if (null != dis) {
dis.close();
}
}
}
}
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
return true;
}
#Override
protected void onProgressUpdate(DownloadProgressInfo... values) {
onDownloadProgress(values[0]);
super.onProgressUpdate(values);
}
#Override
protected void onPostExecute(Boolean result) {
if (result) {
mDashboard.setVisibility(View.VISIBLE);
mCellMessage.setVisibility(View.GONE);
mStatusText.setText(R.string.text_validation_complete);
mPauseButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
finish();
}
});
mPauseButton.setText(android.R.string.ok);
} else {
mDashboard.setVisibility(View.VISIBLE);
mCellMessage.setVisibility(View.GONE);
mStatusText.setText(R.string.text_validation_failed);
mPauseButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
finish();
}
});
mPauseButton.setText(android.R.string.cancel);
}
super.onPostExecute(result);
}
};
validationTask.execute(new Object());
}
/**
* If the download isn't present, we initialize the download UI. This ties
* all of the controls into the remote service calls.
*/
private void initializeDownloadUI() {
mDownloaderClientStub = DownloaderClientMarshaller.CreateStub
(this, SampleDownloaderService.class);
setContentView(R.layout.downloader_ui);
mPB = (ProgressBar) findViewById(R.id.progressBar);
mStatusText = (TextView) findViewById(R.id.statusText);
mProgressFraction = (TextView) findViewById(R.id.progressAsFraction);
mProgressPercent = (TextView) findViewById(R.id.progressAsPercentage);
mAverageSpeed = (TextView) findViewById(R.id.progressAverageSpeed);
mTimeRemaining = (TextView) findViewById(R.id.progressTimeRemaining);
mDashboard = findViewById(R.id.downloaderDashboard);
mCellMessage = findViewById(R.id.approveCellular);
mPauseButton = (Button) findViewById(R.id.pauseButton);
mWiFiSettingsButton = (Button) findViewById(R.id.wifiSettingsButton);
mPauseButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if (mStatePaused) {
mRemoteService.requestContinueDownload();
} else {
mRemoteService.requestPauseDownload();
}
setButtonPausedState(!mStatePaused);
}
});
mWiFiSettingsButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
startActivity(new Intent(Settings.ACTION_WIFI_SETTINGS));
}
});
Button resumeOnCell = (Button) findViewById(R.id.resumeOverCellular);
resumeOnCell.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
mRemoteService.setDownloadFlags(IDownloaderService.FLAGS_DOWNLOAD_OVER_CELLULAR);
mRemoteService.requestContinueDownload();
mCellMessage.setVisibility(View.GONE);
}
});
}
/**
* Called when the activity is first create; we wouldn't create a layout in
* the case where we have the file and are moving to another activity
* without downloading.
*/
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
/**
* Both downloading and validation make use of the "download" UI
*/
initializeDownloadUI();
/**
* Before we do anything, are the files we expect already here and
* delivered (presumably by Market) For free titles, this is probably
* worth doing. (so no Market request is necessary)
*/
if (!expansionFilesDelivered()) {
try {
Intent launchIntent = ExpansionFileDownloaderActivity.this
.getIntent();
Intent intentToLaunchThisActivityFromNotification = new Intent(
ExpansionFileDownloaderActivity
.this, ((Object) this).getClass() //..... this.getClass()
);
intentToLaunchThisActivityFromNotification.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
Intent.FLAG_ACTIVITY_CLEAR_TOP);
intentToLaunchThisActivityFromNotification.setAction(launchIntent.getAction());
if (launchIntent.getCategories() != null) {
for (String category : launchIntent.getCategories()) {
intentToLaunchThisActivityFromNotification.addCategory(category);
}
}
// Build PendingIntent used to open this activity from
// Notification
PendingIntent pendingIntent = PendingIntent.getActivity(
ExpansionFileDownloaderActivity.this,
0, intentToLaunchThisActivityFromNotification,
PendingIntent.FLAG_UPDATE_CURRENT);
// Request to start the download
int startResult = DownloaderClientMarshaller.startDownloadServiceIfRequired(this,
pendingIntent, SampleDownloaderService.class);
if (startResult != DownloaderClientMarshaller.NO_DOWNLOAD_REQUIRED) {
// The DownloaderService has started downloading the files,
// show progress
initializeDownloadUI();
return;
} // otherwise, download not needed so we fall through to
// starting the movie
} catch (NameNotFoundException e) {
Log.e(LOG_TAG, "Cannot find own package! MAYDAY!");
e.printStackTrace();
}
} else {
validateXAPKZipFiles();
}
}
/**
* Connect the stub to our service on start.
*/
#Override
protected void onStart() {
if (null != mDownloaderClientStub) {
mDownloaderClientStub.connect(this);
}
super.onStart();
}
/**
* Disconnect the stub from our service on stop
*/
#Override
protected void onStop() {
if (null != mDownloaderClientStub) {
mDownloaderClientStub.disconnect(this);
}
super.onStop();
}
/**
* Critical implementation detail. In onServiceConnected we create the
* remote service and marshaler. This is how we pass the client information
* back to the service so the client can be properly notified of changes. We
* must do this every time we reconnect to the service.
*/
#Override
public void onServiceConnected(Messenger m) {
mRemoteService = DownloaderServiceMarshaller.CreateProxy(m);
mRemoteService.onClientUpdated(mDownloaderClientStub.getMessenger());
}
/**
* The download state should trigger changes in the UI --- it may be useful
* to show the state as being indeterminate at times. This sample can be
* considered a guideline.
*/
#Override
public void onDownloadStateChanged(int newState) {
setState(newState);
boolean showDashboard = true;
boolean showCellMessage = false;
boolean paused;
boolean indeterminate;
switch (newState) {
case IDownloaderClient.STATE_IDLE:
// STATE_IDLE means the service is listening, so it's
// safe to start making calls via mRemoteService.
paused = false;
indeterminate = true;
break;
case IDownloaderClient.STATE_CONNECTING:
case IDownloaderClient.STATE_FETCHING_URL:
showDashboard = true;
paused = false;
indeterminate = true;
break;
case IDownloaderClient.STATE_DOWNLOADING:
paused = false;
showDashboard = true;
indeterminate = false;
break;
case IDownloaderClient.STATE_FAILED_CANCELED:
case IDownloaderClient.STATE_FAILED:
case IDownloaderClient.STATE_FAILED_FETCHING_URL:
case IDownloaderClient.STATE_FAILED_UNLICENSED:
paused = true;
showDashboard = false;
indeterminate = false;
break;
case IDownloaderClient.STATE_PAUSED_NEED_CELLULAR_PERMISSION:
case IDownloaderClient.STATE_PAUSED_WIFI_DISABLED_NEED_CELLULAR_PERMISSION:
showDashboard = false;
paused = true;
indeterminate = false;
showCellMessage = true;
break;
case IDownloaderClient.STATE_PAUSED_BY_REQUEST:
paused = true;
indeterminate = false;
break;
case IDownloaderClient.STATE_PAUSED_ROAMING:
case IDownloaderClient.STATE_PAUSED_SDCARD_UNAVAILABLE:
paused = true;
indeterminate = false;
break;
case IDownloaderClient.STATE_COMPLETED:
showDashboard = false;
paused = false;
indeterminate = false;
validateXAPKZipFiles();
return;
default:
paused = true;
indeterminate = true;
showDashboard = true;
}
int newDashboardVisibility = showDashboard ? View.VISIBLE : View.GONE;
if (mDashboard.getVisibility() != newDashboardVisibility) {
mDashboard.setVisibility(newDashboardVisibility);
}
int cellMessageVisibility = showCellMessage ? View.VISIBLE : View.GONE;
if (mCellMessage.getVisibility() != cellMessageVisibility) {
mCellMessage.setVisibility(cellMessageVisibility);
}
mPB.setIndeterminate(indeterminate);
setButtonPausedState(paused);
}
/**
* Sets the state of the various controls based on the progressinfo object
* sent from the downloader service.
*/
#Override
public void onDownloadProgress(DownloadProgressInfo progress) {
mAverageSpeed.setText(getString(R.string.kilobytes_per_second,
Helpers.getSpeedString(progress.mCurrentSpeed)));
mTimeRemaining.setText(getString(R.string.time_remaining,
Helpers.getTimeRemaining(progress.mTimeRemaining)));
progress.mOverallTotal = progress.mOverallTotal;
mPB.setMax((int) (progress.mOverallTotal >> 8));
mPB.setProgress((int) (progress.mOverallProgress >> 8));
mProgressPercent.setText(Long.toString(progress.mOverallProgress
* 100 /
progress.mOverallTotal) + "%");
mProgressFraction.setText(Helpers.getDownloadProgressString
(progress.mOverallProgress,
progress.mOverallTotal));
}
#Override
protected void onDestroy() {
this.mCancelValidation = true;
super.onDestroy();
}
}
this is the SampleDownloaderService class,
import com.google.android.vending.expansion.downloader.impl.DownloaderService;
/**
* Created by Abdulkarim Kanaan on 22-Mar-14.
*/
public class SampleDownloaderService extends DownloaderService {
// You must use the public key belonging to your publisher account
public static final String BASE64_PUBLIC_KEY = "<Place Key provided by Google Play Publisher>";
// You should also modify this salt
public static final byte[] SALT = new byte[] { 1, 42, -12, -1, 54, 98,
-100, -12, 43, 2, -8, -4, 9, 5, -106, -107, -33, 45, -1, 84
};
#Override
public String getPublicKey() {
return BASE64_PUBLIC_KEY;
}
#Override
public byte[] getSALT() {
return SALT;
}
#Override
public String getAlarmReceiverClassName() {
return SampleAlarmReceiver.class.getName();
}
}
Finally, this is the Main Activity creation method, where the check is carried out to see whether the expansion file exists or not.
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
String.valueOf(ExpansionFileDownloaderActivity.expansionFilesDelivered(this)));
if (!ExpansionFileDownloaderActivity.expansionFilesDelivered(this)){
startActivity(new Intent(this, ExpansionFileDownloaderActivity.class));
}
//startApp(); // Expansion files are available, start the app
}
the problem is that after uploading the expansion file to Google Play, when the program starts, it shows that downloading process is going to be carried out. After a short period, it stops saying that "Download failed because the resources could not be found". When I place the file (obb file) in the SD Card, the application works as expected. However, I am trying now to download the file from Google Play.
What is the problem?
Thank you in advance,
I was having the same problem and no matter what I tried it still didnt work.
Finally I managed to download the apk expansion file. What I did was the following.
*Make sure your file size is correct (file size not disk size).
*Make sure versionCode matches with your manifest's versionCode.
*Make sure you have changed the SALT bytes.
Step by step:
1- Create a new app in the developer console.
2- Obtain the LVL Licence Key and repace it in your code.
3- Sign and export your application and create a zip with your expansion file without compression (compression: "store" in win rar).
4-Upload your app to google play but upload it in the Beta Testing Section. (without a production version listed, this is very important you must not have a production version saved in draft).
5- Create a google group with the people you want to allow beta acess and under the Beta Testing tab you must add that group.
5- Now its the scary part, you must click on publish. Your app will not show in google play, it will only be shown to your testing groups, TRUST ME. It should look like this:
*Note: Make sure you don't promote the beta to production or it will be shown, also notice I don't have a production version listed.
6- Under Beta Testing tab there is a link that a you must click in order to use the beta.
After that just download the app from google play, you can just type your package name for example:
https://play.google.com/store/apps/details?id=com.mydomain.myapp
and the app will be installed along with the expansion package.
If you want to test the files download, install the app and then delete the obb file downloaded. This will start the obb download process.
Hope this helps, as it did for me!
Cheers.
I've been trying to get some AR (Augmented Reality) SDK's working in a Fragment.
However, I can't seem to get it working.
I've found some code of someone who has got Metaio (AR Framework) working in a fragment.
So I've applied that code to my own project, it is working but the code is not programmed to scan a picture. I want to scan a picture mark with it.
I copied some code to scan a picture mark from a Sample Project of Metaio, but it doesn't work.
Right now it is failing at (Debug logs after that don't get logged):
trackingConfigFile = AssetsManager.getAssetPath(getActivity().getApplicationContext(), "AEDApp/Assets/TrackingData_PictureMarker.xml");
This is my full code:
package com.example.bt6_aedapp;
import android.app.Application;
import android.content.res.Configuration;
import android.hardware.Camera.CameraInfo;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.metaio.cloud.plugin.MetaioCloudPlugin;
import com.metaio.sdk.MetaioDebug;
import com.metaio.sdk.MetaioSurfaceView;
import com.metaio.sdk.SensorsComponentAndroid;
import com.metaio.sdk.jni.ERENDER_SYSTEM;
import com.metaio.sdk.jni.ESCREEN_ROTATION;
import com.metaio.sdk.jni.IGeometry;
import com.metaio.sdk.jni.IMetaioSDKAndroid;
import com.metaio.sdk.jni.IMetaioSDKCallback;
import com.metaio.sdk.jni.MetaioSDK;
import com.metaio.sdk.jni.TrackingValuesVector;
import com.metaio.sdk.jni.Vector3d;
import com.metaio.tools.Screen;
import com.metaio.tools.SystemInfo;
import com.metaio.tools.io.AssetsManager;
public class fragmentA extends Fragment implements MetaioSurfaceView.Callback {
private Application mAppContext;
private ViewGroup mRootLayout;
String trackingConfigFile;
private MetaioSDKCallbackHandler mCallback;
private IGeometry mModel;
private IMetaioSDKAndroid mMetaioSDK;
private MetaioSurfaceView mSurfaceView;
private static boolean mNativeLibsLoaded = false;
private boolean mRendererInitialized;
private SensorsComponentAndroid mSensors;
static {
mNativeLibsLoaded = IMetaioSDKAndroid.loadNativeLibs();
}
#Override
public void onCreate(Bundle savedInstanceState) {
MetaioCloudPlugin.startJunaio(null, getActivity().getApplicationContext());
super.onCreate(savedInstanceState);
Log.d("LifeCycle", "onCreate");
mAppContext = getActivity().getApplication();
mMetaioSDK = null;
mSurfaceView = null;
mRendererInitialized = false;
try {
mCallback = new MetaioSDKCallbackHandler();
if (!mNativeLibsLoaded){
throw new Exception("Unsupported platform, failed to load the native libs");
}
// Create sensors component
mSensors = new SensorsComponentAndroid(mAppContext);
// Create Unifeye Mobile by passing Activity instance and
// application signature
mMetaioSDK = MetaioSDK.CreateMetaioSDKAndroid(getActivity(), getResources().getString(R.string.metaioSDKSignature));
mMetaioSDK.registerSensorsComponent(mSensors);
} catch (Throwable e) {
MetaioDebug.log(Log.ERROR, "ArCameraFragment.onCreate: failed to create or intialize metaio SDK: " + e.getMessage());
return;
}
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
Log.d("LifeCycle", "onCreateView");
View view = inflater.inflate(R.layout.fragment_a, container, false);
mRootLayout = (ViewGroup)getActivity().findViewById(R.id.pager);
return view;
}
#Override
public void onStart() {
super.onStart();
Log.d("LifeCycle", "onStart");
if(mMetaioSDK == null){
return;
}
MetaioDebug.log("ArCameraFragment.onStart()");
try {
mSurfaceView = null;
// Start camera
startCamera();
// Add Unifeye GL Surface view
mSurfaceView = new MetaioSurfaceView(mAppContext);
mSurfaceView.registerCallback(this);
mSurfaceView.setKeepScreenOn(true);
MetaioDebug.log("ArCameraFragment.onStart: addContentView(mMetaioSurfaceView)");
mRootLayout.addView(mSurfaceView, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT));
mSurfaceView.setZOrderMediaOverlay(true);
} catch (Exception e) {
MetaioDebug.log(Log.ERROR, "Error creating views: " + e.getMessage());
MetaioDebug.printStackTrace(Log.ERROR, e);
}
}
#Override
public void onResume() {
super.onResume();
Log.d("LifeCycle", "onResume");
// make sure to resume the OpenGL surface
if (mSurfaceView != null) {
mSurfaceView.onResume();
}
if(mMetaioSDK != null){
mMetaioSDK.resume();
}
}
#Override
public void onPause() {
super.onPause();
Log.d("LifeCycle", "onPause");
// pause the OpenGL surface
if (mSurfaceView != null) {
mSurfaceView.onPause();
}
if (mMetaioSDK != null) {
// Disable the camera
mMetaioSDK.pause();
}
}
#Override
public void onStop() {
super.onStop();
Log.d("LifeCycle", "onStop");
if (mMetaioSDK != null) {
// Disable the camera
mMetaioSDK.stopCamera();
}
if (mSurfaceView != null) {
mRootLayout.removeView(mSurfaceView);
}
System.runFinalization();
System.gc();
}
#Override
public void onDestroy() {
super.onDestroy();
mCallback.delete();
mCallback = null;
/*Log.d("LifeCycle", "onDestroy");
try {
mRendererInitialized = false;
} catch (Exception e) {
MetaioDebug.printStackTrace(Log.ERROR, e);
}
MetaioDebug.log("ArCameraFragment.onDestroy");
if (mMetaioSDK != null) {
mMetaioSDK.delete();
mMetaioSDK = null;
}
MetaioDebug.log("ArCameraFragment.onDestroy releasing sensors");
if (mSensors != null) {
mSensors.registerCallback(null);
mSensors.release();
mSensors.delete();
mSensors = null;
}
// Memory.unbindViews(activity.findViewById(android.R.id.content));
System.runFinalization();
System.gc();*/
}
#Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
final ESCREEN_ROTATION rotation = Screen.getRotation(getActivity());
mMetaioSDK.setScreenRotation(rotation);
MetaioDebug.log("onConfigurationChanged: " + rotation);
}
#Override
public void onDrawFrame() {
if(mMetaioSDK != null) {
TrackingValuesVector poses = mMetaioSDK.getTrackingValues();
if(poses.size() != 0) {
mModel.setCoordinateSystemID(poses.get(0).getCoordinateSystemID());
}
}
// Log.d("LifeCycle", "onDrawFrame");
/* if (mRendererInitialized) {
mMetaioSDK.render();
} */
}
#Override
public void onSurfaceCreated() {
Log.d("LifeCycle", "onSurfaceCreated");
try {
if (!mRendererInitialized) {
mMetaioSDK.initializeRenderer(mSurfaceView.getWidth(), mSurfaceView.getHeight(), Screen.getRotation(getActivity()),
ERENDER_SYSTEM.ERENDER_SYSTEM_OPENGL_ES_2_0);
mRendererInitialized = true;
} else {
MetaioDebug.log("ArCameraFragment.onSurfaceCreated: Reloading textures...");
mMetaioSDK.reloadTextures();
}
MetaioDebug.log("ArCameraFragment.onSurfaceCreated: Registering audio renderer...");
// mMetaioSDK.registerAudioCallback(mSurfaceView.getAudioRenderer());
mMetaioSDK.registerCallback(mCallback);
MetaioDebug.log("ARViewActivity.onSurfaceCreated");
} catch (Exception e) {
MetaioDebug.log(Log.ERROR, "ArCameraFragment.onSurfaceCreated: " + e.getMessage());
}
mSurfaceView.queueEvent(new Runnable() {
#Override
public void run() {
loadContents();
}
});
}
private void loadContents() {
try {
trackingConfigFile = AssetsManager.getAssetPath(getActivity().getApplicationContext(), "AEDApp/Assets/TrackingData_PictureMarker.xml");
boolean result = mMetaioSDK.setTrackingConfiguration(trackingConfigFile);
Log.d("result", Boolean.toString(result));
MetaioDebug.log("Tracking data loaded: " + result);
String aedLogo = AssetsManager.getAssetPath(getActivity().getApplicationContext(), "AEDApp/Assets/metaioman.md2");
Log.d("aedLogo", "aaa: " + aedLogo);
if(aedLogo != null) {
mModel = mMetaioSDK.createGeometry(aedLogo);
if(mModel != null) {
mModel.setScale(new Vector3d(4.0f, 4.0f, 4.0f));
}
else {
MetaioDebug.log(Log.ERROR, "Error loading geometry: " + aedLogo);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
#Override
public void onSurfaceChanged(int width, int height) {
Log.d("LifeCycle", "onSurfaceChanged");
mMetaioSDK.resizeRenderer(width, height);
}
#Override
public void onSurfaceDestroyed() {
Log.d("LifeCycle", "onSurfaceDestroyed");
MetaioDebug.log("ArCameraFragment.onSurfaceDestroyed(){");
mSurfaceView = null;
// mMetaioSDK.registerAudioCallback(null);
}
protected void startCamera() {
final int cameraIndex = SystemInfo.getCameraIndex(CameraInfo.CAMERA_FACING_BACK);
if (mMetaioSDK != null) {
mMetaioSDK.startCamera(cameraIndex, 640, 480);
}
}
final class MetaioSDKCallbackHandler extends IMetaioSDKCallback {
#Override
public void onTrackingEvent(final TrackingValuesVector trackingValues) {
super.onTrackingEvent(trackingValues);
if(!trackingValues.isEmpty() && trackingValues.get(0).isTrackingState()){
Log.d("Track", "NOT EMPTY");
}
}
}
}
I really hope someone can help me with this as I can not figure it out.. :(
EDIT
The Error (e.printStackTrace()) is throwing is:
03-24 20:25:19.068: W/System.err(28062): java.lang.NullPointerException: null string
03-24 20:25:19.068: W/System.err(28062): at com.metaio.sdk.jni.MetaioSDKJNI.IMetaioSDK_setTrackingConfiguration__SWIG_1(Native Method)
03-24 20:25:19.068: W/System.err(28062): at com.metaio.sdk.jni.IMetaioSDK.setTrackingConfiguration(IMetaioSDK.java:106)
03-24 20:25:19.068: W/System.err(28062): at com.example.bt6_aedapp.fragmentA.loadContents(fragmentA.java:278)
03-24 20:25:19.068: W/System.err(28062): at com.example.bt6_aedapp.fragmentA.access$0(fragmentA.java:274)
03-24 20:25:19.068: W/System.err(28062): at com.example.bt6_aedapp.fragmentA$1.run(fragmentA.java:268)
03-24 20:25:19.068: W/System.err(28062): at android.opengl.GLSurfaceView$GLThread.guardedRun(GLSurfaceView.java:1463)
03-24 20:25:19.068: W/System.err(28062): at android.opengl.GLSurfaceView$GLThread.run(GLSurfaceView.java:1240)
What I want to do with it:
Being able to 'scan' a picture (https://encrypted-tbn2.gstatic.com/images?q=tbn:ANd9GcQFqKIurD3QMU0zVeiwEhtm1twLmTCDlnFulfCwDkxTA1_XQjIQ) and detect the image in the app. The image is referenced in the app in the Assets folder of the project, and I've made a xml file where the marker for it is defined as is stated on the Metaio website. After detecting I'm going to do some database stuff, but for now I need to get the detecting part working.
EDIT
If anyone knows how i can make another AR Framework in Fragments I would love tot know.
I don't know pretty much anything about fragments but as for the null string I think that happens because you haven't extracted the assets.
In this video http://youtu.be/KVtCi-WwmFU?t=30m29s it's explained.
Basically what you have to do is add this code
private class AssetsExtracter extends AsyncTask<Integer, Integer, Boolean>{
#Override
protected Boolean doInBackground(Integer... params){
try
{
AssetsManager.extractAllAssets(getApplicationContext(), BuildConfig.DEBUG);
}catch (IOException e){
MetaioDebug.printStackTrace(Log.ERROR, e);
return false;
}
return true;
}
}
to your activity (or in this case, I guess, your fragment).
Then you have to add a field of this class like
private AssetsExtracter mTask;
and inside the onCreate() method you put
mTask = new AssetsExtracter();
mTask.execute(0);
After that your assets should be avaliable from AssetsManager.getAssetPath(..) and it shouldn't return a null string anymore.
My app is a Wifi chat app with which you can communicate between two Android units with text messages and snap camera pictures and send them. The pictures are stored to the SD-card.
I used to have an OutOfMemoryError thrown after a couple of sent images, but I solved that problem by sending the
options.inPurgeable = true;
and
options.inInputShareable = true;
to the BitmapFactory.decodeByteArray method. This makes the pixels "deallocatable" so new images can use the memory. Thus, the error no longer remains.
But, the internal memory is still full of images and the "Low on space: Phone storage space is getting low" warning appears. The app no longer crashes but there's no more memory on the phone after the app finishes. I have to manually clear the app's data in Settings > Applications > Manage Applications.
I tried recycling the bitmaps and even tried to explicitly empty the app's cache, but it doesn't seem to do what i expect.
This function receives the picture via a TCP socket, writes it to the SD-card and starts my custom Activity PictureView:
public void receivePicture(String fileName) {
try {
int fileSize = inStream.readInt();
Log.d("","fileSize:"+fileSize);
byte[] tempArray = new byte[200];
byte[] pictureByteArray = new byte[fileSize];
path = Prefs.getPath(this) + "/" + fileName;
File pictureFile = new File(path);
try {
if( !pictureFile.exists() ) {
pictureFile.getParentFile().mkdirs();
pictureFile.createNewFile();
}
} catch (IOException e) { Log.d("", "Recievepic - Kunde inte skapa fil.", e); }
int lastRead = 0, totalRead = 0;
while(lastRead != -1) {
if(totalRead >= fileSize - 200) {
lastRead = inStream.read(tempArray, 0, fileSize - totalRead);
System.arraycopy(tempArray, 0, pictureByteArray, totalRead, lastRead);
totalRead += lastRead;
break;
}
lastRead = inStream.read(tempArray);
System.arraycopy(tempArray, 0, pictureByteArray, totalRead, lastRead);
totalRead += lastRead;
}
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(pictureFile));
bos.write(pictureByteArray, 0, totalRead);
bos.flush();
bos.close();
bos = null;
tempArray = null;
pictureByteArray = null;
setSentence("<"+fileName+">", READER);
Log.d("","path:"+path);
try {
startActivity(new Intent(this, PictureView.class).putExtra("path", path));
} catch(Exception e) { e.printStackTrace(); }
}
catch(IOException e) { Log.d("","IOException:"+e); }
catch(Exception e) { Log.d("","Exception:"+e); }
}
Here's PictureView. It creates a byte[ ] from the file on the SD-card, decodes the array to a Bitmap, compresses the Bitmap and writes it back to the SD-card. Lastly, in the Progress.onDismiss, the picture is set as the image of a full screen imageView:
public class PictureView extends Activity {
private String fileName;
private ProgressDialog progress;
public ImageView view;
#Override
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
Log.d("","onCreate() PictureView");
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
view = new ImageView(this);
setContentView(view);
progress = ProgressDialog.show(this, "", "Laddar bild...");
progress.setOnDismissListener(new OnDismissListener() {
public void onDismiss(DialogInterface dialog) {
File file_ = getFileStreamPath(fileName);
Log.d("","SETIMAGE");
Uri uri = Uri.parse(file_.toString());
view.setImageURI(uri);
}
});
new Thread() { public void run() {
String path = getIntent().getStringExtra("path");
Log.d("","path:"+path);
File pictureFile = new File(path);
if(!pictureFile.exists())
finish();
fileName = path.substring(path.lastIndexOf('/') + 1);
Log.d("","fileName:"+fileName);
byte[] pictureArray = new byte[(int)pictureFile.length()];
try {
DataInputStream dis = new DataInputStream( new BufferedInputStream(
new FileInputStream(pictureFile)) );
for(int i=0; i < pictureArray.length; i++)
pictureArray[i] = dis.readByte();
} catch(Exception e) { Log.d("",""+e); e.printStackTrace(); }
/**
* Passing these options to decodeByteArray makes the pixels deallocatable
* if the memory runs out.
*/
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPurgeable = true;
options.inInputShareable = true;
Bitmap pictureBM =
BitmapFactory.decodeByteArray(pictureArray, 0, pictureArray.length, options);
OutputStream out = null;
try {
out = openFileOutput(fileName, MODE_PRIVATE);
/**
* COMPRESS !!!!!
**/
pictureBM.compress(CompressFormat.PNG, 100, out);
pictureBM = null;
progress.dismiss(); }
catch (IOException e) { Log.e("test", "Failed to write bitmap", e); }
finally {
if (out != null)
try { out.close(); out = null; }
catch (IOException e) { }
} }
}.start();
}
#Override
protected void onStop() {
super.onStop();
Log.d("","ONSTOP()");
Drawable oldDrawable = view.getDrawable();
if( oldDrawable != null) {
((BitmapDrawable)oldDrawable).getBitmap().recycle();
oldDrawable = null;
Log.d("","recycle");
}
Editor editor =
this.getSharedPreferences("clear_cache", Context.MODE_PRIVATE).edit();
editor.clear();
editor.commit();
}
}
When the user presses the back key, the picture isn't supposed to be available anymore from within the app. Just stored on the SD-card.
In onStop() I recycle the old Bitmap and even try to empty the app's data. Still the "Low on space" warning appears. How can I be sure the images won't allocate the memory anymore when they're not needed?
EDIT: It appears the problem is the compress method. If everything after compress is commented, the problem remains. If I delete compress, the problem disappears. Compress seems to allocate memory that's never released, and it's 2-3 MB per image.
Ok, I solved it. The problem was, I was passing an OutputStream to compress, which is a stream to a private file in the app's internal memory. That's what I set as the image later. This file is never allocated.
I didn't get that I had two files: one on the SD-card and one in the internal memory, both with the same name.
Now, I'm just setting the SD-card file as the ImageView's image. I never read the file into the internal memory as a byte[], thus never decoding the array to a bitmap, thus never compressing the bitmap into the internal memory.
This is the new PictureView:
public class PictureView extends Activity {
public ImageView view;
private String path;
#Override
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
Log.d("","onCreate() PictureView");
path = getIntent().getStringExtra("path");
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
view = new ImageView(this);
setContentView(view);
Uri uri = Uri.parse( new File(path).toString() );
view.setImageURI(uri);
}
#Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
Log.d("","Back key pressed");
Drawable oldDrawable = view.getDrawable();
if( oldDrawable != null) {
((BitmapDrawable)oldDrawable).getBitmap().recycle();
oldDrawable = null;
Log.d("","recycle");
}
view = null;
}
return super.onKeyDown(keyCode, event);
}
}
Is it bad practice to put an external file as the image of an ImageView? Should I load it into internal memory first?
If you specifically want the image to be nullified from memory for sure when a user presses back you could override the back button and make your image clean up calls there. I do that in some of my apps and it seems to work. maybe something like this:
#Override
protected void onBackPressed() {
super.onBackPressed();
view.drawable = null;
jumpBackToPreviousActivity();
}
Im pretty sure there are some view methods that clear other caches and things like that. You can recycle the bitmap but that doesnt guarantee that it will be dumped right then but only at some point when the gc gets to it.....but Im sure you probably know that already :)
EDIT: You could also do the same thing in the onPause method. That one is guaranteed to get called. The other two may never get called according to the android docs.
http://developer.android.com/reference/android/app/Activity.html