当前位置: 首页 > article >正文

APK知识框架

项目结构

基本结构

image-20241211172933910

说明:

  • manifests\AndroidManifest.xml 文件,这是APP的核心配置文件,定义了包名、组件权限、等。

  • com.example.demo_study:源代码

  • om.example.demo_study (androidTest):依赖设备测试,依赖 Android 框架,适合进行 UI、数据库操作等涉及设备的测试。

  • com.example.demo_study (test):本地测试,不依赖设备,速度快主要用于逻辑验证。

  • res:资源文件,

    • drawable:存放图像资源(如 png、jpg 和xml)。比如ic_launcher_background.xml 和 ic_launcher_foreground.xml,用于定义启动图标的背景和前景。

    • mipmap:存放启动图标的多分辨率版本。比如ic_launcher/ 和 ic_launcher_round/,分别是矩形和圆形启动图标,引用drawable中的背景和前景。

    • layout:布局文件。比如activity_main.xml,

    • values:

      • colors.xml
      • strings.xml
      • themes.xml:定义应用的主题(普通主题)。
      • themes.xml (night) 是夜间模式主题。
    • xml:存放通用配置文件。比如backup_rules.xml:定义数据备份的规则。data_extraction_rules.xml:定义数据提取的规则。

    • res (generated):自动生成的资源文件,通常是 R 文件,它将资源文件映射为 Java 常量。

Gradle Scripts

  • build.gradle.kts (Project: demo_study):项目级别,定义插件等
  • build.gradle.kts (Module: :app):模块级别的 Gradle 配置文件,依赖库、编译 SDK 等。
  • proguard-rules.pro:混淆规则文件,用于优化代码并保护代码安全。
  • gradle.properties:定义全局的 Gradle 属性。
  • gradle-wrapper.properties:Gradle Wrapper 的配置文件 、Gradle路径和版本。
  • local.properties:本地配置文件,通常包含 Android SDK 的路径。
  • settings.gradle.kts:定义项目包含的模块。

更详细的内容可以在资源管理器查看:

  • demo/app/src/main/java/包名:源码
  • demo/app/src/main/res:资源
  • demo/app/src/test/
  • demo/build/outputs/apk/debug/demo.apk

内容解释

AndroidManifest.xml

<!--  xml版本和编码格式  -->
<?xml version="1.0" encoding="utf-8"?>

<!-- 命名空间,Android/tools 特定的属性,比如android:label、tools:targetApi -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    
    <!-- <application> 节点  -->
    <!--supportsRtl:从右到左的文本布局(Right to Left)-->
    <!--targetApi:表示此应用的目标 API 级别是 31(Android 12)-->
    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"		
        android:theme="@style/Theme.Demo_study"
        tools:targetApi="31">
        
        
        <!--activity节点:指明类、是否是否可以由外部应用组件启动-->
        <activity
            android:name=".MainActivity"
            android:exported="true">
            
            <!--intent-filter定义了活动可以响应的意图-->
            <intent-filter>
                <!--表明此activity是应用的入口点-->
                <action android:name="android.intent.action.MAIN" />
                <!--显示icon在设备的抽屉中-->
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>            
        </activity>        
        
    </application>
</manifest>

intent-filter标签:

  • action标签:这个组件能响应的操作。android:name为比如MAIN(应用入口)、VIEW(查看)、SEND(发送)
  • category标签:区分不同的启动场景。android:name为比如LAUNCHER(启动器中启动的活动)、DEFAULT(默认处理某种 Intent 的组件)、BROWSABLE(通过浏览器或 Web 启动)、
  • data标签:处理的指定数据类型或 URI。android:scheme:指定协议(如 http、https);android:host:指定主机名(如 www.example.com);android:path:指定路径(如 /home);android:mimeType:指定数据类型(如 image/*、text/plain)
<!-- 最常用:默认的 -->
<intent-filter>
    <!-- 最表明app入口 -->
    <action android:name="android.intent.action.MAIN" />
    <!-- 在桌面应用中 -->
    <category android:name="android.intent.category.LAUNCHER" />
</intent-filter>

<!-- 分享图片时会打开此组件 -->
<intent-filter>
    <action android:name="android.intent.action.SEND" />
    <category android:name="android.intent.category.DEFAULT" />
    <data android:mimeType="image/*" />
</intent-filter>


<!-- 自定义特定组件 -->
<intent-filter>
    <!-- am start -a com.example.demo.CUSTOM_ACTION调用 -->
    <!-- context.startActivity(new Intent("com.example.demo.CUSTOM_ACTION")) -->
    <action android:name="com.example.demo.CUSTOM_ACTION" />
    <category android:name="android.intent.category.DEFAULT" />
</intent-filter>

源码
class MainActivity : AppCompatActivity() {
    // Bundle 是一个键值对集合,可以通过此参数恢复之前的状态。
    override fun onCreate(savedInstanceState: Bundle?) {
        // 确保父类逻辑也被执行(例如,支持库的一些内部初始化操作)。
        super.onCreate(savedInstanceState)
        // 标准做法:打印日志
        Log.d("MainActivity", "MainActivity has been created!");
        setContentView(R.layout.activity_main)
    }
}
test
package com.example.demo_study;

import org.junit.Test;		// 引入Test注解
import static org.junit.Assert.*;

public class ExampleUnitTest {
    @Test		// 表明这个是一个测试类,直接可以运行
    public void addition_isCorrect() {
        // 测试一个简单的加法函数
        assertEquals(4, 2 + 2);
    }
}

androidTest
@RunWith(AndroidJUnit4::class)  // 等效于java中@RunWith(AndroidJUnit4.class)
class ExampleInstrumentedTest {
    @Test
    fun useAppContext() {
        // 获取设备 -> 获取context
        val appContext = InstrumentationRegistry.getInstrumentation().targetContext
        println("sout ${appContext.packageName}")   // 在android中推荐使用Log进行打印
         // 只能打印在android设备,不能打印在控制台
        Log.d("TestLog", "这是一个测试日志   ${appContext.packageName}");  
        assertEquals("com.example.demo_study", appContext.packageName)
    }
}
drawable

矢量图:用于图标的前景和背景。

ic_launcher_background.xml、 ic_launcher_foreground.xml

<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="108dp"
    android:height="108dp"
    android:viewportWidth="108"
    android:viewportHeight="108">
    <path
        android:fillColor="#3DDC84"
        android:pathData="M0,0h108v108h-108z" />
    <path
        android:fillColor="#00000000"
        android:pathData="M9,0L9,108"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
	<此处省略很多path>
</vector>

layout

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>
mipmap

自适应图标

<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
    <background android:drawable="@drawable/ic_launcher_background" />
    <foreground android:drawable="@drawable/ic_launcher_foreground" />
    <monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>
values

colors.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!-- 使用@color/black-->
    <color name="black">#FF000000</color>
    <color name="white">#FFFFFFFF</color>
</resources>

strings.xml

<resources>
    <string name="app_name">demo_study</string>
</resources>

themes.xml

<resources xmlns:tools="http://schemas.android.com/tools">
    <!-- Base application theme. -->
    <style name="Base.Theme.Demo_study" parent="Theme.Material3.DayNight.NoActionBar">
        <!-- Customize your light theme here. -->
        <!-- <item name="colorPrimary">@color/my_light_primary</item> -->
    </style>
	
    <!-- 引用@style/Theme.Demo_study -->
    <style name="Theme.Demo_study" parent="Base.Theme.Demo_study" />
</resources>
xml

规则文件

backup_rules.xml

<?xml version="1.0" encoding="utf-8"?>
<full-backup-content>
    <!--
   <include domain="sharedpref" path="."/>
   <exclude domain="sharedpref" path="device.xml"/>
-->
</full-backup-content>

data_extraction_rules.xml

<?xml version="1.0" encoding="utf-8"?>
<data-extraction-rules>
    <cloud-backup>
        <!-- TODO: Use <include> and <exclude> to control what is backed up.
        <include .../>
        <exclude .../>
        -->
    </cloud-backup>
    <!--
    <device-transfer>
        <include .../>
        <exclude .../>
    </device-transfer>
    -->
</data-extraction-rules>

Gradle构建

build.gradle.kts (Project: demo_study)

plugins {
    // 提供了构建、打包、资源管理等功能。数字表示AGP(Android Gradle Plugin) 版本
    // false: 表示插件在当前模块中未被应用,仅在子模块中使用,比如app
    id("com.android.application") version "8.2.1" apply false
    // 为 Android 项目添加 Kotlin 语言支持。
    id("org.jetbrains.kotlin.android") version "1.9.22" apply false

    /*
    com.android.application vs com.android.library
    application用于apk
    library用于aar(Android Archive),供其他模块使用。类似于jar
     */
}

build.gradle.kts (Module: :app)

// 已经解释过
plugins {
    id("com.android.application")
    id("org.jetbrains.kotlin.android")
}

//  Android 构建配置
android {
    //   R 文件资源的命名空间以namespace
    namespace = "com.example.demo_study"
    // SDK的API是34
    compileSdk = 34

    defaultConfig {
        // 应用的唯一标识符,作为包名在设备中安装。可以和namespace不同
        applicationId = "com.example.demo_study"
        // 最低版本,也就是我实现的版本。
        minSdk = 31
        /* 应用针对的 Android API 版本。
            如果运行在 API 34 的设备上,系统会启用 API 34 的所有行为和限制。
            如果运行在 API 33(或更低版本)设备上,系统会以兼容模式运行你的应用。
         */
        targetSdk = 34
        // apk的版本 代码和名称
        versionCode = 1
        versionName = "1.0"

        //  Android 仪器化测试。
        //  配合androidTestImplementation 引入的依赖,如 androidx.test.ext:junit 和 androidx.test.espresso。
        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
    }

    // 构建类型
    buildTypes {
        release {
            // 是否启用代码压缩
            isMinifyEnabled = false
            // 配置混淆文件
            proguardFiles(
                // 使用 Android 提供的默认混淆规则文件。
                getDefaultProguardFile("proguard-android-optimize.txt"),
                // 使用自定义混淆规则文件。
                "proguard-rules.pro"
            )
        }
    }

    // 编译配置
    compileOptions {
        // 源代码兼容性,开发时用的Java语言版本
        sourceCompatibility = JavaVersion.VERSION_1_8
        // 编译时兼容性,编译后可以运行的Java环境版本
        targetCompatibility = JavaVersion.VERSION_1_8
    }
    // kotlin配置
    kotlinOptions {
        // kotlin的JVM目标版本
        jvmTarget = "1.8"
    }
}

// 三种依赖:源码、test、androidTest
dependencies {
    // 提供 Kotlin 扩展函数,简化 Android 开发
    implementation("androidx.core:core-ktx:1.10.1")
    // 支持旧版本 Android 的现代 UI 元件。
    implementation("androidx.appcompat:appcompat:1.6.1")
    // 提供 Material Design UI 组件。
    implementation("com.google.android.material:material:1.9.0")
    // 支持 ConstraintLayout 的库。
    implementation("androidx.constraintlayout:constraintlayout:2.1.4")
    // 用于注解Test
    testImplementation("junit:junit:4.13.2")
    //  提供扩展的 JUnit 测试框架,测试组件
    androidTestImplementation("androidx.test.ext:junit:1.1.5")
    // 提供 UI 测试框架,用于模拟用户交互和验证界面行为。
    androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
}

proguard-rules.pro:需要再学习。


gradle.properties

# 多看看英文,有好处


# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
# jvm参数,统一编码
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# 是否启用并行模式,可以加快速度
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app's APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
# 指定项目使用 AndroidX 库,而不是旧的 Android 支持库(Support Library)。
android.useAndroidX=true
# Kotlin code style for this project: "official" or "obsolete":
# 指定 Kotlin 代码的风格标准,默认值为 official。
kotlin.code.style=official
# Enables namespacing of each library's R class so that its R class includes only the
# resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library
# 每个库的 R 类仅包含自身的资源,而不包含其依赖库的资源。减少每个库 R 类的大小,降低耦合
android.nonTransitiveRClass=true

gradle-wrapper.properties:

Gradle Wrapper 的配置文件,主要用于定义项目构建过程中使用的 Gradle 版本以及下载路径等信息。

#Wed Dec 11 17:04:56 CST 2024
# 定义 Gradle Wrapper 下载的 Gradle 版本存储的基础路径。
distributionBase=GRADLE_USER_HOME
# 在本地存储下载的 Gradle 发行包(distribution)的相对路径。
distributionPath=wrapper/dists
# url/版本,bin.zip:只有二进制压缩,常用。all.zip:二进制文件及 Gradle 文档和源代码。
# 团队合作,使用统一的 Gradle 版本,避免因版本差异导致构建失败。
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
#  Gradle ZIP 文件存储的基础路径和相对路径
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

settings.gradle.kts

配置项目结构、依赖解析规则及插件管理。

// 配置 Gradle 插件的管理方式。
pluginManagement {
    // 指定插件的下载源。
    repositories {
        // Google Maven 仓库,用于获取 Android 相关插件
        google()
        // Maven Central 仓库,用于获取常规插件
        mavenCentral()
        // Gradle 官方插件门户,用于获取各种 Gradle 插件
        gradlePluginPortal()
    }
}

// 设置依赖解析规则。
dependencyResolutionManagement {
    // 强制所有依赖库从全局的 repositories 中解析,保证统一管理。
    // 如果子模块中单独声明了 repositories(例如在 build.gradle(app) 中 声明repositories { jcenter()}),构建时会报错。
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        // 用于解析 Android 依赖库
        google()
        // 用于解析通用 Java/Kotlin 依赖库
        mavenCentral()
    }
}

// 定义项目的根名称,
rootProject.name = "demo_study"

// 指定项目包含的模块。表示位于项目根目录下的 app/ 文件夹中。
include(":app")


ContentProvider

通过 URI来访问和管理数据。

package com.example.demo2_study

import android.content.ContentProvider
import android.content.ContentValues
import android.database.MatrixCursor
import android.net.Uri

class MyContentProvider : ContentProvider() {
    private val data = mutableListOf("Item1", "Item2", "Item3")

    override fun onCreate(): Boolean {
        return true
    }

    override fun query(
        uri: Uri,
        projection: Array<out String>?,
        selection: String?,
        selectionArgs: Array<out String>?,
        sortOrder: String?
    ): MatrixCursor {
        // 定义返回的列
        val cursor = MatrixCursor(arrayOf("item"))
        data.forEach { item ->
            cursor.addRow(arrayOf(item))
        }
        return cursor
    }

    override fun insert(uri: Uri, values: ContentValues?): Uri? {
        val newItem = values?.getAsString("item") ?: return null
        data.add(newItem)
        return uri
    }

    override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int {
        val itemToRemove = selectionArgs?.get(0) ?: return 0
        return if (data.remove(itemToRemove)) 1 else 0
    }

    override fun update(
        uri: Uri,
        values: ContentValues?,
        selection: String?,
        selectionArgs: Array<out String>?
    ): Int {
        return 0
    }

    override fun getType(uri: Uri): String? {
        return "vnd.android.cursor.item/vnd.com.example.demo_study.provider"
    }
}

1

import android.annotation.SuppressLint
import android.content.ContentValues
import android.content.Intent
import android.location.LocationManager
import android.net.Uri
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.Button

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val uri = Uri.parse("content://com.example.demo_study.provider")

        // 插入数据
        val newItemValues = ContentValues().apply {
            put("item", "NewItem")
        }
        contentResolver.insert(uri, newItemValues)

        // 删除数据
        val deleteArgs = arrayOf("Item1")
        contentResolver.delete(uri, null, deleteArgs)

        // 查询剩余数据
        val cursor = contentResolver.query(uri, null, null, null, null)
        cursor?.let {
            while (it.moveToNext()) {
                val item = it.getString(it.getColumnIndexOrThrow("item"))
                Log.d("ContentProviderExample", "Remaining item: $item")
            }
            it.close()
        }
    }
}

1

<provider
    android:name=".MyContentProvider"
    android:authorities="com.example.contentproviderexample.provider"
    android:exported="true" />

1

四大组件

Activity

表示应用中的一个界面,用户与应用交互时的主要方式。

/*
	新建SecondActivity.kt,用于跳转目的地
*/
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button
class MainActivity : AppCompatActivity() {
    @SuppressLint("MissingInflatedId")
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val button = findViewById<Button>(R.id.mybutton)
        button.setOnClickListener(){
            // 这里的this表示MainActivity( Activity 是 Context 的子类) ,
            // 如果有歧义可以使用,this@MainActivity
            // SecondActivity::class获取 Kotlin KClass 对象。::class.java获取KClass 对象对应的 Java Class 对象。
            val intent = Intent(this, SecondActivity::class.java)
            intent.putExtra("name", "Tom")
            intent.putExtra("age", 18)
            intent.putExtra("isStudent", true)
            startActivity(intent)
        }
        
        /*
        // 等效,View.OnClickListener接口
        button.setOnClickListener(object : View.OnClickListener {
            override fun onClick(v: View?) {
                val intent = Intent(this@MainActivity, SecondActivity::class.java)
                startActivity(intent)
            }
        })
		*/        
        
    }
}

ADK在建立新的Activity时,会自动创建布局文件和在Manifest中增加相应的activity组件。

SecondActivity.kt:

class SecondActivity : AppCompatActivity() {
    @SuppressLint("SetTextI18n")
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_second)

        val name = findViewById<TextView>(R.id.textViewname)
        val age = findViewById<TextView>(R.id.textViewage)
        val isStudent = findViewById<TextView>(R.id.textViewisStudent)

        name.text = "name: ${intent.getStringExtra("name")}"
        age.text = "name: ${intent.getIntExtra("age", 0)}"
        isStudent.text = "name: ${intent.getBooleanExtra("isStudent", false)}"

    }
}

activity_second.xml:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".SecondActivity">


    <TextView
        android:id="@+id/textViewname"
        android:text="name: "
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:paddingTop="8dp"
        tools:ignore="MissingConstraints" />

    <TextView
        android:id="@+id/textViewage"
        android:text="age: "
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/textViewname"
        android:paddingTop="8dp"
        tools:ignore="MissingConstraints" />

    <TextView
        android:id="@+id/textViewisStudent"
        android:text="isStudent: "
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/textViewage"
        android:paddingTop="8dp"
        tools:ignore="MissingConstraints" />

</androidx.constraintlayout.widget.ConstraintLayout>

Service

不需要与用户直接交互,用于处理后台任务,比如下载文件。

/*
	在MainActivity.kt中startActivity(Intent(this, SecondActivity::class.java))即可调用服务
*/

import android.app.Service
import android.content.Intent
import android.os.Handler
import android.os.IBinder
import android.util.Log

class MyService : Service() {
    private val handler = Handler()
    private val runnable = object : Runnable {
        override fun run() {
            Log.d("MyService", "Service is running")	// logcat | grep MyService可以看到
            // 延迟 5 秒后重新放入消息队列,不会阻塞任何线程。
            handler.postDelayed(this, 5000)
        }
    }

   	// 开始执行服务
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        handler.post(runnable)
        return START_STICKY
    }

    override fun onDestroy() {
        super.onDestroy()
        handler.removeCallbacks(runnable)
    }

    override fun onBind(intent: Intent?): IBinder? = null
}

BroadcastReceiver

接收并响应应用或系统广播的事件

动态注册:

MainActivity:

// 创建广播
val myReceiver = MyReceiver()
// 注册广播
val filter = IntentFilter("com.example.demo2_study.MY_BROADCAST")
registerReceiver(myReceiver, filter)

val button = findViewById<Button>(mybutton)
button.setOnClickListener {
    val intent = Intent("com.example.demo2_study.MY_BROADCAST")
    intent.putExtra("message", "this is a message")
    // 发送广播
    sendBroadcast(intent)

}

MyReceiver.kt

class MyReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context?, intent: Intent?) {
        val message = intent?.getStringExtra("message")
        Toast.makeText(context, "message: $message", Toast.LENGTH_LONG).show()

    }
}

静态注册:

去掉动态注册代码中的注册。在AndroidManifest.xml中注册,其他相同。

<receiver android:name=".MyReceiver"
          android:exported="true">
    <intent-filter>
        <action android:name="com.example.demo2_study.MY_BROADCAST"/>
    </intent-filter>
</receiver>

tip:从 Android 8.0 开始,系统更注重优化电池和性能,因此很多后台广播接收被限制,静态无法生效。

ContentProvider

应用间的数据共享,提供数据操作接口(如 CRUD)。存数数据的方式:数据库、文件系统、共享内存

MainActivity.kt

class MainActivity : AppCompatActivity() {
    @SuppressLint("Range")
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val insert = findViewById<Button>(R.id.insertbutton)
        insert.setOnClickListener {
            // 使用ContentValues存储,键值对
            val values = ContentValues().apply {
                put("message", " hello, Contentprovider")
            }
            // 调用contentResolver.insert,插入指定位置的ContentValues
            contentResolver.insert(Uri.parse("content://com.example.demo_contentprovider/simple"), values)
        }
        
        
        val query = findViewById<Button>(R.id.querybutton)
        query.setOnClickListener{
            val cursor = contentResolver.query(
                // 使用uri找到SimpleContentProvider而不是通过对象
                Uri.parse("content://com.example.demo_contentprovider/simple"),
                null, null, null, null
            )
            cursor?.let {
                while (it.moveToNext()){
                    val id = it.getInt(it.getColumnIndex("id"))
                    val value = it.getString(it.getColumnIndex("value"))
                    Log.d("MainActivity", " " + id + " == " + value)

                }
                it.close()
            }
        }
    }
}

SimpleContentProvider.kt

class SimpleContentProvider : ContentProvider() {
    // 通过list存储,也可以通过数据库
    private val data = mutableListOf<String>()

    override fun onCreate(): Boolean {
        // 如需,这里可以初始化,比如数据库
        return true;
    }

    override fun query(
        uri: Uri,
        projection: Array<out String>?,
        selection: String?,
        selectionArgs: Array<out String>?,
        sortOrder: String?
    ): Cursor? {
        // 创建MatrixCursor, 用于存储查询结果。每一行数据都包含两列:id 和 value。
        val cursor = MatrixCursor(arrayOf("id", "value"))
        for ((index, item) in data.withIndex())
            // addRow表示向MatrixCursor添加数据
            cursor.addRow(arrayOf(index, item))
        return cursor
    }

    override fun getType(uri: Uri): String? {
        return "vnd.android.cursor.dir/com.example.demo_contentprovider.simple"
    }

    override fun insert(uri: Uri, values: ContentValues?): Uri? {
        // 表示从values中键值为message的值,并插入到data
        values?.getAsString("message")?.let {
            data.add(it)
        }
        // ".../${data.size-1}"表示插入的位置,并返回uri用于判定null是否成功
        return Uri.parse("content://com.example.demo_contentprovider/simple/${data.size-1}")
    }

    override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int {
        return 0
    }

    override fun update(
        uri: Uri,
        values: ContentValues?,
        selection: String?,
        selectionArgs: Array<out String>?
    ): Int {
        return 0
    }
}

布局

常用布局:LinearLayout、RelativeLayout、TableLayout、FrameLayout、GridLayout、ConstraintLayout(重要)

