Showing posts with label android-asynctask. Show all posts
Showing posts with label android-asynctask. Show all posts

Saturday, October 21, 2017

How to do transverse data in Handler thread

Leave a Comment

I have an activity, which contains 2 fragments, each fragment has 2 list views, each has an adapter. So I get pretty long data in json from the server, I use AsyncTask and on post execute, I parse the data. The problem is that it freezes the loading animation for 4-5 seconds.

I have tried to use Handler thread, but the problem is still there, I must be doing something terribly wrong here.

public class DataService extends AsyncTask<String, String, Void> {      @Override         protected Void doInBackground(String... params) {          url = new URL(DATA_URL);             //Connection stuff          String jsonString = stringBuilder.toString();         jsonObject = new JSONObject(jsonString);          //Adding data in Java Objects      }       @Override     protected void onPostExecute(Void aVoid) {             super.onPostExecute(aVoid);                //Upcoming List in Fragment              allDataFragment.setUpcomingDataList(UpcomingAllDataList);              awayFragment.setUpcomingDataList(tUpcomingAwayList);              homeFragment.setUpcomingDataList(tUpcomingHomeList);                //Past List in Fragment                          allDataFragment.setPastDataList(pastAllDataList);              awayFragment.setPastDataList(pastAwayList);              homeFragment.setPastDataList(pastHomeList);          } 

I added log messages in adapter, and I can see that at the time of parsing rows it freezes, so it take all the post execute stuff in handler thread

