For educational reasons I am learning about parsing and scrapping HTML content.
I saw a lot of questions and answers about retrieving the html content displayed in a webview.
My Problem is, that I am not able to get the whole html, how I suppose it should look like. When I inspect the URL in Safari all Items can be located in the HTML, but when I load the HTML of the same URL from webview there are missing items.
My code right now:
`
private WebView wv;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
wv = (WebView) findViewById(R.id.webView);
wv.getSettings().setJavaScriptEnabled(true);
wv.addJavascriptInterface(new LoadListener(), "HTMLOUT");
}
#Override
protected void onStart() {
super.onStart();
wv.setWebViewClient(new WebViewClient() {
public void onPageFinished(WebView view, String url) {
if(wv.getProgress() == 100) {
view.loadUrl("javascript:window.HTMLOUT.processHTML(document.documentElement.innerHTML);");
}
}
});
wv.loadUrl("http://hdfilme.tv/movie-movies?order_f=view&order_d=desc&per_page=#");
}
class LoadListener{
#JavascriptInterface
public void processHTML(String html) {
longInfo(html);
}
}
public static void longInfo(String str) {
if (str.length() > 4000) {
Log.i("DEBUG", str.substring(0, 4000));
longInfo(str.substring(4000));
} else
Log.i("DEBUG", str);
}`
Usuallay I use Jsoup for connecting and parsing HTML, but in this case the webpage is using CloudFlare and I was not able to succesfully load the page HTML.
What I found out is that every Listitem of the webpage which includes the <span class="hot"></span> tag is not loaded in my code right now.
What am I missing ?
UPDATE1
The hint of F.Klein did it!
I really do not understand why setting the user agent as follows:
wv.getSettings().setUserAgentString("Mozilla/5.0 (Linux; U; Android 3.0; en-us; Xoom Build/HRI39) AppleWebKit/534.13 (KHTML, like Gecko) Version/4.0 Safari/534.13");
changed the retrieved HTML as I supposed it to be. The shown content in the webview was before and after setting the user agent the same.
UPDATE2
I have been to early pleased...changing the user agent ended up in missing items to show up but other items which were before loaded now are gone??? I donĀ“t get the problem.
As previously guessed, the mobile version of the page seems to be different from the default version: with the mobile version there are 50 divelements with the class box-productfound while the default version finds 70 elements.
Fix
String userAgent = "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.116 Safari/537.36";
wv.getSettings().setUserAgentString(userAgent);
Your mentioned user-agent (Update 1) is one for the mobile version, hence the same result as without setting a user-agent (defaulting to mobile version on android).
Alternative without WebView using Rhino (to calculate the challenge to acquire the access cookie):
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new BackgroundTask().execute();
}
class BackgroundTask extends AsyncTask<Void,Void,Void>{
#Override
protected Void doInBackground(Void... voids) {
scrapePage();
return null;
}
}
private void scrapePage() {
Map<String, String> cookies;
final String referrer = "http://hdfilme.tv/movie-movies?order_f=view&order_d=desc&per_page=0";
try {
Connection.Response res = Jsoup.connect("http://hdfilme.tv/movie-movies?order_f=view&order_d=desc&per_page=#")
.followRedirects(true).header("Connection", "keep-alive").userAgent(userAgent)
.ignoreHttpErrors(true).header("host", "hdfilme.tv").referrer(referrer).method(Connection.Method.GET)
.execute();
cookies = res.cookies();
Document doc = Jsoup.parse(res.body());
String[] scriptlines = doc.select("script").toString().split("\n");
StringBuilder builder = new StringBuilder();
builder.append("var a={};");
for (String line : scriptlines) {
if (line.contains("var s,t,o,p,b,r,e,a,k,i,n,g")) {
builder.append(line.trim());
builder.append("t=\"hdfilme.tv\"");
}
if (line.contains("a.value = parseInt")) {
builder.append(line.trim());
}
}
int jschl_answer = runRhino(builder.toString());
String jschl_vc = doc.select("input[name=jschl_vc]").first().attr("value");
String pass = doc.select("input[name=pass]").first().attr("value");
Log.e("info","Acquiring access cookies (takes about 4 seconds).");
try {
Thread.sleep(4000); //DDOS protection
} catch (InterruptedException e) {
e.printStackTrace();
}
String requestUrl = "http://hdfilme.tv/cdn-cgi/l/chk_jschl";
res = Jsoup.connect(requestUrl).followRedirects(true).userAgent(userAgent)
.header("Referer", "http://hdfilme.tv/movie-movies?order_f=view&order_d=desc&per_page=")
.data("jschl_vc", jschl_vc).ignoreHttpErrors(true).cookies(cookies)
.data("jschl_answer", "" + jschl_answer).data("pass", pass).header("host", "hdfilme.tv")
.method(Connection.Method.GET).timeout(5000).execute();
Log.e("repsonse",res.statusCode() + " - " + res.statusMessage());
cookies.putAll(res.cookies());
builder = new StringBuilder();
builder.append("\tcookies");
for (String cookie : cookies.keySet()) {
builder.append("\n\t\t" + cookie + ":" + cookies.get(cookie));
}
Log.e("cookies", builder.toString());
doc = Jsoup.connect("http://hdfilme.tv/movie-movies?order_f=view&order_d=desc&per_page=#")
.cookies(cookies)
.userAgent(userAgent)
.timeout(10000).referrer(referrer).header("host", "hdfilme.tv").followRedirects(true).get();
builder = new StringBuilder();
builder.append("elements");
for (Element element : doc.select(selector)) {
builder.append("\n"+element.select("a[href]").attr("abs:href"));
}
Log.e("elements","total number of elements="+doc.select(selector).size()+"\n"+builder.toString());
} catch (IOException e) {
e.printStackTrace();
}
}
private int runRhino(String jsSource) {
Context rhino = Context.enter(); // org.mozilla.javascript.Context;
// Turn off optimization to make Rhino Android compatible
rhino.setOptimizationLevel(-1);
Scriptable scope = rhino.initStandardObjects();
rhino.evaluateString(scope, jsSource, "ScriptAPI", 1, null);
NativeObject object = (NativeObject) scope.get("a", scope);
return (int) ((Double) object.get("value") / 1);
}
Related
I want to implement a mechanism in a custom webview client (without JavaScript injection) that can block ads. Is a way I can catch ads and replace them with other ads from a trusted source?
Thanks
In your custom WebViewClient, you can override the function shouldInterceptRequest(WebView, WebResourceRequest).
From Android docs:
Notify the host application of a resource request and allow the application to return the data.
So the general idea is to check if the request is coming from an ad URL (plenty of black list filters out there), then return a "fake" resource that isn't the ad.
For a more in depth explanation plus an example, I recommend checking out this blog post.
To implement this, you have two options:
Use Javascript injected code to do this (which you explicitely said, don't want)
In WebView, instead of "http://example.com" load "http://myproxy.com?t=http://example.com" (properly escaped, of course) and setup "myproxy.com" to be a proxy which will fetch the upstream page (given in "t" query parameter, or in any other way) and replace ads with the trusted ones before sending response to the client. This will be pretty complex, though, because ads can be in many forms, they're usually Javascript injected themselves and you'd probably need to rewrite a lot of URL's in the fetched HTML, CSS and JS files etc.
I made a custom WebViewClient like:
public class MyWebViewClient extends WebViewClient {
#Override
public void onPageFinished(WebView view, String url) { }
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (url.endsWith(".mp4")) {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.parse(url), "video/*");
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
view.getContext().startActivity(intent);
return true;
} else if (url.startsWith("tel:") || url.startsWith("sms:") || url.startsWith("smsto:")
|| url.startsWith("mms:") || url.startsWith("mmsto:")) {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
view.getContext().startActivity(intent);
return true;
} else {
return super.shouldOverrideUrlLoading(view, url);
}
}
private Map<String, Boolean> loadedUrls = new HashMap<>();
#SuppressWarnings("deprecation")
#Override
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
boolean ad;
if (!loadedUrls.containsKey(url)) {
ad = AdBlocker.isAd(url);
loadedUrls.put(url, ad);
} else {
ad = loadedUrls.get(url);
}
return ad ? AdBlocker.createEmptyResource() :
super.shouldInterceptRequest(view, url);
}
}
And created an AdBlocker class like:
public class AdBlocker {
private static final Set<String> AD_HOSTS = new HashSet<>();
public static boolean isAd(String url) {
try {
return isAdHost(getHost(url));
} catch (MalformedURLException e) {
Log.e("Devangi..", e.toString());
return false;
}
}
private static boolean isAdHost(String host) {
if (TextUtils.isEmpty(host)) {
return false;
}
int index = host.indexOf(".");
return index >= 0 && (AD_HOSTS.contains(host) ||
index + 1 < host.length() && isAdHost(host.substring(index + 1)));
}
public static WebResourceResponse createEmptyResource() {
return new WebResourceResponse("text/plain", "utf-8", new ByteArrayInputStream("".getBytes()));
}
public static String getHost(String url) throws MalformedURLException {
return new URL(url).getHost();
}
}
And use this WebViewClient in your oncreate like:
webview.setWebViewClient(new MyWebViewClient());
so a quick overview of what I'm doing
I am using Android Webview to Render JavaScript and then reading the HTML from the javascript to parse it.
I am currently having trouble with retrieving the HTML from a website called Sport Chek.
Here is the code for my SportChekSearch class:
public class SportChekSearch extends SearchQuery{
public Elements finalDoc;
private ArrayList<Item> processed;
private final Handler uiHandler = new Handler();
public int status = 0;
//This basically is just so that the class knows which Activity we're working with
private Context c;
protected class JSHtmlInterface {
#android.webkit.JavascriptInterface
public void showHTML(String html) {
final String htmlContent = html;
uiHandler.post(
new Runnable() {
#Override
public void run() {
Document doc = Jsoup.parse(htmlContent);
}
}
);
}
}
/**
* Constructor method
* #param context The context taken from the webview (So that the asynctask can show progress)
*/
public SportChekSearch(Context context, String query) {
final Context c = context;
try {
final WebView browser = new WebView(c);
browser.setVisibility(View.INVISIBLE);
browser.setLayerType(View.LAYER_TYPE_NONE, null);
browser.getSettings().setJavaScriptEnabled(true);
browser.getSettings().setBlockNetworkImage(true);
browser.getSettings().setDomStorageEnabled(true);
browser.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE);
browser.getSettings().setLoadsImagesAutomatically(false);
browser.getSettings().setGeolocationEnabled(false);
browser.getSettings().setSupportZoom(false);
browser.getSettings().setUserAgentString("Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36");
browser.addJavascriptInterface(new JSHtmlInterface(), "JSBridge");
browser.setWebViewClient(
new WebViewClient() {
#Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);
}
#Override
public void onPageFinished(WebView view, String url) {
browser.loadUrl("javascript:window.JSBridge.showHTML('<html>'+document.getElementsByTagName('html')[0].innerHTML+'</html>');");
}
}
);
browser.loadUrl("https://www.sportchek.ca/search.html#q=" + query.replaceAll(" ", "+") + "&lastVisibleProductNumber=3");
browser.loadUrl(browser.getUrl());
final String link = browser.getUrl();
new fetcher(c).execute(link);
}
catch(Exception e){
e.printStackTrace();
}
//Get the link from the WebView, and save it in a final string so it can be accessed from worker thread
}
/**
* This subclass is a worker thread meaning it does work in the background while the user interface is doing something else
* This is done to prevent "lag".
* To call this class you must write fetcher(Context c).execute(The link you want to connect to)
*
*/
class fetcher extends AsyncTask<String, Void, Elements> {
Context mContext;
ProgressDialog pdialog;
public fetcher(Context context) {
mContext = context;
}
#Override
protected void onPreExecute() {
super.onPreExecute();
pdialog = new ProgressDialog(mContext);
pdialog.setTitle(R.string.finding_results);
pdialog.setCancelable(false);
pdialog.show();
}
//This return elements because the postExecute() method needs an Elements object to parse its results
#Override
protected Elements doInBackground(String... strings) {
//You can pass in multiple strings, so this line just says to use the first string
String link = strings[0];
//For Debug Purposes, Do NOT Remove - **Important**
System.out.println("Connecting to: " + link);
try {
doc = Jsoup.connect(link)
.ignoreContentType(true)
.userAgent("Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36")
.timeout(10000)
.get();
finalDoc = doc.select("body section.product-grid-wrapper");
System.out.println(finalDoc.toString());
} catch (IOException e) {
e.printStackTrace();
}
return finalDoc;
}
#Override
protected void onPostExecute(Elements result) {
//This line clears the list of info in the Search activity
//I should probably be using a getter method but adapter is a static variable so it shouldn't matter
//parse seperates document into elements
//crunch results formats those elements into item objects
//I am saving the result of this to an ArrayList<Item> called "processed"
processed = crunchResults(result);
//For debug purposes, do NOT remove - **Important**
System.out.println(processed.size() + " results have been crunched by Sport Chek.");
//Adds all of the processed results to the list of info in Search activity
ClothingSearch.adapter.addAll(processed);
//For debug purposes, do NOt remove - **Important
System.out.println("Adapter has been notified by Sport Chek.");
//Closes the progress dialog called pdialog assigned to the AsyncTask
pdialog.dismiss();
ClothingSearch.adapter.notifyDataSetChanged();
SearchQueueHandler.makeRequest(mContext, processed, SearchQueueHandler.CLOTHING_SEARCH);
}
}
public ArrayList<Item> crunchResults(Elements e){
ArrayList<Item> results = new ArrayList<Item>();
try {
for (int i = 0; i < e.size(); i++) {
Element ele = e.get(i);
String link = "https://www.sportchek.ca" + ele.select(" a.product-grid__link").attr("href");
System.out.println("https://www.sportchek.ca" + ele.select(" a.product-grid__link").attr("href"));
String title = ele.select(" span.product-title-text").text();
String pricestring = ele.select(" span.product-price__wrap").text();
price = Double.parseDouble(pricestring.substring(pricestring.lastIndexOf("$")));
System.out.println(pricestring);
//*******************************************
String store = "Sport Chek";
//Adds the formatted item to an ArrayList of items
results.add(new Item(title, store, price, link));
//Prints the object's to String to console
//For debug purposes, do NOT remove - **Important
System.out.println(results.get(i).toString());
}
} catch (Exception a){
a.printStackTrace();
}
return results;
}
public int getStatus(){
return status;
}
}
The two relevant methods are doInBackground in my AsyncTask and the crunchResults method.
Here is the result I get from using Ctrl+Shift+I on the actual website (Desired Result):
But when running the above code and using a println here is the result that I get for the tag section class="product-grid-wrapper" :
<section class="product-grid-wrapper">
<ul data-module-type="SearchProductGrid" class="product-grid__list product-grid__list_quickview">
<!-- #product-grid__item-template -->
</ul>
</section>
Can anyone help me figure out why I am not getting my desired result?
All help is appreciated
EDIT: for this specific search that the println data was collected from, the link was https://www.sportchek.ca/search.html#q=men+coat&lastVisibleProductNumber=3
It looks like what you are actually getting is the actual html sent by the server, and that your 'desired result' is what the DOM looks like after the JavaScript runs.
Your 'actual' is what I see if I use "View Source" in Chrome, while your "desired result" is what I see if I use Chrome's DOM inspector.
On further inspection, I see that you are not actually getting the HTML from the browser, you are (indirectly) using JSoup's Connection object to get the HTML directly. Unfortunately, that's not going to run the Javascript.
Instead, you're going to have to get the HTML from the WebView after the JavaScript runs. For a possible way to do that, see How do I get the web page contents from a WebView?
Then, you give the HTML that you get from that to JSoup with
Jsoup.parse(html);
I am just a learner of Android since a week before, It might be a trivial for someone but this looks big for me as I am new to this. I searched a lot and couldn't found a straight forward question or solution. So, I dont want to spend much time in confusing and post my question here.
I am having a webview in my app, which loads a webpage. I placed a button "Generate" below webview and calling an JS function "saveData()" in that web webpage in that button's onclick. This function needs few seconds to execute and return its response based on the user's network connection speed. Then, I am showing "copy" button to copy the return value.
generate.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
generate.setVisibility(View.GONE);
copy.setVisibility(View.VISIBLE);
webView.loadUrl("javascript:alert(saveData)"); // this function needs few seconds.
}
});
I am catching the return value of the JS function as follows, and setting the value of string "JsVal"
String JsVal;
public String getJsVal() {
return jsVal;
}
public void setJsVal(String jsVal) {
this.jsVal = jsVal;
}
webView.setWebChromeClient(new WebChromeClient() {
#Override
public boolean onJsAlert(WebView view, String url, String message,JsResult result) {
Log.d("LogTag from js call method", message);
setJsVal(message);
result.confirm();
return true;
}
});
Here, copy button code is making text copied for paste to somewhere,
copy.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Toast.makeText(getApplicationContext(), " Copied", Toast.LENGTH_SHORT).show();
pb.setVisibility(View.GONE);
ClipboardManager myClipboard;
myClipboard = (ClipboardManager)getSystemService(CLIPBOARD_SERVICE);
ClipData myClip;
myClip = ClipData.newPlainText("text", tVal);
myClipboard.setPrimaryClip(myClip);
}
});
Problem is, if I click the generate button, immediatly copy button is available. If I click on the copy button immediatly and paste in notes app, it shows null. If I copy after 2-3 secondes its not null.
I want to show copy button after the JS return value assigned to String variable "JsVal", untill I need to show nothing.
How to do this,
Try the following:
webView.setWebViewClient(new WebViewClient() {
#Override
public void onPageFinished(WebView view, String url) {
copy.setVisibility(View.VISIBLE);
}
});
generate.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
generate.setVisibility(View.GONE);
webView.loadUrl("javascript:alert(saveData)"); // this function needs few seconds.
}
});
You should do it using a AsyncTask in button's onClick.
You can take a look at this documentation, https://developer.android.com/reference/android/os/AsyncTask.html
change your code as follows,
generate.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
generate.setVisibility(View.GONE);
//copy.setVisibility(View.VISIBLE);
webView.loadUrl("javascript:alert(saveData)"); // this function needs few seconds.
AsyncTaskRunner runner = new AsyncTaskRunner();
runner.execute();
}
});
Also you need to add the following code to get the AsyncTask works.
private class AsyncTaskRunner extends AsyncTask<String, String, String> {
#Override
protected String doInBackground(String... params) {
try {
if (getVal() == null) {
doInBackground(params);
}
} catch (Exception e) {
e.printStackTrace();
}
return "success";
}
#Override
protected void onPostExecute(String result) {
copy.setVisibility(View.VISIBLE);
}
#Override
protected void onPreExecute() {
}
#Override
protected void onProgressUpdate(String... text) {
}
}
This is the solution you need to implement and it should work as you expected.
You'll need to extend the onPageFinished method via WebViewClient:
webView.setWebViewClient(new WebViewClient()
{
#Override
public void onPageFinished(WebView view, String url) {
// Enable button
}
});
Try this
webView.setWebChromeClient(new WebChromeClient() {
#Override
public boolean onJsAlert(WebView view, String url, String message,JsResult result) {
Log.d("LogTag from js call method", message);
setJsVal(message);
result.confirm();
return true;
}
#Override
public void onProgressChanged(WebView view, int progress) {
if (progress == 100) {
generate.setVisibility(View.GONE);
copy.setVisibility(View.VISIBLE);
}
}
});
From this Activity I am sending data to another activity
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final EditText etxt=(EditText)findViewById(R.id.editText1);
final EditText etxt1=(EditText)findViewById(R.id.editText2);
Button btn=(Button)findViewById(R.id.button1);
btn.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View arg0) {
// TODO Auto-generated method stub
String s= etxt.getText().toString();
String s1= etxt1.getText().toString();
Intent intent= new Intent(getApplicationContext(),WebActivity.class);
intent.putExtra("key", s);
intent.putExtra("key1", s1);
}
});
}
}
My second activity where i receive the data from intent and want to display it in the webview.
public class WebActivity extends Activity {
WebView webView;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.webpage);
Bundle b = getIntent().getExtras();
String s = b.getString("key");
String s1=b.getString("key1");
webView=(WebView)findViewById(R.id.webview);
}
}
From some searching on Google i found that it can be accomplished by using javascript variables but I dont know how to do it. Kindly answer.
Did you checked Android WebView documentation?
// Simplest usage: note that an exception will NOT be thrown
// if there is an error loading this page (see below).
webview.loadUrl("http://slashdot.org/");
// OR, you can also load from an HTML string:
String summary = "<html><body>You scored <b>192</b> points.</body></html>";
webview.loadData(summary, "text/html", null);
// ... although note that there are restrictions on what this HTML can do.
// See the JavaDocs for loadData() and loadDataWithBaseURL() for more info.
You can simply add your data in webview by
webview.loadData("your string text", "text/html", null);
I am trying to call some javascript functions sitting in an html page running inside an android webview. Pretty simple what the code tries to do below - from the android app, call a javascript function with a test message, which inturn calls a java function back in the android app that displays test message via toast.
The javascript function looks like:
function testEcho(message){
window.JSInterface.doEchoTest(message);
}
From the WebView, I have tried calling the javascript the following ways with no luck:
myWebView.loadUrl("javascript:testEcho(Hello World!)");
mWebView.loadUrl("javascript:(function () { " + "testEcho(Hello World!);" + "})()");
I did enable javascript on the WebView
myWebView.getSettings().setJavaScriptEnabled(true);
// register class containing methods to be exposed to JavaScript
myWebView.addJavascriptInterface(myJSInterface, "JSInterface");
And heres the Java Class
public class JSInterface{
private WebView mAppView;
public JSInterface (WebView appView) {
this.mAppView = appView;
}
public void doEchoTest(String echo){
Toast toast = Toast.makeText(mAppView.getContext(), echo, Toast.LENGTH_SHORT);
toast.show();
}
}
I've spent a lot of time googling around to see what I may be doing wrong. All examples I have found use this approach. Does anyone see something wrong here?
Edit: There are several other external javascript files being referenced & used in the html, could they be the issue?
I figured out what the issue was : missing quotes in the testEcho() parameter. This is how I got the call to work:
myWebView.loadUrl("javascript:testEcho('Hello World!')");
From kitkat onwards use evaluateJavascript method instead loadUrl to call the javascript functions like below
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
webView.evaluateJavascript("enable();", null);
} else {
webView.loadUrl("javascript:enable();");
}
public void run(final String scriptSrc) {
webView.post(new Runnable() {
#Override
public void run() {
webView.loadUrl("javascript:" + scriptSrc);
}
});
}
I created a nice wrapper to call JavaScript methods; it also shows JavaScript errors in log:
private void callJavaScript(String methodName, Object...params){
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("javascript:try{");
stringBuilder.append(methodName);
stringBuilder.append("(");
for (int i = 0; i < params.length; i++) {
Object param = params[i];
if(param instanceof String){
stringBuilder.append("'");
stringBuilder.append(param.toString().replace("'", "\\'"));
stringBuilder.append("'");
}
if(i < params.length - 1){
stringBuilder.append(",");
}
}
stringBuilder.append(")}catch(error){Android.onError(error.message);}");
webView.loadUrl(stringBuilder.toString());
}
You need to add this too:
private class WebViewInterface{
#JavascriptInterface
public void onError(String error){
throw new Error(error);
}
}
And add this interface to your webview:
webView.getSettings().setJavaScriptEnabled(true);
webView.addJavascriptInterface(new WebViewInterface(), "AndroidErrorReporter");
Yes you have the syntax error. If you want to get your Javascript errors and printing statements in your logcat you must implement the onConsoleMessage(ConsoleMessage cm) method in your WebChromeClient. It gives the complete stack traces like Web console(Inspect element). Here is the method.
public boolean onConsoleMessage(ConsoleMessage cm)
{
Log.d("Message", cm.message() + " -- From line "
+ cm.lineNumber() + " of "
+ cm.sourceId() );
return true;
}
After implementation you will get your Javascript errors and print statements (console.log) on your logcat.
Modification of #Ilya_Gazman answer
private void callJavaScript(WebView view, String methodName, Object...params){
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("javascript:try{");
stringBuilder.append(methodName);
stringBuilder.append("(");
String separator = "";
for (Object param : params) {
stringBuilder.append(separator);
separator = ",";
if(param instanceof String){
stringBuilder.append("'");
}
stringBuilder.append(param.toString().replace("'", "\\'"));
if(param instanceof String){
stringBuilder.append("'");
}
}
stringBuilder.append(")}catch(error){console.error(error.message);}");
final String call = stringBuilder.toString();
Log.i(TAG, "callJavaScript: call="+call);
view.loadUrl(call);
}
will correctly create JS calls e.g.
callJavaScript(mBrowser, "alert", "abc", "def");
//javascript:try{alert('abc','def')}catch(error){console.error(error.message);}
callJavaScript(mBrowser, "alert", 1, true, "abc");
//javascript:try{alert(1,true,'abc')}catch(error){console.error(error.message);}
Note that objects will not be passed correctly - but you can serialize them before passing as an argument.
Also I've changed where the error goes, I've diverted it to the console log which can be listened by:
webView.setWebChromeClient(new CustomWebChromeClient());
and client
class CustomWebChromeClient extends WebChromeClient {
private static final String TAG = "CustomWebChromeClient";
#Override
public boolean onConsoleMessage(ConsoleMessage cm) {
Log.d(TAG, String.format("%s # %d: %s", cm.message(),
cm.lineNumber(), cm.sourceId()));
return true;
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<WebView
android:id="#+id/webView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</RelativeLayout>
MainActivity.java
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import com.bluapp.androidview.R;
public class WebViewActivity3 extends AppCompatActivity {
private WebView webView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_web_view3);
webView = (WebView) findViewById(R.id.webView);
webView.setWebViewClient(new WebViewClient());
webView.getSettings().setJavaScriptEnabled(true);
webView.loadUrl("file:///android_asset/webview1.html");
webView.setWebViewClient(new WebViewClient(){
public void onPageFinished(WebView view, String weburl){
webView.loadUrl("javascript:testEcho('Javascript function in webview')");
}
});
}
}
assets file
<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.0//EN" "http://www.wapforum.org/DTD/xhtml-mobile10.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><title>WebView1</title>
<meta forua="true" http-equiv="Cache-Control" content="max-age=0"/>
</head>
<body style="background-color:#212121">
<script type="text/javascript">
function testEcho(p1){
document.write(p1);
}
</script>
</body>
</html>
Here is an example to load js script from the asset on WebView.
Put script to a file will help reading easier
I load the script in onPageFinished because I need to access some DOM element inside the script (to able to access it should be loaded or it will be null). Depend on the purpose of the script, we may load it earlier
assets/myjsfile.js
document.getElementById("abc").innerText = "def"
document.getElementById("abc").onclick = function() {
document.getElementById("abc").innerText = "abc"
}
WebViewActivity
webView.settings.javaScriptEnabled = true
webView.webViewClient = object : WebViewClient() {
override fun onPageFinished(view: WebView?, url: String?) {
super.onPageFinished(view, url)
val script = readTextFromAsset("myjsfile.js")
view.loadUrl("javascript: $script")
}
}
fun readTextFromAsset(context: Context, fileName: String): String {
return context.assets.open(fileName).bufferedReader().use { it.readText()
}