白乐天

道阻且长,行则将至。

安卓开发

项目目录结构

新建一个项目,分析项目目录结构

1.gradle和.idea

这两个目录下放置的都是Android Studio 自动生成的一些文件,我们无需关心和编辑。

2.app

项目中的代码、资源等内容几乎都是放置在这个目录下的,我们后面的开发工作也都是在这个目录下进行的。

3.gradle

这个目录包含了gradle wrapper的配置文件。

app目录

build

包含编译时自动生成的文件。

src

存放源代码。

androidTest

存放测试代码。

main

存放应用核心代码和资源。

java

放置java代码。

res

存放资源文件,包括布局、图像、字符串等。

AndroidManifest.xml

这是整个Android项目的配置文件,在程序中定义的所有四大组件都需要在这个文件里注册,还可以在这个文件中添加权限声明。

test

用来编写UnitTest测试用例的,是对项目及逆行自动化测试的另一种方式。

build.gradle

app模块的gradle构建脚本,这个文件会指定很多项目构建相关的配置。

Activity

Activity 是一种用户界面的组件,是 Android 应用的一个核心部分。每个 Activity 通常代表一个单独的屏幕或界面,用户与应用的互动通常发生在 Activity 中。

MainActivity

在使用Android Studio创建项目的时候,它会自动帮我们创建MainActivity。
其内容如下:

1
2
3
4
5
6
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//// 设置布局,接受一个布局文件的资源 ID,并将该布局文件作为当前活动的界面。
setContentView(R.layout.activity_main);
}

布局文件

布局文件定义了用户界面(UI)的结构和视图元素。

在res目录里有一个layout目录,展开后可以看到有一个activity_main.xml文件,其内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<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:id="@+id/main"
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>

Activity的注册

所有的活动都要在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
<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.HelloWorld"
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>标签内,通过<activity>标签来进行注册。
<activity>标签中使用android:name指定具体注册的活动,这里.MainActivity是省略了包名。

程序入口activity

在程序的actitity中添加如下内容指定程序入口activity

1
2
3
4
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>

action中的.MAIN动作用于启动一个应用程序的主界面。

category中的LAUNCHER类别意味着这个活动应该在设备的应用程序启动器(应用抽屉)中显示一个图标,允许用户点击图标来启动应用程序。

Intent

使用Intent可以从主活动跳转到其他活动。
Intent是Android程序中各组件之间进行交互的一种重要方式。

显式 Intent

显式 Intent 直接指定了目标组件,通常用于在应用内部启动 ActivityService 或与之通信。

启动 Activity

1
2
Intent secondintent = new Intent(this, SecondActivity.class);
startActivity(secondintent)

隐式 Intent

隐式 Intent 不直接指定目标组件,而是声明一个意图(通常是动作),然后由 Android 系统根据该动作来匹配适合的组件来处理 Intent。这种方式不需要明确指定目标组件,系统会根据设备上所有能够处理该动作的应用来选择合适的组件。

生命周期

四种状态

  • 运行状态
    活动位于栈顶并在前台运行,用户可以与之交互。
  • 暂停状态
    活动部分可见但未完全覆盖用户界面,它仍在前台但无法与用户交互。
  • 停止状态
    活动完全不可见,已被其他活动完全覆盖。
  • 销毁状态
    活动已被系统或用户完全移除,实例不再存在。

生命周期的回调方法

onCreate()

调用时机:当 Activity 被创建时,系统会调用此方法。此时 Activity 尚未显示在屏幕上。

onStart()

调用时机:当 Activity 即将变得可见时,系统会调用此方法。这时 Activity 还没有与用户交互。

onResume()

调用时机:当 Activity 准备好与用户交互时,系统会调用此方法,表示 Activity 已经位于前台并处于活动状态。

onPause()

调用时机:当系统即将启动或恢复另一个 Activity 时,当前的 Activity 将被暂停。

onStop()

调用时机:当 Activity 不再对用户可见时,系统会调用此方法。

onRestart()

调用时机:当 Activity 从停止状态恢复并重新变得可见时,系统会调用此方法。

onDestroy()

调用时机:当 Activity 被销毁时(如 finish() 被调用、系统内存不足等),系统会调用此方法。

启动模式

Activity 的启动模式决定了新的 Activity 如何在任务栈(task stack)中管理和显示。

standard

这是 Activity 默认的启动模式。每次启动一个新的 Activity 时,都会在任务栈的顶部创建一个新的 Activity 实例。

无论栈中是否已经有该 Activity 的实例,都会新建一个 Activity 实例并添加到栈中。

在standard模式下,每当启动一个新的Activity,它就会在返回栈中入栈,并处于栈顶的位置。

singleTop

如果任务栈的顶部已经有该 Activity 的实例,则不会再创建一个新的实例,而是复用栈顶的实例,调用 onNewIntent() 方法来处理新的 Intent

使用方式:在 AndroidManifest.xml 中配置 launchModesingleTop

1
2
<activity android:name=".MainActivity"
android:launchMode="singleTop" />

如果 MainActivity 已经在栈顶,则会复用现有实例,并通过 onNewIntent() 接收新的 Intent

1
2
3
4
5
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
// 处理新的 Intent
}

singleTask

该模式下,Activity 会被启动为一个新的任务,并且栈中只能有该 Activity 的一个实例。即使栈中已经有该 Activity 的实例,也会先销毁栈中所有的 Activity,然后启动这个新的 Activity

使用方式: 在 AndroidManifest.xml 中配置 launchModesingleTask

1
2
<activity android:name=".MainActivity"
android:launchMode="singleTask" />

singleInstance

