Android WebView onPageCommitVisible not fired - android

I have a WebView, which loads an html page from server, but the page is invisible on the WebView.
The html is loaded properly (I've debugged with chrome://inspect and the html, including all javascripts exist), but it is invisible on the phone screen.
There was no changes in my code, when this bug appeared. The bug appeared when I installed updates to Android System WebView on my phone.
If I uninstall the updates, all works properly again.
In addition, I've checked the callbacks of the WebViewClient and noticed that onPageCommitVisible is not called. So somehow, the page is not loaded properly. Only if I press the Back button, to exit the WebView, I see that the onPageCommitVisible is called for my webpage (buat it doesn't help, as the back button exists the WebView, as expected).
Here is my code for the webview:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
RelativeLayout rlMain = new RelativeLayout(getActivity());
rlMain.setContentDescription(Constants.STARTAPP_AD_MAIN_LAYOUT_CONTENT_DESCRIPTION);
rlMain.setId(Constants.MAIN_LAYOUT_ID);
getActivity().setContentView(rlMain);
// Create WebView and set its parameters
try{
webView = new WebView(getActivity().getApplicationContext());
webView.setBackgroundColor(0xFF000000);
getActivity().getWindow().getDecorView().findViewById(android.R.id.content).setBackgroundColor(0x00777777);
webView.setVerticalScrollBarEnabled(false);
webView.setHorizontalScrollBarEnabled(false);
webView.getSettings().setJavaScriptEnabled(true);
webView.setWebChromeClient(new WebChromeClient());
// set software acceleration
if (softwareAcceleration) {
ApiUtil.setWebViewLayerTypeSoftware(webView, null);
}
webView.setOnLongClickListener(new OnLongClickListener() {
#Override
public boolean onLongClick(View v) {
return true;
}
});
webView.setLongClickable(false);
webView.addJavascriptInterface(createJsInterface(), Constants.INTERFACE);
setWebViewSpecificParameters(webView);
webView.loadDataWithBaseURL("http://www.xxxxxx.com", getHtml(), "text/html", "utf-8", null);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
WebView.setWebContentsDebuggingEnabled(true);
}
webView.setWebViewClient(new MyWebViewClient());
RelativeLayout.LayoutParams webviewPrms = new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.MATCH_PARENT,
RelativeLayout.LayoutParams.MATCH_PARENT
);
rlMain.addView(webView, webviewPrms);
}
public void setWebViewSpecificParameters(final WebView webView) {
webView.setOnTouchListener(new View.OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
return (event.getAction() == MotionEvent.ACTION_MOVE);
}
});
}
private class MyWebViewClient extends WebViewClient {
#Override
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
Logger.log(TAG, Log.DEBUG, "!!!!!shouldInterceptRequest" );
return super.shouldInterceptRequest(view, url);
}
#Override
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
Logger.log(TAG, Log.DEBUG, "!!!!!shouldInterceptRequest" );
return super.shouldInterceptRequest(view, request);
}
#Override
public void onPageFinished(WebView view, String url) {
setWebViewBackground(view);
runJavascript(Constants.JAVASCRIPT_SET_MODE_SERVER, getPosition());
runJavascript(Constants.JAVASCRIPT_ENABLE_SCHEME, "externalLinks");
InterstitialMode.this.onWebviewPageFinished();
}
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
return clicked(url);
}
}

In an app that navigates from one website to another based on user input, there's a high chance (>50%) that the second website won't display at all after calling WebView.loadUrl and the previous website stays visible - it's even interactive, i.e. scrolling works. The problem is usually resolved after calling WebView.loadUrl again. There's no obvious indication of the bug occurring other than the user not seeing the second website. Relying on the user to reload the page manually is not a satisfying solution since the bug occurs quite often.
I was able to workaround this issue by using a custom WebViewClient to detect that the second website was not loaded properly and triggering a reload:
setWebViewClient(new WebViewClient() {
#Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
buggyWebViewHandler.postDelayed(new Runnable() {
#Override
public void run() {
if (!wasCommitCalled) {
loadUrl(url);
}
}
}, 2500);
}
}
#Override
public void onPageCommitVisible(WebView view, String url) {
wasCommitCalled = true;
}
});
Where buggyWebViewHandler is a Handler or any other class that allows deferring a piece of code for some time. Additionally, you'll need to set wasCommitCalled = false; whenever WebView.loadUrl is called - for example by overriding the method.
Note that this only works for Android 23 onwards because that's when onPageCommitVisible was added. See here for a full implementation: https://github.com/TomTasche/OpenDocument.droid/blob/8c2eec5c57e5962e9ac4c46549be2241b259eb32/app/src/main/java/at/tomtasche/reader/ui/widget/PageView.java#L72-L96
If anyone is brave enough to dig deeper into why this is happening: while debugging it seemed that onPageStarted is not called whenever this bug occurs. Maybe that helps...

