第一个安卓项目

环境:

  • Android Studio 2024.2
  • Java 21.0.5
  • SDK:API 24
  • 虚拟机:Pixel 9 Pro API 27 / Android 8.1

项目目录

新建项目,可得到目录内容:

app
├── manifests
│ └── AndroidManifest.xml
├── java
│ ├── com.dta.first
│ │ └── MainActivity.java
│ ├── com.dta.first (androidTest)
│ ├── com.dta.first (test)
│ └── java (generated)
├── res
│ ├── drawable
│ ├── layout
│ ├── mipmap
│ ├── values
│ └── xml
└── res (generated)
Gradle Scripts

manifests目录

其中manifest文件用来存放一些安卓应用程序的配置文件,通常每个安卓应用都包含AndroidManifest.xml,是一个全局配置文件,会在内部定义一些组件、权限、图标等配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<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.FirstApplication"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>

</manifest>

其中application节点包含了应用项目中application组建中的根结点

配置 含义
android:allowBackup=”true” 允许备份,默认为true
android:icon=”@mipmap/ic_launcher” 应用程序图标
android:label=”@string/app_name” 应用程序的名字
android:roundIcon=”@mipmap/ic_launcher_round” 圆形图标
android:theme=”@style/Theme.FirstApplication” 应用主题

activity节点(安卓里重要的组成部分),默认界面

1
2
3
4
# 主界面
<action android:name="android.intent.action.MAIN" />
# 启动界面
<category android:name="android.intent.category.LAUNCHER" />

java目录

然后还有java目录,其下存放的就是我们的代码,创建时我选择了一个默认界面,因此运行后可以得到:

image-20241226222544240

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.dta.first;

import android.os.Bundle;

import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;

public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_main);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
}
}

可以看到默认的代码,其中继承了一个AppCompatActivity类,重写了一个onCreate方法,onCreate就是应用程序启动后打开第一个界面时会回调该方法。

res目录

drawable:存放应用所需的图片资源(如 PNG、JPG 等)。

layout:存放 XML 格式的布局文件,定义了应用的 UI 结构。

mipmap:存放应用图标(通常是不同分辨率的图标文件)。

values:存放资源值文件(如 strings.xml、colors.xml 等),用于定义字符串、颜色、样式等资源。

xml:存放自定义的 XML 配置文件(如导航、网络安全配置等)。

**res (generated)**:这是由工具生成的资源文件夹,内容通常是编译时生成的。

Gradle Scripts目录

其下可以配置一些需要打包或者要引入一些外部的类库,或指定gradle版本等。包含项目的构建脚本,主要用于定义依赖关系、构建配置和任务。

编写应用程序

布局

先看看默认的布局:

image-20241226224213093

此时,我希望这个App最上方,显示出来我的项目名称,和他的logo,就可以通过调整themes下的style中继承的类即可修改:

image-20241226224558780

image-20241226224606262

在layout中可以修改页面的样式,TextView中就是页面中间默认的那个存放了Hello World的文本框,我们也可以在其中加一些内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 <TextView
android:id="@+id/sample_text"
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" />
<Button
android:id="@+id/btn_change"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="change"
tools:ignore=",MissingConstraints" />
</Button>

可以看到,我加了一个按钮组件,id为btn_change,其中的text设置为change:

image-20241226225745822

java代码

默认代码中存在以下:

1
setContentView(R.layout.activity_main);

这个就是显示系统默认的布局文件,也就是默认关联到layout下的activity_main。此处也可以在onCreate下通过关联id操作组件:

为了顺利绑定,需要设置botton的id:

1
2
3
<Button
android:id="@+id/btn_change"
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class MainActivity extends AppCompatActivity {

// 声明两个成员变量,用于引用按钮和文本框控件
private Button btn_change; // “change” 按钮
private TextView tv_helloworld; // “Hello World!” 文本框

@Override
protected void onCreate(Bundle savedInstanceState) {
// onCreate 是 Activity 生命周期中的第一个被调用的方法
// 在这里我们进行初始化操作
super.onCreate(savedInstanceState);

// 设置当前 Activity 使用的布局文件为 activity_main.xml
setContentView(R.layout.activity_main);

// 通过 findViewById 方法查找布局中的控件,获取按钮对象引用
btn_change = findViewById(R.id.btn_change);

// 获取 TextView 对象引用
tv_helloworld = findViewById(R.id.sample_text);

// 为按钮设置点击事件监听器
btn_change.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 当按钮被点击时,修改 TextView 的文字内容
tv_helloworld.setText("我被点击了~");
}
});
}
}

