2

I have some fragments loaded in a ViewPager, where each "page" is loaded from a row in a cursor. Each fragment shows an image (JPEG) on the device. When the user dismisses the fragment (i.e swipe/page change, hits back/up, or just closes the app entirely) I want to invoke a method which opens the JPEG file for writing and does an update of its metadata. The actual work is eventually handled by the Apache Commons Imaging library.

I've implemented this by invoking my saveToFile() method from each fragment's life cycle onStop() handler. Does this mean the entire file operation ends up running on the UI thread? Should I definitely set up an AsyncTask for this?

Say the file write for some reason suddenly (for some jpeg) should take a long time, eg 2 minutes. What would then happen? Would the UI just wait (freeze) at this page/fragment before resuming? Or would the process (write to file) carry on "in the background" somehow? Or would the process just be killed, stopped short mid-process?

The way I have this wired up currently (onStop invoking saveToFile(), which calls up the imaging library and then updates the file) seems to work as it should. Even if I end the app, I still see my Toast text popping up, saying "Writing to file..." Seemingly, the process is never disturbed, and I can't say I'm experiencing any UI lag.

joakimk
  • 822
  • 9
  • 26
  • Yes all lifecycle methods of Activity & Fragment are executed on main thread (aka UI thread). You can confirm it by the fact that you can play with Views in these methods. – Sufian Jul 07 '17 at 12:51

1 Answers1

2

onStop() handler. Does this mean the entire file operation ends up running on the UI thread? Should I definitely set up an AsyncTask for this?

YES

An AsyncTask has several parts: a doInBackground method that does, in fact, run on a separate thread and the onPostExecute method that runs on the UI thread.

You can also use some sort of observer pattern such as EventBus to run async and post results to the UI.


Say the file write for some reason suddenly (for some jpeg) should take a long time, eg 2 minutes. What would then happen? Would the UI just wait (freeze)

The application will crash because Android will forcefully close it due to ANR (Application Not Responding).

Refer to the official documentation for details on this: https://developer.android.com/training/articles/perf-anr.html

Android applications normally run entirely on a single thread by default the "UI thread" or "main thread"). This means anything your application is doing in the UI thread that takes a long time to complete can trigger the ANR dialog because your application is not giving itself a chance to handle the input event or intent broadcasts.

Therefore, any method that runs in the UI thread should do as little work as possible on that thread. In particular, activities should do as little as possible to set up in key life-cycle methods such as onCreate() and onResume(). Potentially long running operations such as network or database operations, or computationally expensive calculations such as resizing bitmaps should be done in a worker thread (or in the case of databases operations, via an asynchronous request).

The most effective way to create a worker thread for longer operations is with the AsyncTask class.

Here is what I recommend though. Use the above mentioned, EventBus and create a BaseActivity which will automatically save the data for you onClose() by firing an event that runs Async. You then extend that base activity in all the places where you need autosave capabilities.

Here's what I mean with an example that uses EventBus.

public abstract class BaseActivity extends Activity{

 @Override
 protected void onResume(){
   if(!EventBus.getDefault().isRegistered(this))
       EventBus.getDefault().register(this);
   super.onResume();
 }

 @Override
 protected void onDestroy() {
   if(EventBus.getDefault().isRegistered(this))
     EventBus.getDefault().unregister(this);
   super.onDestroy();
 }

 @Override
 protected void onStop() {
     super.onStop();
     //We fire event and pass the current parent class that inherited this base.
     EventBus.getDefault().post(new EventBusProcessMySaveData(this.getClass()));
 }
}

//Your model class to use with EventBus
public final class EventBusProcessMySaveData{
     private final Class className;

     public EventBusProcessMySaveData(final Class className){
        this.className = className;
     }

     public Class getClassName(){
        return this.className;
     }
}


public class MyMainActivity extends BaseActivity{
  //Do you standard setup here onCreate() and such...

    //Handle Event for Saving Operation, async.
    //This will fire everytime theres an onClose() IN ANY activity that
    //extends BaseActivity, but will only process if the class names match.
    @Subscribe(threadMode = ThreadMode.ASYNC)
    public void methodNameDoesNotReallyMatterHere(final EventBusProcessMySaveData model){
        //We make sure this is the intended receiving end by comparing current class name
        //with received class name.
        if(model.getClassName().equals(this.getClass())){
          //Do whatever you need to do that's CPUintensive here.
        }
    }

}
Dayan
  • 7,634
  • 11
  • 49
  • 76
  • Right, thanks. However, an AsyncTask is described as a class with which "to perform long-running tasks that result in updates to the GUI." In my case, I just want to update the file in the background, after the user has, in effect, finished with the UI. When the task does not produce a change in the UI, should I still use AsyncTask or perhaps some other" worker thread"? – joakimk Jul 07 '17 at 13:20
  • "When the task does not produce a change in the UI, should I still use AsyncTask or perhaps some other" worker thread"? " - If you don't need to talk to the UI then just spin up a new background thread or a worker thread. Refer to the docs again - https://developer.android.com/guide/components/processes-and-threads.html – Dayan Jul 07 '17 at 13:39