Avoid restarting threads on orientation change - android

There are some threads in my activity's oncreate() method. When the orientation changes the threads are restarting again (new instance of threads gets created on every orientation change).
I don't want to use android:configChanges or android:screenOrientation. Because the Activity is orientation dependent.

Use android:configChanges, but in overrided onConfigurationChanged() method only call super.onConfigurationCanged() method (or don't override it, generally).
In time of rotation onCreate() not be called and your treads not be restarted, but your layout will be rotate.

I am using this approach:
I have a field in the activity which stores the thread. In onRetainNonConfigurationInstance() answer this field. It gets saved this way and it is available to the fresh instance of the activity later.
In onStart() I get the thread from getLastNonConfigurationInstance(). This is either null (thread not jet started) or its the reference to the thread saved by onRetainNonConfigurationInstance().
If you are showing (and need to restore) a progress dialog you should also have a state in the thread (e.g. STARTED, RUNNING, DONE, and so on) to handle restoring the progress display in onStart().
If you need to communicate with the thread you might want to inject a handler (e.g. as parameter to the thread's constructor).
Here is an example. The thread reads GPS data from a database for later post-processing. I tried to only show the relevant code here, method names of omitted methods should speak for themselfes,
This is all from the activity class:
private ProgressDialog progressDialog = null;
private LoadGpsDataThread loadGpsLogThread = null;
This is the handler used to communicate:
/**
* This handler updates the progress dialog when the logged GPS data is loaded.
*/
final Handler progressHandler = new Handler() {
#Override
public void handleMessage(final Message msg) {
Bundle b;
switch( msg.arg2 ) {
case UPDATE_LOADER:
// Update from GPS data loading thread
final int total = msg.arg1;
if( GpsPostprocessingActivity.this.progressDialog != null )
GpsPostprocessingActivity.this.progressDialog.setProgress(total);
if( GpsPostprocessingActivity.this.loadGpsLogThread != null && GpsPostprocessingActivity.this.loadGpsLogThread.state == STATE_DONE ) {
GpsPostprocessingActivity.this.dismissProgress();
GpsPostprocessingActivity.this.fillGraphView();
}
break;
case IGpsDataPostProccessor.STATUS_ANALYZER:
GpsPostprocessingActivity.this.statusView.setText(msg.arg1);
break;
case IGpsDataPostProccessor.UPDATE_ANALYZER:
int sample;
switch( msg.arg1 ) {
// ...
}
break;
case IGpsDataPostProccessor.GRAPH_UPDATE:
GpsPostprocessingActivity.this.fillGraphView();
break;
}
break;
}
}
};
Here is the method, which starts the thread, note the handler as constructor parameter:
/**
* Load the GPS data from the database.
* #param loading if <code>true</code> the load thread is already
* running. In this case only the progress dialog is opened.
*/
private void loadGpsData(final boolean loading) {
if( DEBUG )
Log.d( TAG, "loadGpsData: Loading GPS data, already loading = " + loading);
final int dataSize = this.gpsFlight.size();
final String title = this.globalState.getString(R.string.titel_load_gps_data);
final String msg = this.globalState.getFormattedTemplate(R.string.msg_tmpl_loading_gps_data, this.flightDesc);
this.showProgress(title, msg, dataSize);
if( ! loading ) {
this.loadGpsLogThread = new LoadGpsDataThread(this.progressHandler);
this.loadGpsLogThread.start();
}
}
#Override
public Object onRetainNonConfigurationInstance() {
// Dialog is removed in onSaveInstanceState(), see comment there
// Check that there is a worker thread that
// needs preserving
if (this.loadGpsLogThread != null) {
// remove reference to this activity (important to avoid memory leak)
this.loadGpsLogThread.handler = null;
// Return the instance to be retained
if( DEBUG )
Log.d( TAG, "onRetainNonConfigurationInstance: saved process");
return this.loadGpsLogThread;
}
return super.onRetainNonConfigurationInstance();
}
Here is the start logic:
#Override
protected void onStart() {
if( DEBUG )
Log.d(TAG, "onStart");
super.onStart();
this.refreshData();
this.flightView.setText(this.flightDesc);
this.logView.setText(this.getGpsLogDescription());
this.statusView.setText(null);
this.initProfileSpinner();
// graphView is set asynchronously by the GPS data loading thread
// Get the last load thread and check whether it is still running
if (this.getLastNonConfigurationInstance() != null) {
this.loadGpsLogThread = (LoadGpsDataThread) this.getLastNonConfigurationInstance();
this.loadGpsLogThread.handler = this.progressHandler;
switch (this.loadGpsLogThread.state) {
case STATE_RUNNING:
// Show the progress dialog again
this.loadGpsData(true);
break;
case STATE_NOT_STARTED:
// Close the progress dialog in case it is open
this.dismissDialog(PROGRESS_DIALOG);
break;
case STATE_DONE:
this.loadGpsLogThread = null;
// Close the progress dialog in case it is open
this.dismissDialog(PROGRESS_DIALOG);
break;
default:
// Close the progress dialog in case it is open
// Get rid of the sending thread
if( DEBUG )
Log.d(TAG, "Unknown progress thread state");
this.dismissProgress();
}
}
else {
if( ! this.globalState.detectorState.isGpsDataCacheAvailable(this.gpsFlight) ) {
this.loadGpsData(false);
this.analysisResult = null;
}
else
// data already loaded
this.fillGraphView();
}
this.graphView.setShowLines(this.globalState.getBooleanPref(IPreferences.PREFS_GPS_GRAPH_LINES));
this.processSubActivityResult();
}
This is the thread as inner class:
/**
* This thread loads the GPS data from the database and
* updates the progress dialog via the handler.
*/
private class LoadGpsDataThread extends Thread {
Handler handler;
int state;
int stepsDone;
LoadGpsDataThread(final Handler h) {
this.handler = h;
this.state = STATE_NOT_STARTED;
}
#Override
public void run() {
this.state = STATE_RUNNING;
this.stepsDone = 0;
final Cursor c = GpsPostprocessingActivity.this.queryGpsData();
try {
while (c.moveToNext() && (this.state == STATE_RUNNING)) {
final TrackData row = GpsPostprocessingActivity.this.globalState.getDb().readGpsData(c);
GpsPostprocessingActivity.this.globalState.detectorState.gpsData[this.stepsDone] = row;
this.stepsDone += 1;
if( this.handler != null ) {
// can be null if the activity has been destroyed
final Message msg = this.handler.obtainMessage();
msg.arg1 = this.stepsDone;
msg.arg2 = UPDATE_LOADER;
this.handler.sendMessage(msg);
}
}
}
finally {
this.state = STATE_DONE;
c.close();
}
if( DEBUG )
Log.d(TAG, "Data load thread finished");
}
}

Related

How to load a ListView using AsyncTask in android

I have a method called fetchData() to fetch some data from the database and load those to a ListView. But when the activity starts there is a small lag because of this. So I need to load the data in background. I was wondering if anyone could tell me how to do this using AsyncTask.
This is my fetchData() method.
public void fetchData() {
database = helper.getReadableDatabase();
Cursor c;
Date cDate = new Date();
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd");
final String fDate = sdf.format(cDate);
int thisMonth=Integer.parseInt(fDate.split("-")[1]);
Month mn=new Month();
String month=mn.getMonth(thisMonth);
Calendar cal=Calendar.getInstance();
int today=Integer.parseInt(fDate.split("-")[2]);
int curTab=position;
String whereClause="";
String sort="";
if(curTab==0){
whereClause=null;
sort=Database.NAME;
}
else if(curTab==1){
whereClause=Database.MONTH+" = '"+month+"' and "+Database.DAY+" ="+today;
sort=Database.NAME;
}
else if(curTab==2){
cal.add(Calendar.DAY_OF_MONTH, 1);
int monthn=cal.get(Calendar.MONTH)+1;
Month mnN=new Month();
String monthTomorrow=mnN.getMonth(monthn);
int tomorrow=cal.get(Calendar.DAY_OF_MONTH);
whereClause=Database.MONTH+" = '"+monthTomorrow+"' and "+Database.DAY+" ="+tomorrow;
sort=Database.DAY;
}
else if(curTab==3){
whereClause=Database.MONTH+" = '"+month+"'";
sort=Database.DAY;
}
if(DrawerMain.pos==1){
if(curTab==0){
whereClause=Database.TYPE+"='birthday'";
}
else{
whereClause=whereClause+" and "+Database.TYPE+"='birthday'";
}
}
else if(DrawerMain.pos==2){
if(curTab==0){
whereClause=Database.TYPE+"='anniversary'";
}
else{
whereClause=whereClause+" and "+Database.TYPE+"='anniversary'";
}
}
c = database.query(Database.TABLE_EVENT, null, whereClause, null, null, null, sort);
String[] fromDB={Database.NAME,Database.MONTH,Database.DAY};
int[] toView={R.id.tvName_lv,R.id.tv_month_lv,R.id.tv_day_lv};
CustomCursorAdapter adapter=new CustomCursorAdapter(getActivity(), c, 0, R.layout.events_list_item,fromDB,toView);
lv.setAdapter(adapter);
database.close();
}
You should consider using AsyncTaskLoader instead. AsyncLoaders will handle orientation changes better than AsyncTasks.
You can find a tutorial here: http://www.androiddesignpatterns.com/2012/08/implementing-loaders.html
Code (copied directly from the tutorial)
public class SampleLoader extends AsyncTaskLoader<List<SampleItem>> {
// We hold a reference to the Loader’s data here.
private List<SampleItem> mData;
public SampleLoader(Context ctx) {
// Loaders may be used across multiple Activitys (assuming they aren't
// bound to the LoaderManager), so NEVER hold a reference to the context
// directly. Doing so will cause you to leak an entire Activity's context.
// The superclass constructor will store a reference to the Application
// Context instead, and can be retrieved with a call to getContext().
super(ctx);
}
/****************************************************/
/** (1) A task that performs the asynchronous load **/
/****************************************************/
#Override
public List<SampleItem> loadInBackground() {
// This method is called on a background thread and should generate a
// new set of data to be delivered back to the client.
List<SampleItem> data = new ArrayList<SampleItem>();
// TODO: Perform the query here and add the results to 'data'.
return data;
}
/********************************************************/
/** (2) Deliver the results to the registered listener **/
/********************************************************/
#Override
public void deliverResult(List<SampleItem> data) {
if (isReset()) {
// The Loader has been reset; ignore the result and invalidate the data.
releaseResources(data);
return;
}
// Hold a reference to the old data so it doesn't get garbage collected.
// We must protect it until the new data has been delivered.
List<SampleItem> oldData = mData;
mData = data;
if (isStarted()) {
// If the Loader is in a started state, deliver the results to the
// client. The superclass method does this for us.
super.deliverResult(data);
}
// Invalidate the old data as we don't need it any more.
if (oldData != null && oldData != data) {
releaseResources(oldData);
}
}
/*********************************************************/
/** (3) Implement the Loader’s state-dependent behavior **/
/*********************************************************/
#Override
protected void onStartLoading() {
if (mData != null) {
// Deliver any previously loaded data immediately.
deliverResult(mData);
}
// Begin monitoring the underlying data source.
if (mObserver == null) {
mObserver = new SampleObserver();
// TODO: register the observer
}
if (takeContentChanged() || mData == null) {
// When the observer detects a change, it should call onContentChanged()
// on the Loader, which will cause the next call to takeContentChanged()
// to return true. If this is ever the case (or if the current data is
// null), we force a new load.
forceLoad();
}
}
#Override
protected void onStopLoading() {
// The Loader is in a stopped state, so we should attempt to cancel the
// current load (if there is one).
cancelLoad();
// Note that we leave the observer as is. Loaders in a stopped state
// should still monitor the data source for changes so that the Loader
// will know to force a new load if it is ever started again.
}
#Override
protected void onReset() {
// Ensure the loader has been stopped.
onStopLoading();
// At this point we can release the resources associated with 'mData'.
if (mData != null) {
releaseResources(mData);
mData = null;
}
// The Loader is being reset, so we should stop monitoring for changes.
if (mObserver != null) {
// TODO: unregister the observer
mObserver = null;
}
}
#Override
public void onCanceled(List<SampleItem> data) {
// Attempt to cancel the current asynchronous load.
super.onCanceled(data);
// The load has been canceled, so we should release the resources
// associated with 'data'.
releaseResources(data);
}
private void releaseResources(List<SampleItem> data) {
// For a simple List, there is nothing to do. For something like a Cursor, we
// would close it in this method. All resources associated with the Loader
// should be released here.
}
/*********************************************************************/
/** (4) Observer which receives notifications when the data changes **/
/*********************************************************************/
// NOTE: Implementing an observer is outside the scope of this post (this example
// uses a made-up "SampleObserver" to illustrate when/where the observer should
// be initialized).
// The observer could be anything so long as it is able to detect content changes
// and report them to the loader with a call to onContentChanged(). For example,
// if you were writing a Loader which loads a list of all installed applications
// on the device, the observer could be a BroadcastReceiver that listens for the
// ACTION_PACKAGE_ADDED intent, and calls onContentChanged() on the particular
// Loader whenever the receiver detects that a new application has been installed.
// Please don’t hesitate to leave a comment if you still find this confusing! :)
private SampleObserver mObserver;
}
Put your fetchData() method in loadInBackground(). Close your Cursor in the releaseResources() method. In your onCreate() call
getLoaderManager().initLoader(0, null, this);

Android Multiple download pause resume in listview with progress update

I am trying to download multiple files in listview with progressbar. What I achieved is, I can start a particular download, pause/resume it using AsyncTask and progress bar is updated(for single file), this part works well
My problem is I am not able to download multiple files simultaneously and when I leave the listview to another screen although my download is going on in the background but progress is not updated, progress bar shows 0 progress as if it is not downloading but its been downloading in the background.
Finally I found the answer which was much simpler than I thought, here it is as follows
Create a service having Asynctask for downloading and hashtable of values(url, Asynctask)
Pass the value(url, Asynctask) when a list item is clicked and check whether that hashtable contain the value already if yes cancel that Asynctask task if no add it to hashtable and start Asynctask
now for updating progress in my adapter I ran a thread which iterate over hashtable and passes the value using BroadcastListener.
And in activity intercept the broadcast and depending on the ListItem visible update the progress
PS: If anybody needs some code I can provide basic code of the description explained above
public class DownloadingService extends Service {
public static String PROGRESS_UPDATE_ACTION = DownloadingService.class.getName() + ".progress";
private static final long INTERVAL_BROADCAST = 800;
private long mLastUpdate = 0;
private Hashtable<String, DownloadFile> downloadTable;
private LocalBroadcastManager broadcastManager;
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
MessageEntity entityRecieved = (MessageEntity) intent.getSerializableExtra("ENTITY");
queueDownload(entityRecieved);
return super.onStartCommand(intent, flags, startId);
}
private void queueDownload(MessageEntity entityRecieved){
if (downloadTable.containsKey(entityRecieved.getPacketID())) {
DownloadFile downloadFile = downloadTable.get(entityRecieved.getPacketID());
if (downloadFile.isCancelled()) {
downloadFile = new DownloadFile(entityRecieved);
downloadTable.put(entityRecieved.getPacketID(), downloadFile);
startDownloadFileTask(downloadFile);
} else {
downloadFile.cancel(true);
downloadTable.remove(entityRecieved.getPacketID());
}
} else {
DownloadFile downloadFile = new DownloadFile(entityRecieved);
downloadTable.put(entityRecieved.getPacketID(), downloadFile);
startDownloadFileTask(downloadFile);
}
}
#Override
public void onCreate() {
super.onCreate();
downloadTable = new Hashtable<String, DownloadFile>();
broadcastManager = LocalBroadcastManager.getInstance(this);
}
#TargetApi(Build.VERSION_CODES.HONEYCOMB)
void startDownloadFileTask(DownloadFile asyncTask) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
asyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
else
asyncTask.execute();
}
private void publishCurrentProgressOneShot(boolean forced) {
if (forced || System.currentTimeMillis() - mLastUpdate > INTERVAL_BROADCAST) {
mLastUpdate = System.currentTimeMillis();
int[] progresses = new int[downloadTable.size()];
String[] packetIds = new String[downloadTable.size()];
int index = 0;
Enumeration<String> enumKey = downloadTable.keys();
while (enumKey.hasMoreElements()) {
String key = enumKey.nextElement();
int val = downloadTable.get(key).progress;
progresses[index] = val;
packetIds[index++] = key;
}
Intent i = new Intent();
i.setAction(PROGRESS_UPDATE_ACTION);
i.putExtra("packetIds", packetIds);
i.putExtra("progress", progresses);
mBroadcastManager.sendBroadcast(i);
}
class DownloadFile extends AsyncTask<Void, Integer, Void> {
private MessageEntity entity;
private File file;
private int progress;
public DownloadFile(MessageEntity entity) {
this.entity = entity;
}
#Override
protected Void doInBackground(Void... arg0) {
String filename = entity.getMediaURL().substring(entity.getMediaURL().lastIndexOf('/') + 1);
file = new File(FileUtil.getAppStorageDir().getPath(), filename);
downloadFile(entity.getMediaURL(), file);
return null;
}
public String downloadFile(String download_file_path, File file) {
int downloadedSize = 0;
int totalSize = 0;
try {
// download the file here
while ((bufferLength = inputStream.read(buffer)) > 0 && !isCancelled()) {
progress = percentage;
publishCurrentProgressOneShot(true);
}
} catch (final Exception e) {
return null;
}
return file.getPath();
}
}
On big problem with AsynchTask is when you finish its activity, AsynchTask looses it's track with your UI. After that when you return back to that activity the progressBar is not updating even if the download progress still running in background. In fact that AsynchTask is not belong to the new Activity you lunched so the new instance of progress bar in new Activity will not updating.
To fix this problem I suggest you:
1- Run a thread with a timerTask in onResume() which updates ur progressbar with values updating from the AsyncTask running background. Something like this:
private void updateProgressBar(){
Runnable runnable = new updateProgress();
background = new Thread(runnable);
background.start();
}
public class updateProgress implements Runnable {
public void run() {
while(Thread.currentThread()==background)
try {
Thread.sleep(1000);
Message msg = new Message();
progress = getProgressPercentage();
handler.sendMessage(msg);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (Exception e) {
}
}
}
private Handler handler = new Handler(){
#Override
public void handleMessage(Message msg) {
progress.setProgress(msg.what);
}
};
and when your activity is not visible you must destroy the thread:
private void destroyRunningThreads()
{
if(background!=null)
{
background.interrupt();
background=null;
}
}
2- Define a global static boolean variable. Set it true in onPreExecute and in onPostExecute set it to false. It shows that you are downloading or not, so you can check if the variable is equal to true, show the previous progressbar dialog.(you can do something like this with an integer value-or array of integers- in order to show the update percentage for each download progress).
3- The last way I personally used is to show the download progress in Notification Bar and in my list view I just show that it is downloading right now or not.(using 2nd method with a boolean values). In this way even if you finish the activity the notification bar is still updated with download progress.
when you leave your activity, the activity that asynctask shows the progressbar is killed and thus the progressBar dose not show anymore when you come back on new activity because the asynctask dose not aware of your new activity.
General solution that will work in any cases for example when your user closes your app and again opens it and wants to know the progressBar is separating your presentation completely. that means you can create sharedPreferences or database table and put your state of your file in to it while your asynctask is downloading. for example every 500 milisecond update the sharedPreferences or database table with how much downloaded from total file size. then when user come back to your new activity you read from DB or sharedPreferences to show progressBar and update it every for example 1000 milisecond. In this way your user will know the progressBar even if he closes the app and opens it again. I know it takes a bit more work but it surely makes your users be happy.
in order to read and update at fixed rate you can use scheduleAtFixedRate

If I finish an activity and call it again, it's attribute values still remain inside thread (RunOnUiThread)

In this case, I have 2 activities. I'm on Activity 1 and go to Activity 2. The application works as intended.
The problem starts when I go back to Activity 1, and start Activity 2 again.
See code below:
public class ScreenWActivity extends SerialComActivity {
private static final String tag = "ScreenWActivity";
private TextView mReception, m_tvDate, mtvPesoPercent, mtvState;
public String mCommand = null;
public int mActualProcess, mNextProcess;
private Commands mLastCommand;
public SettingsGlobal mSettings;
public int mAttempts = 0;
public long mStartTime, mTimeout;
#Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_W);
this.mSettings = new SettingsGlobal(this); // get global settings
this.mtvState = (TextView) findViewById(R.id.tv_state); // label to update the current state
startSerialConnection(); // open serial port and start connection. inherited from SerialComActivity (the upper class)
this.mTimeout = 10; // timeout for commands response in seconds
this.mNextProcess = 1; // the next step in the process, its updated in the stepN() methods
this.mActualProcess = 1; // current step in the processo
this.mLastCommand = Commands.OPEN_DOOR; // the last command I've sent, to know what to expect in return
this.executeWorkflow(mNextProcess); // starts the workflow
}
private void step1(){
this.mtvState.setText("Closing door."); // update status
this.writeSerial(Commands.OPEN_DOOR.command("080").getBytes()); // sends the command to the outputstream, the external device reads the command, execute it and respond back
this.mNextProcess = 2; // the next step in the process is 2
this.mActualProcess = 1; // just tracking
this.mLastCommand = Commands.OPEN_DOOR;
startCounting(); // starts the timout, I've sent the command, now I wait for an answer
}
private void step2(){
this.mtvState.setText("Testando peso das balanças 1.");
this.writeSerial(Commands.GET_W.command().getBytes()); // get weight from weighing-machine
mLastCommand = Commands.GET_W; // the last command i sent i requested the weight - now I know what to expect
mNextProcess = 3; // next step in the sequence in case everything goes according to plan
this.mActualProcess = 2; // tracking
startCounting(); // starting timeout to get an answer
}
private void step3(){...}
private void step4(){...}
private void step5(){...}
private void step6(){...}
#Override
protected void writeSerial(byte[] buffer){...}
public void startCounting(){
mStartTime = System.currentTimeMillis();
timerHandler.postDelayed(timerRunnable, 0);
}
public void stopCounting(){
timerHandler.removeCallbacks(timerRunnable);
}
#Override
protected void onDestroy() {
// TODO Auto-generated method stub
stopCounting();
timerRunnable = null;
if(this.mSerialPort != null)
this.mSerialPort.close();
this.mSerialPort = null;
if(AppConfig.DEBUG) Log.i(tag, "finishing!");
finish();
super.onDestroy();
}
public void executeWorkflow(int step) {
switch(step){
case 1:
step1();
break;
case 2:
step2();
break;
case 3:
step3();
break;
case 4:
step4();
break;
case 5:
step5();
break;
case 6:
step6();
break;
}
}
protected boolean validateReturn(String resposta) {
/// we check the command we've sent and the response we're given. if it matches, then we return true, else false
}
// overrided from SerialComActivity, called when the external equipment sends a message to us
// ITS CALLED WHEN THERE IS INPUT FROM THE EXTERNAL EQUIPMENT
#Override
protected void onDataReceived(final byte[] buffer, final int size) {
runOnUiThread(new Runnable() {
public void run() {
stopCounting(); // we remove the callbacks from the timeout thread
if( validateReturn(new String(buffer, 0, size).trim()) ){ // we check if the response is good
executeWorkflow(mNextProcess); // if its good, we move to the next step
}else{
mtvState.setText("invalid return"); // if not we message the user
executeWorkflow(mActualProcess); // we try again
}
}
});
}
// RESPONSIBLE FOR THE TIMEOUT
// the code below was created intending to implement a timeout timer for waiting a response from the external device
Handler timerHandler = new Handler();
Runnable timerRunnable = new Runnable() {
#Override
public void run() {
long millis = System.currentTimeMillis() - mStartTime;
long seconds = (millis / 1000);
timerHandler.postDelayed(this, 500);
if(mTimeout - seconds == 0 ){
mAttempts += 1;
if(mAttempts == 3){ // we make 3 attempts to get a response, if it is the third, we quit trying and give error
mAttempts = 0;
mtvState.setText("Could not communicate.");
stopCounting(); // we end the timer
}else{
executeWorkflow(mActualProcess); // if we can still try, we send the command again
}
}
}
};
}
Inside the method onDataReceived(), which is called everytime I get a response from the external equipment, I use the attribute mLastCommand (which indicates the last command I've sent), so this way I know how to validate the response I get.
When I go back to Activity 2, in the class scope the values of the attributes are the same as the ones I've defined in the onCreate() method. In the LogCat I saw that the attributes values are correctly defined as stated in OnCreate.
BUT, when the method onDataReceived (it's inside a Thread in the SerialComActivity class) is called (which is called when I get data from outside) the value of this same attribute mLastCommand is the same as the first time I started the activity, regardless of the value I define for it. As if the the runnable inside RunOnUiThread is still holding the old values from the first time I entered the activity, and outside of it the class has the values I have defined.
It's like having two different attributes with the same name in the ScreenWActivity.
I tried nulling the attributes inside the onDestroy() method, but to no avail.
Below is the code for the SerialComActivity class:
public abstract class SerialComActivity extends Activity{
SerialPort mSerialPort = null;
protected OutputStream mOutputStream;
protected InputStream mInputStream;
protected ReadThread mReadThread;
private class ReadThread extends Thread {
#Override
public void run() {
super.run();
while(!isInterrupted()) {
int size;
try {
byte[] buffer = new byte[64];
if (mInputStream == null) return;
size = mInputStream.read(buffer);
if (size > 0) {
onDataReceived(buffer, size);
}
} catch (IOException e) {
e.printStackTrace();
return;
}
}
}
}
protected void startSerialConnection(){
try {
mSerialPort = new SerialPort(new File("/dev/ttyS2"), 38400, 0);
} catch (SecurityException e) {
// TODO Auto-generated catch block
if(AppConfig.DEBUG)
Log.e("SERIAL", "portopen ERR: " + e.getMessage());
} catch (IOException e) {
// TODO Auto-generated catch block
if(AppConfig.DEBUG)
Log.e("SERIAL", "portopen ERR: " + e.getMessage());
}
mOutputStream = mSerialPort.getOutputStream();
mInputStream = mSerialPort.getInputStream();
/* Create a receiving thread */
mReadThread = new ReadThread();
mReadThread.start();
}
protected abstract void onDataReceived(final byte[] buffer, final int size);
protected abstract void writeSerial(byte[] buffer);
#Override
protected void onDestroy() {
if (mReadThread != null){
mReadThread.interrupt();
if(AppConfig.DEBUG) Log.i("ThreadSerial", "interrupting");
}
if(mSerialPort != null)
mSerialPort.close();
mSerialPort = null;
finish();
super.onDestroy();
}
}
I'm still in the process of learning Java and Android programming, so please forgive me if I'm doing something wrong. I looked up around, and the thing that you can't use variables other than "final" inside the RunOnUiThred came up. But I think it's not the issue, since it works the first time I start the activity.
Try doing your clean up in onPause() instead of onDestroy(), onDestroy() may not be called immediately which means there may be a read conflict on SerialPort. Also if you are already in onDestroy(), calling finish() doesn't really do anything.
Lastly, for a finite resource like SerialPort connection, it's better to put it in a Service.
I'm a newbie in Java, but I think I found out what was happening. The thing is that I asked the wrong question.
The problem is in the mInputStream.read(). As I've come to know, it's a blocking operation. I'm creating a thread that stays blocked in that read() method. After I finish the Activity, (go back to the first one), the thread keeps running. I know that because when a send some information through the serial interface, that thread responds.
So what I did, and it's working for me, altough many people stated that this method is not recommended is use mInputStream.available():
try {
if (mInputStream == null){ Log.i(tag,"returning"); return null ;}
Log.i(tag,"reading");
mEmptyStream = true;
while(mEmptyStream && !mFinish){
Log.i(tag,"input while");
/// checking if there is info, so we don't block the thread
if(mInputStream.available() > 0){
Log.i(tag,"input avail : " + InputStream.available());
//stream not empty
mEmptyStream = false;
size = mInputStream.read(buffer); //
}
}
if (size > 0) {
Log.i(tag,"size > 0 = " + new String(buffer, 0, size));
return new String(buffer,0,size);
}else{
Log.i(tag,"size <= 0");
}
}
Basically I loop using available(). When I finish the activity, in the onPause() method I set the field mFinish to true, this way the thread finds it's way out of execution and can end properly. It's the way I found and it's working so far. I improved the code significantly after the original post, like not running non UI jobs in the RunOnUiThread :)
But I tested it and it's working.

Android Rotation with IntentService

I have an application that uses IntentService to run a background task where I pull data from a website, parse the data out, and create calendar events based on the results. Everything seems to be working create, except I'm running into an issue with rotation.
Using the code below, when I rotate the screen, the ProgressDialog box stays visible, but is never updated with new text when the process is updated, and never goes away once the call is completed. I'm using an IntentService instead of an ASyncTask because the user can also schedule the IntentService to run at other times without having to interface with the app. Any help is appreciated, thanks!
public void onCreate(Bundle savedInstanceState) {
Object retained = getLastNonConfigurationInstance();
if (retained instanceof CalendarHandler) {
// CH is a class level variable defined at the top which references my IntentService, aptly named CalendarHandler
ch = (CalendarHandler) retained;
ch.setActivity(this);
} else {
ch = null;
}
activity = this;
btnLogin.setOnClickListener(OnClickListener(View view) {
ch = new CalendarHandler();
ch.setActivity(MyTlc.this);
// Do other stuff, like run the intent service
}
}
public Handler handler = new Handler() {
public void handleMessage(Message message) {
// We read the information from the message and do something with it
// based on what the result code is
String result = message.getData().getString("status");
if (result.equals("ERROR")) {
activity.removeDialog(PROGRESS_DIALOG);
results.setText(message.getData().getString("error"));
} else if (result.equals("DONE")) {
activity.removeDialog(PROGRESS_DIALOG);
int count = message.getData().getInt("count", 0);
activity.results.setText("Added " + count + " shifts to the calendar");
} else {
activity.pDialog.setMessage(result);
}
super.handleMessage(message);
}
};
From what I understand, this should work, and like I said the ProgressDialog box does stay properly, I just can't seem to pass information to the dialog box after rotating.

Reference to running Thread is null

I can't get a spawned thread to stop:
I'm implementing the vibration-part of the Ringer-class in the regular Android Phone.apk (basically word for word), but after vibrating once (and stopping) correctly, the second time I call startVibration() and subsequently stopVibration(), it doesn't stop the thread (the log prints out that mVibratorThread is null, even though an instance of it is clearly still active, because the phone is vibrating :-)!)...
public volatile boolean mContinueVibrating;
public VibratorThread mVibratorThread;
private static final int VIBRATE_LENGTH = 1000; // ms
private static final int PAUSE_LENGTH = 1000; // ms
public void startVibration(){
//Start the vibration alarm
if (mVibratorThread == null) {
mContinueVibrating = true;
mVibratorThread = new VibratorThread();
Log.i(TAG, "Starting vibration...");
mVibratorThread.start();
}
}
public void stopVibration(){
//Stop the vibration alarm
Log.i(TAG, "Stopping vibration...");
if (mVibratorThread != null){
mContinueVibrating = false;
mVibratorThread = null;
Log.i(TAG, "Thread wasn't null, but is now set to null...");
} else {
Log.i(TAG, "Thread was null...");
}
}
private class VibratorThread extends Thread {
public void run() {
Vibrator mVibrator = (Vibrator) m_context.getSystemService(Context.VIBRATOR_SERVICE);
while (mContinueVibrating) {
mVibrator.vibrate(VIBRATE_LENGTH);
SystemClock.sleep(VIBRATE_LENGTH + PAUSE_LENGTH);
Log.i(TAG, "VIBRATING NOW!!" + mContinueVibrating);
}
}
}
I've already tried the method described in Where to stop/destroy threads in Android Service class?
Thanks for your help,
Nick
Please call the startVibrator and stopVibrator from a Handler
here is the tutorial for Handler http://www.tutorialforandroid.com/2009/01/using-handler-in-android.html
The issue was that the class that was referencing the Thread was being re-initiated halfway through the process, and the new instance of the class naturally had no knowledge of the thread that was initiated by its predecessor, so the reference to the thread was null. I fixed it by putting the thread in its own, singleton class.

Categories

Resources