功能简述:点击btn后,textview中内容变成我被点击了~:

image-20241226230903567

常见组件

四大组件

  • Activity(活动)

    Activity是Android应用的核心组件之一,负责管理用户界面和用户交互。每个Activity代表一个屏幕或界面。每个Activity之间通过Intent进行通信。

    主要功能:管理UI,处理用户输入,控制界面跳转和生命周期。

    生命周期:包括onCreate、onStart、onResume等方法,用于管理从创建到销毁的整个过程。

    关键点

    1. 每个Activity都有一个Intent,用于启动自身或其他Activity。

    2. startActivityForResult可以在不同Activity之间返回数据。

    Activity的四种基本状态:

    (1)Active/Running:一个新的Activity启动入栈后,会显示在屏幕最前端,处于栈顶的页面为可见并可与用户交互的激活状态,叫做活动状态或者运行状态。

    (2)Paused:当Activity失去焦点,被一个非全屏幕的Activity或者一个透明的Activity被放置在栈顶时,被叫做暂停状态。 但是此时依旧存在所有的状态,依然可见,但是已失去了焦点故不可与用户交互。

    (3)Stopped:如果一个Activity被另外的Activity完全覆盖掉,叫做停止状态。依然保持所有的状态和成员信息,但是不再可见。

    (4)Killed:如果一个Activity是Paused或者Stopped状态,系统可以将该Activiy从内存中删除。

    当一个Activity状态被创建、销毁或者启动另一个Activity时,它在这四种状态之间进行转换,这种转换的发生依赖于用户程序的动作。

  • BroadCastReceiver(广播接收器)

    BroadCastReceiver用于接收系统或应用发出的广播事件,并根据事件触发相应逻辑。用于不同组件之间通信(包括应用内/不同应用之间);用于与Android系统在特定情况下的通信(如当电话呼入时、网络可用时);还可用于多线程通信。

    主要功能:监听系统或自定义事件,例如电量变化、网络状态、电话呼入等。

    注册方式

    1. 静态注册:在AndroidManifest.xml中声明。应用即使未运行,系统广播依然能触发。

    2. 动态注册:在代码中使用registerReceiver()注册,随应用运行和销毁。

    典型广播

    1. 系统广播:android.intent.action.BOOT_COMPLETED(设备启动完成)

    2. 自定义广播:应用内部自定义事件广播。

    实现原理:

    Android中的广播使用了设计模式中的观察者模式:基于消息的发布/订阅事件模型

    模型中有三个角色:

    1. 消息订阅者(广播接收者)
    2. 消息发布者(广播发布者)
    3. 消息中心(AMS,Activity Manager Service)

    过程:

    1. 广播接收者通过Binder机制在AMS注册
    2. 广播发送者通过Binder机制向AMS发送广播
    3. AMS根据广播发送者要求,在已注册列表中,寻找合适的广播接收者(寻找依据:IntentFilter/Permission)
    4. AMS将广播发送到合适的广播接收者相应的消息循环队列中
  • Service(服务)

    Service用于在后台执行长时间运行的任务,无需与用户直接交互。是一种长生命周期的,没有可视界面,运行与后台的一种服务程序。一个Service可以和多个客户绑定,当多个客户都解除绑定后,系统会销毁Service。

    主要功能:进行耗时操作(如下载、播放音乐)或持续性任务(如后台同步)。

    类型

    前台服务:用户可感知,带有通知栏提示。

    后台服务:用户不可感知,在后台运行。

    生命周期

    1. onStartCommand:启动服务的方法,常用于执行任务。

    2. onBind:绑定服务,提供与其他组件的通信接口。

    3. onDestroy:销毁服务,释放资源。

    关键点:Android 8.0(API 26)后,限制后台服务,需要使用前台服务或JobScheduler。

  • Content Provider(内容提供者)

    ContentProvider用于在不同应用间安全地共享数据。

    主要功能:提供统一的接口,允许其他应用访问或修改数据。

    访问方式:通过URI访问,支持CRUD(增删改查)操作。

    典型应用:访问联系人数据、共享媒体文件或应用数据库。

    核心方法

    1. query:查询数据。
    2. insert:插入数据。
    3. update:更新数据。
    4. delete:删除数据。

    权限控制:通过<permission>标签限制外部访问,防止数据泄露。