Related

WebView doesn't show content after onPause

I have a WebView inside a RecyclerView
I configured the WebViewClient to run onPuase() when page finished loading.
The problem is that some websites (like IMDB) are not viewed, unless I scroll the page down/up, or if the page in stored in cache.
Not working code:
getWebview().setWebViewClient(new WebViewClient() {
#Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
view.onPause();
}
});
If I delay the onPause, it works (delay time differs between different devices)
getWebview().setWebViewClient(new WebViewClient() {
#Override
public void onPageFinished(final WebView view, String url) {
super.onPageFinished(view, url);
getWebview().postDelayed(new Runnable() {
#Override
public void run() {
getWebview().onPause();
}
}, 5000);
}
});
I also tried getWebview().postInvalidateDelayed() and getWebview().requestLayout().
Is there anyway to force the webview to display the loaded content, or simulate whatever happens when I scroll the page?
I use Lollipop with Android System WebView 43.0.2357.121
If you try to debug or put some logs in onPagefinshed() method, you will come to know that Webview's onPauuse() will call 2-3 times before site the
loads completely in case of URL redirecting.

WebView shouldOverrideUrlLoading() not called for invalid links

There are two types of links in the HTML file:
(1) A normal link like http://www.bbb.com/q?type=normal
(2) A short link like /q?type=short.
For the first kind, just load the url. For the second kind, I should prepend it with a fixed address like http://www.abc.com before loading the url.
I am trying to do this with overriding the shouldOverrideUrlLoading() function in WebViewClient. However this function doesn't gets called for the second type of link. I tried prepending the "http://www.abc.com" to the second type of links in the HTML file. Then the function does get called when I click the second kind of link.
I think what's happening is WebView will first check if the link is a valid url. Only if it is valid will the function gets called. Am I right? How can I solve this? Thanks in advance.
contentWebView = new WebView(context);
webViewClient = new WebViewClient() {
#Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);
}
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
// String not in Logger.
Log.d(TAG, "Here!");
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
context.startActivity(intent);
return true;
}
#Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
if (hosted) {
contentWebView.setVisibility(VISIBLE);
} else {
summaryTextView.setVisibility(VISIBLE);
articleLinkButton.setVisibility(VISIBLE);
}
progressBar.setVisibility(View.GONE);
}
};
contentWebView.setWebViewClient(webViewClient);
contentWebView.getSettings().setJavaScriptEnabled(true);
contentWebView.loadData(fullString, "text/html", "utf-8");
contentWebView.setVisibility(GONE);
More on this:
I tried changing
contentWebView.loadData(fullString, "text/html", "utf-8");
to
contentWebView.loadDataWithBaseURL("http://www.abc.com", fullString, "text/html", "utf-8", null);
Then the function gets called.
If I change the short link to a full link in the html string manually. Then the function also gets called.
So I think this is probably what is happening: The WebView checks if the link URL is valid. Only when the URL is valid will the shouldOverrideUrlLoading() be called.
You're probably using the KitKat WebView. This is a known issue (I think it's outlined in the migration guide) where URLs that can't be resolved against the base URL are dropped on the floor (you won't get any callbacks for them, neither shouldOverrideUrlLoading nor onPageStarted).
The problem is that your base URL is a data url, so you're trying to resolve '/q?type=short' against 'data:text/html,...' which doesn't make much sense and so the whole attempt to navigate to the URL gets ignored.
This was different for the pre-KK WebView which used KURL instead of GURL for URL processing. GURL is generally more strict (and more secure) than KURL, which is the cause for some incompatibility between the two WebView versions.
Maybe try using onPageStarted method
solution that worked for me was to use loadDataWithBaseURL with an invalid baseUrl and detect that and remove it and replace with "http://" during setWebViewClient
public class MyActivity
extends Activity
{
private static final String badurl = "http://myappname.invalid/";
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
...
WebView wv = ((WebView)findViewById(R.id.webview));
WebSettings settings = wv.getSettings();
settings.setJavaScriptEnabled(false);
settings.setSupportMultipleWindows(true);
wv.setWebChromeClient(new WebChromeClient() {
#Override
public boolean onCreateWindow(WebView view, boolean isDialog, boolean isUserGesture, Message resultMsg)
{
handleUrlview.getHitTestResult().getExtra());
return true;
}
});
wv.setWebViewClient(new WebViewClient() {
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url)
{
handleUrl(url);
return true;
}
});
wv.loadDataWithBaseURL(badurl,text,"text/html","utf-8",null);
}
private void handleUrl(String url)
{
if (url.startsWith(badurl))
url = "http://"+url.substring(badurl.length());
try {
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
} catch (ActivityNotFoundException e) { }
}
}
i faced this problem too and solved it by replacing my html response. In my html response there is no any host in "href" html tags. Then i replaced it following codes and thats working like a charm now :)
String htmlString = AppCache.homePageResponse.showcaase.replace("href=\"/", "href=\"" + "evidea://" );
I found that if your page runs in an iframe, clicking on external (http://www...) links does NOT trigger shouldOverrideUrlLoading() !
See shouldOverrideUrlLoading() not called for external links from iframe
Try this
private static WebView webView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); //should be activity_main
webView = (WebView) findViewById(R.id.web);
webView.setWebViewClient(new webviewclient());
webView.getSettings().setJavaScriptEnabled(true);
webView.loadUrl("http://www.yahoo.com");
}
public class webviewclient extends WebViewClient{
#Override
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
view.loadUrl(request.toString());
return true;
}

