I'm trying to read in a website (XML/XSLT), and change the theme to one that I've created so that it looks better on the web. The site currently is horribly formatted for mobile devices, and i want to fix that. :) Is this possible? If so, how?
So far I've got my app loading the website:
WebView view = (WebView) findViewById(R.id.webview);
view.setWebChromeClient(new WebChromeClient() {
public void onProgressChanged(WebView view, int progress) {
activity.setTitle("Loading...");
activity.setProgress(progress * 100);
if (progress == 100)
activity.setTitle(R.string.app_name);
}
});
view.setWebViewClient(new MyViewerClient());
view.getSettings().setJavaScriptEnabled(true);
view.loadUrl("http://www.somewebsite.com");
Now i'm looking for something like:
String html = view.getHTML(); // <---- does a function like this exist?
html = myparser(html); // parse html and change out xsl theme
view.loadData(html); // set html back and continue on
Side info: this site has a login (SSL), and uses cookies (haven't got there yet but don't want to corner myself with a solution that won't work in those instances)
Few things, firstly, if you have access to the server side code, you might want to make changes there so that the web site looks better on mobile devices, intact devices of may different sizes. This is a very common problem nowadays and I believe the recommended solution is to use different css files to theme your website appropriately for different screen sizes.
Now if you can't or don't have access to server side code, then we have a problem. You'll really have to reverse engineer the site/page's code and strip out the pieces of html/css that are not of interest on a smaller device. Note that this will not reduce the traffic as you'd still need to get all of the page locally and then make a pass to strip out the unneeded pieces and insert new ones.
If you favor the second approach, know that it is going to be brittle. Any changes to the web site and boom, your application would stop working as expected. If you still insist, then search for ways to perform html parsing on the android. I know there are several xml parsers, but I'm not sure about html.
Related
Perhaps a typical and well-trodden question on first glance. But all other answers I've looked at (there are many) have consistently suggested (almost always) changing the user-agent of a WebView to a desktop string. Consistently, people have responded that this does not work for them. Myself included.
As a web design novice, from what digging I have done it seems that at some point in the last few years "responsive design" became the recommended and most widely used web design implementation philosophy of choice to determine how to deliver/display a site.
Which is why I believe changing a user-agent of a WebView is having no effect, as the site seems to be determining how to deliver content based on the meta tag "viewport", for example:
<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=0">
Has anyone else overcome this yet? Would my (layman) analysis of the issue be correct?
As far as my understanding, i think this seems like the below situation
Please look into this answer Showing the desktop version of a fully responsive website on tablets
So I think I may have found a solution, in part due to #hussnain-muavia answer, though there was slightly more I had to expand which I thought would warrant its own response.
WebView Settings
The WebView will need to be able to execute JavaScript:
WebSettings settings = _webView.getSettings();
settings.setJavaScriptEnabled(true);
In order for the site to be completely zoomed-out (i.e. displaying the full site and without scrollbars) additional settings will have to be applied to the WebView:
settings.setUseWideViewPort(true);
settings.setInitialScale(1);
settings.setLoadWithOverviewMode(true);
Achieving a Desktop Layout
Ultimately the thing that will result in a desktop view is the following JavaScript:
function desktopMode() {
var viewPort = document.getElementsByName("viewport");
if (viewPort != null) {
viewPort = viewPort[0];
}
else if ((viewPort = document.getElementById("viewport")) == null) {
return;
}
viewPort.setAttribute("content", "width=" + (screen.width + 1) + "; initial-scale=0.5");
}
As I mention in my question, I am a web design novice. Though one thing I notice is inconsistency (sorry web devs?) so I've intentionally built in a check by name and ID. We should expect only one tag, hence why name assumes the zeroth element.
Further to this solution I found I was getting a desktop layout but not quite the complete thing. So I modified the calculation thusly:
getViewport.setAttribute("content", "width=" + (screen.width * 4) + "; initial-scale=0.5");
I don't like it. But it works. My suspicion is there probably is some better calculation or constant for this.
It's probably worth mentioning, it's still worthwhile changing the user-agent string, in case the site does not use a responsive design. But this is an unrelated topic.
A major drawback of this solution is that it will result in some hideous UI, where the user can see the site being resized. As I put the JS in onPageStarted, but of course the functionality gets overridden. So I put it after the super onPageStarted call or in onPageFinished and this results in the same result of the user seeing the resize happening. Any ideas out there?
Would like to ask for some advice on what's best way to implement on enabling and disabling the web view on Android?
I have this app wherein it can open urls within (by using web views) which then popups up and covers 80% of the UI, when the user navigates to another page of the app it should hide/close the web view but can be re-opened again when needed.
Here's a snippet of the code
private WebViewInterface webViewInterface = new WebViewInterface() {
#Override
public void onOpenURL(String url) {
navBrowserWV.setVisibility(View.VISIBLE);
navBrowserWV.getSettings().setJavaScriptEnabled(true);
navBrowserWV.setWebViewClient(new WebViewClient() {
#Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error){
handler.proceed();
}
});
navBrowserWV.loadUrl(url);
}
};
then this is how I close it
private void closeWebView() {
Log.d(LOGTAG, "closing webview...");
// Destroy WebView if it exists
if (this.navBrowserWV != null) {
this.navBrowserWV.stopLoading();
this.navBrowserWV.loadUrl("about:blank");
this.navBrowserWV.clearHistory();
this.navBrowserWV.clearCache(true);
this.navBrowserWV.pauseTimers();
this.navBrowserWV.setVisibility(View.GONE);
}
}
The problem with this implementation is that it displays the page properly at first but when I close it and then open the web view again with a url, it does not load the page anymore just a white background (no errors on the logger btw).
Would like to ask for help on how to resolve this one. Thanks
Finally, after a long time of digging up answers on the internet I found this old post regarding killing Android webview [link].
There's no real way to kill the WebView (it will always run in your process and you can't do anything about it ATM). So you only have to tell the WebView to load a bogus page, for me I did:
this.navBrowserWV.loadUrl("about:blank");
And it works now!
Quote from the website
4.1 .getSettings().setJavaScriptEnabled() Inhalt
Well, you would think that disabling JavaScript in the WebView’s settings would have an instant effect, in short: it does not. Only after reloading the page would there be no javascript any more, and this is not nice by design, since the page is still more or less well rendered.
4.2 .stopLoading() Inhalt
Since XHR „loads“ something from another server, you might think that calling „webview.stopLoading()“ would have an effect. In short: it does not. Works only on ressources contained within the HTML-file. Pity, is it not… Well, maybe not, since there is no „startLoading()“ method to resume XHR after resuming the activity anyway.
4.3 .destroy() Inhalt
As a last resort one might think about „destroy()“ing that thing, and true enough, the WebView itself is not accessible after that. Its threads however continue to exist as zombies somewhere in the vast RAM space and also continue to send XHR requests…
4.4 .pauseTimers() / resumeTimers() Inhalt
In short: Nope, does not work. I even don’t know what these methods are good for if not for controlling JavaScript timers. There aren’t any in plain HTML, AFAIK.
Update: When it comes to timers only, these functions seem to work on 2.3.5 and upwards, however, when there is no timer active at the time of calling the function, all in vain. With my use case: When pausing the app while there is an XHR active (instead of the running timer that schedules the next XHR call), nothing happens and the next timer continues unhindered.
I hope this will help someone who has the same problem as mine.
I want to make an app that allows users to log-in multiple accounts of same site using different webview.
For example, I have 2 WebView.
Each WebView will load the same site such as gmail.com.
And user can log-in using separate account in separate WebView.
But the problem I am facing is that the 2 WebView always log-in to same account.
I've googled a lot, and here are some related titles,
Facebook MultiLogin in Android Webview
Using WebView for multi-page login to website and fetch data
Multiple Log-Ins on Separate WebViews? (Android)
but still no acceptable answer is found.
Would it be possible in Android using WebView?
How can I achieve what I want?
The tricky part is android.webkit.CookieManager, used by WebView to keep cookies, is designed to be a singleton. This means there'll be only one CookieManager instance per Java/Dalvik process, and your multi WebView instances inside the same process share a same set of cookies.
Like #ToYonos proposed, you may try overriding certain hooks to work around this, but I don't think it will 100% work...Also think about android.webkit.WebStorage: it's another singleton!
That said, this might work a bit more reliably: duplicate your top level WebView activity in manifest and assign them to run in different processes:
<activity
android:name=".WebViewActivity" />
<activity
android:name=".WebView1Activity"
android:process=":web1" />
<activity
android:name=".WebView2Activity"
android:process=":web2" />
...
So that you'll have isolated processes and different CookieManager/WebStorage instances.
But be warned: different WebStorage instances still writes to same path(s) in your app data folder! This may be worked around by calling webView.getSettings().setDatabasePath() to use different db paths for different processes, but this API has been deprecated in API level 19 (KitKat). Well as long as the web page you're visiting doesn't use HTML5 local storage this should be fine...
I think that you'll have to implement your own system. You could try something like that :
private static final String DOMAIN = "http://cookiedomain.com";
private final Map<WebView, String> cookiesMap = new HashMap<WebView, String>();
// [...]
WebView w = new WebView(this);
// Loading url and stuff
w.setWebViewClient(new WebViewClient()
{
public void onLoadResource (WebView view, String url)
{
// If cookies have already been stored for this WebView
if (cookiesMap.get(view) != null)
{
CookieManager.getInstance().removeAllCookie();
CookieManager.getInstance().setCookie(DOMAIN, cookiesMap.get(view));
}
}
public void onPageFinished(WebView view, String url)
{
// Check if the url matches the after-login page or whatever you want
boolean condition = ...;
if(condition)
{
// Getting new cookies
String cookies = CookieManager.getInstance().getCookie(DOMAIN);
cookiesMap.put(view, cookies);
}
}
});
// Do the same for the 2nd WebView
This is a simple example, to be improved, but it could be a good start for a sustainable solution.
Limits :
It will only work if each WebView does not make request at the same time as others. Otherwise, it will surely tangle cookies.
This will work for one domain only
I have a webview that shows ads (not my ads), the problem is when user clicks the "x" button to exit the ad, the ad still directs them to a site. What I wonder is since I can't control the ads, can I instead Disable page directing/forwarding inside webview? that means even if user clicks a link inside my webview nothing should happen.
You are looking for WebClient.shouldOverrideUrlLoading method.
webview.setWebViewClient(new WebViewClient() {
public boolean shouldOverrideUrlLoading (WebView view, String url){
//True if the host application wants to leave the current WebView and handle the url itself, otherwise return false.
return true;
}
});
I tried using shouldOverrideUrlLoading, but it didn't work. It looks like that this method is called only once when the html is loaded. After that, you click a link but the method is no more invoked.
I am also making a WebView embedding Youtube Player. Instead of forwarding ads redirect from the WebView, I prefer to open ads in a browser. So I override onLoadResource method:
#Override
public void onLoadResource(final WebView view, final String url) {
if(url.indexOf("googleadservices.")>-1){
view.getSettings().setJavaScriptEnabled(false);
view.stopLoading();
view.postDelayed(
new Runnable(){
#Override
public void run(){
Uri uri=Uri.parse(url);
Intent i=new Intent(Intent.ACTION_VIEW,uri);
i.setClassName("com.android.browser","com.android.browser.BrowserActivity");
startActivity(i);
}
}
,100
);
}
}
It worked. When I clicked the ads link, a new browser is opened in which ads site is displayed well, and the WebView was not redirected. When I push the return button, WebView show up again and I can continue watching video.
But there were still problems. If I repeat opening browser and returning to WebView for many times, the WebView might fail to block redirecting to the ads site. It is just redirected to the ads site. If I am lucky I could repeat opening and returning for 100 times. But sometimes It failed just when I repeat several times. I don't know why.
Does anyboday have any idea about how to improve it? Or is there another way to disable ads redirect?
You can build undetected webview build-id adblocker
I know it is too late for answering this question, however, for the sake of others who have the same question.
Well, you can build webview build-id adblocker, if you wish to prevent ads from loading, and provide smooth experience to the users, I am confident, because I have already implemented it in may app.
The Idea
Is to have a black list of all possible ad-serves domain name, then while webview load resources, you will prevent loading from black list domains. so it depends on how many ads-serves domain you have in the black list, fortunately, there is one website (pgl.yoyo.org/as/) which provide you with a very long list of ad-serves domai names, and listed them in many flavoures.
you can read this article for:
how to implement webview build-id adblocker
, you will build it %100 as long as you follow step by step instructions.
A summary of what we need to do:
Get the list of ad hostnames from pgl.yoyo.org.
Save the list somewhere, load it when application starts.
UseWebViewClient.shouldInterceptRequest(WebView, String) to intercept
requests.
Check if the request URL belongs to one of the hostnames in
the list and override it, returning a dummy resource instead of the
actual one, which is supposed to be ads
I have an android application that loads a webview from a server. I do not have the server code so I cannot change anything in Javascript. I want to figure out when a button is being clicked in a webview and what is the label in the button. I do not know the Id, I just want to get the label.
I tried searching for this but could not find an answer. I found solutions where you can work in the javascript but in my case I cannot.
This suggestion may help to find useful information that could lead to determination of your button label. Override shouldOverrideUrlLoading(), shouldInterceptRequest() and/or onLoadResource() for the WebViewClient so you can get at the URL of any redirects.
Example:
webview.setWebViewClient(new WebViewClient() {
public boolean shouldOverrideUrlLoading(WebView view, String url) {
// Try to learn something useful from the 'url' here.
// Continue as normal, loading the 'url' within this WebView.
view.loadUrl(url);
return false; // Allow the WebView to handle the request.
}
// Optional: Add similar for "shouldInterceptRequest()" and/or "onLoadResource()".
});
Note: Overriding shouldOverrideUrlLoading() as above is the standard way to keep redirects within the same WebView rather than redirecting to the default browser application.
You might really want to check this page:
Building Web Apps in WebView (Google API Guides)
Specifically, it seems that addJavascriptInterface might be what you are looking for:
addJavascriptInterface(Object object, String name)
It allows you to execute your Java code from javascript and, paired with the ability to insert code in a page, it's an incredibly powerful tool for granting you a high level of coupling between your Activity and your page.
I think that at this point you will already know what to do, but I'll sketch a possible course of action anyway:
create a javascript interface with the callbacks you want executed in your activity when a button is pressed
as soon as your page loads, install the code to call your javascript interface in each button (or link) by injection
Hope this helps