Android WebView JS的注入
JavaScript在Web開發中非常有用,而現在越來越多的App介面也由Javascript來建立,我們需要解決一個問題:java與javaScript怎麼進行互動呢?
例如,我們可以在html中建立一個按鈕,為按鈕事件新增一個介面。然後你可以使用html按鈕跳轉到另一個Activity中。
(1)本地Html檔案
新增一個新的Assert資料夾
右鍵點選“ App ”module>new>folder>assert folder,然後將為您建立一個Assert Folder。
建立一個html檔案
之後,通過右鍵單擊Assert Folder>new >file建立一個html檔案。將html檔案命名為“sample.html”。然後按照下面的原始碼新增html檔案中的按鈕並建立javascript介面。
<html>
<head>
<style>
body{
}
input{
width: 300px;
padding:10px;
}
div#content{
padding:20px;
}
</style>
<script type="text/javascript">
function showToastA(toastmsg) {
InterfaceName.showToast(toastmsg);
}
function navigateToAnotherActivityA() {
InterfaceName.navigateToAnotherActivity();
}
</script>
</head>
<body>
<center>
<h3>Javascript bind to Android</h3>
<div>
<input type="button" value="Show Toast" onClick="showToastA('Message from Javascript')" /><br/><br />
<input type="button" value="Go to Another Activity" onClick="navigateToAnotherActivityA()" />
</div>
</center>
</body>
</html>
建立第二個Activity
建立html檔案後,轉到專案中建立一個新的活動,所以稍後你將使用html按鈕導航到這個Activity。
activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.questdot.javascriptwebviewexample.MainActivity">
<WebView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/webkit"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
</RelativeLayout>
MainActivity.java
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
WebView browser;
browser=(WebView)findViewById(R.id.webkit);
browser.getSettings().setJavaScriptEnabled(true);
browser.addJavascriptInterface(new WebAppInterface(this), "InterfaceName");
browser.loadUrl("file:///android_asset/sample.html");
}
public class WebAppInterface {
Context mContext;
WebAppInterface(Context c) {
mContext = c;
}
@JavascriptInterface
public void showToast(String toast) {
Toast.makeText(mContext, toast, Toast.LENGTH_SHORT).show();
}
@JavascriptInterface
public void navigateToAnotherActivity(){
AlertDialog.Builder alertDialog = new AlertDialog.Builder(mContext);
alertDialog.setTitle("Alert Message");
alertDialog.setMessage("You want to Go another Activity?");
alertDialog.setPositiveButton("YES",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
Intent chnIntent = new Intent(MainActivity.this, SecondActivity.class);
startActivity(chnIntent);
}
});
alertDialog.setNegativeButton("NO",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
}
});
// Showing Alert Message
alertDialog.show();
}
}
}
addJavascriptInterface
中的第二個引數“InterfaceName”應該與html檔案中的相同,否則將報錯。
還要注意的是4.2之前向webview注入的物件所暴露的介面toString沒有註釋語句@JavascriptInterface
,而4.2及以後的則多了註釋語句@JavascriptInterface
經過查官方文件所知,因為這個介面允許JavaScript 控制宿主應用程式,這是個很強大的特性,但同時,在4.2的版本前存在重大安全隱患,因為javascript 可以使用反射訪問注入webview的Java物件的public fields,在一個包含不信任內容的WebView中使用這個方法,會允許攻擊者去篡改宿主應用程式,使用宿主應用程式的許可權執行java程式碼。因此4.2以後,任何為js暴露的介面,都需要加。
(2)動態網頁
圖片點選
我們還可以對網頁中的圖片新增點選事件,獲得該圖片的資訊,比如連結等。
主要程式碼如下:
// 注入js函式監聽
private void addImageClickListner() {
// 這段js函式的功能就是,遍歷所有的img結點,並新增onclick函式,函式的功能是在圖片點選的時候呼叫本地java介面並傳遞url過去
contentWebView.loadUrl("javascript:(function(){" +
"var objs = document.getElementsByTagName(\"img\"); " +
"for(var i=0;i<objs.length;i++) " +
"{"
+ " objs[i].onclick=function() " +
" { "
+ " window.imagelistner.openImage(this.src); " +
" } " +
"}" +
"})()");
}
// js通訊介面
public class JavascriptInterface {
private Context context;
public JavascriptInterface(Context context) {
this.context = context;
}
public void openImage(String img) {
System.out.println(img);
//
Intent intent = new Intent();
intent.putExtra("image", img);
intent.setClass(context, ShowWebImageActivity.class);
context.startActivity(intent);
System.out.println(img);
}
}
// 監聽
private class MyWebViewClient extends WebViewClient {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
return super.shouldOverrideUrlLoading(view, url);
}
@Override
public void onPageFinished(WebView view, String url) {
view.getSettings().setJavaScriptEnabled(true);
super.onPageFinished(view, url);
// html載入完成之後,新增監聽圖片的點選js函式
addImageClickListner();
}
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
view.getSettings().setJavaScriptEnabled(true);
super.onPageStarted(view, url, favicon);
}
@Override
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
super.onReceivedError(view, errorCode, description, failingUrl);
}
}
// 隨便找了個帶圖片的網站
contentWebView.loadUrl("http://www.weim.me/12408.html");
// 新增js互動介面類,並起別名 imagelistner
contentWebView.addJavascriptInterface(new JavascriptInterface(this), "imagelistner");
contentWebView.setWebViewClient(new MyWebViewClient());
這種注入JS的方法基本可以說是放之四海而皆準的方法,我們還可以用此方法攔截按鈕的點選事件。
按鈕點選
我們以豆瓣登陸這個網頁為例。
檢視網頁原始碼,登陸按鈕在這段程式碼中可以找到:
<form id="lzform" name="lzform" method="post" onsubmit="return validateForm(this);" action="https://accounts.douban.com/login">
<div style="display:none;">
<img src="https://www.douban.com/pics/blank.gif" onerror="document.lzform.action='https://accounts.douban.com/login'"/>
</div>
<input name="source" type="hidden" value="book"/>
<input name="redir" type="hidden" value="https://book.douban.com/subject/1041482/"/>
<div class="item-right">
<a href="?redir=https://book.douban.com/subject/1041482/&source=book&login_type=sms">手機驗證碼登入</a>
</div>
<div class="item">
<label>帳號</label>
<input id="email" name="form_email" type="text" class="basic-input"
maxlength="60" value="郵箱/手機號/使用者名稱" tabindex="1"/>
</div>
<div class="item">
<label>密碼</label>
<input id="password" name="form_password" type="password" class="basic-input" maxlength="20" tabindex="2"/>
</div>
<!-- tsYR99_fISw | 175.191.30.52 -->
<div class="item">
<label> </label>
<p class="remember">
<input type="checkbox" id="remember" name="remember" tabindex="4"/>
<label for="remember" class="remember">下次自動登入</label>
| <a href="https://accounts.douban.com/resetpassword">忘記密碼了</a>
</p>
</div>
<div class="item">
<label> </label>
<input type="submit" value="登入" name="login" class="btn-submit" tabindex="5"/>
</div>
<div class="item item-3rd">
<label>
第三方登入:
</label>
<a target="_top" href="https://www.douban.com/accounts/connect/wechat/?from=book&redir=https%3A//book.douban.com/subject/1041482/" class="item-wechat"><img src="https://img3.doubanio.com/f/accounts/1b6cc3ca91f78cf47f41eafa91fbcd4918ae239c/pics/connect_wechat.png" title="微信"></a>
<a target="_top" href="https://www.douban.com/accounts/connect/sina_weibo/?from=book&redir=https%3A//book.douban.com/subject/1041482/&fallback=" class="item-weibo"><img src="https://img3.doubanio.com/f/accounts/e2f1d8c0ede93408b46cbbab4e613fb29ba94e35/pics/connect_sina_weibo.png" title="新浪微博"></a>
</div>
</form>
那麼要怎麼獲取這個登入按鈕的事件呢?關鍵程式碼如下:
//注入javascript到頁面
webview1.setWebViewClient(new WebViewClient() {
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
StringBuilder sb = new StringBuilder();
//獲得網頁中所有form節點的標籤陣列,這裡獲得陣列的第一個form節點
//onsubmit 事件會在表單中的確認按鈕被點選時發生。
sb.append("document.getElementsByTagName('form')[0].onsubmit = function () {");
//方法中的區域性變數,用來儲存使用者名稱和密碼
sb.append("var objPWD, objAccount;var str = '';");
//獲得form節點下所有的input節點陣列
sb.append("var inputs = document.getElementsByTagName('input');");
sb.append("for (var i = 0; i < inputs.length; i++) {");
//獲得輸入的密碼
sb.append("if (inputs[i].type.toLowerCase() === 'password') {objPWD = inputs[i];}");
//獲得輸入的使用者名稱
sb.append("else if (inputs[i].name.toLowerCase() === 'form_email') {objAccount = inputs[i];}");
sb.append("}");
sb.append("if (objAccount != null) {str += objAccount.value;}");
sb.append("if (objPWD != null) { str += ' , ' + objPWD.value;}");
sb.append("window.MYOBJECT.processHTML(str);");
sb.append("return true;");
sb.append("};");
view.loadUrl("javascript:" + sb.toString());
}
});
//建立一個由javascrip呼叫的類
class MyJavaScriptInterface
{
@JavascriptInterface
public void processHTML(String html)
{
//called by javascript
Log.d("henrytest", html);
AlertDialog.Builder builder = new AlertDialog.Builder(Main.this);
builder.setTitle("AlertDialog from app")
.setMessage(html)
.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// TODO Auto-generated method stub
}
})
.setCancelable(false).show();
}
}
//為javascript註冊介面
webview1.getSettings().setJavaScriptEnabled(true);
webview1.addJavascriptInterface(new MyJavaScriptInterface(), "MYOBJECT");