Sunday, April 9, 2017

latch(used for awaiting async response) freezes the WebView (and the UI)

Leave a Comment

I have an app that displays a webview on the whole layout. Sometimes I need to call an async method, that async operation is done by a 3-party sdk, and then I wait for it for a while until I get the response to a designated listener. I have solved it with a latch - once the response is received in the listener, I countDown the latch and then the initiating method can continue with this response. Unfortunately, when I do this, the WebView is stuck. I imagined the native UI would be stuck , but I didn't expect the webview to get frozen as well. How can that be overcome?

To make it clearer, here is an example. I need the ajaxFunc to wait until MyAsyncListener gets a certain response, and then return this exact response.

part of JS I inject to the webview :

var response = jsHandler.ajaxFunc(request.data); 

I have a variable global variable called response.

public class JavaScriptInterface {      @JavascriptInterface      public String ajaxFunc(String data)      {          return MyUtils.handleData( data );      } } 

the handleData method :

 public String handleData( String data )  {      SomeClass.startAsyncRequest(); // starts the async request, response is to come at the listener's callbacks .       latch = new CountDownLatch(1);      try {          latch.await(30, TimeUnit.SECONDS);      }      catch (InterruptedException e) {          e.printStackTrace();      }       return response;    } 

now, once the handleData function calls a function, that does something asynchronously, the async function then returns some answer inside some Listener :

myAsyncListener = new MyAsyncListener() {     @Override         public Response handleEvent(CustomEvent event) {             //Now I need to return this 'event' back to the handData/ajaxFunc function <--               response = event.getName();             latch.countDown();          }     }); 

3 Answers

Answers 1

I need the ajaxFunc to wait until MyAsyncListener gets a certain response, and then return this exact response.

Since you are dispatching an async request, you shouldn't expect the response to arrive in the same method call you made to start the async code. If you do this, the caller thread will block until the async response arrives, that is, it won't be async at all.

You can redesign your code to something like this:

public class JavaScriptInterface {     @JavascriptInterface     public void ajaxFunc(String data) {         // draw a waiting animation or something         MyUtils.handleData(data);     } } 
public void handleData(String data) {     SomeClass.startAsyncRequest(); } 
myAsyncListener = new MyAsyncListener() {     @Override     public void handleEvent(CustomEvent event) {         // Do what you need to do         // (update UI, call javascript etc.).         //         // Mind the thread that will execute this callback         // to prevent multithreading issues.     } } 

Answers 2

You have put CountDownLatch inside your code which will cause blocking of your main thread. Thats the reason of lag on UI.

For better you should show some ProgressDialog before doing main work of Async task which generally written in doInBackground() method of Async task and hide it after completing your task, Async provide onPostExecute() method for that purpose.

If you need to put some timeout like your CountDownLatch is doing, you can write timer in doInBackground() of Async. That timer will stop your current Async will allotted time is elapsed.

Update, you could use EventBus for your purpose -

EventBus works on Publish/Subscribe pattern. You can subscribe in Activity and publish result from JavaScriptInterface.

Add it in gradle using -

compile 'org.greenrobot:eventbus:3.0.0' 

In your Activity start subscribing, generally subscriptions done in onCreate method of Activity -

EventBus.getDefault().register(this); 

Also remove subscriptions in onDestroy method of Activity or if your work is done.

EventBus.getDefault().unregister(this); 

To listen subscription use below code in your Activity -

@Subscribe(threadMode = ThreadMode.MAIN)        public void onMessageEvent(MyMessageEvent event) {      // Do your work after getting result from ajaxFunc method  }; 

You just have to post message notification from your method now and remove CountDown latch from your code if it is not needed somewhere else.

myAsyncListener = new MyAsyncListener() {     @Override         public Response handleEvent(CustomEvent event){          response = event.getName();         // Create object of message and post it         final MyMessageEvent msgEvent = new MessageEvent(response);         EventBus.getDefault().post(msgEvent);     } }); 

MyMessageEvent.class

import android.support.annotation.NonNull;  public class MyMessageEvent {      private String mMessage;      public MyMessageEvent(@NonNull final String message) {         mMessage = message;     }      public String getMessage() {         return mMessage;     }  } 

Answers 3

Android Thread Model

As JCIP introduced, many UI framework is single threaded. They use thread confinement to avoid deadlock and integrity of data. Android also use this thread model.

By default, all components of the same application run in the same process and thread (called the “main” thread).

So if you wait on main thread by using lacth, the UI will be not updated or response your action.

Background Task

But sometimes, we want to perform some long time task like using network to download something and read from db. In order to make UI responsive, we should perform those jobs in another thread (Notice: not in the Service, which default still run on the main thread).

Java Way

So you may use thread or executorto fetch your data, then read data back into main thread like following method when some event happens:

Activity.runOnUiThread(Runnable) 

Android Way

Or you may use Android native AsyncTask which is conveinent. For example, when you want to check the username/password of user, you have to connect to your server to verify it. In such scenario, you can put the request and verification process into a AsyncTask and the UI thread can make some animation when there is no response. Following is some code:

public class UserLoginTask extends AsyncTask<Void, Void, Boolean> {      private final String mName;     private final String mPassword;      UserLoginTask(String name, String password) {         mName = name;         mPassword = password;     }      // backgroud thread     @Override     protected Boolean doInBackground(Void... params) {         // network access.          for (String credential : CREDENTIALS) {             String[] pieces = credential.split(":");             if (pieces[0].equals(mName)) {                 // Account exists, return true if the password matches.                 return pieces[1].equals(mPassword);             }         }          // register the new account here.         return true;     }      // UI thread     @Override     protected void onPostExecute(final Boolean success) {         mAuthTask = null;         showProgress(false);          if (success) {             // start next             finish();             Intent intent = new Intent(LoginActivity.this, Drawer.class);             startActivity(intent);         } else {             mPasswordView.setError(getString( R.string.error_incorrect_password ));             mPasswordView.requestFocus();         }     }      @Override     protected void onCancelled() {         mAuthTask = null;         showProgress(false);     } } 

Ref:

If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment