4

I want to use Chrome custom tabs to properly handle URLs out of my domain. Here is the code

webView.setWebViewClient(new WebViewClient() {
    public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request){
        String url = request.getUrl().toString();
        if(url.startWith("http://my.domain.name"))
          return false;
        else{
            CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
            builder.setToolbarColor(getResources().getColor(R.color.colorPrimary));
            builder.setStartAnimations(getActivity(), R.anim.slide_in_right, R.anim.slide_out_left);
            builder.setExitAnimations(getActivity(), R.anim.slide_in_left, R.anim.slide_out_right);
            Intent actionIntent = new Intent(
                                getApplicationContext(), ActionBroadcastReceiver.class);
            actionIntent.setData(Uri.parse(url));
            PendingIntent menuItemPendingIntent =
                                PendingIntent.getBroadcast(getApplicationContext(), 0, actionIntent, 0);
            builder.addMenuItem(getString(R.string.action_share), menuItemPendingIntent);
            CustomTabsIntent customTabsIntent = builder.build();
            customTabsIntent.launchUrl(getActivity(), Uri.parse(url));

            return true;
        }
    }
});

However when I click on the out-of-domain URLs, occasionally I get ANR dialog and UI of the app is freezing.

I try to debug the anr traces but I found no suspect thread. The ANR traces file is quite long, so I posted it here: https://gist.github.com/hoavt-54/42f1109c0619eed81e82a9a8d1128a6d

If you have any suggestion on how to debug the app or how to understand the trace file, I would really appreciate that.

Hoa Vu
  • 2,865
  • 4
  • 25
  • 33
  • running on ui thread? – TWL Jul 22 '17 at 23:48
  • @TWL: I'm pretty sure no, but it could be – Hoa Vu Jul 22 '17 at 23:49
  • see https://stackoverflow.com/questions/11411022/how-to-check-if-current-thread-is-not-main-thread – TWL Jul 22 '17 at 23:51
  • even if shouldOverrideUrlLoading run on UI thread, it should not take more than 5 seconds which triggers ANR dialog. – Hoa Vu Jul 22 '17 at 23:54
  • Hoa: ANR happens only on the main (ui) thread. Never do any network call on the UI thread, absolutely none. – Amir Uval Jul 27 '17 at 21:55
  • @HoaVu : If you could refer to my answer, I have intercepted and wrote a sw-toolbox parallel implementation with cache builder, so have decent enough knowledge on handling Webviews in Android. – Anukalp Katyal Aug 01 '17 at 15:53

3 Answers3

3

The 5 seconds rule is not relevant - Even 1 second blocking the main thread will appear to the user as a stuck app.

The amount of work the system does in the background and in the foreground for your app all the time is huge, and it's all being done on the main thread. So when you don't return immediately from any callback (onXyz()) - the whole world is blocking waiting for you, nothing will be drawn, no touch event will arrive, etc.

So never do any network call on the main thread. It will always cause ANR at least on some devices some of the time, for example when their network is off.

Never do any of these on the main thread:

  • read/write to the local file system, including properties and database
  • heavy calculations
  • network
  • long running/busy loops of any kind

All the above will cause ANR's randomly for users.

Amir Uval
  • 14,425
  • 4
  • 50
  • 74
2

I would suggest you take an advantage of StrictMode atleast in Debug mode. Use below code to get logs of any issue which slows down your App on main thread.

StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
            .detectAll()
            .penaltyLog()
            .build());

You can set different penalties -

penaltyLog() // to print log
penaltyDeath() // This will crash you App(so costly penalty)
penaltyDialog() // Show alert when something went lazy on Main thread

There is so much about https://developer.android.com/reference/android/os/StrictMode.html

Rahul
  • 10,457
  • 4
  • 35
  • 55
0

As per your code you are overriding Webview url to move to custom chrome tabs.

As per my understanding of webview it gives a callback in chrome thread, so ideally you should run your code in activity using callback and handler with activity main thread.

   @Override
        public void retainOldWebView() {
            if (mmtWebViewPop != null) {
                mHandler.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        if (mmtWebViewPop != null) {
                            webViewFrameLayout.removeView(mmtWebViewPop);
                            mmtWebViewPop = null;
                        }
  //TODO: write your own implementation here
                    }
                }, 100);
            }
        }

To debug webview I would suggest to verify logs with chromium tags or while doing such experiments enable remote debugging

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
    WebView.setWebContentsDebuggingEnabled(true);
}

I as a developer would not want to mix up the implementation of both in one single activity, even if I want to I would rather create separate fragments for customChromeTabs and WebView and replace fragments whenever necessary. Because both of them work as a separate entity and shouldn't mix up the implementation for both.

Please share the code or implement something like below :-

  1. WebViewActivity intercept requests and if url pattern doesn't match give a callback and start new ChromeTabActivity with url passed in intent bundle.
  2. ChromeTabActivity where your code for customChromeTab should be present and work independently of each other and finish to resume webview from where you left of.
  3. But I never used ChromeTabActivity for the similar cases, I have a use cases where fb and google login weren't working for me so I opened the same in another webview in frameLayout, which is working fine for my usecases. As soon as the pageload is completed I call this function.

            if (null != mmtWebView) {
                mmtWebView.setWebChromeClient(new WebChromeClientImpl(getApplicationContext()));
            }
    

class WebChromeClientImpl extends WebChromeClient {

private final Context appContext;

public WebChromeClientImpl(Context appContext) {
    this.appContext = appContext;
}

@Override
public boolean onCreateWindow(WebView view, boolean isDialog, boolean isUserGesture, Message resultMsg) {
    mmtWebViewPop = new WebView(view.getContext());
    mmtWebViewPop.setVerticalScrollBarEnabled(false);
    mmtWebViewPop.setHorizontalScrollBarEnabled(false);
    mmtWebViewPop.setWebViewClient(new WebViewClientImpl(appContext, null));
    mmtWebViewPop.getSettings().setJavaScriptEnabled(true);
    mmtWebViewPop.setLayoutParams(new FrameLayout.LayoutParams(
            ViewGroup.LayoutParams.MATCH_PARENT,
            ViewGroup.LayoutParams.MATCH_PARENT));
    webViewFrameLayout.addView(mmtWebViewPop);
    WebView.WebViewTransport transport = (WebView.WebViewTransport) resultMsg.obj;
    transport.setWebView(mmtWebViewPop);
    resultMsg.sendToTarget();
    return true;
}

@Override
public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
    return super.onConsoleMessage(consoleMessage);
}

@Override
public void onCloseWindow(WebView window) {
    super.onCloseWindow(window);
    retainOldWebView();
}

}