实操部分——Activity的使用

在 Android 应用开发中,Activity 是最基本的组件之一,代表应用的一个界面。在本节中,我们将通过三个类:MainActivity、SubActivity02 和 SubActivity03,实战演示:

  • 如何启动一个新的 Activity;
  • 如何从另一个 Activity 返回数据;
  • 如何使用 ActivityResultLauncher 实现更现代的返回机制

一、启动另一个 Activity(标准跳转)

在 MainActivity 中点击按钮 btn_start,通过 Intent 启动 SubActivity02:

1
2
3
4
5
6
7
8
btn_start = findViewById(R.id.btn_startActicity);
btn_start.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this, SubActivity02.class);
startActivity(intent);
}
});

这段代码使用了传统方式 startActivity(intent) 进行跳转。被启动的 SubActivity02:

1
2
3
4
5
6
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sub02);
Log.i("ttttage", "SubActivity02 onCreate");
}

在 Logcat 中会输出日志:

1
I/ttttage: SubActivity02 onCreate

二、启动 Activity 并获取结果(返回数据)

除了普通跳转,有时候我们还需要 启动一个 Activity 并在它关闭后获取结果,这时候可以使用:

方法一(推荐):ActivityResultLauncher

1
2
3
4
5
6
7
8
ActivityResultLauncher launcher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
new ActivityResultCallback<ActivityResult>() {
@Override
public void onActivityResult(ActivityResult o) {
Log.i("ttttag", o.getData().getStringExtra("key1"));
}
});

当点击 btn_startForResult 按钮时,会启动 SubActivity03:

1
2
3
4
5
6
7
8
btn_startForResult = findViewById(R.id.btn_startActicityGetResult);
btn_startForResult.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this, SubActivity03.class);
launcher.launch(intent);
}
});

在 SubActivity03 中,我们模拟用户点击一个 TextView 之后返回数据:

1
2
3
4
5
6
7
8
9
10
11
tv = findViewById(R.id.tv_setResult);
tv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent();
intent.setClass(SubActivity03.this, MainActivity.class);
intent.putExtra("key1", "这是结果1,来自SubActivity03");
setResult(234, intent);
SubActivity03.this.finish();
}
});

结果在 MainActivity 的回调中处理,如前所述,直接打印:

1
I/ttttag: 这是结果1,来自SubActivity03

方法二(旧方法):startActivityForResult + onActivityResult

1
2
3
4
5
6
7
8
9
10
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);

if (data == null) return;
if (resultCode == requestCode && requestCode == 234) {
String ret = data.getStringExtra("key1");
Log.d("ttttag", ret);
}
}

虽然可以使用,但 Google 已经建议使用 ActivityResultLauncher 来替代这种方式

完整代码

MainActivity.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
package com.dta.test02;

import androidx.activity.result.ActivityResult;
import androidx.activity.result.ActivityResultCallback;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;

import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;

/**
* 演示 Activity 启动与获取返回结果的两种方式:
* 1. 普通启动(不关心返回值)
* 2. 启动并获取返回值(新 API 与传统 API 对比)
*/
public class MainActivity extends AppCompatActivity {
Button btn_start, btn_startForResult;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

// ===== 普通启动 Activity =====
btn_start = findViewById(R.id.btn_startActicity);
btn_start.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 创建跳转到 SubActivity02 的 Intent
Intent intent = new Intent(MainActivity.this, SubActivity02.class);
// 启动 Activity(不接收返回值)
startActivity(intent);
}
});