这是最严格的启动模式。系统会创建一个新的任务栈,并且栈中只能有该 Activity 的一个实例。即使有其他 Activity 启动该 Activity,也会使用独立的栈,不与其他任务栈共享。

1
2
<activity android:name=".MainActivity"
android:launchMode="singleInstance" />

常用控件

TextView

1
2
3
4
5
6
7
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello, World!"
android:textSize="20sp"
android:textColor="#FF0000" />

Button

1
2
3
4
5
6
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button" />

EditText

1
2
3
4
5
6
7
<EditText
android:id="@+id/editText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="input"
android:inputType="text" />

ImageView

1
2
3
4
5
6
<ImageView
android:id="@+id/imageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/image" />

WebView

1
2
3
4
5
<WebView
android:id="@+id/webView"
android:layout_width="match_parent"
android:layout_height="match_parent" />

Broadcast

广播(Broadcast)是一种消息传递机制,允许应用之间或应用内部的不同组件之间进行通信。

广播的分类

标准广播

  • 是一种异步广播,所有接收者几乎在同一时间接收广播。

  • 优点:效率高,因为广播是同时分发的。

  • 缺点:接收器之间无法中断广播,无法保证接收顺序。

有序广播

  • 是一种同步广播,按接收器的优先级依次接收广播。

  • 每个接收器可以处理后终止广播的传递,或者修改广播内容。

  • 优点:可控制广播的传播,并允许对广播进行修改。

  • 缺点:效率较低,因为接收器按顺序逐个处理广播。

广播的类型

系统广播

安卓系统定义了一些广播来通知应用程序系统事件。

自定义广播

开发者可以发送自己定义的广播,用于应用内部或多个应用之间的通信。

广播接收器

广播接收器可以通过两种方式注册:

静态注册

静态注册的广播接收器在应用未运行时也可以接收到广播。

静态注册广播实现开机启动

创建广播接收器,右键Java包->New->Other->Broadcast Reveiver

实现onReceive方法

1
2
3
4
5
6
7
8
public class BootCompleteReceiver extends BroadcastReceiver {

@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context,"Boot Complete!",Toast.LENGTH_SHORT).show();
Log.d("Broadcast","Boot Complete!");
}
}

AndroidManifest.xml进行注册和添加权限

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<!-- 声明权限:允许应用接收设备启动完成后的广播 -->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<application
...
<receiver
android:name=".BootCompleteReceiver"
android:enabled="true"
android:exported="true">
<!-- 定义接收器要监听的广播类型 -->
<intent-filter>
<!-- 接收系统广播:设备启动完成 -->
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<activity
...
</activity>
</application>
</manifest>

关闭设备,重新启动,就可以看到开机广播。

动态注册

动态注册的广播只在应用运行时有效。

动态注册广播监听网络变化

声明权限

需要在 AndroidManifest.xml 文件中添加以下权限:

1
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

定义广播接收器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class NetworkReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
ConnectivityManager connectivityManager =
(ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
if(connectivityManager!=null){
NetworkInfo activeNetwork = connectivityManager.getActiveNetworkInfo();
if(activeNetwork!=null && activeNetwork.isConnected()){
// 网络已连接
if(activeNetwork.getType()==ConnectivityManager.TYPE_WIFI){
Toast.makeText(context, "已连接 Wi-Fi", Toast.LENGTH_SHORT).show();
}else if (activeNetwork.getType()==ConnectivityManager.TYPE_MOBILE){
Toast.makeText(context, "已连接移动网络", Toast.LENGTH_SHORT).show();
}
}else {
// 网络断开
Toast.makeText(context, "网络已断开", Toast.LENGTH_SHORT).show();
}
}
}
}

动态注册广播

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
public class MainActivity extends AppCompatActivity {

// 定义一个广播接收器,用于监听网络状态变化
private NetworkReceiver networkReceiver;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);

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

// 创建网络状态广播接收器实例
networkReceiver = new NetworkReceiver();

// 创建 IntentFilter,指定接收的广播类型为网络连接状态变化
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);

// 注册广播接收器,将其绑定到网络状态变化的广播上
registerReceiver(networkReceiver, intentFilter);
}

@Override
protected void onDestroy() {
super.onDestroy();

// 在 Activity 销毁时注销广播接收器,避免内存泄漏
unregisterReceiver(networkReceiver);
}
}

在切换网络状态的时候就会看到广播效果。

使用本地广播

定义广播接收器

1
2
3
4
5
6
7
8
public class LocalBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// 处理广播内容
String message = intent.getStringExtra("message");
Toast.makeText(context, "LocalBroadcast: " + message, Toast.LENGTH_SHORT).show();
}
}

发送和接收本地广播

添加一个按钮,通过按钮触发本地广播的发送

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
private LocalBroadcastReceiver localBroadcastReceiver;

private LocalBroadcastManager localBroadcastManager;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);

// 初始化广播接收器
localBroadcastReceiver = new LocalBroadcastReceiver();

// 创建 IntentFilter,并设置要监听的 Action
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("com.example.helloworld.LOCAL_BROADCAST");

// 注册本地广播接收器
localBroadcastManager = LocalBroadcastManager.getInstance(this);
localBroadcastManager.registerReceiver(localBroadcastReceiver,intentFilter);

Button send_button = findViewById(R.id.send_broadcast);
send_button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent("com.example.helloworld.LOCAL_BROADCAST");
intent.putExtra("message","This is a Broadcast message!");
localBroadcastManager.sendBroadcast(intent);
}
});
}

@Override
protected void onDestroy() {
super.onDestroy();
// 注销广播接收器,防止内存泄漏
unregisterReceiver(localBroadcastReceiver);
}

点击按钮,发送广播。