安卓学习
安卓架构
![[images/Pasted image 20240916164832.png]]
总的来说可以分为5层
linux内核层
Android 平台的基础是 Linux 内核。为整个系统提供了基础功能,如文件管理,进程调用等。
硬件抽象层
即将各种硬件抽象为一个个可以被程序使用的标准接口。
Native系统库与Android Runtime
Native系统库即使用c或者c++编写的一些库,实现了安卓的核心功能。为java提供了一些方法来进行调用,简化开发.
Android Runtime可以简单理解为特别定制的一种jvm虚拟机。
Java API 框架
即使用java对各种功能进行了再次抽象,来简化开发。
系统应用
这里既为我们平时安装使用的app了。
APP架构
![[images/Pasted image 20240916185148.png]]
- 界面层:
用于显示数据,同时当数据进行变化时,界面也要进行对应的一个变化。 - 数据层 :
包含应用的业务逻辑并公开应用数据,数据层由多个仓库组成,其中每个仓库都可以包含零到多个数据源。 - 网域层:
以简化和重复使用界面层与数据层之间的交互,封装复杂的业务逻辑。
项目结构
MyAndroidProject/
├── build.gradle
├── settings.gradle
├── gradle/
├── app/
│ ├── build.gradle
│ ├── src/
│ │ ├── main/
│ │ │ ├── java/ 存放 Java 或 Kotlin 源代码,按包名结构组织。
│ │ │ ├── res/ 存放资源文件
│ │ │ ├── AndroidManifest.xml 应用的清单文件,声明应用的组件和权限。
│ │ ├── test/
│ │ └── androidTest/ 存放 Android 仿真器和设备上运行的测试代码。
└── gradle/wrapper/
- build.gradle:项目级的 Gradle 构建文件,用于配置整个项目的构建设置。
- settings.gradle:用于包含多个模块的设置。
- gradle:存放 Gradle Wrapper 的脚本和配置文件。
我们直接使用Android Studio创建文件会发现是class MainActivity : ComponentActivity()。这里我们使用AppCompatActivity来进行代码的编写。
在build.gradle.kts文件中导入对应的依赖:
implementation(libs.androidx.appcompat)
并在AndroidManifest.xm导入对应的主题即可:
android:theme="@style/Theme.AppCompat.Light"
日志使用
为什么使用日志
很多人会非常喜欢使用System.out.println()方法来打印日志,在Kotlin中使用与之对应的是println()方法,在真正的项目开发中,是极度不建议使用System.out.println()或println()方法的,如果你在公司的项目中经常使用这两个方法来打印日志的话,就很有可能要挨骂了。那缺点在哪儿了呢?这个就太多了,比如日志开关不可控制、不能添加日志标签、日志没有级别区分等。
简单使用
Android中的日志工具类是Log(android.util.Log),这个类中提供了如下5个方法来供我们打印日志:
- Log.v()。用于打印那些最为琐碎的、意义最小的日志信息。对应级别verbose,是Android日志里面级别最低的一种。
- Log.d()。用于打印一些调试信息,这些信息对你调试程序和分析问题应该是有帮助的。对应级别debug,比verbose高一级。Log.i()。用于打印一些比较重要的数据,这些数据应该是你非常想看到的、可以帮你分析用户行为的数据。对应级别info,比debug高一级。
- Log.w()。用于打印一些警告信息,提示程序在这个地方可能会有潜在的风险,最好去修复一下这些出现警告的地方。对应级别warn,比info高一级。
- Log.e()。用于打印程序中的错误信息,比如程序进入了catch语句中。当有错误信息打印出来的时候,一般代表你的程序出现严重问题了,必须尽快修复。对应级别error,比warn高一级。
四大核心组件
在 Android 应用开发中,有四大核心组件,它们分别是:
- Activity:
代表用户界面的单一屏幕,负责与用户交互。
可以启动其他 Activity 以创建多屏幕应用。 - Service:
在后台运行的组件,不提供用户界面。
用于执行长时间运行的操作或处理后台任务,例如播放音乐或下载文件。 - Broadcast Receiver:
用于接收和处理广播消息(如系统或应用程序的事件)。
允许应用在特定事件发生时响应,例如网络状态变化或电池电量变化。 - Content Provider:
提供应用间的数据共享机制。
允许一个应用访问另一个应用的数据(如联系人、媒体等),并管理对这些数据的访问。
Activity使用
Activity是一个单一的界面,用户可以在该界面与应用进行交互。每个Activity通常代表应用中的一个屏幕。
Activity有一组生命周期方法,帮助开发者管理其状态和行为。常见的生命周期方法包括:
- onCreate(): 初始化Activity时调用,设置布局等。
- onStart(): Activity即将变为可见时调用。
- onResume(): Activity已可见并与用户交互时调用。
- onPause(): Activity即将进入不可见状态时调用。
- onStop(): Activity已不可见时调用。
- onDestroy(): Activity即将被销毁时调用,清理资源
代码示例
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.first_layout)
// 初始化界面元素
val textView: TextView = findViewById(R.id.textView)
textView.text = "欢迎来到我的应用!"
}
override fun onStart() {
super.onStart()
// Activity即将可见
}
override fun onResume() {
super.onResume()
// Activity已可见并与用户交互
}
override fun onPause() {
super.onPause()
// Activity即将不可见
}
override fun onStop() {
super.onStop()
// Activity已不可见
}
override fun onDestroy() {
super.onDestroy()
// 清理资源
}
}
<?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">
<TextView android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="24sp"
android:layout_centerInParent="true" />
</RelativeLayout>
Service使用
Service是Android中实现程序后台运行的解决方案,它非常适合执行那些不需要和用户交互而
且还要求长期运行的任务,例如:
-
下载文件
-
上传数据
-
播放音乐
-
处理网络请求
在Android的Service类中,有几个重要的方法用于管理服务的生命周期和行为: -
onCreate():当服务第一次被创建时调用。可以在此方法中进行一次性初始化操作,如创建线程或初始化资源。
-
onStartCommand():每次调用startService()时都会调用此方法。可以在这里处理启动服务时传入的Intent,并执行实际的后台任务。
-
onBind():当其他组件通过bindService()方法绑定到服务时调用。如果服务不支持绑定,可以返回null。
-
onUnbind():当所有客户端都解除绑定时调用,可以在此方法中执行清理工作。
-
onRebind():当新的客户端绑定到已经解绑的服务时调用。
-
onDestroy():当服务被销毁时调用。可以在此方法中释放资源或停止后台任务。
代码示例
class MyService : Service() {
override fun onBind(intent: Intent): IBinder {
TODO("Return the communication channel to the service.")
}
override fun onCreate() {
super.onCreate()
Log.d("MyService", "onCreate executed")
}
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
Log.d("MyService", "onStartCommand executed")
return super.onStartCommand(intent, flags, startId)
}
override fun onDestroy() {
super.onDestroy()
Log.d("MyService", "onDestroy executed")
}
}
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button android:id="@+id/startServiceBtn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Start Service" />
<Button android:id="@+id/stopServiceBtn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Stop Service" />
</LinearLayout>
class MainActivity : AppCompatActivity() {
private lateinit var startServiceBtn: Button
private lateinit var stopServiceBtn: Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
startServiceBtn = findViewById(R.id.startServiceBtn)
stopServiceBtn = findViewById(R.id.stopServiceBtn)
startServiceBtn.setOnClickListener {
val intent = Intent(this, MyService::class.java)
startService(intent) // 启动Service
}
stopServiceBtn.setOnClickListener {
val intent = Intent(this, MyService::class.java)
stopService(intent) // 停止Service
}
}
}
Broadcast Receiver使用
为Android中的每个应用程序都可以对自己感兴趣的广播进行注册,这样该程序就只会收到自己所关心的广播内容,这些广播可能是来自于系统的,也可能是来自于其他应用程序的。
- 隐式广播: 不指定发送广播的组件,只指定意图(Intent)中的动作。适用于大多数系统广播。系统或其他应用发出,接收者不明确指定。
- 显式广播: 指定特定组件(如某个Activity或Service)来接收广播。程序发出,明确指定接收者。
代码示例
动态注册
动态注册是在代码中注册BroadcastReceiver,通常在Activity或Service的生命周期方法中进行。
class TimeChangeReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
val currentTime = SimpleDateFormat("HH:mm:ss", Locale.getDefault()).format(Date())
Toast.makeText(context, "Time has changed $currentTime", Toast.LENGTH_SHORT).show()
}
}
import android.content.Intent
import android.content.IntentFilter
import android.os.Bundle
import android.widget.Button
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
private lateinit var startServiceBtn: Button
private lateinit var stopServiceBtn: Button
private lateinit var timeChangeReceiver: TimeChangeReceiver
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
startServiceBtn = findViewById(R.id.startServiceBtn)
stopServiceBtn = findViewById(R.id.stopServiceBtn)
startServiceBtn.setOnClickListener {
val intent = Intent(this, MyService::class.java)
startService(intent) // 启动Service
}
stopServiceBtn.setOnClickListener {
val intent = Intent(this, MyService::class.java)
stopService(intent) // 停止Service
}
timeChangeReceiver = TimeChangeReceiver()
}
override fun onStart() {
super.onStart()
// 注册BroadcastReceiver
val filter = IntentFilter(Intent.ACTION_TIME_TICK)
registerReceiver(timeChangeReceiver, filter)
}
override fun onStop() {
super.onStop()
// 注销BroadcastReceiver
unregisterReceiver(timeChangeReceiver)
}
}
动态注册的BroadcastReceiver可以自由地控制注册与注销,在灵活性方面有很大的优势。但
是它存在着一个缺点,即必须在程序启动之后才能接收广播,因为注册的逻辑是写在
onCreate()方法中的
静态注册
编写对应的一个逻辑:
class MyReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
Toast.makeText(context, "received in MyBroadcastReceiver",
Toast.LENGTH_SHORT).show()
}
}
注意这里需要将其注册进行AndroidManifest.xml文件中
<receiver
android:name=".MyReceiver"
android:enabled="true"
android:exported="true">
<intent-filter> <action android:name="com.example.broadcasttest.MY_BROADCAST"/>
</intent-filter></receiver>
这里的
"com.example.broadcasttest.MY_BROADCAST"
即表明他要接收的类型。
class MainActivity : AppCompatActivity() {
private lateinit var button: Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
button=findViewById(R.id.button)
button.setOnClickListener {
val intent = Intent("com.example.broadcasttest.MY_BROADCAST")
intent.setPackage(packageName)
sendBroadcast(intent)
}
}
}
这样即可实现
ContentProvider使用
Content Provider是Android应用中的一种组件,用于管理和共享应用数据。它为其他应用提供了一种安全的、标准化的方式来访问数据。、
在业务中承担的角色:
- 数据共享: 允许一个应用访问另一个应用的数据,这对于多应用之间的数据交互非常重要。
- 抽象数据存储: 提供统一的接口来访问不同类型的数据源,如SQLite数据库、文件系统或网络。
- 安全性: 通过权限控制,确保只有授权的应用才能访问特定的数据。
ContentProvider的用法一般有两种:一种是使用现有的ContentProvider读取和操作相应程
序中的数据;另一种是创建自己的ContentProvider,给程序的数据提供外部访问接口。
权限处理
在我们读取其他数据之前,我们首先要处理的是权限的问题。比如我们读取照片和电话信息时,app都会跳出对应的弹窗要求我们授予对应的权限。
流程可以大概分为:
- 在Manifest中声明权限。
- 检查是否已获得权限。
- 如果未获得权限,申请权限。
- 处理用户的授权结果。
声明权限
我们首先要声明权限,即安装时就要求拥有的权限.
<uses-permission android:name="android.permission.READ_CONTACTS" />
检查权限
这里要使用的函数为checkSelfPermission函数
public static int checkSelfPermission(@NonNull Context context, @NonNull String permission)
参数
- context: 当前的上下文,通常是 Activity 或 Application。
- permission: 要检查的权限字符串,例如 Manifest.permission.CAMERA。
返回值
- 返回 PackageManager.PERMISSION_GRANTED (值为 0) 表示权限已获得。
- 返回 PackageManager.PERMISSION_DENIED (值为 -1) 表示权限未获得。
申请权限
public void requestPermissions(@NonNull String[] permissions, int requestCode)
参数
- permissions: 一个字符串数组,包含要请求的权限。
- requestCode: 一个整型值,用于标识这个请求,以便在回调中处理。
处理授权结果
当用户做出权限选择后,系统会调用这个方法。
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults)
- requestCode: 之前传递的请求代码。
- permissions: 请求的权限数组。
- grantResults: 权限请求的结果数组,其中每个元素对应于 permissions 数组的元素,值为 PackageManager.PERMISSION_GRANTED 或 PackageManager.PERMISSION_DENIED。
完整代码示例
package com.example.permissionexample
import android.Manifest
import android.content.pm.PackageManager
import android.os.Bundle
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
class MainActivity : AppCompatActivity() {
private val REQUEST_PHONE_PERMISSION = 1
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
checkAndRequestPhonePermission()
}
private fun checkAndRequestPhonePermission() {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE) == PackageManager.PERMISSION_GRANTED) {
// 权限已获得
Toast.makeText(this, "权限已获得", Toast.LENGTH_SHORT).show()
} else {
// 请求权限
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CALL_PHONE), REQUEST_PHONE_PERMISSION)
}
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
if (requestCode == REQUEST_PHONE_PERMISSION) {
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// 用户已同意权限
Toast.makeText(this, "用户同意权限", Toast.LENGTH_SHORT).show()
} else {
// 用户拒绝权限
Toast.makeText(this, "用户不同意权限", Toast.LENGTH_SHORT).show()
}
}
}
}
读取数据
基本方法
这里我们使用的ContentResolver,来进行对应的CRUD:
- 查询数据: 使用 query() 方法从 ContentProvider 获取数据。
- 插入数据: 使用 insert() 方法向 ContentProvider 添加新数据。
- 更新数据: 使用 update() 方法修改 ContentProvider 中的现有数据。
- 删除数据: 使用 delete() 方法从 ContentProvider 移除数据。
contentResolver.query(uri, null, null, null, null)
参数:
- uri: 要查询的内容 URI。
- projection: 要返回的列名数组(可为 null,表示返回所有列)。
- selection: 选择条件(WHERE 子句,或 null)。
- selectionArgs: 选择条件的参数数组(或 null)。
- sortOrder: 排序顺序(或 null)。
对于结果的处理,我们这里使用Cursor,来实现结果的梳理:
- 遍历结果: 使用 moveToNext() 方法移动到下一行。
- 获取列值: 使用 getString()、getInt() 等方法获取特定列的数据。
- 获取列索引: 使用 getColumnIndex() 方法根据列名获取列的索引。
- 资源管理: 提供 close() 方法以释放相关资源。
代码示例
val contentResolver: ContentResolver = contentResolver //创建ContentValues 实例
val uri: Uri = ContactsContract.Contacts.CONTENT_URI //
val cursor: Cursor? = contentResolver.query(uri, null, null, null, null)
cursor?.use {
val nameIndex = it.getColumnIndex(ContactsContract.Contacts.CONTACT_STATUS)
while (it.moveToNext()) { //将光标移动到下一行。如果还有行可供访问,返回 true,否则返回 false。
val name = it.getString(nameIndex)
// 显示联系人名称
Toast.makeText(this, "联系人: $name", Toast.LENGTH_SHORT).show()
}
}
这里使用了getColumnIndex方法,使用列名ContactsContract.Contacts.CONTACT_STATUS来获取对应的一个索引。
WebView使用
WebView 是一种用于在应用程序中显示网页内容的组件,常用于 Android 和 iOS 开发。它允许开发者在本地应用程序中嵌入网页浏览功能,而不需要打开系统浏览器。
网页加载
url加载
myWebView.loadUrl("https://www.example.com")
直接加载数据
val htmlData = "<html><body><h1>Hello, WebView!</h1></body></html>"
myWebView.loadData(htmlData, "text/html", "UTF-8")
网页控制
- goBack():返回到上一个网页。
- goForward():返回到下一个网页。
- reload():重新加载当前网页。
if (myWebView.canGoBack()) {
myWebView.goBack()
}
if (myWebView.canGoForward()) {
myWebView.goForward()
}
myWebView.reload()
JavaScript使用(JSBridge)
启用 JavaScript交互权限
myWebView.settings.javaScriptEnabled = true`
安卓调用javascript
有以下两种方法可以进行执行
WebView.loadUrl(String url);
WebView.evaluateJavascript(String script, ValueCallback<String> resultCallback); // android 4.4增加
即直接加载对应的url,或者是手动传入javascript代码。
示例代码
import android.os.Bundle
import android.webkit.WebChromeClient
import android.webkit.WebView
import android.webkit.WebViewClient
import android.widget.Button
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
private lateinit var myWebView: WebView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
myWebView = findViewById(R.id.webview)
myWebView.webViewClient = WebViewClient()
myWebView.webChromeClient = WebChromeClient() // 设置 WebChromeClient
myWebView.settings.javaScriptEnabled = true // 启用 JavaScript
// 加载本地 HTML 文件
myWebView.loadUrl("file:///android_asset/sample.html")
val button: Button = findViewById(R.id.button_execute_js)
button.setOnClickListener {
// 执行 JavaScript 弹窗
executeJavaScript()
}
}
private fun executeJavaScript() {
// JavaScript 代码: 显示弹窗
val jsCode = "alert('This is a JavaScript alert!');"
myWebView.evaluateJavascript(jsCode) { result ->
// 处理返回值(如果需要)
}
}
}
src/main/assets 目录下创建一个名为 sample.html 的文件
<!DOCTYPE html>
<html>
<head>
<title>Sample Page</title>
</head>
<body>
<h1>Hello, WebView!</h1>
<p>This is a sample page.</p>
</body>
</html>
JavaScript 调用安卓
js调用java有三种方法:
- 注入API:将native对象或方法注入到WebView的window对象中(有点类似于RPC);
- 拦截urlschema:native层拦截WebView请求并做相应的操作(有点类似于jsonp);
- WebChromeClient中的onXxx()xxx方法,如:onConsoleMessage()、onJsPrompt()、onJsAlert()、onJsConfirm()等;
addJavascriptInterface注入
将本地对象暴露给 JavaScript,使 JavaScript 能够调用本地方法。
myWebView.addJavascriptInterface(MyJavaScriptInterface(), "AndroidInterface")
- 第一个参数是要暴露的对象(在此例中是 MyJavaScriptInterface 的实例)。
- 第二个参数是 JavaScript 中访问该对象时使用的名称(在此例中是 "AndroidInterface")。
创建调用对象
定义一个包含可供 JavaScript 调用的方法的 Java 类。在这些方法前加上 @JavascriptInterface 注解,以使其可被 JavaScript 调用。
class MyJavaScriptInterface(private val context: Context) {
@JavascriptInterface
fun showToast(message: String) {
Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
}
@JavascriptInterface
fun getDeviceInfo(): String {
return "Device Info: ${Build.MODEL}, ${Build.VERSION.RELEASE}"
}
}
创建接口
myWebView.addJavascriptInterface(MyJavaScriptInterface(this), "AndroidInterface")
JavaScript 调用
function callAndroid() {
AndroidInterface.showToast("Hello from JavaScript!");
var deviceInfo = AndroidInterface.getDeviceInfo();
console.log(deviceInfo);
}
示例代码
class MainActivity : AppCompatActivity() {
private lateinit var myWebView: WebView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
myWebView = findViewById(R.id.webview)
myWebView.webViewClient = WebViewClient()
myWebView.webChromeClient = WebChromeClient() // 设置 WebChromeClient myWebView.settings.javaScriptEnabled = true // 启用 JavaScript // 加载本地 HTML 文件
myWebView.loadUrl("file:///android_asset/1.html")
myWebView.addJavascriptInterface(MyJavaScriptInterface(this), "AndroidInterface")
myWebView.settings.javaScriptEnabled = true
val button: Button = findViewById(R.id.button_execute_js)
button.setOnClickListener {
// 执行 JavaScript 弹窗
executeJavaScript()
}
}
private fun executeJavaScript() {
// JavaScript 代码: 显示弹窗
val jsCode = "function callAndroid() {\n" +
" AndroidInterface.showToast(\"Hello from JavaScript!\");\n" +
" var deviceInfo = AndroidInterface.getDeviceInfo();\n" +
" console.log(deviceInfo);\n" +
"};\n" +
"callAndroid();"
myWebView.evaluateJavascript(jsCode) { result ->
// 处理返回值(如果需要)
}
}
class MyJavaScriptInterface(private val context: Context) {
@JavascriptInterface
fun showToast(message: String) {
println("ssdfsdf")
Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
}
@JavascriptInterface
fun getDeviceInfo(): String {
return "Device Info: ${Build.MODEL}, ${Build.VERSION.RELEASE}"
}
}
}
html和xml
<!DOCTYPE html>
<html>
<head>
<title>Console Log Example</title>
</head>
<body>
<h1>Welcome to WebView</h1>
</body>
</html>
<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" />
<Button android:id="@+id/button_execute_js"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Execute JavaScript"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="16dp" />
</RelativeLayout>
请求拦截
houldOverrideUrlLoading 是 Android 中 WebViewClient 类的一个方法,用于拦截和处理 WebView 中的 URL 加载请求。通过重写这个方法,开发者可以控制 URL 的加载行为,例如在特定条件下拦截请求、打开外部浏览器、或处理自定义 URL scheme。
override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean
参数:
- view: WebView: 当前的 WebView 实例。
- request: WebResourceRequest: 包含请求信息的对象,包括 URL、HTTP 方法等。
返回值:
Boolean: 如果返回 true,表示你已经处理了这个请求,WebView 不会继续加载该 URL;如果返回 false,表示 WebView 将继续加载该 URL。
class MyWebViewClient : WebViewClient() {
override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean {
val url = request.url.toString()
// 拦截特定的 URL
if (url.startsWith("http://example.com")) {
// 处理该 URL
// 例如,打开一个新的 Activity,或在 WebView 中加载
return true // 表示我们处理了这个请求
}
// 对于其他 URL 继续加载
return false
}
}
// 在你的 Activity 中设置 WebViewClient
myWebView.webViewClient = MyWebViewClient()
代码示例
import android.net.Uri
import android.os.Bundle
import android.webkit.WebResourceRequest
import android.webkit.WebView
import android.webkit.WebViewClient
import android.widget.Button
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
private lateinit var myWebView: WebView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
myWebView = findViewById(R.id.webview)
myWebView.webViewClient = object : WebViewClient() {
override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean {
val url = request.url.toString()
if (url.startsWith("native://")) {
handleNativeRequest(url)
return true // 表示我们处理了这个请求
}
return false // 继续加载其他 URL
}
}
// 启用 JavaScript
myWebView.settings.javaScriptEnabled = true
// 加载本地 HTML 文件
myWebView.loadUrl("file:///android_asset/sample.html")
val button: Button = findViewById(R.id.button_execute_js)
button.setOnClickListener {
// 触发 JavaScript 函数
myWebView.evaluateJavascript("callAndroid();", null)
}
}
private fun handleNativeRequest(url: String) {
// 解析 URL
val uri = Uri.parse(url)
val action = uri.host // 获取请求的操作
if (action == "showToast") {
val message = uri.getQueryParameter("message") // 获取参数
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
}
// 可以添加更多的处理逻辑
}
}
<!DOCTYPE html>
<html>
<head>
<title>JavaScript to Android</title>
</head>
<body>
<h1>Hello, WebView!</h1>
<button onclick="callAndroid()">Call Android Method</button>
<script>
function callAndroid() {
// 通过 native URL 调用 Android 方法
window.location.href = "native://showToast?message=Hello from JavaScript!";
}
</script>
</body>
</html>
<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" />
<Button
android:id="@+id/button_execute_js"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="调用 Android 方法"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"/>
</RelativeLayout>
回调函数
android.webkit.WebChromeClient类中定义了大量的回调函数使用,以便开发者可以处理 JavaScript 交互、文件选择、视频播放、进度更新等功能。
下文以onConsoleMessage函数进行举例
override fun onConsoleMessage(message: String, lineNumber: Int, sourceID: String)
- message: String: 控制台输出的消息内容,通常是通过 console.log() 打印的字符串。
- lineNumber: Int: 产生该消息的行号,方便开发者定位问题。
- sourceID: String: 消息来源的文件名,通常是 JavaScript 文件或 HTML 文件的名称。
代码示例
<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" />
<ProgressBar
android:id="@+id/progress_bar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true" />
</RelativeLayout>
import android.os.Bundle
import android.util.Log
import android.webkit.WebChromeClient
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
private lateinit var myWebView: WebView
private lateinit var progressBar: ProgressBar
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
myWebView = findViewById(R.id.webview)
progressBar = findViewById(R.id.progress_bar)
// 设置 WebViewClient
myWebView.webViewClient = WebViewClient()
// 设置 WebChromeClient 来捕获 console.log
myWebView.webChromeClient = object : WebChromeClient() {
override fun onConsoleMessage(message: String, lineNumber: Int, sourceID: String) {
// 捕获 console.log 输出
Log.d("WebViewConsole", "$message -- From line $lineNumber of $sourceID")
}
override fun onProgressChanged(view: WebView, newProgress: Int) {
progressBar.progress = newProgress
if (newProgress == 100) {
progressBar.visibility = View.GONE // 完成时隐藏进度条
}
}
}
// 启用 JavaScript
myWebView.settings.javaScriptEnabled = true
// 加载本地 HTML 文件
myWebView.loadUrl("file:///android_asset/sample.html")
}
}
<!DOCTYPE html>
<html>
<head>
<title>Console Log Example</title>
</head>
<body>
<h1>Welcome to WebView</h1>
<script>
console.log("Hello from JavaScript!");
console.log("This is a test message.");
// 模拟一个计算
let result = 5 + 10;
console.log("The result of 5 + 10 is: " + result);
</script>
</body>
</html>
App唤醒
url scheme
URL scheme(也称为深度链接)可以让其他应用或网页通过特定的 URL 打开你的应用。这里是基本的格式
<scheme>://<host>:<port>/<path>?<query>#<fragment>
其中的<scheme>://<host>
即为定位对应app的
首先我们配置对应的AndroidManifest.xml,来设置意图过滤器
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="test" android:host="test" />
</intent-filter>
唤醒app代码
class MainActivity : AppCompatActivity() {
private lateinit var webView: WebView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
webView = findViewById(R.id.webView)
webView.settings.javaScriptEnabled = true
// 加载 HTML 内容
webView.loadDataWithBaseURL(null, getHtmlContent(), "text/html", "UTF-8", null)
}
private fun getHtmlContent(): String {
return """
<!DOCTYPE html>
<html>
<head>
<title>Trigger Intent URI</title>
<script type="text/javascript">
function triggerIntent() {
// 触发自定义 Intent URI
window.location.href = "test://test/demo?id=2&name=jack";
}
</script>
</head>
<body>
<button onclick="triggerIntent()">Open App</button>
</body>
</html>
"""
}
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">
<WebView
android:id="@+id/webView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
被唤醒app代码
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 获取 Intent 和 URI val intent: Intent? = intent
var data: Uri? = intent?.data
if (data != null) {
// 从 URI 中提取参数
val id = data.getQueryParameter("id")
// 在 TextView 中显示提取的 ID val textView: TextView = findViewById(R.id.textView)
textView.text = "Received ID: $data"
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">
<TextView android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="18sp" />
</LinearLayout>
intent-based URI
是一种特殊格式的 URI(统一资源标识符),用于在 Android 中通过 Intent 启动应用并传递数据。它结合了标准的 URI 格式和 Intent 的描述信息。
和URL Scheme的区别是
Intent-based URI 提供了更强大的功能和灵活性,适合复杂的 Android 应用间交互。仅支持在Android平台使用。
intent://<host>/<path>?<query>#Intent;scheme=<scheme>;package=<package_name>;end;
- 主机(host):
指定要访问的主机,例如 kuaishou.com。 - 路径(path):
表示特定的资源或功能,例如 /view/c/c/c。 - 查询参数(query):
以 ? 开头,用于传递额外的数据,例如 id=223。 - Fragment(锚点)部分:
以 Intent; 开头,后面跟随 Intent 的详细描述。 - Intent 描述:
scheme=: 指定要使用的 URL scheme(如 myapp)。
package=: 指定要启动的目标应用的包名(如 com.example.targetapp)。
end;: 表示 Intent 描述的结束。
唤醒app代码
class MainActivity : AppCompatActivity() {
private lateinit var webView: WebView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
webView = findViewById(R.id.webView)
webView.settings.javaScriptEnabled = true
webView.loadDataWithBaseURL(null, getHtmlContent(), "text/html", "UTF-8", null)
}
private fun getHtmlContent(): String {
return """
<!DOCTYPE html>
<html>
<head>
<title>Trigger Intent URI</title>
<script type="text/javascript">
function triggerIntent() {
window.location.href = "intent://view/c/c/c?id=223#Intent;scheme=myapp;end;";
}
</script>
</head>
<body>
<button onclick="triggerIntent()">Open App</button>
</body>
</html>
"""
}
}
app link
App Links 提供了一种强大而灵活的方式来创建深度链接,允许用户从网页或其他应用无缝地导航到你的 Android 应用中的特定内容。
服务端配置
我们首先在网址下放好对应的配置文件
https://www.example.com/.well-known/assetlinks.json
- /.well-known/ 是一个标准的 URI 路径,用于存放各种协议的元数据。
- assetlinks.json 文件必须位于这个路径下,以便 Android 在处理 App Links 时能够自动查找。
[{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.xing.jnigo",
"sha256_cert_fingerprints":
["FA:2A:03:CB:38:9C:F3:BE:28:E3:CA:7F:DA:2E:FA:4F:4A:96:3B:DF"]
}
}]
- package_name 需要你修改成你自己的包名;
- sha256_cert_fingerprints 需要获取你的apk 的sha-256签名
客户端配置
配置对应的服务端网址
<activity android:name=".MainActivity"> <intent-filter android:autoVerify="true"> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="https" android:host="www.example.com" /> </intent-filter> </activity>
编写一个简单的Activity来接收url数据
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
private lateinit var textView: TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
textView = findViewById(R.id.textView)
// 检查 Intent 是否包含数据
intent?.let {
if (Intent.ACTION_VIEW == it.action) {
handleDeepLink(it.data)
}
}
}
private fun handleDeepLink(uri: Uri?) {
uri?.let {
val itemId = it.getQueryParameter("id")
val itemName = it.getQueryParameter("name")
textView.text = "Item ID: $itemId\nItem Name: $itemName"
}
}
}
<?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"
android:padding="16dp">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="18sp" />
</RelativeLayout>
这样访问 https://www.example.com/?id=223&name=jack 的链接。应用就会自动打开,并显示传递的 ID 和名称。
webview访问本app内其他组件
唤醒app代码
class MainActivity : AppCompatActivity() {
private lateinit var webView: WebView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
webView = findViewById(R.id.webView)
webView.settings.javaScriptEnabled = true
// 设置 WebViewClient
// webView.webViewClient = object : WebViewClient() {
// }
// 加载 HTML 内容
webView.loadDataWithBaseURL(null, getHtmlContent(), "text/html", "UTF-8", null)
}
private fun getHtmlContent(): String {
return """
<!DOCTYPE html>
<html>
<head>
<title>Trigger Intent URI</title>
<script type="text/javascript">
function triggerIntent() {
// 触发自定义 Intent URI
window.location.href = "intent://test/c/c/c?id=223#Intent;scheme=test;end;";
}
</script>
</head>
<body>
<button onclick="triggerIntent()">Open App</button>
</body>
</html>
"""
}
}
同时你会发现如果把注释去掉,你会发现此时就无法调用其他组件了。
底层原因是如果未指定webviewClient,就给android处理。android系统会在系统中寻找注册了https scheme的app。
如果你指定了webviewClient,那么就由你指定的这个webviewClient来处理,这里的webviewClien就要你自己编写了对应的Intent URI处理逻辑,才会进行打开对应的组件了。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义