// ===== 新方式启动并接收返回结果 =====
// 通过 registerForActivityResult 注册一个 launcher,用于启动 Activity 并接收返回值
ActivityResultLauncher<Intent> launcher = registerForActivityResult(
// 指定启动类型为 StartActivityForResult
new ActivityResultContracts.StartActivityForResult(),
// 回调处理返回结果
new ActivityResultCallback<ActivityResult>() {
@Override
public void onActivityResult(ActivityResult result) {
if (result.getData() != null) {
String ret = result.getData().getStringExtra("key1");
Log.i("ttttag", "新方式返回数据:" + ret);
}
}
});

// 启动 SubActivity03,并使用 launcher 接收它的返回数据
btn_startForResult = findViewById(R.id.btn_startActicityGetResult);
btn_startForResult.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this, SubActivity03.class);
launcher.launch(intent); // 使用新 API 启动
}
});
}

// ===== 旧方式(onActivityResult)获取返回值 =====
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);

// 防止空指针
if (data == null) return;

// 判断 requestCode 与 resultCode 是否匹配
if (resultCode == requestCode && requestCode == 234) {
String ret = data.getStringExtra("key1");
Log.d("ttttag", "旧方式返回数据:" + ret);
}
}

// 示例:调用 C/C++ 代码的本地方法
public native String stringFromJNI();
}

SubActivity02.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.dta.test02;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;

import androidx.annotation.Nullable;

public class SubActivity02 extends Activity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sub02);

Log.i("ttttage", "SubActivity02 onCreate");
}
}

SubActivity03.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package com.dta.test02;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;

import androidx.annotation.Nullable;

public class SubActivity03 extends Activity {
TextView tv;

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sub03);

// 点击文本返回结果
tv = findViewById(R.id.tv_setResult);
tv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent resultIntent = new Intent();
resultIntent.putExtra("key1", "这是结果1,来自SubActivity03");
setResult(234, resultIntent);
finish();
}
});
}
}

效果

主页面:

image-20250730014249849

点击第一个按钮,跳转到Activity02,且能在日志中看到SubActivity02 onCreate:

image-20250730014317278

image-20250730014411952

点击第三个按钮,跳转到Activity03,且点击03页面中的textview,会关闭03页面,并返回Main页面,且带回数据到log:

image-20250730014615650

image-20250730014645778

回到主页面,03页面关闭,并发送了结果回到主页面:

image-20250730014659419

实操部分——Service的使用

MainActivity.java代码节选

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
public class MainActivity extends AppCompatActivity {
// 四个按钮:分别用于启动/停止普通服务,绑定/解绑绑定式服务
Button btn_startService, btn_stopService;
Button btn_bindService, btn_unbindService;

MyBindService.MyBinder myBinder;

// ===== 启动普通服务 =====
btn_startService = findViewById(R.id.btn_startService);
btn_startService.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 创建启动 MyService01 的 Intent
Intent intent = new Intent(MainActivity.this, MyService01.class);
// 调用 startService() 启动服务
// 运行效果:MyService01 会在后台运行,即使 Activity 关闭也继续执行
startService(intent);
}
});

// ===== 停止普通服务 =====
btn_stopService = findViewById(R.id.btn_stopService);
btn_stopService.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 创建停止 MyService01 的 Intent
Intent intent = new Intent(MainActivity.this, MyService01.class);
// 调用 stopService() 停止服务
// 运行效果:后台的 MyService01 会被销毁,不再执行任务
stopService(intent);
}
});

// ===== 绑定式服务的连接回调对象 =====
ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// 当绑定成功时调用
// 如果返回的 IBinder 是 MyBindService.MyBinder 类型,就保存引用
if (service instanceof MyBindService.MyBinder) {
myBinder = (MyBindService.MyBinder) service;
}
// 运行效果:可以通过 myBinder 调用绑定服务里暴露的方法
}

@Override
public void onServiceDisconnected(ComponentName name) {
// 当绑定的服务被意外断开(如崩溃)时调用
}
};

// ===== 绑定绑定式服务 =====
btn_bindService = findViewById(R.id.btn_bindService);
btn_bindService.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 创建绑定 MyBindService 的 Intent
Intent intent = new Intent(MainActivity.this, MyBindService.class);
// 调用 bindService() 绑定服务,并在绑定成功后自动创建(BIND_AUTO_CREATE)
// 运行效果:Activity 可以直接调用 MyBindService 提供的方法
bindService(intent, conn, Context.BIND_AUTO_CREATE);
}
});

