WebView 专题
第一个WebView程序:加载远程网址
-
Layout添加WebView组件;
<WebView android:id="@+id/webView_first" android:layout_width="match_parent" android:layout_height="match_parent"/>
-
初始化组件,加载Url;
public class FirstWebViewActivity extends AppCompatActivity{ private WebView webView_first; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_first_web_view); webView_first=findViewById(R.id.webView_first); webView_first.loadUrl("https://www.baidu.com/"); } @Override protected void onDestroy() { super.onDestroy(); webView_first.removeAllViews(); webView_first.destroy(); } }
-
Manifest文件添加网络访问权限
<uses-permission android:name="android.permission.INTERNET" />
-
出现错误:
NetworkSecurityConfig com.hymy.webviewstarter D No Network Security Config specified, using platform default Denied starting an intent without a user gesture, URI
解决方法:
res/xml下添加 network_security_config.xml 文件:
<?xml version="1.0" encoding="utf-8"?> <network-security-config> //默认配置:允许明文通信 <base-config cleartextTrafficPermitted="true" /> </network-security-config>
在AndroidManifest.xml中引用
<application android:networkSecurityConfig="@xml/network_security_config" ...
-
出现错误:
访问bing.com无法加载封面图片:
The resource xxx was preloaded using link preload but not used within a few seconds from the window's load event. Please make sure it has an appropriate `as` value and it is preloaded intentionally.
解决方法:
webView_first=findViewById(R.id.webView_first); //启用JavaScript WebSettings webSettings = webView_first.getSettings(); webSettings.setJavaScriptEnabled(true); webView_first.loadUrl("https://cn.bing.com/?mkt=zh-CN");
-
下载:在bing搜索网站首页,好看的壁纸,点击可以下载图片,将图片下载到 SD 卡下的Downloader目录下
参考:https://cloud.tencent.com/developer/article/1742327
-
将 JavaScript代码绑定到Android代码
js调用Android代码中的方法
新建 WebAppInterface 类
/** * @Author : alex * @Date : on 2023/11/14 09:21. * @Description :描述 */ public class WebAppInterface { Context mContext; /** * 初始化接口,并设置context * @param c */ WebAppInterface(Context c){ mContext=c; } /** * 在Web页面显示消息提示 * @param toast */ @JavascriptInterface public void showToast(String toast){ Toast.makeText(mContext,toast,Toast.LENGTH_SHORT).show(); } }
webview 绑定接口:
webView_first.addJavascriptInterface(new WebAppInterface(this),"Android");
html代码示例:
<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Bootstrap demo</title> <link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.3.1/css/bootstrap.min.css" rel="stylesheet"> </head> <body> <h1>Hello, world!</h1> <input type="button" value="Say hello" onClick="showAndroidToast('Hello Android!')" /> <script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.3.1/js/bootstrap.bundle.min.js"></script> <script type="text/javascript"> function showAndroidToast(toast) { Android.showToast(toast); } </script> </body> </html>
-
webview中的跳转连接
参考:https://www.digitalocean.com/community/tutorials/android-webview-example-tutorial
当 html 中有跳转链接的时候,可以直接阻止、选择性阻止在我们的App跳转、或者可以打开系统浏览器,加载外部链接。
webView_first.setWebViewClient(new MyWebViewClient()); private class MyWebViewClient extends WebViewClient { @Override public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { Log.i("FirstWebViewActivity",request.getUrl().getHost()); if ("192.168.96.108".equals(request.getUrl().getHost())) { // This is my website, so do not override; let my WebView load the page return false; } // Otherwise, the link is not for a page on my site, so launch another Activity that handles URLs Intent intent = new Intent(Intent.ACTION_VIEW, request.getUrl()); startActivity(intent); return true; } }
-
webview加载本地网页,实现 sqlite 数据库增删改查
使用的库 gson:
implementation 'com.google.code.gson:gson:2.8.5'
首先新建assets文件夹,android默认工程没有创建。
编写 DBHelper ,定义数据库名,在 onCreate 中初始化数据库:
public class MyDatabaseHelper extends SQLiteOpenHelper { public static final String DATABASE_NAME = "student.db"; public static final int DATABASE_VERSION =1; public MyDatabaseHelper(Context context){ super(context,DATABASE_NAME,null,DATABASE_VERSION); } @Override public void onCreate(SQLiteDatabase db) { // 数据库首次创建时调用,执行创建表等语句 // 创建student表 db.execSQL("CREATE TABLE student (" + "id INTEGER PRIMARY KEY AUTOINCREMENT," + "name TEXT," + "age INTEGER," + "birth TEXT)"); // 如果需要初始化数据可以在这里插入: db.execSQL("INSERT INTO student (name, age, birth) " + "VALUES ('张三', 18, '1980-01-01')"); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { // 数据库版本更新时调用 } }
然后定义 MyApplication,在程序启动的时候初始化数据库:
public class MyApplication extends Application { @Override public void onCreate() { super.onCreate(); //检测数据库是否存在,不存在则创建数据库 Context context = getApplicationContext(); MyDatabaseHelper dbHelper = new MyDatabaseHelper(context); SQLiteDatabase db = dbHelper.getWritableDatabase(); } }
定义数据增、查接口类,供H5中调用:
public class JsDBInterface { private SQLiteDatabase db; public JsDBInterface(Context context){ // 在构造函数中打开数据库 MyDatabaseHelper helper = new MyDatabaseHelper(context); db = helper.getWritableDatabase(); } /** * 插入数据 * @param name * @param age * @param birth */ @JavascriptInterface public void insertData(String name,int age,String birth){ //检查参数 if(name==null||name.isEmpty()){ return; } //插入数据 ContentValues values = new ContentValues(); values.put("name",name); values.put("age",age); values.put("birth",birth); db.insert("student",null,values); Log.i("插入数据",name); } /** * 查询数据 * @return */ @JavascriptInterface public String getAllStudents(){ List<Student> students = new ArrayList<>(); //查询数据库中的所有学生 Cursor cursor = db.query("student",null,null,null,null,null,null); //获取所有列 // String[] cols = cursor.getColumnNames(); // int nameIndex=-1; while (cursor.moveToNext()){ int columnIndex1=cursor.getColumnIndex("id"); int columnIndex2=cursor.getColumnIndex("name"); int columnIndex3=cursor.getColumnIndex("age"); int columnIndex4=cursor.getColumnIndex("birth"); int id=0;String name="null";int age=0;String birth="0000-00-00"; if(columnIndex1>=0){ id = cursor.getInt(columnIndex1); } if(columnIndex2>=0){ name=cursor.getString(columnIndex2); } if(columnIndex3>=0){ age = cursor.getInt(columnIndex3); } if(columnIndex4>=0){ birth=cursor.getString(columnIndex4); } Student stu = new Student(id,name,age,birth); students.add(stu); } cursor.close(); Gson gson = new Gson(); String json = gson.toJson(students); return json; } }
数据实体类:
public class Student { private int id; private String name; private int age; private String birth; public int getId() { return id; } public Student(int id,String name, int age, String birth) { this.id=id; this.name = name; this.age = age; this.birth = birth; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getBirth() { return birth; } public void setBirth(String birth) { this.birth = birth; } }
在 Activity 中注册接口:
webView.addJavascriptInterface(new JsDBInterface(MainActivity.this),"JsDBInterface"); 加载本地网页: private String localUrl="file:///android_asset/index.html"; webView_first.loadUrl(localUrl);
HTML 使用了vue,调用代码如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <script src="./vue.min.js"></script> </head> <body> <div id="app"> <h2>{{message}}</h2> <ul> <li v-for="user in users" :key="user.id">{{user.name}}</li> </ul> <button id="btnInsertStudent" onclick="addStudent()">新增学生</button> <button id="btnGetAllStudents" onclick="getAllStudent()">查询所有学生</button> <ul id='studentList'></ul> </div> <script> var app = new Vue({ el:'#app', data(){ return { message:'Hello Vue!' } }, mounted(){ } }) function addStudent(){ JsDBInterface.insertData("小明",18,"2010-01-01"); alert('添加成功') } function getAllStudent(){ var data = JsDBInterface.getAllStudents(); var students = JSON.parse(data); var studentTxt=''; for(var i = 0; i < students.length; i++) { var stu = students[i]; studentTxt += '<li>' + stu.name + ',' + stu.age + ',' + stu.birth + '</li>'; } var ul = document.getElementById('studentList'); ul.innerHTML = studentTxt; } </script> </body> </html>
结果:
-
webview h5 alert 不运行
参考:https://copyprogramming.com/howto/modify-alert-title-javascript-in-android-webview
最简单的处理方法是:添加下面的代码
webView_first.setWebChromeClient(new WebChromeClient());
但是上面的标题会显示网址,可以用下面的方法进行修改:
webView_first.setWebChromeClient(new WebChromeClient(){
@Override
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
AlertDialog dialog = new AlertDialog.Builder(view.getContext())
.setTitle("提示")
.setMessage(message)
.setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// do nothing
}
}).create();
dialog.show();
result.confirm();
return true;
}
});
修改后:
此外如果对 alert, prompt, confirm统一进行修改,可以使用下面代码:
webView_first.setWebChromeClient(new WebChromeClient(){
@Override
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
new AlertDialog.Builder(view.getContext())
.setTitle("alert")
.setMessage(message)
.setPositiveButton("确定", (DialogInterface dialog, int which) -> result.confirm())
.setOnDismissListener((DialogInterface dialog) -> result.confirm())
.create()
.show();
return true;
}
@Override
public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
new AlertDialog.Builder(view.getContext())
.setTitle("confirm")
.setMessage(message)
.setPositiveButton("确定", (DialogInterface dialog, int which) -> result.confirm())
.setNegativeButton("取消", (DialogInterface dialog, int which) -> result.cancel())
.setOnDismissListener((DialogInterface dialog) -> result.cancel())
.create()
.show();
return true;
}
@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
final EditText input = new EditText(view.getContext());
input.setInputType(InputType.TYPE_CLASS_TEXT);
input.setText(defaultValue);
new AlertDialog.Builder(view.getContext())
.setTitle("prompt")
.setMessage(message)
.setView(input)
.setPositiveButton("确定", (DialogInterface dialog, int which) -> result.confirm(input.getText().toString()))
.setNegativeButton("取消", (DialogInterface dialog, int which) -> result.cancel())
.setOnDismissListener((DialogInterface dialog) -> result.cancel())
.create()
.show();
return true;
}
});