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 executor
to 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); } }
0 comments:
Post a Comment