// ===== 解绑绑定式服务 =====
btn_unbindService = findViewById(R.id.btn_unbindService);
btn_unbindService.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try {
// 调用 unbindService() 解除绑定
// 运行效果:Activity 与服务断开连接,无法再调用服务方法
unbindService(conn);
} catch (Exception e) {
// 如果未绑定就解绑会报错,这里用异常捕获防止崩溃
throw new RuntimeException(e);
}
}
});
}

MyService01.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package com.dta.test02.Service;

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

import androidx.annotation.Nullable;

public class MyService01 extends Service {
// 日志标签,方便在 Logcat 中过滤输出
String logTag = "ttttttag";

/**
* 服务创建时调用(只会在第一次启动时执行一次)
* 运行效果:在 Logcat 输出 "MyService01 onCreate"
*/
@Override
public void onCreate() {
super.onCreate();
Log.i(logTag, "MyService01 onCreate");
}

/**
* 服务销毁时调用(stopService() 或系统回收时)
* 运行效果:在 Logcat 输出 "MyService01 onDestroy"
*/
@Override
public void onDestroy() {
super.onDestroy();
Log.i(logTag, "MyService01 onDestroy");
}

/**
* 普通服务一般不绑定,所以这里返回 null
* 如果是绑定式服务,这里会返回一个 IBinder 供客户端调用方法
*/
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null; // 不支持绑定
}
}

MyBindService.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
package com.dta.test02.Service;

import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;

import androidx.annotation.Nullable;

public class MyBindService extends Service {
// 日志标签,用于 Logcat 输出调试信息
String logTag = "tttttag";

// 自定义 Binder 对象,用于把服务实例返回给绑定它的客户端(Activity)
private MyBinder binder = new MyBinder();

/**
* 自定义 Binder 内部类
* 作用:提供方法给绑定它的 Activity 获取当前服务实例
*/
public class MyBinder extends Binder {
public MyBinder() {
Log.i(logTag, "MyBinder 构造方法调用"); // 绑定时会输出日志
}

/**
* 获取当前 Service 的实例
* Activity 绑定后可以通过它直接调用服务里的方法
*/
public MyBindService getServiceBinder() {
return MyBindService.this;
}
}

/**
* 当客户端(Activity)调用 bindService() 绑定服务时触发
* 返回一个 IBinder 对象供客户端通信
*/
@Nullable
@Override
public IBinder onBind(Intent intent) {
return binder; // 返回自定义 Binder
}

/**
* 当所有客户端都解绑时触发
* 运行效果:Logcat 输出 "onUnbind"
*/
@Override
public boolean onUnbind(Intent intent) {
Log.i(logTag, "onUnbind");
return super.onUnbind(intent);
}

/**
* 服务第一次创建时调用
*/
@Override
public void onCreate() {
super.onCreate();
}

/**
* 服务销毁时调用
*/
@Override
public void onDestroy() {
super.onDestroy();
}
}

实操部分——Broadcast的使用

MainActivity.java节选

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
public class MainActivity extends AppCompatActivity {
Button btn_sendBroadcast, btn_orderBroadcas;

// ===== 无序广播(普通广播)发送 =====
btn_sendBroadcast = findViewById(R.id.btn_broadcast);
btn_sendBroadcast.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 创建一个 Intent 指向 MyReceiver(无序广播接收器)
Intent intent = new Intent(MainActivity.this, MyReceiver.class);
// 携带额外数据
intent.putExtra("key2", "这是无序广播内容1");
// 设置广播的 Action
intent.setAction("guolvRuleaaa");
// 发送无序广播(所有匹配该 Action 的接收器都会几乎同时收到,不保证顺序)
sendBroadcast(intent);
}
});

// ===== 有序广播的动态注册 =====
OrderReceiver01 orderReceiver01 = new OrderReceiver01();
OrderReceiver02 orderReceiver02 = new OrderReceiver02();
OrderReceiver03 orderReceiver03 = new OrderReceiver03();

// 创建意图过滤器,只接收 action 为 guolvRuleaaa 的广播
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("guolvRuleaaa");

