安卓学习

安卓架构

![[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 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处理逻辑,才会进行打开对应的组件了。

参考链接

https://developer.android.com/codelabs/basic-android-kotlin-compose-first-app?continue=https%3A%2F%2Fdeveloper.android.com%2Fcourses%2Fpathways%2Fandroid-basics-compose-unit-1-pathway-2%23codelab-https%3A%2F%2Fdeveloper.android.com%2Fcodelabs%2Fbasic-android-kotlin-compose-first-app&%3Bhl=zh-cn&hl=zh-cn#4

posted @   Ho1d_F0rward  阅读(27)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
点击右上角即可分享
微信分享提示