安卓布局其实和HTML里的盒子模型类型,也存在padding,margin等。

image-20241223165226044

常用属性:

属性描述
layout_width/layout_height宽/高
padding内边距
margin外边距
visibilityvisible:显示;
invisible:不现实但是占用空间;
gone:不显示也不占用空间
focusable焦点
enabled是否启用该控件
background背景色
id唯一标识符,用于获取空间

LinearLayout

是指按照线性顺序排

demo1

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"			// horizontal
    android:padding="16dp"
    android:gravity="center_horizontal">	// 有top、center、left、bottom | center_horizontal等属性

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello, LinearLayout!"
        android:layout_gravity="right"		// layout_gravity属性可以修改在父gravity基础上叠加
        android:textSize="20sp"
        android:layout_marginBottom="50dp"/>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button 1"
        android:layout_marginBottom="10dp"/>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button 2" />

</LinearLayout>

demo2

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:weightSum="3"			// 也可以不定义,控制会自动计算和
    android:orientation="horizontal">

    <TextView
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:text="TextView" />

    <Button
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="2"  
        android:text="Button" />

</LinearLayout>
/* 由于layout_width为0dp,所以layout_weight且为所占的比例,比如Button占 2/3
*/

RelativeLayout

相对布局是指相对于父亲或者兄弟部件进行位置

demo1

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <!-- TextView 位于顶部并居中 -->
    <TextView
        android:id="@+id/textView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="This is a TextView"
        android:textSize="18sp"
        android:layout_centerHorizontal="true"	// 水平且居中
        android:layout_alignParentTop="true"	// 与父视图的顶部对齐
        android:layout_marginTop="20dp" />

    <!-- Button 1 在 TextView 下方并居中 -->
    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button 1"
        android:layout_below="@id/textView1"	// 在属性控件的下面
        android:layout_centerHorizontal="true"
        android:layout_marginTop="20dp" />

    <!-- Button 2 在 Button 1 下方并居中 -->
    <Button
        android:id="@+id/button2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button 2"
        android:layout_below="@id/button1"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="-10dp" />	// 可以为负值

</RelativeLayout>

demo2

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <!-- TextView 居中 -->
    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="This is a centered TextView"
        android:textSize="18sp"
        android:layout_centerInParent="true" />

    <!-- Button 1 居中并放置在 TextView 的下方 -->
    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button 1"
        android:layout_below="@id/textView"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="20dp" />

    <!-- Button 2 放置在 Button 1 的右侧 -->
    <Button
        android:id="@+id/button2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button 2"
        android:layout_toRightOf="@id/button1"		// button2放在button1的右侧
        android:layout_marginLeft="20dp"			// button2左边margin为20dp
        android:layout_alignTop="@id/button1" />	// button2顶部和button1对齐
    
     <Button
        android:id="@+id/button3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button 3"
        android:layout_alignParentRight="true" // Parent有top/bottom/left/right
        android:layout_centerHorizontal="true"	// 还有属性值layout_centerVertical
        android:layout_marginTop="20dp" />

</RelativeLayout>

demo3

ConstraintLayout

<androidx.constraintlayout.widget.ConstraintLayout 	       				    				       xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
/*
    ConstraintLayout 允许你通过约束来放置视图
    android、app、tools表示 XML 命名空间,用于引用 Android 系统和工具属性。
	layout_width/layout_height: 区分match_parent和wrap_content
    tools:context:表示为MainActivity 活动设计的    
*/
    
<TextView
    android:id="@+id/hellotextvie"
    android:text="Hello World!"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"    
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent" />
	
    /*略*/
<Button
    android:id="@+id/insertbutton"
    android:text="insert"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toBottomOf="@id/hellotextvie"
    android:padding="8dp"
    tools:ignore="MissingConstraints" />
/*
    表明padding之间为8dp
    tools:ignore="MissingConstraints"表明开发者已经明确忽略警告。有无都行
    */
</androidx.constraintlayout.widget.ConstraintLayout>

部分问题

androidTest:最终如何使用?


Bundle

没成功

<!-- 浏览器输入指定网址,跳转此组件 -->
<intent-filter android:priority="1000">
    <action android:name="android.intent.action.VIEW" />
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />
    <data android:scheme="http" android:host="www.example.com" android:path="/home" />
</intent-filter>


// 验证方法
adb shell am start  -a android.intent.action.VIEW -d "http://www.example.com"
// 指定报名才成功
adb shell am start -n com.example.demo_study/.MainActivity -a android.intent.action.VIEW -d "http://www.example.com"

. androidx.test.ext:junitandroidx.test.espresso 的功能

5.1 androidx.test.ext:junit

  • 功能

    :

    • 提供扩展的 JUnit 测试框架,支持 Android 特性。
    • 允许测试 Android 组件(如 Activity)的行为。
  • 常见特性

    :

    • ActivityScenario

      :

      • 用于在测试中启动并控制 Activity 的生命周期。
    • 提供与 Android 生命周期集成的断言方法。

  • 例子

    :

    kotlin复制代码@Test
    fun testActivityLaunch() {
        val scenario = ActivityScenario.launch(MainActivity::class.java)
        assertThat(scenario.state, equalTo(Lifecycle.State.RESUMED))
    }
    

5.2 androidx.test.espresso

  • 功能

    :

    • 提供 UI 测试框架,用于模拟用户交互和验证界面行为。
  • 主要特点

    :

    • 提供简单的 API 来定位视图并执行操作(如点击、输入文本等)。
    • 支持同步操作,避免多线程问题。
  • 例子

    :

    kotlin复制代码@Test
    fun testButtonClick() {
        onView(withId(R.id.my_button))  // 定位按钮
            .perform(click())          // 模拟点击
        onView(withId(R.id.result_text)) // 检查结果视图
            .check(matches(withText("Clicked!"))) // 验证文本
    }
    


Kotlin简介