// 动态注册 3 个接收器(RECEIVER_EXPORTED 表示可以接收外部应用的广播)
registerReceiver(orderReceiver01, intentFilter, RECEIVER_EXPORTED);
registerReceiver(orderReceiver02, intentFilter, RECEIVER_EXPORTED);
registerReceiver(orderReceiver03, intentFilter, RECEIVER_EXPORTED);

// ===== 有序广播发送 =====
btn_orderBroadcast = findViewById(R.id.btn_orderBroadcast);
btn_orderBroadcast.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 创建广播 Intent
Intent intent = new Intent();
intent.setAction("guolvRuleaaa");

// 初始化有序广播的初始数据
String data = "这是原始数据";
Bundle extData = new Bundle();

// 发送有序广播
// 参数说明:
// - intent 广播 Intent
// - null 广播权限(无权限要求)
// - new MyReceiver() 最终接收的结果接收器(可选)
// - null 结果接收器的 Handler(使用主线程)
// - 0 初始代码
// - data 初始数据(String)
// - extData 附加数据(Bundle)
sendOrderedBroadcast(intent, null, new MyReceiver(), null, 0, data, extData);
}
});
}

MyReceiver.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package com.dta.test02.MyReceiver;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;

/**
* 自定义广播接收器 MyReceiver
* 既可以作为普通广播的接收端,也可以作为有序广播的“最终接收器”
*/
public class MyReceiver extends BroadcastReceiver {

@Override
public void onReceive(Context context, Intent intent) {
// 获取当前广播的 Action
String action = intent.getAction();

// 判断是否是我们关心的广播
if ("guolvRuleaaa".equals(action)) {
// 无序广播中可以直接用 intent.getStringExtra("key2") 获取发送端附带的数据
// 有序广播中可以用 getResultData() 获取上一个接收器传下来的数据
String data = getResultData();

// 打印接收到的内容
Log.i("tttttag", "接收到内容:" + data);
}
}
}

OrderReceiver01/02/03.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.dta.test02.MyReceiver;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;

/**
* 有序广播接收器 OrderReceiver01
* 作为有序广播链路中的第一个接收者(优先级最高)
* 可以读取并修改广播数据,后续接收器将接收到修改后的结果
*/
public class OrderReceiver01 extends BroadcastReceiver {

@Override
public void onReceive(Context context, Intent intent) {
// 获取广播的 Action
String action = intent.getAction();

if ("guolvRuleaaa".equals(action)) {
// 获取当前链路上的数据(有序广播)
String data = getResultData();
Log.i("ttttttaaaag", "接收到内容:" + data);

// 修改广播数据,传递给下一个接收器
setResultData("内容已经被 OrderReceiver01/02/03 修改");

// 如果不调用 setResultData(),下一个接收器会收到原始数据
// 如果调用 abortBroadcast() 则会直接中断广播传递
}
}
}

实操部分——ContentProvider的使用

MainActivity.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
public class MainActivity extends AppCompatActivity {

// 四个按钮:增、删、改、查
Button btn_add, btn_del, btn_update, btn_select;

// ContentProvider 的 URI(对应 db 模块的 MyProvider)
static final Uri uri = Uri.parse("content://com.dta.db.authority/user");

// 【添加数据】
btn_add = findViewById(R.id.btn_add);
btn_add.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 封装要插入的记录
ContentValues values = new ContentValues();
values.put("uid", uid);
values.put("name", "王五");
values.put("age", "20");
values.put("score", "78");

// 通过 ContentResolver 调用 ContentProvider 的 insert 方法
ContentResolver contentResolver = getContentResolver();
contentResolver.insert(uri, values); // 插入 values 中的数据
}
});

// 【删除数据】
btn_del = findViewById(R.id.btn_del);
btn_del.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ContentResolver contentResolver = getContentResolver();
// 删除年龄小于 18 的记录
int delCount = contentResolver.delete(uri,
"age < ?", new String[]{"18"});
if (delCount > 0) {
Log.i("ttttagggg", "del Count : " + delCount);
}
}
});

// 【更新数据】
btn_update = findViewById(R.id.btn_update);
btn_update.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ContentValues values = new ContentValues();
values.put("age", "66"); // 要更新的字段

