I'm trying to write the stream of my array that is coming from Bluetooth module and read from (HandleRead), to the internal storage directly. Is that possible in the first place?
Note that I am reading 100 samples per second. That means the file will fill up quickly. I am not familiar with storage, and my code isn't executed as I expected.
public class MainActivity extends ActionBarActivity implements SensorEventListener {
File Root = Environment.getExternalStorageDirectory();
File Dir = new File (Root.getAbsolutePath()+"/myAppFile");
File file = new File(Dir,"Message.txt");
#Override
protected void onCreate(Bundle savedInstanceState){
String state;
state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)){
if (!Dir.exists()){
Dir.mkdir();
}
}
private void handleRead(Message msg) {
byte[] readBuf = (byte[]) msg.obj;
String readMessage = new String(readBuf);
ByteBuffer buffer = ByteBuffer.wrap(readBuf, 0, readBuf.length);
buffer.order(ByteOrder.BIG_ENDIAN);
buffer.clear();
final String[] strNumbers = readMessage.split("\n");
for (int j = 1; j <= strNumbers.length - 2; j++) {
pressure = Integer.parseInt(readMessage2);
MyFinalPressure = (float) (9.677 +0.831 * pressure);
// trying to store directly to internal sotrage
activity.save.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
try {
FileOutputStream fileOutputStream = new FileOutputStream(activity.file);
fileOutputStream.write((int) MyFinalPressure);
fileOutputStream.close();
Toast.makeText(activity.getApplicationContext(),"Message saved ", Toast.LENGTH_SHORT).show();
} catch (FileNotFoundException e){
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
}
}
}
It appears you are not setting the FileOutputStream to 'append' (you need to add 'true' as 2nd parameter in constructor.)
This would write over the file from the file-start every time
also your 'setOnClickListener' is INSIDE your loop. This doesn't do anything for you as far as I can tell.
I recommend always setting up UI elements in a private void setupUI() {...} method that onCreate calls. The public void onClick(View v) {buttonForSavingPresssed()} where buttonForSavingPressed(){...} is the 'logic' of your onClick() method.
This will help you clean up the class and not have stray onClickListener assignments, etc.
My guess is that either your multiple assignments is very inefficient, since clickListeners aren't cheap, or... the clickListener might not even work at all because of a timing issue (if your loop is long running and you press the button and the listener has already been swapped for a new one)
I've cleaned up your code some, There are some suggestions and some log statements that should help you figure out what is going on.
// this is inside your onCreate()
...
activity.save.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) { buttonPressed();}
});
...
// Here is where you would put your logic when the button is presssed
public void buttonPressed(){
Toast.makeText(activity.getApplicationContext(),"Button Pressed ",
Toast.LENGTH_SHORT).show();
}
// you should make 'helper' functions that consolidate separate pieces of logic like this,
// that way you can more easily track what is happening in each method.
// Plus it helps keep each method shorter for ease of understanding, etc.
public void writeToFile(float finalPressure){
Log.d(LOG_TAG // where LOG_TAG is the String name of this class
"writeToFile(float) called." );
try{
// true here for 'append'
FileOutputStream fileOutputStream = new
FileOutputStream(activity.file, true);
fileOutputStream.write((int) finalPressure);
fileOutputStream.close();
}catch (FileNotFoundException e){
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
// now back to your handleRead, is this method called async wenever
// a message is read? Then wouldn't this be called a lot? I'm lost as to why
// you had the button in here at all.
private void handleRead(Message msg) {
Log.d(LOG_TAG // where LOG_TAG is the String name of this class
"handleRead(Message) called." );
byte[] readBuf = (byte[]) msg.obj;
String readMessage = new String(readBuf);
ByteBuffer buffer = ByteBuffer.wrap(readBuf, 0, readBuf.length);
buffer.order(ByteOrder.BIG_ENDIAN);
buffer.clear();
final String[] strNumbers = readMessage.split("\n");
Log.d(LOG_TAG // where LOG_TAG is the String name of this class
"strNumbers length: " + strNumbers.length );
for (int j = 1; j <= strNumbers.length - 2; j++) {
pressure = Integer.parseInt(readMessage2);
MyFinalPressure = (float) (9.677 +0.831 * pressure);
// trying to store directly to internal sotrage
writeToFile(MyFinalPressure);
}
}
Related
I have a variable MyFinalPressure which is populated with sensor data from the sensors pressure. If MyFinalPressure is == to 4000 (4000 points or 40 sec) then stop writing to local storage.
But it looks like when I debug the code, it hits the boolean MaxPoints and is still writing. I wonder if my logic is wrong or not.
Could you please help me out.
public Boolean Store = false;
Boolean MaxPoints = false;
if (activity.Store) {
activity.writeToFile(MyFinalPressure);//MyFinalPressure is float of one dimension array or stream of array.
}
if (MyFinalPressure==4000){ //this conditon, am trying to stop wrting to local memory.
activity.MaxPoints = true;
}
FileOutputStream fileOutputStream;
//method to write into local memory.
public void writeToFile(final float MyFinalPressure) {
Log.d(TAG, "writeToFile.");
String finalData;
finalData = String.valueOf(MyFinalPressure);
try {
// true here for 'append'
fileOutputStream = new FileOutputStream(file, true);
String Space = " ";
byte[] convert = Space.getBytes();
fileOutputStream.write(finalData.getBytes());
fileOutputStream.write(convert);
fileOutputStream.flush();
fileOutputStream.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
//write to file.
StartWriting = (ToggleButton) findViewById(R.id.startWriting);
StartWriting.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (StartWriting.isChecked()) {
Store = true;
Toast.makeText(getApplicationContext(), "Data Starts writing into (Message.txt) file", Toast.LENGTH_LONG).show();
} else {
if (!StartWriting.isChecked()|| MaxPoints==true) { //here - this is wrong logic to stop writing to my file.
Toast.makeText(getApplicationContext(), "Data Stored at myAppFile", Toast.LENGTH_SHORT).show();
String finalData1;
finalData1 = String.valueOf(fileOutputStream);
Log.i(TAG, "of_writes: " + finalData1);
// Toast.makeText(getApplicationContext(), "Data_write_number: " + finalData1.length(), Toast.LENGTH_SHORT).show();
Store = false;
}
}
}
});
Just take out !StartWriting.isChecked()
Because of the above if statement StartWriting.isChecked() will always be false. Then because you are checking for "!StartWriting.isChecked()" it will always enter the statement.
This code is able to make the android device as a USB host for the hardware model. It also can read data from the hardware correctly in Main Activity. However, as soon as I moved it to another activity, everything still works but the data reading is incorrect.
For instance, I'm trying to write the data read into file. First activity is to input filename and just a button to send to another activity. The code below is in the second activity
public class Temp extends Activity {
private FileOutputStream outputStream;
public static D2xxManager ftD2xx= null;
Handler mHandler = new Handler();
FT_Device ftDev = null;
int devCount = 0;
UsbDevice device = null;
TextView Text =null;
String temp = null;
_4DPoint P = null;
int rd = 0;
byte[] byt = null;
byte[] Fdata = null;
String outp = "";
String From_Serial = "";
int Min = -1;
String fileName;
Context c;
final Runnable updateResults = new Runnable() {
#Override
public void run() {
// TODO Auto-generated method stub
Text.setText("" + Min + '\n' + temp);
}
};
public void getData(){
try {
outputStream = openFileOutput(fileName, Context.MODE_PRIVATE);
byt = new byte[256];//{(byte)'a','b','c','d',};
Toast.makeText(getBaseContext(), "start " + fileName , Toast.LENGTH_LONG).show();
Text = (TextView)findViewById(R.id.test2);
device = (UsbDevice) getIntent().getParcelableExtra("USB");
ftD2xx = D2xxManager.getInstance(c);
ftD2xx.addUsbDevice(device);
devCount = ftD2xx.createDeviceInfoList(c);
if (devCount > 0) {
ftDev = ftD2xx.openByUsbDevice(c, device);
}
if( ftDev.isOpen() == true ) {
ftDev.setBitMode((byte)0 , D2xxManager.FT_BITMODE_RESET);
ftDev.setBaudRate(38400);
ftDev.setDataCharacteristics(D2xxManager.FT_DATA_BITS_8, D2xxManager.FT_STOP_BITS_1, D2xxManager.FT_PARITY_NONE);
ftDev.setFlowControl(D2xxManager.FT_FLOW_NONE, (byte) 0x0b, (byte) 0x0d);
Thread t = new Thread() {
public void run() {
int i;
while(true){
rd=0;
while (rd==0){
rd = ftDev.read(byt, 14);
}
for(i=0; i<rd; i++)
outp += (char)byt[i];
From_Serial = new String(outp);
P = new _4DPoint(From_Serial);
temp = String.format("%s: %f %f %f %f %d\n", From_Serial, P.R, P.G, P.B, P.L, P.camera);
try {
outputStream.write(temp.getBytes());
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
outp = "";
mHandler.post(updateResults);
}
}
};
t.start();
}
} catch (FileNotFoundException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
} catch (D2xxException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_color);
// Show the Up button in the action bar.
setupActionBar();
Intent intent = getIntent();
fileName = intent.getStringExtra("File Name");
c = this;
getData();
}
The set up should be fine since it's reading data from hardware, but the data read is incorrect.
Also, I'm wondering why we need to create new thread while reading data. I tried not creating new thread and it didn't work well, but still have no idea why? I tried to contact the person who wrote the code to read data but no reply.
Any help would be really appreciated :)
You state that you receive data, therefor I think you should look at your ftDev settings. Try for example to set ftDev.setBaudRate(115200) (this worked for me) or try playing with your other ftDev Settings a little bit.
The settings I use in my programm are:
int baudRate = 115200;
byte stopBit = 1; /*1:1stop bits, 2:2 stop bits*/
byte dataBit = 8; /*8:8bit, 7: 7bit*/
byte parity = 0; /* 0: none, 1: odd, 2: even, 3: mark, 4: space*/
byte flowControl = 1; /*0:none, 1: flow control(CTS,RTS)*/
If this won't work, it is wise to first check this data communication with a computer program e.g. or to analyse the incomming 'wrong' data.
I have an application that saves data to a file called 'sensorLog.txt'. I am not sure where exactly this is stored but I know this is only accessible by the applicationand it is in the internal memory.
I want to be able to write a copy the current file to an external storage when I click on a button "export". I have pasted a small bit of my program, But i am not sure how to copy sensorLog.txt file to the external storage.
public class MainActivity extends Activity {
private static final String DEBUG_TAG = "MainActivity";
private Button buttonStartService;
private Button buttonStopService;
private Button buttonSettings;
private Button buttonExport;
private TextView textStatus;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Context context = getApplicationContext();
setContentView(R.layout.activity_main);
buttonStartService = (Button) findViewById(R.id.button_start_service);
buttonStopService = (Button) findViewById(R.id.button_stop_service);
buttonSettings = (Button) findViewById(R.id.button_settings);
buttonExport = (Button) findViewById(R.id.button_export);
textStatus = (TextView) findViewById(R.id.text_status);
buttonStartService.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
startSensorService();
}
});
buttonStopService.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
stopSensorService();
}
});
//export button listener
buttonExport.setOnClickListener(export_handler);
}
public void startSensorService() {
// Schedule
AlarmManager scheduler = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(getApplicationContext(), SensorService.class);
PendingIntent scheduledIntent = PendingIntent.getService(getApplicationContext(), 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
// 30 seconds
long interval = 30 * 1000;
scheduler.setInexactRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), interval, scheduledIntent);
Log.d(DEBUG_TAG, "Service started");
}
public void stopSensorService() {
// Cancel
AlarmManager scheduler = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(this, SensorService.class);
PendingIntent scheduledIntent = PendingIntent.getService(getApplicationContext(), 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
scheduler.cancel(scheduledIntent);
Log.d(DEBUG_TAG, "Service stopped");
}
View.OnClickListener export_handler = new View.OnClickListener() {
public void onClick(View v)
{
// Here is the part I am not sure what to do. I want to copy a file sensorLog.txt that has all my sensor information to sd card
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state))
{
Log.d(DEBUG_TAG, "SD card detected");
stopSensorService();
File file = new File(Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_DOCUMENTS),"SensorLog.txt");
// delete file from the internal storage once exported
context.deleteFile("SensorLog.txt");
startSensorService();
}
else
{
Log.d(DEBUG_TAG, "No external storage detected(cannot copy file)");
}
}
};
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
}
Part where I create sensorLog.txt***(I dont think it is necessary to read for this question but just in case someone needs it)*:
private class SensorServiceLoggerTask extends AsyncTask<SensorFrame, Void, Void> {
#Override
protected Void doInBackground(SensorFrame... frames) {
SensorFrame frame = frames[0];
BufferedWriter bufWr = null;
try {
File file = new File(getApplicationContext().getFilesDir(), "SensorLog.txt");
if (file.exists()) {
// Write to new file
bufWr = new BufferedWriter(new FileWriter(file, true));
} else {
file.createNewFile();
Log.d(DEBUG_TAG, "New log file created");
// Append to existing file
bufWr = new BufferedWriter(new FileWriter(file, false));
// Write header
bufWr.append(sensorHeader.toString());
}
// Write frame
bufWr.append(sensorFrame.toString());
bufWr.flush();
Log.d(DEBUG_TAG, "Added frame to log");
} catch (IOException ex) {
// TODO: useful error handling
} finally {
// Cleanup
if (bufWr != null) {
try {
bufWr.close();
} catch (IOException ex) {
// TODO: useful error handling
}
}
}
return null;
}
}
I also have 2 more queries:
Lets say I want to append some information at the top of the file just before moving it how can I do that?
My aim is to transfer the sensorLog.txt file from internal to external storage when the export button is pressed. delete or empty the internal sensorLog.txt file and then the same thing happens again if i press export again, then I would have to rename my file when I export it right? would there not be a name clash? How do I handle that? could I give a name dynamically?
Thank you.
EDIT: Some corrections
View.OnClickListener export_handler = new View.OnClickListener() {
public void onClick(View v)
{
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state))
{
Log.d(DEBUG_TAG, "SD card detected");
stopSensorService();
Log.d(DEBUG_TAG, "stopSensorService for file transfer");
//make the timestamp the file name
long TS = System.currentTimeMillis();
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(TS);
stringBuilder.append(".txt");
String file_name = stringBuilder.toString();
//file name stored in file_name
File file_ext = new File(Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_DOCUMENTS),file_name);
// attempt to create this new directory
//read from sensorLog.txt file
try
{
file_ext.createNewFile();
File file = getBaseContext().getFileStreamPath("sensorLog.txt");
if(file.exists())
{
FileInputStream read_file = openFileInput("sensorLog.txt");
//read contents of internal file
InputStreamReader inputStreamReader = new InputStreamReader(read_file);
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
StringBuilder sb = new StringBuilder();
sb.append("Timestamp of export to SD"+TS+"/n");
String line;
while ((line = bufferedReader.readLine()) != null) {
sb.append(line);
}
BufferedWriter bufWr = null;
bufWr = new BufferedWriter(new FileWriter(file_ext, false));
// Write header
bufWr.append(sb.toString());
inputStreamReader.close();
bufWr.close();
read_file.close();
//delete sensor file once exported
getApplicationContext().deleteFile("sensorLog.txt");
}
}
catch(Exception e){}
But for some reason my file is not getting stored in the SD card.
Check out the Android documentation. If you can read your sensorLog.txt file, then you can save it in a String and then save the string to a file in the external storage.
In a ListView of sound files in a folder I want to show the length in seconds of the files. The steps that I take:
First I create an ArrayList for the instances of the soundFiles.
Then in a for loop I add the data to the instance by soundFile.setLength(calculateLength(file[i])).
After this I initiate my CustomArrayAdapter and I apply it to my listView.
In my CustomArrayAdapter I apply it: tvFileLength.setText(soundFile.getLength()); (whith a holder though..)
But since I am doing this, my app is slower than a turtle! (having 400 files)
Is there any way I can fix this speed?
private int calculateLength(File yourFile)
throws IllegalArgumentException, IllegalStateException, IOException {
MediaPlayer mp = new MediaPlayer();
FileInputStream fs;
FileDescriptor fd;
fs = new FileInputStream(yourFile);
fd = fs.getFD();
mp.setDataSource(fd);
mp.prepare();
int length = mp.getDuration();
length = length / 1000;
mp.release();
return length;
}
**EDIT**
New code I am having:
Activity
myList = new ArrayList<RecordedFile>();
File directory = Environment.getExternalStorageDirectory();
file = new File(directory + "/test/");
File list[] = file.listFiles();
for (int i = 0; i < list.length; i++) {
if (checkExtension(list[i].getName()) == true) {
RecordedFile q = new RecordedFile();
q.setTitle(list[i].getName());
q.setFileSize(readableFileSize(list[i].length()));
//above is the size in kB, is something else but I
//also might move this to the AsyncTask!
myList.add(q);
}
}
new GetAudioFilesLength(myList).execute();
AsyncTask
List<RecordedFile> mFiles = new ArrayList<RecordedFile>();
public GetAudioFilesLength(List<RecordedFile> theFiles) {
mFiles = theFiles;
}
#Override
protected String doInBackground(Void... params) {
File directory = Environment.getExternalStorageDirectory();
// File file = new File(directory + "/test/");
String mid = "/test/";
for (RecordedFile fileIn : mFiles) {
File file = new File(directory + mid + fileIn.getTitle());
try {
int length = readableFileLengthSeconds(file);
fileIn.setFileLengthSeconds(length);
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalStateException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// Do something with the length
// You might want to update the UI with the length of this file
// with onProgressUpdate so that you display the length of the files
// in real time as you process them.
}
return mid;
}
#Override
protected void onProgressUpdate(Void... values) {
}
#Override
protected void onPostExecute(String result) {
// Update the UI in any way you want. You might want
// to store the file lengths somewhere and then update the UI
// with them here
}
/*
* #Override protected void onPreExecute() { }
*/
public int readableFileLengthSeconds(File yourFile)
throws IllegalArgumentException, IllegalStateException, IOException {
MediaPlayer mp = new MediaPlayer();
FileInputStream fs;
FileDescriptor fd;
fs = new FileInputStream(yourFile);
fd = fs.getFD();
mp.setDataSource(fd);
mp.prepare(); // might be optional
int length = mp.getDuration();
length = length / 1000;
mp.release();
return length;
}
Awesome, it works partly, but! I got 2 remaining questions:
Does this looks ok and efficient?
It works for lets say the first 100 elements in my listview, after that it displays 0 s, it has something to do with onProgressUpdate I assume, but I am not sure how I can make this work.
Reading the files in so that MediaPlayer can find the duration is clearly taking some time. Since you are running this on the UI thread, that's going to slow down the entire application.
I don't have any suggestions for how to speed up the process, but you can make your application behave much more smoothly if you do this work in a background thread with AsyncTask. That might look something like this:
private class GetAudioFilesLength extends AsyncTask<Void, Void, Void> {
List<File> mFiles = new ArrayList<File>();
public GetAudioFilesLength(List<File> theFiles){
mFiles = theFiles;
}
#Override
protected String doInBackground(String... params) {
for(File file : mFiles){
int length = calculateLength(file);
// Do something with the length
// You might want to update the UI with the length of this file
// with onProgressUpdate so that you display the length of the files
// in real time as you process them.
}
}
#Override
protected void onPostExecute(String result) {
// Update the UI in any way you want. You might want
// to store the file lengths somewhere and then update the UI
// with them here
}
#Override
protected void onPreExecute() {
}
#Override
protected void onProgressUpdate(Void... values) {
}
}
When you want to start the processing, just call new GetAudioFilesLength(files).execute()
Edit to answer additional questions:
It looks as efficient as your original code. The difference now is that the user will still be able to interact with your application because the work will be done in the background thread. It is possible that there is a more efficient way to read in the length of an audio file, but I don't know what that is. If you knew the sample rate and encoding, I can imagine you could write code that would calculate the length of the audio without loading it into MediaPlayer, which takes longer. Again, though, someone else would have to help with that.
I'm not sure I understand what the problem is, but I think you are asking how to use onProgressUpdate to update the UI and add the lengths to a ListView?
You could change the middle argument to the AsyncTask generation to be a String (or something else) AsyncTask<Void, String, Void>, that tells onProgressUpdate what you will be passing to it. You can then callpublishProgress` from doInBackground to update the UI accordingly.
#Override
protected String doInBackground(Void... params) {
for(File file : mFiles){
int length = calculateLength(file);
// Do something with the length
// You might want to update the UI with the length of this file
// with onProgressUpdate so that you display the length of the files
// in real time as you process them.
publishProgress("The length of " + file.getName() + " is: " + length);
}
}
#Override
protected void onPostExecute(Void result) {
// Update the UI in any way you want. You might want
// to store the file lengths somewhere and then update the UI
// with them here
}
#Override
protected void onProgressUpdate(String... values) {
// You'll have to implement whatever you'd like to do with this string on the UI
doSomethingWithListView(values[0]);
}
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