Kotlin由JetBrains公司开发。谷歌宣布其成为安卓第一开发语言。

兼容Java,可以和Java混编。

语言类型

  • 编译型

编译器直接将源代码一次性编译成与CPU相配的二进制文件,计算机可直接执行,例如C,C++。

特点:一次编译。不同操作系统(编译的后二进制文件需要调用此OS的api)和CPU(指令集)需要重新编译。

tip:编译后的文件,如果想要在另一个机器上跑,需要相同的操作系统(需要调用此系统的API),还需要相同的CPU(指令集)

  • 解释型

程序运行时,解释器会将源码一行一行实时解析成二进制再执行。例如JS,Python。

特点:安装对应的VM即可运行。效率低。

Java的语言类型:

java准确来说属于混合型语言,但更偏向于解释型。

编译:java存在JIT和AOT,JIT将可将热点代码直接编译成机器码,AOT可在安装时把代码编译成机器码

解释:java运行时需编译成class文件,JVM在解释class文件。

基本

变量、函数

fun main(args: Array<String>){
    val a = 1   // 定义变量。推导为Int(包装类)。
    var b = 2   // 定义常量
    println(myFun(4, 5))
}


fun test() {}	// 无参无返

// 下面等效
fun myFun(a: Int, b: Int): Int {	// 有参有返
    return a + b
}
fun myFun(a: Int, b: Int): Int = a + b
fun myFun(a: Int, b: Int) = a + b	// 推导类型

val:定义只读变量,可以是任何类型

const:常量,值必须在编译时已知,并且只能是基本数据类型或 String

多个变量

fun main(){
    printNums(1, 2, 3, 4)	// 打印1 2 3 4
}

fun printNums(vararg numbers : Int){
    for (number in numbers)
        println(number)
}

条件

fun getMax(a: Int, b: Int) = if (a > b) a else b	// 此处必须有else,否则无返报错

// Kotlin中==等价于Java的equals比较的时是对象里的内容
fun myIf(name: String): String{
    if (name == "D") return "不及格"
    else return "Others"	// 此处必须有else,否则无返报错
}



fun getScore(name: String) = when (name) {
    "A" -> "best"	
    "B" -> "better"
    else -> "bad"	// 此处必须有else,否则无返报错
}

// when 参数检查
fun checkNumber(num: Number) {
    when(num){
        is Int -> println("Integer")
        is Double -> println("Double")
        else -> println("others")	// 可以省略,因为无返
    }
}

循环

// 输出 0 ≤ ..  ≤ 10
val range = 0..10	// [0, 10]
val range = 0 until 10	// [0, 10)
val range = 0 until 10 step 2	// 同上,步长为2
val range = 10 downTo 0	// [10, 0]

for (i in range) {      
    println(i)
}

// Demo1: 拥有主构造器
// 如果不带open,decompile则为final,不可继承
open class Person(val name: String, val age: Int) {
    init {
        println("name = " + name + ", age = " + age)
    }

}

// 主构造。如果父类有主构造则子类必须调用。无参的主构造也是
class Student(name: String, age: Int, val number: String, val grade: Int) : Person(name, age) {
    // 次构造调用主构造
    constructor(name: String, age: Int, number: String) : this(name,age,number,0)
    constructor() : this("", 0, "", 0)
}

fun main(args: Array<String>){
    // 每次创建类都会调用Person类中的init代码块
    val  s1 = Student("Tom", 18, "123456", 6) // 打印信息。
    val  s2 = Student("Tommy", 19, "123457")
    val  s3 = Student()
    
}


// Demo2: 没有主构造器
open class Person(name: String, age: Int) {
    init {
        println("name = " + name + ", age = " + age)
    }

}
class Student : Person{
    // num不可以在其他地方使用,报错。
    constructor(name: String, age: Int, num: String) : super(name, age) // 调用父类的构造器

}
fun main(args: Array<String>){
    val  s1 = Student("Tom", 18, "123456")
}

接口

interface Study {
    fun read()
    // 和java(default,static除外)不同,可以在接口中直接实现
    fun eat() {
        println("eat...")
    }
}
// 类继承父类和接口
class Student : Person, Study{
    override fun read() {
        println("read...")
    }
}

数据类data

Idea中创建Data类

// 在java中
public class UserBean {
    
    private String id;
    private String name;
    private String pwd;

    // 拥有get/set方法,空构造器和所有属性的构造器
	// 重写equals、hashCode、toString方法
}



// 在kotlin中
// 拥有有get/set方法
class UserBean(var id: String, var name: String, var pwd: String)
// data会自动重写equals、hashCode、toString方法
data class UserBean(var id: String, var name: String, var pwd: String)
    
fun main(args: Array<String>){
    val user = UserBean("123", "Tom", "123456")    
    user.name = "Bob"	// 本质在调用set方法
    println(user.name)	// 本质在调用get方法
} 

单例object

Idea中创建object类

object Singleton {
    fun test() {
        println("test")
    }
}

// 使用
fun main(args: Array<String>){
    Singleton.test()		// 相等于java代码为Singleton.INSTANCE.test();
}

decomplie的java文件

public final class Singleton {
   @NotNull
   public static final Singleton INSTANCE;

   public final void test() {
      String var1 = "test";
      System.out.println(var1);
   }

   private Singleton() {
   }

   static {
      Singleton var0 = new Singleton();
      INSTANCE = var0;
   }
}

数组

fun main(){
    // 不可变
    val array1 = arrayOf(1, 2, 3)   // 返回Array<Int>
    val array2 = arrayOf(1, "hello", 3) // 返回Array<Any>

    val ints = array1 + 4   // 新的数组

    val toMutableList = array1.toMutableList()  // 转为MutableList也可以
    ArrayList<Int>(listOf(1, 2, 3)) // 或者使用ArrayList就可变
}

集合