ContentResolver contentResolver = getContentResolver();
// 将 uid=3 的记录的 age 改为 66
int updateCount = contentResolver.update(uri,
values, "uid = ?", new String[]{"3"});
if (updateCount > 0) {
Log.i("ttttaggg", "update Count : " + updateCount);
}
}
});

// 【查询数据】
btn_select = findViewById(R.id.btn_select);
btn_select.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ContentResolver contentResolver = getContentResolver();
// 查询 id、name、age、score 四个字段
Cursor cursor = contentResolver.query(uri,
new String[]{"id", "name", "age", "score"},
null, null, null); // SQL 等价于 "SELECT id, name, age, score FROM user"

if (cursor == null) return;

// 遍历结果集
while (cursor.moveToNext()) {
@SuppressLint("Range") int uid = cursor.getInt(cursor.getColumnIndex("uid"));
@SuppressLint("Range") String name = cursor.getString(cursor.getColumnIndex("name"));
@SuppressLint("Range") int age = cursor.getInt(cursor.getColumnIndex("age"));
@SuppressLint("Range") int score = cursor.getInt(cursor.getColumnIndex("score"));

Log.i("tttttag", "uid=" + uid + ", name=" + name + ", age=" + age + ", score=" + score);
}
}
});
}

代码运行效果

  1. 新增数据 → 点击后往数据库的 user 表插入一条 uid、姓名、年龄、分数。
  2. 删除数据 → 删除所有 age < 18 的用户。
  3. 更新数据 → 修改 uid=3 的用户的年龄为 66。
  4. 查询数据 → 读取并输出所有用户的 id、name、age、score。

以下为另一module db的内容:

DBHelper.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
package com.dta.db;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

import androidx.annotation.Nullable;

/**
* 数据库帮助类,用于创建和管理 SQLite 数据库
* 继承自 SQLiteOpenHelper,封装了数据库的创建和升级逻辑
*/
public class DBHelper extends SQLiteOpenHelper {

// 数据库文件名
private static final String DBNAME = "myDbName.db";
// 数据库版本(版本号变更会触发 onUpgrade)
private static final int VERSION = 1;
// 表名(对外暴露常量,便于其他类引用)
public static final String TABLE_USER = "user";

/**
* 构造方法
* @param context 上下文对象
* @param name 数据库名称(这里传 null,会使用 DBNAME)
* @param factory 游标工厂(通常传 null,使用默认实现)
* @param version 数据库版本
*/
public DBHelper(@Nullable Context context, @Nullable String name,
@Nullable SQLiteDatabase.CursorFactory factory, int version) {
// 这里直接传入固定的 DBNAME 和 VERSION,忽略外部传入的 name、version
super(context, DBNAME, factory, VERSION);
}

/**
* 数据库第一次创建时调用
* 在这里建表
*/
@Override
public void onCreate(SQLiteDatabase db) {
// 创建 user 表,包含 uid、name、age、score 四个字段
String sqlStr = "CREATE TABLE IF NOT EXISTS " + TABLE_USER +
" (uid INTEGER PRIMARY KEY AUTOINCREMENT," + // 自增主键
" name VARCHAR(30)," + // 姓名
" age INTEGER," + // 年龄
" score DOUBLE )"; // 分数

db.execSQL(sqlStr); // 执行建表语句
}

/**
* 数据库版本升级时调用
* @param db 数据库对象
* @param oldVersion 旧版本号
* @param newVersion 新版本号
*/
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// 暂时不做升级逻辑
// 可以在这里进行表结构的修改、数据迁移等操作
}
}

代码运行效果

  1. 新增数据 → 点击后往数据库的 user 表插入一条 uid、姓名、年龄、分数。
  2. 删除数据 → 删除所有 age < 18 的用户。
  3. 更新数据 → 修改 uid=3 的用户的年龄为 66。
  4. 查询数据 → 读取并输出所有用户的 id、name、age、score。
  • DBHelper 是 ContentProvider 数据存储的核心,负责底层数据库的创建和维护。
  • 当 MyProvider 第一次被调用时,如果数据库文件不存在,就会触发 onCreate() 建表。
  • 在这个例子中,user 表结构和 MainActivity 按钮的 CRUD(增删改查)操作是一一对应的。

MyProvider.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
package com.dta.db;