WebView back history without redirects

I implemented android webview and onKeyDown method for back key. (It implements webview.goBack();)
My problem is exactly similar to the question in this post below (no answers there)
How to control the Android WebView history/back stack?
PROBLEM - When I press back button, webview selects the previous URL, but if that URL was actually a redirect, it goes into this vicious cycle/loop. If you look at chrome or stock browser it correctly handles the back without going back to the redirects.
How can this be solved?
Example: go to gap.com. Then select "My Gap Credit Card". This opens a redirect link and then the final page. Now when I click back, it never goes to Gap.com home page.
Any suggestions...
Additional Information: I did implement the shouldOverrideUrlLoading. If I remove that method, it seems to work fine but with this method it does not...
I've just tested this on jellybean and it seems to work.
Essentially, whenever a new URL is loaded in the WebView keep a copy of the url.
On the next URL request, double check they we aren't already on this page, if they are, then go back in the webview history another step.
Essentially this is relying on the url passed into the override step being the redirected url, rather than the final redirected url.
public class MainActivity extends Activity {
private Button mRefreshButton;
private WebView mWebView;
private String mCurrentUrl;
public void onCreate(Bundle savedInstance) {
super.onCreate(savedInstance);
setContentView(R.layout.main);
mWebView = (WebView) findViewById(R.id.webview);
mRefreshButton = (Button) findViewById(R.id.refresh);
mRefreshButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
mWebView.reload();
}
});
WebSettings webSettings = mWebView.getSettings();
webSettings.setJavaScriptEnabled(true);
mWebView.setWebViewClient(new WebViewClient() {
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if(mCurrentUrl != null && url != null && url.equals(mCurrentUrl)) {
mWebView.goBack();
return true;
}
view.loadUrl(url);
mCurrentUrl = url;
return true;
}
});
mWebView.loadUrl("http://www.gap.com/");
}
#Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if(event.getAction() == KeyEvent.ACTION_DOWN) {
switch(keyCode) {
case KeyEvent.KEYCODE_BACK:
if(mWebView.canGoBack()){
mWebView.goBack();
return true;
}
break;
}
}
return super.onKeyDown(keyCode, event);
}
}
I hope this answer if anyone is still looking for it.I had been hunting to fix similar issues in my project and had tried multiple approaches like using
- WebView.HitTestResult
- Pushing the urls into the list
- onKeyDown and so on...
I think most of it would work if your app consists of just webview. But my project had a combination of native and webview and handles some native schema.
Essentially found that the key is how you override the method shouldOverrideUrlLoading. Since i wanted my app to handles some of the urls and the webview to handle some of the other ones especially the back handling.I used a flag for back presses something like ..
#Override
public void onBackPressed() {
if (mWebView.canGoBack()) {
mClient.setIsBackPressed(true);
//mClient is an instance of the MyWebviewClient
mWebView.goBack();
} else {
super.onBackPressed();
}
}
public class MyWebviewClient extends WebViewClient {
private Boolean isBackPressed = false;
public void setIsBackPressed(Boolean isBackPressed) {
this.isBackPressed = isBackPressed;
}
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (isBackPressed){
return false;
}
else {
// handle the url by implementing your logic
return true;
}
}
#Override
public void onPageFinished(WebView view, String url) {
isBackPressed = false;
super.onPageFinished(view, url);
}
}
In this way, whenever there is a redirect when you click back, then it return false and hence mocks the behaviour of the webview. At the same time, you make sure that the isBackPressed is set to false after the page finishes loading.
Hope this helps !!

Android webview capture link clicks