// Demo1: List
val list = ArrayList<Int>();
list.add(1);
list.add(2);
val listOf = listOf<Int>(1, 2, 3)    // 不可变
println(listOf.get(1))
val mutableListOf = mutableListOf<Int>(1, 2, 3)  // 可变
mutableListOf.add(4)
println(mutableListOf.get(2))
for (i in mutableListOf) {
    println(i)
}

// Demo1 : list- 遍历
fun main(){
    val list = mutableListOf<Int>(1, 2, 3, 4)
    for ((index, item) in list.withIndex()){\
        // 输出:index = 0, item = 1 ...
        println("index = " + index + ", item = " + item)
    }
}

// Demo2: Set 相似与List



// Demo3: Map
val map = HashMap<String, String>()
map.put("1", "Tom")
map.put("2", "Alice")
map["3"] = "Bob"	// 等效上面。hashmap指向类似下标的操作
map["4"] = "Jerry"

// 等效println(map.get("3"))
println(map["3"])

val mapOf = mapOf<String, String>("1" to "Tom", "2" to "Bob")   // 不可变

val mutableMapOf = mutableMapOf<String, String>("1" to "Tom", "2" to "Bob") // 可变
mutableMapOf.put("3", "alice")

// Hashmap遍历
for (mutableEntry in mutableMapOf) {
    println(mutableEntry.toString())
}
for ((key, value) in mutableMapOf) {
    println(key + " " + value)
}

Lambda

List

val listOf = listOf<String>("a", "bb", "ccc", "dddd")
var maxLengthString = ""		// 常规方法
for (s in listOf) {
    if (s.length > maxLengthString.length) maxLengthString = s;
}
println(maxLengthString)

// 等效
var lambda = {str: String -> str.length}
println(listOf.maxByOrNull(lambda)) // maxByOrNull是一个普通方法,需要一个Lambda参数

// 等效
listOf.maxByOrNull(){str: String -> str.length} // 若Lambda为方法的最后一个参数,则可将{}提到外面
listOf.maxByOrNull {str: String -> str.length}  // 若有且仅有一个参数且是Lambda,则可去掉()
listOf.maxByOrNull {str -> str.length}  // kotlin有推导机制
listOf.maxByOrNull {it.length}  // 若Lambda只有一个参数,则可用it(iterator)替代参数名

// 类型的方法
listOf.filter { it.length > 2 }    // 过滤长度大于2的字符串
listOf.map { it.toUpperCase() }    // 字符全部转为大写
listOf.any {it.length > 3}      // 是否有长度大于3的字符串
listOf.all { it.length > 1 }    // 是否有长度均大于1的字符串

Thread

// object用于实现接口,即声明匿名内部类
Thread(object : Runnable {
    override fun run() {
        println("test")
    }
}).start()

// Runnable是Java单抽象方法接口,可对代码进行简化
Thread( Runnable {
    println("test")
}).start()

//  Runnable接口只用一个方法,使用Lambda
Thread({
    println("test")
}).start()

//  Thread只需一个参数Runnable参数,则可省略()
Thread { println("test") }.start()

只要是只接受一个函数式接口的,都可以这样写。比如button.setOnClickListener { println("test") }

空指针检查机制

在java中处理空指针

public void doStudy(Study study) {
    study.doHomework();
    study.readBooks();
}

// 上述代码时存在空指针风险的,传入null,则程序崩溃
public void doStudy(Study study) {
    if (study != null) {
        study.doHomework();
        study.readBooks();
    }
}

对于kotlin来说

// kotlin会在编译期自动检查
fun study(study: Study) {
    study.doHomework()
    study.readBooks()
}

fun main() {
    study(null) //报错
    study(Student()) //正确
}


// `?`表示可以传入null
fun study(study: Study?) {
    if (study != null) {	// 程序员必须要保证不会空
        study.doHomework()
        study.readBooks()
    }
}

// `?.`表示非空才执行
fun study(study: Study?) {
    study?.doHomework()	// study不空才执行方法
    study?.readBooks()
}
//此时靠?.则保证了study肯定不为空,才会执行let函数
fun study(study: Study?) {
    study?.let {
        //it为study
        it.doHomework()
        it.readBooks()
    }
}


// `?:` 表示a不空才为b
val c = a ?: b

// Demo: 例子
fun getTextLength(text: String?) = text?.length ?: 0
fun getTextLength(text: String?): Int {
    if (text != null) {
        return text.length
    }
    return 0
}


// 强行通过编译,就需要依靠`!!`,这时就是程序员来保证安全
fun study(study: Study?) {
    //假设此时为空抛出异常,则和java一样
    study!!.doHomework()
    study!!.readBooks()
}

// study可以为Study也可为空
var study: Study? = null

内嵌表达式

fun main(args: Array<String>){

    val name = "World"
    println("Hello $name")      // $name等效于${name}
    println("Win ${if (2 > 1) 2 else "1"}")	// ${if...}
    println("fun ${say("World")}")			// // ${fun()}
}

fun say(content: String): String = "say $content"

函数参数默认值

// Demo1: 
fun main(args: Array<String>){
    say(100)
    say(100, "World")
}

fun say(num: Int, str: String = "default") {
    println("num = $num, str = $str")
}

// Demo2: 指定形参传实参
fun main(){
    say(2, "Tom")
    say(str = "Bob")	// 不传具有默认值的
}

fun say(num: Int = 100, str: String) {	// 默认值在前面
    println("num = $num, str = $str")
}

构造器中的默认值

class Student(name: String,  age: Int, val number: String, val grade: Int) : Person(name, age){
    constructor(name: String, age: Int, number: String) : this(name, age, number, 0) {

    }
    ...
}

// 等效于上面
class Student(name: String,  age: Int, val number: String, val grade: Int = 0) : Person(name, age){
	...
}

TODO

TODO() 是一个有效的表达式,且返回类型是 Nothing,表示该代码永远不会正常返回。

public fun TODO(reason: String? = null): Nothing = throw NotImplementedError(reason)

为什么不会报错?

override fun onBind(intent: Intent): IBinder {
    TODO("Return the communication channel to the service.")
}