import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.text.TextUtils;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

/**
* 自定义 ContentProvider,用于跨应用/跨组件共享数据
* 这里封装了对 SQLite 数据库的 CRUD(增删改查)操作
*/
public class MyProvider extends ContentProvider {

// Uri 匹配器,用于根据外部传入的 Uri 找到对应的操作对象
private static final UriMatcher uriMatcher;

// ContentProvider 的唯一标识(与 AndroidManifest.xml 中 authorities 一致)
private static final String AUTHORITY_PROVIDER = "com.dta.db.authority";

// 用户表的匹配码
private static final int CODE_PROVIDER_USER = 11;

// 数据库相关
private DBHelper dbHelper;
private SQLiteDatabase db;

// 静态代码块:配置 UriMatcher
static {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
// 当 URI 为 content://com.dta.db.authority/user 时,匹配到 CODE_PROVIDER_USER
uriMatcher.addURI(AUTHORITY_PROVIDER, DBHelper.TABLE_USER, CODE_PROVIDER_USER);
}

/**
* Provider 初始化方法(进程启动后第一次访问时调用)
*/
@Override
public boolean onCreate() {
// 初始化数据库
dbHelper = new DBHelper(getContext(), "", null, 1);
db = dbHelper.getWritableDatabase();

// 初始化时插入测试数据
db.execSQL("INSERT INTO " + DBHelper.TABLE_USER + " VALUES(1, '张三', 19, 87)");
db.execSQL("INSERT INTO " + DBHelper.TABLE_USER + " VALUES(2, '李四', 18, 86)");

return true; // 返回 true 表示 Provider 创建成功
}

/**
* 查询数据(对应 ContentResolver.query())
*/
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection,
@Nullable String selection, @Nullable String[] selectionArgs,
@Nullable String sortOrder) {
String tableName = getTableName(uri); // 根据 URI 获取表名
if (TextUtils.isEmpty(tableName)) {
return null;
}
return db.query(tableName, projection, selection, selectionArgs,
null, null, sortOrder, null);
}

/**
* 返回 MIME 类型(这里不做特殊处理)
*/
@Nullable
@Override
public String getType(@NonNull Uri uri) {
return "";
}

/**
* 根据 URI 获取对应的表名
*/
public String getTableName(Uri uri) {
if (uriMatcher.match(uri) == CODE_PROVIDER_USER) {
return DBHelper.TABLE_USER;
}
return "";
}

/**
* 插入数据(对应 ContentResolver.insert())
*/
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
String tableName = getTableName(uri);
if (TextUtils.isEmpty(tableName)) {
return null;
}
long insertCount = db.insert(tableName, null, values);
Log.i("ttttaaggg", insertCount > 0 ? "insert success" : "insert fail");
return uri;
}

/**
* 删除数据(对应 ContentResolver.delete())
*/
@Override
public int delete(@NonNull Uri uri, @Nullable String selection,
@Nullable String[] selectionArgs) {
String tableName = getTableName(uri);
if (TextUtils.isEmpty(tableName)) {
return 0;
}
int deleteCount = db.delete(tableName, selection, selectionArgs);
Log.i("ttttaaggg", deleteCount > 0 ? "delete success" : "delete fail");
return deleteCount;
}

/**
* 更新数据(对应 ContentResolver.update())
*/
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues values,
@Nullable String selection, @Nullable String[] selectionArgs) {
String tableName = getTableName(uri);
if (TextUtils.isEmpty(tableName)) {
return 0;
}
int updateCount = db.update(tableName, values, selection, selectionArgs);
Log.i("ttttaaggg", updateCount > 0 ? "update success" : "update fail");
return updateCount;
}
}
  1. ContentResolver 与 ContentProvider 的关系
    • MainActivity 用 ContentResolver 调用 insert、delete、update、query。
    • ContentResolver 会自动把请求转发到匹配 authorities 的 ContentProvider(这里就是 MyProvider)。
  2. UriMatcher 的作用
    • 能根据传入的 URI 判断要操作的表。
    • 好处是可以扩展多个表,只要多加 addURI() 即可。
  3. 调用链
1
MainActivity → ContentResolver → MyProvider → DBHelper → SQLite 数据库