The application i'm developing has a webview, i need to capture webview request click events(eg: click on a link, click on a button, click on a youtube video play button such as...)
This can be done
you have to set a WebViewClient to your WebView. this is how to do that.
WebView webView;//make sure to initialize
webView.setWebViewClient(webViewClient);
WebViewClient webViewClient= new WebViewClient(){
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url){
return true;
}
#Override
public void onLoadResource(WebView view, String url){
if( url.equals("http://yoururl.com") ){
// do something
}
}
}
use this code it's work for me
webview.setWebViewClient(new WebViewClient()
{
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url)
{
return true;
}
});
You can do something like this:
WebView myWebView = (WebView) findViewById(R.id.yourWebView);
myWebView.setWebViewClient(new MyWebViewClient());
myWebView.loadUrl("yourLink");
but then don't forget to create a WebViewClient:
public class MyWebViewClient extends WebViewClient {
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
return true;
}
}
When you handle links in your app, WebViewClient callbacks such as shouldOverrideUrlLoading and doUpdateVisitedHistory may not present consistent or correct values. In such cases, #android.webkit.JavascriptInterface is your friend. The idea is to write javascript click handlers (in the content) that pass information to your app through JavascriptInterface methods.
Here's an example of javascript for capturing the scrollY at the source of an internal jump to a #-anchor. Once in doUpdateVisitedHistory, it is generally too late to have this information available.
if (typeof AndroidCode != "undefined") {
var coll = document.getElementsByTagName("a");
for (let i = 0; i < coll.length; i++) {
var href = coll[i].getAttribute("href");
if (href.indexOf("#") >= 0) {
coll[i].addEventListener("click", function() {
AndroidCode.reportInternalJump();
});
}
}
}
AndroidCode is the name of the object that is declared through WebView.addJavascriptInterface.
Here's another useful example of this technique, though it is not directly related to links. It will report to OnPageFinished that the page is fully loaded (useful for instance when complex formatting is taking place and you don't want to setScrollY until lines have finished moving around).
if (typeof AndroidCode != "undefined") {
AndroidCode.setLoadedStatus(false);
window.onload = function () {
AndroidCode.setLoadedStatus(true);
}
}

Clicking URLs opens default browser

I have loaded an external URL in my WebView. Now what I need is that when the user clicks on the links on the page loaded, it has to work like a normal browser and open the link in the same WebView. But it's opening the default browser and loading the page there?
I have enabled JavaScript. But still it's not working. Have I forgotten something?
If you're using a WebView you'll have to intercept the clicks yourself if you don't want the default Android behaviour.
You can monitor events in a WebView using a WebViewClient. The method you want is shouldOverrideUrlLoading(). This allows you to perform your own action when a particular URL is selected.
You set the WebViewClient of your WebView using the setWebViewClient() method.
If you look at the WebView sample in the SDK there's an example which does just what you want. It's as simple as:
private class HelloWebViewClient extends WebViewClient {
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url);
return true;
}
}
in some cases you might need an override of onLoadResource if you get a redirect which doesn't trigger the url loading method. in this case i tried the following:
#Override
public void onLoadResource(WebView view, String url)
{
if (url.equals("http://redirectexample.com"))
{
//do your own thing here
}
else
{
super.onLoadResource(view, url);
}
}
Official documentation says, click on a link in a WebView will launch application that handles URLs. You need to override this default behavior
myWebView.setWebViewClient(new WebViewClient() {
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
return false;
}
});
or if there is no conditional logic in the method simply do this
myWebView.setWebViewClient(new WebViewClient());
Add this 2 lines in your code -
mWebView.setWebChromeClient(new WebChromeClient());
mWebView.setWebViewClient(new WebViewClient());
The method boolean shouldOverrideUrlLoading(WebView view, String url) was deprecated in API 24. If you are supporting new devices you should use boolean shouldOverrideUrlLoading (WebView view, WebResourceRequest request).
You can use both by doing something like this:
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
newsItem.setWebViewClient(new WebViewClient() {
#Override
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
view.loadUrl(request.getUrl().toString());
return true;
}
});
} else {
newsItem.setWebViewClient(new WebViewClient() {
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url);
return true;
}
});
}
Arulx Z's answer was exactly what I was looking for.
I'm writing an app with Navigation Drawer with recyclerview and webviews, for keeping the web browsing inside the app regardless of hyperlinks clicked (thus not launching the external web browser). For that it will suffice to put the following 2 lines of code:
mWebView.setWebChromeClient(new WebChromeClient());
mWebView.setWebViewClient(new WebViewClient());
exactly under your WebView statement.
Here's a example of my implemented WebView code:
public class WebView1 extends AppCompatActivity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
WebView wv = (WebView) findViewById(R.id.wv1); //webview statement
wv.setWebViewClient(new WebViewClient()); //the lines of code added
wv.setWebChromeClient(new WebChromeClient()); //same as above
wv.loadUrl("http://www.google.com");
}}
this way, every link clicked in the website will load inside your WebView.
(Using Android Studio 1.2.2 with all SDK's updated)

Categories

Resources