因为 Nothing 是所有类型的子类型,可以暂时替所有类的返回值。编译正确,运行抛出相应异常。

block

作为高阶参数

// 高阶函数参数,允许将代码块(函数或 lambda 表达式)作为参数供函数使用,实现更灵活的代码逻辑和复用。
inline fun <T> myFun(condition : Boolean, block: () -> T): T? {
    return if (condition) block() else null
}


fun main() {
    val myFun = myFun(true) {
        println("hello world")
        "abc"
    }
    println(myFun)
}

T.() 的形式就是定义扩展函数的方式。

// kotlin的Unit( = Java中的void)
// 定义一个扩展函数类型 T.() -> Unit
fun <T> myFun(obj: T, block: T.() -> Unit) {
    // 在 T 对象上调用扩展函数
    obj.block()
}

// 为 String 类型定义一个扩展函数
fun String.printToupper() {
    println(this.uppercase())
}

fun main() {
    val str = "hello"
    myFun(str){
        printToupper()	// 调用扩展函数
    }

}

//Demo2 : apply 函数本质上就是一个 T.() -> Unit 类型的函数
val mutableList = mutableListOf<Int>(1, 2, 3).apply {
    add(4)
    add(5)
}

apply源码

@kotlin.internal.InlineOnly
public inline fun <T> T.apply(block: T.() -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    // 等效this.block(),this就是调用apply的对象
    block()		
    return this
}

委托模式

fun main(){
    val consolePrint = ConsolePrinter()
    val document = Document(consolePrint)

    document.printer("hello")   // ConsolePrint : hello
    document.say("hello")       // say : hello
}

interface Printer{
    fun printer(content : String)
    fun say(content : String)
}

class ConsolePrinter : Printer{
    override fun printer(content : String) {
        println("ConsolePrint : $content")
    }

    override fun say(content: String) {
        println("say : $content")
    }
}

// Document类通过by printer委托了Printer接口的所有方法给了printer对象
class Document(printer : Printer) : Printer by printer {}

型是 Nothing,表示该代码永远不会正常返回。

public fun TODO(reason: String? = null): Nothing = throw NotImplementedError(reason)

为什么不会报错?

override fun onBind(intent: Intent): IBinder {
    TODO("Return the communication channel to the service.")
}

因为 Nothing 是所有类型的子类型,可以暂时替所有类的返回值。编译正确,运行抛出相应异常。

block

作为高阶参数

// 高阶函数参数,允许将代码块(函数或 lambda 表达式)作为参数供函数使用,实现更灵活的代码逻辑和复用。
inline fun <T> myFun(condition : Boolean, block: () -> T): T? {
    return if (condition) block() else null
}


fun main() {
    val myFun = myFun(true) {
        println("hello world")
        "abc"
    }
    println(myFun)
}

T.() 的形式就是定义扩展函数的方式。

// kotlin的Unit( = Java中的void)
// 定义一个扩展函数类型 T.() -> Unit
fun <T> myFun(obj: T, block: T.() -> Unit) {
    // 在 T 对象上调用扩展函数
    obj.block()
}

// 为 String 类型定义一个扩展函数
fun String.printToupper() {
    println(this.uppercase())
}

fun main() {
    val str = "hello"
    myFun(str){
        printToupper()	// 调用扩展函数
    }

}

//Demo2 : apply 函数本质上就是一个 T.() -> Unit 类型的函数
val mutableList = mutableListOf<Int>(1, 2, 3).apply {
    add(4)
    add(5)
}

apply源码

@kotlin.internal.InlineOnly
public inline fun <T> T.apply(block: T.() -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    // 等效this.block(),this就是调用apply的对象
    block()		
    return this
}

委托模式

fun main(){
    val consolePrint = ConsolePrinter()
    val document = Document(consolePrint)

    document.printer("hello")   // ConsolePrint : hello
    document.say("hello")       // say : hello
}

interface Printer{
    fun printer(content : String)
    fun say(content : String)
}

class ConsolePrinter : Printer{
    override fun printer(content : String) {
        println("ConsolePrint : $content")
    }

    override fun say(content: String) {
        println("say : $content")
    }
}

// Document类通过by printer委托了Printer接口的所有方法给了printer对象
class Document(printer : Printer) : Printer by printer {}

http://www.kler.cn/a/513644.html

相关文章:

  • RV1126+FFMPEG推流项目(8)AENC音频编码模块
  • macOS 安装JDK17
  • 【LC】2239. 找到最接近 0 的数字
  • BUUCTF_Web([GYCTF2020]Ezsqli)
  • GraphRAG: Auto Prompt Tuning 实践
  • python编程-OpenCV(图像读写-图像处理-图像滤波-角点检测-边缘检测)边缘检测
  • AI视频生成技术迎来突破性发展期
  • 大一计算机的自学总结:归并排序及归并分治
  • Git版本控制 – 使用HEAD
  • 【三维分割】Gaga:通过3D感知的 Memory Bank 分组任意高斯
  • Arthas工具详解
  • 03垃圾回收篇(D1_垃圾收集器算法底层导论)
  • 解锁Java正则表达式替换的高级玩法
  • 【矩形拼接——分类讨论】
  • 蓝桥与力扣刷题(73 矩阵置零)
  • Maven多环境打包方法配置
  • SpringBoot拦截器
  • 专题三_穷举vs暴搜vs深搜vs回溯vs剪枝_全排列
  • 【王树森搜索引擎技术】概要04:搜索引擎的链路(查询词处理、召回、排序)
  • Linux的软件包管理器
  • 《Effective Java》学习笔记——第1部分 创建对象和销毁对象的最佳实践
  • Redis使用基础
  • TCP如何保证安全可靠?
  • 我国的金融组织体系,还有各大金融机构的分类,金融行业的组织
  • 【Excel】【VBA】Reaction超限点筛选与散点图可视化
  • 【线性代数】基础版本的高斯消元法