I've been developing hybrid apps for many companies with mobile websites.
And as a matter of fact, there are some websites made with using jsp.
I already had the knowledge that iframes and javascripts xhr requests will not fire webViewClient's shouldOverrideUrlLoading override function. I'm fine with that.
But today I learned that SOME actions such as:
JSP Page Redirects
Link Clicks within a JSP page
JSP/JS induced URL Loads
will not ALWAYS fire this function.
Hence, shouldOverrideUrlLoading() does not fire, when the webView is asked to load a page that it cannot load(i.e. "intent://...",) it shows an error page.
Has anyone encountered this kind of behaviour and is there any solution to work around it ?
Below is the code I'm using to invoke activities, where urls with 'intent:' protocol (which will fail because this function never gets called when above actions are performed)
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
// ... omitted ...
if ( url.startsWith("intent:") ) {
Intent intent = null;
try {
intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME);
// The following flags launch the app outside the current app
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
try {
getActivity().startActivity(intent);
} catch (ActivityNotFoundException e) {
e.printStackTrace();
}
} catch (URISyntaxException e) {
e.printStackTrace();
}
return true;
}
}
ps. please notice that every other websites' page loads will perfectly call shouldOverrideUrlLoading().
I couldn't find any JSP related bugs on android webViews so I'm asking one.
ps. I am happily willing to provide sample websites that some gracious readers will try on.. but the website's written in Korean so I doubt it will help.
Thank you!
Your problem might not related to JSP, the real problem may be shouldOverrideUrlLoading() itself. In this case, using shouldOverrideUrlLoading() may not be a good idea, so why not try another perspective?
I've encountered many problems when using
shouldOverrideUrlLoading() loading XmlHttpRequest. At the end, I
came up with the idea using onProgressChanged() and it solved all
my problems. I've written a similar answer here.
I tried adding your code into my own webview project and tested it with some JSP sites, and looks like it always work. I also added loadUrl() after other activities are invoked, so after pressing the back button, the loading error page will not be displayed again. So try this one :
First declare a global variable to store last URL.
String strLastUrl = null;
Then override onProgressChanged(WebView view, int progress)
mWebView.setWebChromeClient(new MyWebChromeClient(){
#Override
public void onProgressChanged(WebView view, int progress) {
if (progress == 100) {
//A fully loaded url will come here
String StrNewUrl = view.getUrl();
if(TextUtils.equals(StrNewUrl,strLastUrl)){
//same page was reloaded, not doing anything
}else{
String strOldUrl = null;
//save old url to variable strOldUrl before overwriting it
strOldURL = strLastUrl;
//a new page was loaded,overwrite this new url to variable
strLastUrl = StrNewUrl;
if ( strLastUrl.startsWith("intent:") ) {
Log.d("TAG", "intent triggered");
Intent intent = null;
try {
intent = Intent.parseUri(strLastUrl, Intent.URI_INTENT_SCHEME);
// The following flags launch the app outside the current app
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
try {
startActivity(intent);
} catch (ActivityNotFoundException e) {
e.printStackTrace();
}
//reload the page before invoking other activities
view.loadUrl(strOldURL);
} catch (URISyntaxException e) {
e.printStackTrace();
}
}
}
}
super.onProgressChanged(view, progress);
}
});
Related
Is there a way to launch the Square POS Android app from Xamarin.Forms? I noticed all the SDK examples are in Java. Does the app have to be written using Java?
EDIT: Update
I created Bindings library in Visual Studio using the square android SDK aar file. Now, it launches the Point-of-Sale app but then it afterwords no result data is being sent back to the application. Instead, it goes to an error screen where it says "Webpage not available"
The webpage at
intent#intent;action=com.squareup.pos.action.CHARGE (more..)
could not be loaded because
net: ERROR_UNKNOWN_URL_SCHEME
I am trying to do this in a WebView by overriding ShouldOverrideUrlLoading
public override bool ShouldOverrideUrlLoading(WebView view, string url)
{
if (url.StartsWith("http:") || url.StartsWith("https:"))
{
view.LoadUrl(url);
return false;
}
if (url.Contains("CHARGE"))
{
// Process Square Point-Of-Sale Transaction
ChargeRequest chargeRequest =
new ChargeRequest.Builder(1, CurrencyCode.Usd)
.Note("Test")
.RequestMetadata("WEB_CALLBACK_URI=mycallbackurl")
.RestrictTendersTo(ChargeRequest.TenderType.Card, ChargeRequest.TenderType.CardOnFile, ChargeRequest.TenderType.Cash)
.CustomerId("MyCustomerID")
.Build();
try
{
if (PosClient == null)
{
new AlertDialog.Builder(this.activity).SetMessage("Square Point-Of-Sale Client Error").Show();
}
Intent chargeIntent = PosClient.CreateChargeIntent(chargeRequest);
this.activity.StartActivityForResult(chargeIntent, CHARGE_REQUEST_CODE);
}
catch (ActivityNotFoundException e)
{
new AlertDialog.Builder(this.activity).SetMessage(e.Message).Show();
}
}
return false;
}
EDIT: Update
I added an override for OnRecieveError
The errorCode is
Android.Webkit.ClientError.Unsupported Scheme
In the Webview Object there's this
Java.Lang.NoSuchMethodError: no non-static method
"Landroid/webkit/WebView;.getRendererPriorityWaivedWhenNotVisible()Z"
Java.Lang.NoSuchMethodError: no non-static method
"Landroid/webkit/WebView;.getRendererRequestedPriority()I"
EDIT: Update
I have it sort of working now. In the ShouldOverrideUrlLoading method, I changed it to return true if it was processing a transaction. I also had to override OnActivityResult and parse the data coming back from the Android app. It now launches the Square POS App and Returns data back to the calling app. Now, I just have figure out how to process the transaction from there.
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.
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.
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
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 :-
WebViewActivity intercept requests and if url pattern doesn't match give a callback and start new ChromeTabActivity with url passed in intent bundle.
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.
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();
}
}
Edit: Strangely works when duplicating the loadUrl() line
I'm working with WebViews to gather data from a webpage. Sometimes, the webview just does nothing. I tried many proposes, but actually, none of them works....
Strange: webview.loadUrl("url...") doesn't work, but as soon as I call it twice, it works...
checkLogin()
void checkLogin(final Context context) {
Log.d("checkLogin()", "Begin of checkLogin()");
WebView webview = returnNewWebView(context, false);
webview.setWebViewClient(new WebViewClient(){
#Override
public void onPageFinished(WebView view, String url) {
Log.d("url", url);
// GOOGLE.COM ACTUALLY WORKS!
if(url.equals("https://www.google.com/"))
return;
Log.d("checkLogin()", "Loading of checkpage finished");
if(url.contains("loginto.php")) {
Log.d("checkLogin()", "User is logged out");
OnLoginCheckListener.onLoginCheckLoggedOut();
} else {
Log.d("checkLogin()", "User is logged in");
OnLoginCheckListener.onLoginCheckLoggedIn();
}
}
});
String id = sharedPref.getString("id", null);
String transid = sharedPref.getString("transid", null);
if(TextUtils.isEmpty("id") || TextUtils.isEmpty("transid")) {
Log.d("checkLogin()", "No login found, starting LoginActivity()");
Intent myIntent = new Intent(context, LoginActivity.class);
context.startActivity(myIntent);
} else {
Log.d("checkLogin()", "Probably logged in, checking by loading startpage");
webview.loadUrl("https://www.google.com");
// google actually works -.-
webview.loadUrl("https://my.login-page.com/index.php?pageid=1&id=" + id + "&transid=" + transid);
}
}
returnNewWebView()
private WebView returnNewWebView(Context context, Boolean JSInterface) {
// Prepare a webview
WebView WebView = new WebView(context);
if(JSInterface) {
WebView.getSettings().setJavaScriptEnabled(true);
WebView.addJavascriptInterface(new JavaScriptInterface(), "AndroidInterface");
}
// We don't need images for data scraping...
WebView.getSettings().setLoadsImagesAutomatically(false);
WebView.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE);
// Allow cookies
CookieManager.getInstance().acceptCookie();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
CookieManager.getInstance().acceptThirdPartyCookies(WebView);
}
// Prevent webview not loading
// or try it...
WebView.clearCache(true);
WebView.destroyDrawingCache();
return WebView;
}
As you see, I already tried some stuff in order to prevent it but still, it doesn't work. In that cases, as soon I'm going to put the loadUrl() / postUrl()twice, it works.
What can I do that it works in every single case?
I would appreciate any tip!
Thank you very much in advance
logcat (non-filtered)
I actually see this message then..
03-09 07:36:19.061 4111-24251/? E/ctxmgr: [ProducerActiveIntervalImpl]closeActiveInterval: Error: ongoing, trying to close
and this one:
03-09 07:36:28.864 29862-3249/? E/accs.GcmPush: initializeApp occur error!
java.lang.IllegalStateException: FirebaseApp name [DEFAULT] already exists!
at iv.a(Unknown Source)
at com.google.firebase.FirebaseApp.a(Unknown Source)
at com.google.firebase.FirebaseApp.a(Unknown Source)
at org.android.agoo.gcm.GcmRegister$1.run(GcmRegister.java:32)
and this one too:
E/libEGL: validate_display:99 error 3008 (EGL_BAD_DISPLAY)
But I can't tell you if they are fired by the app....
I have gone through the full code for so long and pointed out this :
You are using this -
webview.loadUrl("https://www.google.com");
// google actually works -.-
webview.loadUrl("https://my.login-page.com/index.php?pageid=1&id=" + id + "&transid=" + transid);
in your checkLogin() method which is simply wrong as because one webview can not display the two urls at same time (as you mentioned it you want to achieve that).
Now according to your code the loading of the first url is overridden by the second url.
In order to achieve the loading of two urls in same webview try to implement this using :
Threads or some sessionTimeout methods.
OR
You can achieve this by using okhttp package.
Hope this helps!
onPageFinished is called when the webpage finished loading, but not after all the javascript on the page finished running. Depending on what the page is doing and what you are trying to achieve, it might be that your code is sometimes being executed before some essential javascript is executed on that page.
It's not a pretty solution but try adding some delay (like a Handler's postDelayed() or SystemClock.sleep() - but never on the UI thread!) to your code to see if that is indeed your problem.
In my onCreate method Im creating an webView and then loading an HTML file thats saved in my assets folder. This is all working fine. When a button is pressed it sends a call using javascript to this method to open the qr code scanner.
webView.setWebViewClient(new WebViewClient()
{
/* On Android 1.1 shouldOverrideUrlLoading() will be called every time the user clicks a link,
* but on Android 1.5 it will be called for every page load, even if it was caused by calling loadUrl()! */
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url)
{
System.out.println(url);
if (url.equals("fake://qr_scan"))
{
launchQRScanner(view);
}
return false;
}
});
Here is the method launchQRScanner()
public void launchQRScanner(View v) {
if (isCameraAvailable()) {
Intent intent = new Intent(this, ZBarScannerActivity.class);
intent.putExtra(ZBarConstants.SCAN_MODES, new int[]{Symbol.QRCODE});
startActivityForResult(intent, ZBAR_SCANNER_REQUEST);
} else {
Toast.makeText(this, "Rear Facing Camera Unavailable", Toast.LENGTH_SHORT).show();
}
}
So this works for the first time the button is pressed. The qr code reader open as an intent, it scans, disappears and returns the value correctly. But for every time after the method shouldOverrideUrlLoading() doesn't get called when the button is pressed. Possibly has something to do with leaving the app and coming back? Can't seem to figure it out.
BTW this is the project that I used to implement the qr code reader
https://github.com/DushyanthMaguluru/ZBarScanner
Fixed, Just had to reload the webView after the barcode is scanned. Simple fix.
In my application I've got a WebView which might be used to display www.youtube.com or m.youtube.com content. I've got the following code in the onLoadResource callback:
#Override
public void onLoadResource (WebView view, String url)
{
if(matchYouTubeWatchUrl(url))
{
invokeYouTubePlayer(m_context,url);
}
}
The invokeYouTubePlayer function looks like this:
private static void invokeYouTubePlayer(Context c, String url)
{
try
{
Uri uri = Uri.parse(url);
String videoId = uri.getQueryParameter("v");
if(videoId != null)
{
Intent youtubeIntent = new Intent(Intent.ACTION_VIEW);
youtubeIntent.setData(Uri.parse("vnd.youtube://"+videoId));
try
{
c.startActivity(youtubeIntent);
}
catch(ActivityNotFoundException e)
{
Log.e(TAG,"No handler for native youtube - expanding scope");
// Try an alternate approach if there's no youtube app installed
youtubeIntent = new Intent(Intent.ACTION_VIEW);
youtubeIntent.setData(Uri.parse("http://www.youtube.com/watch?v="+videoId));
try
{
c.startActivity(youtubeIntent);
}
catch(ActivityNotFoundException e2)
{
e2.printStackTrace();
}
}
}
}
catch(NullPointerException e)
{
e.printStackTrace();
}
}
What I'm finding is that on most Android devices the first startActivity call invokes the YouTube application, however on KF what I'm seeing is that it invokes the browser which then invokes a video player application.
Not having a KF myself what I'd like to find out is whether there's a URI that can be passed to startActivity which will directly invoke the video player on Kindle Fire without having to pass through the browser as an intermediate step.
Just a observation. There is a better way to detect if a Intent can be called
private boolean isCallable(Intent intent) {
List<ResolveInfo> list = getPackageManager().queryIntentActivities(intent,
PackageManager.MATCH_DEFAULT_ONLY);
return list.size() > 0;
}
Ok just to be clear on KF it never fires first activity then it fires second and then KF takes over and takes you to the YouTube app.
If so all i can think is that your details in first Intent is not correct or for KF its not the same as in other phones/tab