       Handler handler= new Handler();          handler.post(new Runnable() {             @Override             public void run() {                       //Upcoming List in Fragment                      allDataFragment.setUpcomingDataList(UpcomingAllDataList);                      awayFragment.setUpcomingDataList(tUpcomingAwayList);                      homeFragment.setUpcomingDataList(tUpcomingHomeList);                        //Past List in Fragment                                  allDataFragment.setPastDataList(pastAllDataList);                      awayFragment.setPastDataList(pastAwayList);                      homeFragment.setPastDataList(pastHomeList);                   }                                }); 

I also tried to add a handler in Adapter but it fails to load UI component from there.

This is a snippet from my fragment code.

if (pastListView != null) {         PastListAdapter allGamesAdapter = new PastListAdapter(getContext(), pastAllDataList);         pastListView.setAdapter(allGamesAdapter);         } 

and in Adapter i am doing following.

  @Override     public View getView(int position, View convertView, ViewGroup parent) {              TextView vScore = (TextView) v.findViewById(R.id.v_score);             TextView date = (TextView) v.findViewById(R.id.date);             ImageView image = (ImageView) v.findViewById(R.id.iv_image);             //7 Other views               vScore.setText(dataList.get(position).getScore);             date.setText(dataList.get(position).date);               Log.d(TAG, "getView: Row");              return v;     } 

The loading animation works fine in start but at the time it starts parsing the data in adapter, it hangs t he UI thread.

5 Answers

Answers 1

Use AsyncTask to handle work items shorter than 5ms ( 5 milli seconds) in duration. Since you are getting pretty long JSON data from server, you have to look at other alternatives ( Thread, HandlerThread and ThreadPoolExecutor)

Even though you create Handler, it's running on UI Thread only. Instead of Handler, use HandlerThread

Proposed solution:

  1. Use HandlerThread to get JSON data from server. UI Thread is not blocked to get the data.

  2. Post Data from HandlerThread to Handler of UI Thread. UI Handler will take care of updating UI with data received on handleMessage(Message msg)

Useful posts:

Asynctask vs Thread vs Services vs Loader

Handler vs AsyncTask vs Thread

Why to use Handlers while runOnUiThread does the same? ( This post contains code example for above proposed solution)

Answers 2

If it's blocking the ui thread then don't do it in the ui thread

You already got an asyncTask so just use it and call your parsing json function from there

Then you can return void from that task and update your adopter from there

In other words, move your logic from onPostExecute to doInBackground

Answers 3

Use Volley instead of AsyncTask

Answers 4

at the beginning, i advise you, not be a lazy, and look to libraries

Retrofit - RESTfull client library

OKhttp - http client library

You do not need to write AsyncTasks with these libraries, and this will make your life easier and painless.

But, if you won't, ok. Let's talk about AsyncTask problems and decisions of your concerns.

AsyncTask are runs on a background computation thread and whose result is published on the UI thread. They are defined by 3 generic types, called Params, Progress and Result, and 4 steps, called begin, doInBackground, progress updating and end of computations with updating UI. So, when it is executed, the task goes through 4 steps:

1 step. onPreExecute(), invoked on the UI thread immediately after the task is executed. This step is basically used to configure the task, for example showing a progress bar, progress dialog or any other changes in the UI.

2 step. doInBackground(Params...), invoked on the background thread immediately after onPreExecute() finishes executing. This step is used to perform background computation that can take a long time. The parameters of the asynchronous task are passed to this step. The result of the computation must be returned by this step and will be passed back to the last step. This step can also use publishProgress(Progress...) to publish one or more units of progress. These values are published on the UI thread, in the onProgressUpdate(Progress...) step.

3 step. onProgressUpdate(Progress...), invoked on the UI thread after a call to publishProgress(Progress...). The timing of the execution is undefined. This method is used to display any form of progress in the user interface while the background computation is still executing. For instance, it can be used to animate a progress bar or show logs in a text field.

4 step. onPostExecute(Result), invoked on the UI thread after the background computation finishes. The result of the background computation is passed to this step as a parameter.

So, as you also now, if you don't want catch performance freezing, you have to return result of the computation in 2 step.

public class DataService extends AsyncTask<String, String, List<UpcomingAllDataList>> {      @Override     protected List<UpcomingAllDataList> doInBackground(String... params) {          // making HTTP request          return UpcomingAllDataList; // return results of Http request     }      @Override     protected void onPostExecute(List<UpcomingAllDataList> upcomingAllDataList) {         super.onPostExecute(upcomingAllDataList);           //Upcoming List in Fragment         allDataFragment.setUpcomingDataList(upcomingAllDataList);          // all another work      } } 

Hope, it will be usefull.

Answers 5

you should really use Retrofit for the server call + parsing.
Retrofit Github

Retrofit Site

I think the real problem is that you parse more data in setUpcomingDataList (and all the others) methods.

anyway, your example for HandlerThread is actually happening in the UI thread since Handler's default constructor takes the current thread that you construct the Handler on (which in your case is the UI thread) and post the Runnable to that thread.

anyway 2 - to see if it the methods would really work in a different thread (i guess you will get an android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views. )
you can try to wrap your code with normal thread:

Thread thread = new Thread(new Runnable() {                 @Override                 public void run() {                     //Upcoming List in Fragment                     allDataFragment.setUpcomingDataList(UpcomingAllDataList);                     awayFragment.setUpcomingDataList(tUpcomingAwayList);                     homeFragment.setUpcomingDataList(tUpcomingHomeList);                       //Past List in Fragment                                 allDataFragment.setPastDataList(pastAllDataList);                     awayFragment.setPastDataList(pastAwayList);                     homeFragment.setPastDataList(pastHomeList);                 }             });             thread.start(); 

which is a really bad practice - so as I said before, use Retrofit.

Read More

Monday, September 25, 2017

Could not launch intent within 45 seconds. Perhaps the main thread has not gone idle within a reasonable amount of time?

Leave a Comment

I am writing Espresso Test. Here is the test case

OnClick of Fab, the app launches QuizActivity.

Let me explain to you little about my app.

So the app requirement was -

  1. There is JSON file in assets folder
  2. I have to parse it and store the data in database
  3. On Main Activity load this data from database into recyclerview. There is Fab button, on click of it app pass a random list of data (which I have loaded in recyclerview) to QuizActivity

Here is how I did coding -

  1. In MainActivity's onCreate() used AsyncTask to parse and insert data into database only once.
  2. Once data is available, loaded it in recyclerview via AsyncTaskLoader
  3. set Onclick listener on Fab. and passed required data to QuizActivity.

Below is my test

@RunWith(AndroidJUnit4.class) public class ExampleInstrumentedTest {    /* @Rule     public IntentsTestRule<MainActivity> intentsTestRule =             new IntentsTestRule<MainActivity>(MainActivity.class); */      @Rule     public ActivityTestRule<MainActivity> intentsTestRule =             new ActivityTestRule<MainActivity>(MainActivity.class,false,true);      @Test     public void fabTest(){         onView(withId(R.id.fab)).perform(click());         //Check if the text_question textview is displayed         onView(withId(R.id.text_question)).check(matches(isDisplayed()));     }     /* @Test     public void clickFloatingActionButton() {         onView(withId(R.id.fab))                 .perform(click());         intended(hasComponent(new ComponentName(getTargetContext(), QuizActivity.class)));     }*/ } 

My approach is -

  1. Find Fab
  2. Perform click
  3. Check if the text_question textview is displayed. Note this textview is on QuizActivity.

After I run the test I am getting

java.lang.RuntimeException: Could not launch intent Intent { act=android.intent.action.MAIN flg=0x14000000 cmp=my_package_name/.MainActivity } within 45 seconds. Perhaps the main thread has not gone idle within a reasonable amount of time? There could be an animation or something constantly repainting the screen. Or the activity is doing network calls on creation? See the threaddump logs. For your reference the last time the event queue was idle before your activity launch request was 1505287862579 and now the last time the queue went idle was: 1505287862579. If these numbers are the same your activity might be hogging the event queue.

PS - I have turned off all the animations. I don't have progress bar in code.

Also one more note , if I comment out the AsyncTask, AsyncTaskLoader, RecyclerView part in onCreate() method then the test is passing.

I suspect that it might be causing due to background task.

Anyone faced similar problem? Please let me know if you know the solution. I am struggling from two days. I searched for similar threads on stackoverflow but nothing worked for me.

1 Answers

Answers 1

I finally got the solution!

Actually, the culprit was custom view in my adapter.

Espresso waits until the AsyncTask or AsyncTaskLoader loads the data, but it looks like custom view causing this problem as stated in the exception :

the main thread has not gone idle within a reasonable amount of time? There could be an animation or something constantly repainting the screen.

Anyways to overcome this problem, what I need to do is -

"If the test is running set the visibility of Custom view to GONE"

How would you do that?

 private AtomicBoolean isTestRunning;      public synchronized boolean isTestRunning () {         if (null == isTestRunning) {             boolean istest;              try {                  Class.forName ("Full path of your TestName"); // for e.g. com.example.MyTest                  istest = true;             } catch (ClassNotFoundException e) {                 istest = false;             }              isTestRunning = new AtomicBoolean (istest);         }          return isRunningTest.get ();     } 

Now here I am using Recyclerview so in ViewHolder,

public InsectHolder(View itemView)         {             super(itemView);                        customView = (DangerLevelView) itemView.findViewById(R.id.danger_level_tv);              if(isRunningTest()) {                 customView.setVisibility(View.GONE);             }         } 

And yay! your test will be passed.

Hope someone finds it helpful!

Please let me know if you have any questions in comment section below!

Read More

Tuesday, March 8, 2016

Camera not overriding old image if orientation changes

Leave a Comment

I have a RecyclerView adapter with many different ViewHolders. One of the ViewHolders contains an ImageView, which needs to be able to take a picture, resize it, then display it. For modularity, I want the ViewHolder to be self-contained: it and not the parent activity should handle everything concerning the photo taking and displaying process. Also the file path is constant (it will never change). In fact, it is /storage/emulated/0/com.company.app/myst/cat.jpg. As a result, here is my implementation of the ImageView’s onClick method.

@Override public void onClick(View v) {     final FragmentManager fm = ((MyActivity) getContext()).getSupportFragmentManager();     Fragment auxiliary = new Fragment() {         @Override         public void onActivityResult(int requestCode, int resultCode, Intent data) {             resizeResaveAndDisplayPhoto();             super.onActivityResult(requestCode, resultCode, data);             fm.beginTransaction().remove(this).commit();         }     };     fm.beginTransaction().add(auxiliary, "FRAGMENT_TAG").commit();     fm.executePendingTransactions();      Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);     if (null != takePictureIntent.resolveActivity(view.getContext().getPackageManager())) {         ((MyActivity)view.getContext()).setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);         takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(photoFile));          auxFragment.startActivityForResult(takePictureIntent, Constants.REQUEST_CODE_PHOTO);     } } 

When resizeResaveAndDisplayPhoto is called it executes the following AsyncTask

public static class ResizeThenLoadImageTask extends AsyncTask<String, Void, Bitmap> {      private final WeakReference<ImageView> imageViewWeakReference;     private final WeakReference<File> fileWeakReference;     private final WeakReference<Context> weakContext;     private final int reqHeight;     private final int reqWidth;      public ResizeThenLoadImageTask(Context context, ImageView imageView, File file, int reqHeight, int reqWidth) {         weakContext = new WeakReference<Context>(context);         imageViewWeakReference = new WeakReference<>(imageView);         fileWeakReference = new WeakReference(file);         this.reqHeight = reqHeight;         this.reqWidth = reqWidth;     }      @Override     public Bitmap doInBackground(String... params) {         File file = fileWeakReference.get();         Bitmap bitmap = null;         if (null != file) {             bitmap = ImageUtils.reduceImageSize(file, reqHeight, reqWidth);             ImageUtils.saveBitmapToGivenFile(bitmap, file);         }         return bitmap;     }      @Override     public void onPostExecute(Bitmap bitmap) {         if (null != imageViewWeakReference && null != fileWeakReference) {             ImageView imageView = imageViewWeakReference.get();             File file = fileWeakReference.get();             if (null != imageView) {                 if (null != bitmap) {                     imageView.setImageBitmap(bitmap);                 }                 else {                     imageView.setImageResource(R.drawable.photo);                 }                 imageView.postDelayed(new Runnable() {                     @Override                     public void run() {                         if (null != weakContext.get()) {                             ((MyActivity) weakContext.get()).setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);                         }                     }                 }, 10000);             }         }     } } 

You may notice that I lock the orientation before taking the photo and unlock it 10 seconds after displaying the photo. That trick is part of my troubleshooting. So here is the situation. The system described above works very well. Problems happen in the following case

  • Say I already have a photo in the ImageView but want to replace it.
  • So I click on the ImageView to take a new photo.
  • If I rotate the device to take the new photo, then when I return the new photo displays briefly before the old photo returns.
  • So I lock to orientation to see what was happening. Here is what I found.
  • The new photo displays for as long as I lock the orientation. As soon as the orientation unlocks (10 sec), the old photo returns.
  • If I leave the activity and the returns, the old photo is still displaying.
  • If I close the app completely and then return, then I see the new photo.

1 Answers

Answers 1

When the device is rotated, the running activity is recreated as well the asyncTask attached to it, probably causing a leak.

You probably need to call the asyncTask inside of a service instead in an activity so the asyncTask is going to be attached to the life cycle of the service.

Read More