Android 开发学习笔记
基本概念
Android 应用程序由一些零散的有联系的组件组成,通过一个工程 manifest 绑定在一起。在 manifest 中,描述了每一个组件以及组件的作用,其中有 6 个组件,它们是 Android 应用程序的基石。Android 有四大组件(也有说六大组件的,外加 Intent 和 Notification),分别是 Activity,Service,Content Provider 和 BroadcastReceiver。这四大组件一起组成了完整的 Android 程序。我们将分别简要介绍。
界面显示与逻辑处理:
利用 XML 标记描绘应用界面,使用 Java 代码书写程序逻辑。
Activity
- Activity 指一个完整的占了一个屏幕的页面(上下滑动的内容也算这个界面内的内容,所以它的概念可以理解成类似网站的一个网页一样)。
- Activity 允许显示一些控件、视图,并可以监听处理用户的事件,做出响应等。Activity 之间通过 Intent 通信(调用、跳转等动作)。
- 一个 Activity 实际上是一个 XML 文件,它可以被 Android 系统以可视化的界面展现。每一个 Activity 都与一个 Java 后台程序相联系,这个 Java 程序可以控制这个页面的启动、展示以及数据等信息。页面上展示的内容可以通过 Activity 本身的 xml 文件配置,也可以由相联系的 Java 文件来控制。
Service
- Service 是服务的意思。是 Android 程序中 “不可见” 的部分,但是它负责更新数据源、触发通知等。是一种没有界面的长生命周期的适合监控或者后台运行的程序。
- 最佳的例子是多媒体播放器,多媒体播放器程序可能含有一个或多个 Activity,用户通过这些 Activity 选择并播放音乐。然而,音乐回放并不需要一个 Activity 来处理,因为用户可能会希望音乐一直播放下去,即使退出了播放器去执行其它程序。为了让音乐一直播放,多媒体播放器 Activity 可能会启动一个 Service 在后台播放音乐。Android 系统会使音乐回放 Service 一直运行,即使在启动这个 Service 的 Activity 退出之后。
- Android 服务有两种:一是本地服务,另一种是远程服务。前者只能由托管服务的应用程序访问,后者是指由设备上其他应用程序进行远程访问的服务。
Content Provider
- Content Provider 是指内容提供器。App 运行的时候需要很多外部数据作为支撑,这些数据一般由内容提供器存储、共享。
- 比如,我们可以配置自己的 Content Provider 来存取其他应用程序,或者是通过其他应用程序给出的 Content Provider 来获取他们的数据。
- 系统本身也提供了一些 Content Provider,如联系人信息等。这些数据可以存储在文件系统、SQLite 数据库或者其他一些媒介里。
BroadcastReceiver
- 你的应用可以使用它对外部事件进行过滤,只对感兴趣的外部事件(如当电话呼入时,或者数据网络可用时)进行接受并作出响应。
- 广播接收器没有用户界面,然而,它们可以启动一个 activity 或 serice 来响应它们收到的信息,或者用 NotificationManager 来通知用户。
- 通知可以用很多种方式来吸引用户的注意力──闪动背灯、震动、播放声音等。一般来说是在状态栏上放一个持久的图标,用户可以打开它并获取消息。
Intent
- Intent(意图)是各种组件之间通信的桥梁,可以用来连接 Android 的应用组件,它提供了一种在不同应用之间进行任务运行绑定的工具,它最重要的应用是启动活动 Activity。
- Intent 是异步消息,可以帮助一个应用组件从另一个组件中请求功能。
- Intent 是一个对象,即
android.content.Intent
。
Notification
- Notification 是通知组件,主要是和推送用户信息有关。
Android App 项目中的文件简介
-
AndroidManifest.xml 文件:在 mainfests 文件夹下面,叫做清单文件,它描述了整个项目的信息,包括项目名称、SDK 版本等等。我们在应用程序中的每个活动必须在
AndroidManifest.xml
文件中声明,系统需要根据里面的内容运行 APP 代码,显示界面,注意这个文件名一个字都不能错。<applicationandroid:allowBackup="true"android:icon="@mipmap/ic_launcher"android:label="@string/app_name"android:roundIcon="@mipmap/ic_launcher_round"android:supportsRtl="true"android:theme="@style/AppTheme"><activity android:name=".SmsNewActivity"><!-- <intent-filter>--><!-- <action android:name="android.intent.action.MAIN" />--><!-- <category android:name="android.intent.category.LAUNCHER" />--><!-- </intent-filter>--></activity><activity android:name=".NerEvalActivity"><!-- 下面两行代码设定程序入口 --><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter></activity>
-
Java 文件夹:存放 java 源程序的地方。注意我们这里有一些以 Activity 结尾的程序文件,每一个文件其实对应了一个 Activity 的页面,也就是和下面资源文件夹(res)中的 layout 里面的内容绑定的。
-
Res 文件夹:存放 java 资源的地方,包括图片、布局文件、菜单等等。
-
drawable 资源:存放位图的文件夹
-
layout 资源:存放的是布局资源,也就是指 Android 里面的活动和视图。在 Android 中,占用一个屏幕的 UI 界面称之为 Activity(活动),页面中的按钮、标签、文本字段等称之为 View(视图)。一个活动通常包含一个或者多个视图(也就是一个页面里面有按钮,文本之类的东西)。这里的布局文件都是 XML 文件,因为 Android 中视图都是从 XML 文件加载的,里面描述了位置、大小等视图信息。布局资源下每个文件都将根据其文件名(不包含扩展名)生成一个唯一的常量 ID,可以通过某些手段与 java 源文件绑定,或者被其他页面调用。
-
values 资源:是 Android 中存放数组、颜色、尺寸、字符串和样式的文件夹。其实就是统一存放所有变量的地方,比如主题颜色、app 名称、Logo 的样式等,在 values 资源下统一定义可以使得我们在各个地方都调用同样的资源,在修改的时候也只要更改一处即可。
/res/values/strings.xml /res/values/colors.xml /res/values/dimens.xml /res/values/attrs.xml /res/values/styles.xml# 定义资源:尖括号定义资源类型,name 表示资源名称,里面表示内容 <string name="app_name">乐购</string> <string name="edit_message">请输入您想查询的地点</string>
- minmap:存放程序启动图标的文件夹。一般只存放启动图标(就是桌面图标)。
-
-
Gradle Scripts:工程的编译配置文件
build.gradle
:分为项目级与模块级,用于描述 App 工程的编译规则proguard-rules.pro
:用于描述 java 代码的混淆规则,对编译好的 class 文件进行混淆处理,目的是防止 java 代码被反编译gradle.properties
:用于配置编译工程的命令行参数,一般无须改动settings.gradle
:配置了需要编译哪些模块。初始内容为 include ':app' ,表示只编译 app 模块local.properties
:项目的本地配置文件,在工程编译时自动生成,用于描述开发者电脑的环境配置,包括 SDK 的本地路径,NDK 的本地路径等- 注意每个版本的 Android Studio 都有对应的 Gradle 版本,只有二者的版本正确对应,App 工程才能成功编译,比如 Android studio 4.1 对应的 Graddle 版本为 6.5。
Android 中的资源访问(R 类 / R.java)
在 Android 开发中,所有的外部资源都通过其资源的 ID 来访问,而所有的资源 ID 都在项目中 R 类中定义,而 R.java 这个类是由 aapt 工具自动生成的,用户本身不用修改添加。只要在资源中申明了 ID,那么 R 类会自动将该资源添加到其中。
编译应用时,aapt 会生成 R 类,其中包含您的 res/
目录中所有资源的资源 ID。 每个资源类型都有对应的 R 子类(例如,R.drawable 对应于所有可绘制对象资源),而该类型的每个资源都有对应的静态整型数(例如,R.drawable.icon)。这个整型数就是可用来检索资源的资源 ID。
定义资源 ID 主要包括两个部分,一个是资源类型如 string、drawable 和 layout 等。另一个是资源名称,不包括其扩展名(当然也可以是 xml 中 android:name 属性中的值)。
访问这些资源有两种方式,一种是在 Java 程序中,一种是在 XML 文件中。
// Java 程序中访问资源,如下面的程序设置内容显示为某个 activity
// 可以使用 R.layout.activity 名称的方式。其中 layout 是资源类型,后面的是资源名称
setContentView(R.layout.activity_display_message);
// 这种方式是在 XML 文件中访问资源,使用 @ 开头表示什么类型,然后斜杠后面写上资源名称
@string/hello
NDK 相关
- NDK 是 Native Development Kit 的缩写,是 Android 的工具开发包。
- 作用是快速开发 C/C++ 的动态库,并自动将动态库与应用一起打包到 apk 。
参考:https://blog.csdn.net/afei__/article/details/80897404
开发步骤
创建新的 App 页面
完整的页面创建过程包括三个步骤:
- 在 layout 目录下创建 XML 文件
- 创建与 XML 文件对应的 Java 代码
- 在 AndroidManifest.xml 中注册页面配置
右键之后可以一键创建 Activity,系统同时帮我们做好上述三件事情。
实现界面的跳转
点击事件和长按事件:
- 监听器:意思是专门监听控件的动作行为,只有控件发生了指定的动作,监听器才会触发开关去执行对应的代码逻辑。
- 常用的两种监听器:
- 点击监听器:通过 setOnClickListener 方法设置
- 长按监听器:通过 setOnLongClickListener 方法设置
public class LKdemoActivity extends AppCompatActivity {// 注意 onCreate 方法有两个,我们需要选择参数是 @Nullable Bundle savedInstanceState 的这一个@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_lk);// 跳转到 MobileRecActivityButton button = findViewById(R.id.button2);// 点击监听器button.setOnClickListener((new View.OnClickListener() {@Overridepublic void onClick(View view) {Intent intent = new Intent();// 设置跳转的 Activityintent.setClass(LKdemoActivity.this, MobileRecActivity.class);startActivity(intent);}}));TextView tv_hello = findViewById(R.id.tv_hello);tv_hello.setText(R.string.welcome);tv_hello.setTextSize(30);}
}
还有一种设置监听的方式:button.setOnClickListener(this)
// 如果监听很多,不要创建太多类,可以公用这个类
public class LKdemoActivity extends AppCompatActivity implements View.OnClickListener {// 注意 onCreate 方法有两个,我们需要选择参数是 @Nullable Bundle savedInstanceState 的这一个@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_lk);// 跳转到 MobileRecActivityButton button = findViewById(R.id.button2);// 点击监听器button.setOnClickListener(this);}@Overridepublic void onClick(View view) {// 有很多监听事件,我们需要判断是哪个按钮被点击了if (view.getId() == R.id.button2) {Intent intent = new Intent();// 设置跳转的 Activityintent.setClass(LKdemoActivity.this, MobileRecActivity.class);startActivity(intent);}
}
个人感觉这种方式更加清晰,项目实现也是采用的这种方式。
Button 点击之后显示时间
直接在 layout 中的 XML 文件中设置:
<Buttonandroid:layout_width="0dp"android:layout_height="40dp"android:layout_marginBottom="20dp"android:layout_weight="1"// 在此处进行设置,点击之后执行 doClick 函数android:onClick="doClick" />
虽然上下两种方法都能实现点击之后显示时间,但是下面这种方式有一点问题,在 xml 布局文件中还需要知道调用的方法名称(逻辑代码),这就造成了高耦合,尽量不要这么使用。
下面是这种方法的一个案例:
public class LKdemoActivity extends AppCompatActivity {// bt_time 在两个函数中都要用,所以要设置为类变量private TextView bt_time;// 注意 onCreate 方法有两个,我们需要选择参数是 @Nullable Bundle savedInstanceState 的这一个@SuppressLint("MissingInflatedId")@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_lk);bt_time = findViewById(R.id.tv_result6);}public void doClick(View view) {String desc = String.format("%s 您好,欢迎使用按钮: %s", Common.getNowTime(), ((Button) view).getText());bt_time.setText(desc);}
}// 在 utils 文件夹 Common 类增加获取当前时间的函数
public static String getNowTime() {SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");return sdf.format(new Date());
}
在 activity 文件对应的 xml 文件中增加一个 button 和一个点击之后显示内容的文本框:
<Buttonandroid:id="@+id/button6"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="显示时间"android:onClick="doClick"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintHorizontal_bias="0.489"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent"app:layout_constraintVertical_bias="0.408" /><TextViewandroid:id="@+id/tv_result6"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="这里查看按钮的点击结果"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintHorizontal_bias="0.498"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent"app:layout_constraintVertical_bias="0.335" />
Activity
启停活动页面
Activity 的启动和结束
- 从当前页面跳到新页面:
// Intent 的构造函数需要传入两个参数:上下文和目标组件的 Class 对象。
startActivity(new Intent(源页面.this, 目标页面.class))
- 从当前页面回到上一个页面,相当于关闭当前页面
finish() // 结束当前的活动页面
Activity 的生命周期
private static final String TAG ="ning";protected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);Log.d(TAG, "onCreate: LKdemoActivity");
}@Override
protected void onStart() {super.onStart();Log.d(TAG, "onStart: LKdemoActivity");
}// 在 Logcat 控制台输入 tag:ning 进行过滤筛选
onCreate()
:创建活动,把页面布局加载进内存,进入初始状态onStart()
:开始活动,把活动页面显示在屏幕上,进入就绪状态onResume()
:修复活动,活动页面进入活跃状态,能够与用户正常交互,例如允许响应用户的点击动作、允许输入文字等onPause()
:暂停活动,无法与用户正常交互,Activity 处于部分可见状态。这发生在 Activity 失去焦点但仍部分可见的时候,例如有一个对话框或透明的 Activity 遮盖在其上。如果有动画,在这里暂停onStop()
:停止活动,Activity 处于不可见状态。Activity 将停止显示在屏幕上,并且它失去了用户焦点,但仍然保留在 Activity 堆栈中onDestroy()
:销毁活动,Activity 将被销毁。这发生在用户按下 "Back" 键或调用finish()
方法关闭 Activity 时。一旦 Activity 被销毁,它就会从 Activity 堆栈中移除onNewIntent
:重用已有的活动实例
Activity 的启动模式
我们可以在配置文件中指定启动模式:
<activity android:name=".LKdemoActivity" android:launchMode="standard" />
在 Android 中,Activity 的启动模式(Launch Mode)定义了 Activity 如何在任务栈中启动和管理。不同的启动模式可以影响 Activity 的实例化和任务栈的行为。以下是常见的 Activity 启动模式:
- Standard(标准模式):这是默认的启动模式。每次启动一个 Activity,都会创建一个新的实例,并将其放入任务栈的顶部。无论是否已经存在相同的实例,都会创建新的实例。
- SingleTop(单顶模式):在 SingleTop 模式下,如果要启动的 Activity 已经位于任务栈的顶部(即栈顶有一个相同的实例),则不会创建新的实例,而是调用现有实例的
onNewIntent()
方法来传递新的 Intent 数据。如果 Activity 不在栈顶,仍然会创建新的实例。 - SingleTask(单任务模式):在 SingleTask 模式下,系统会确保一个特定的 Activity 只存在于一个任务栈中的一个实例。如果要启动的 Activity 已经在其他任务栈中存在,系统会将该任务栈移到前台,并调用现有实例的
onNewIntent()
方法传递新的 Intent 数据。如果要启动的 Activity 在当前任务栈中已经存在,则不会创建新的实例,而是将任务栈中其他 Activity 移除,使该 Activity 变为栈顶。
单任务模型的应用场景:
- 程序主界面:我们肯定不希望主界面被创建多次,而且在主界面退出的时候退出整个 app 是最好的效果
- 耗费系统资源的 activity:设置为单任务模式可以减少资源耗费
-
SingleInstance(单实例模式):SingleInstance 是最特殊的启动模式。在 SingleInstance 模式下,系统会为该 Activity 创建一个新的任务栈,并且该任务栈中只会存在一个实例。其他应用程序的 Activity 无法与其共享任务栈。这种模式适用于需要独立存在且与其他应用程序隔离的 Activity。
-
在 JAVA 代码中动态设置启动模式:
- 在两个页面之间跳来跳去:设置
setFlags
以避免重复跳转
public void onClick(View view) {// 有很多监听事件,我们需要判断是哪个按钮被点击了if (view.getId() == R.id.button2) {Intent intent = new Intent();// 设置跳转的 Activityintent.setClass(LKdemoActivity.this, MobileRecActivity.class);// 栈中存在待跳转的活动实例时,则重新创建该活动的实例,并清除原实例上方的所有实例intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);startActivity(intent);}
- 登录成功后不再返回登录界面:设置启动标志 FLAG_ACTIVITY_CLEAR_TASK,该标志会清空当前活动栈里的所有实例,不过全部清空之后,意味着当前栈没法用了,必须另外找个活动栈才行,也就是同时设置启动标志 FLAG_ACTIVITY_NEW_TASK,该标志用于开辟新任务的活动栈。这种操作可以实现从登录界面登录成功之后,无法再次返回到登录界面。
Intent intent = new Intent(); // 设置跳转的 Activity intent.setClass(LKdemoActivity.this, MobileRecActivity.class); // 设置启动标志:跳转到新页面时,栈中的原有实例都被清空,同时开辟新任务的活动栈 intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASKIntent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent); // 跳转到意图指定的活动页面
- 在两个页面之间跳来跳去:设置
在活动之间传递消息
元素名称 | 设置方法 | 说明与用途 |
---|---|---|
componentName | setComponent | 指定意图的来源与目标 |
action(动作) | setAction | 指定意图的动作行为 |
category(类别) | addCategory | 指定意图的操作类别 |
data(数据) | setData | 指定动作要操纵的数据路径 |
type(数据类型) | setType | 指定消息的数据类型 |
extras(扩展信息) | putExtras | 指定装载的包裹信息 |
Flags(标志位) | setFlags | 指定活动的启动标志 |
显式 Intent 和隐式 Intent
-
Intent 是各组件之间信息沟通的桥梁,用于 Android 各组件之间的通信,主要完成下列工作:
- 标明本次通信请求从哪里来、到哪里去、要怎么走;
- 发起方携带本次通信需要的数据内容,接受方从收到的意图中解析数据;
- 发起方若想判断接受方的处理结果,意图就要负责让接受方传回应答的数据内容。
-
显式 Intent:直接指定来源活动与目标活动,属于精确匹配,有三种构建方式:
- 在 Intent 的构造函数中指定
// 创建一个目标明确的意图 Intent intent = new Intent(CurrentActivity.this, NextActivity.class)
- 调用意图对象的
setClass
方法指定
// 创建一个新意图 Intent intent = new Intent(); // 设置跳转的 Activity intent.setClass(CurrentActivity.this, NextActivity.class);
- 调用意图对象的 setComponent 方法指定
// 创建一个新意图 Intent intent = new Intent(); // 创建包含目标活动在内的组件名称对象 ComponentName component = new ComponentName(CurrentActivity.this, NextActivity.class); // 设置意图携带的组件信息 intent.setComponent(component);
-
隐式 Intent:没有明确指定要跳转的目标活动,只给出一个动作字符串让系统自动匹配,属于模糊匹配
- App 不希望向外部暴露活动名称,只给出一个事先定义好的标记串,约定俗成就好
Intent intent = new Intent(); // 设置意图动作为准备拨号 intent.setAction(Intent.ACTION_DIAL);
向下一个 Activity 发送数据
- Intent 使用 Bundle 对象存放待传递的数据信息
- 在代码中发送消息包裹,调用意图对象的
putExtras
方法,即可存入消息包裹 - 在代码中接收消息包裹,调用意图对象的
getExtras
方法,即可取出消息包裹
Intent intent = new Intent(CurrentActivity.this, NextActivity.class);
// 1. 可以直接传入一个字符串
intent.putExtra("key", value);// 2. 也可以创建一个包裹
Bundle bundle = new Bundle();
bundle.putString("request_time", DateUtil.getNowTime());
bundle.putString("request_content", tv_send.getText().toString());
intent.putExtras(bundle);startActivity(intent);// 1. 在目标 Activity 中接收数据:
Intent intent = getIntent();
String name = intent.getStringExtra("key");// 2. 在目标 Activity 中接收包裹
Bundle bundle = getIntent().getExtras();
String request_time = bundle.getString("request_time");
String request_content = bundle.getString("request_content");
向上一个 Activity 发送数据
在 Android 中,Activity 之间的数据传递是单向的,即从一个 Activity 向另一个 Activity 发送数据。如果你想要从目标 Activity 返回数据给上一个 Activity,可以通过以下步骤实现:
- 在目标 Activity 中,创建一个新的 Intent 并使用
putExtra()
方法来添加要返回的数据到 Intent 中。
Intent intent = new Intent();
intent.putExtra("result", "这是返回的数据");
- 在目标 Activity 中调用
setResult()
方法来设置结果码和返回的 Intent。
setResult(Activity.RESULT_OK, intent);
- 在目标 Activity 中调用
finish()
方法来关闭当前 Activity,并返回到上一个 Activity。
finish();
- 在上一个 Activity 中,可以在
onActivityResult()
方法中接收返回的数据。
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {super.onActivityResult(requestCode, resultCode, data);if (requestCode == REQUEST_CODE && resultCode == Activity.RESULT_OK) {if (data != null && data.hasExtra("result")) {String result = data.getStringExtra("result");// 在这里处理返回的数据}}
}
上述步骤中的 REQUEST_CODE
是一个自定义的请求码,用于标识请求的来源。在调用 startActivityForResult()
方法启动目标 Activity 时,可以传递这个请求码。
示例代码如下:
在当前 Activity 中启动目标 Activity:
private static final int REQUEST_CODE = 1;// 启动目标
ActivityIntent intent = new Intent(CurrentActivity.this, TargetActivity.class);
startActivityForResult(intent, REQUEST_CODE);
在目标 Activity 中返回数据:
// 在目标 Activity 中设置返回数据
Intent intent = new Intent();
intent.putExtra("result", "这是返回的数据");
setResult(Activity.RESULT_OK, intent);
finish();
在上一个 Activity 中接收返回的数据:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {super.onActivityResult(requestCode, resultCode, data);if (requestCode == REQUEST_CODE && resultCode == Activity.RESULT_OK) {if (data != null && data.hasExtra("result")) {String result = data.getStringExtra("result");// 在这里处理返回的数据}}
}
通过这种方式,你可以在目标 Activity 中返回数据给上一个 Activity,并在上一个 Activity 中获取并处理这些返回的数据。
Android AsyncTask 异步任务
AsyncTask
是 Android 中用于在后台线程执行异步任务并在主线程更新 UI 的类。它提供了一种简单的方法来执行后台计算、网络请求或其他耗时操作,然后将结果返回到主线程,以便更新用户界面。AsyncTask
可以帮助开发者避免在主线程执行耗时操作而导致的界面卡顿和 ANR(Application Not Responding)问题。
AsyncTask
包含了四个关键的回调方法,它们分别是:
onPreExecute()
: 在后台任务开始执行之前,在主线程中调用。通常用于初始化 UI 或显示进度对话框。doInBackground(Params...)
: 在后台线程中执行耗时操作的方法。在该方法内部进行计算、网络请求或其他耗时任务,但不要更新 UI。doInBackground
结束后会返回一个 String 类型的数据到onPostExecute
中,接下来onPostExecute
就可以使用这个返回的数据来做 UI 更新的逻辑了。onProgressUpdate(Progress...)
: 当调用publishProgress(Progress...)
方法时,在主线程中调用。通常用于更新 UI 进度。onPostExecute(Result)
: 在后台任务执行完成后,在主线程中调用。通常用于处理后台任务的结果,并更新 UI。
使用 AsyncTask
的步骤如下:
- 创建一个继承自
AsyncTask
的子类,并指定泛型参数:Params
(传递给doInBackground
的参数类型)、Progress
(传递给onProgressUpdate
的参数类型)、Result
(传递给onPostExecute
的参数类型)。 - 实现
doInBackground
方法,执行后台任务,并在需要时调用publishProgress
方法来更新进度。 - 实现
onPostExecute
方法,处理后台任务的结果,并更新 UI。 - 在主线程中实例化并执行 AsyncTask,调用
execute()
方法。
下面是一个简单的示例,演示了使用 AsyncTask
进行后台计算,并在完成后更新 UI。
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;public class MainActivity extends AppCompatActivity {private ProgressBar progressBar;private TextView resultTextView;private Button calculateButton;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);progressBar = findViewById(R.id.progressBar);resultTextView = findViewById(R.id.resultTextView);calculateButton = findViewById(R.id.calculateButton);calculateButton.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {// 执行 AsyncTaskMyAsyncTask myAsyncTask = new MyAsyncTask();myAsyncTask.execute(10); // 将计算的参数传递给 AsyncTask}});}private class MyAsyncTask extends AsyncTask<Integer, Integer, Integer> {@Overrideprotected void onPreExecute() {super.onPreExecute();// 初始化 UI 或显示进度对话框progressBar.setVisibility(View.VISIBLE);calculateButton.setEnabled(false);resultTextView.setText("");}@Overrideprotected Integer doInBackground(Integer... params) {// 后台计算任务int num = params[0];int result = 0;for (int i = 1; i <= num; i++) {// 假设这是一个耗时的计算任务result += i;// 更新进度// publishProgress 方法被调用后,会触发 onProgressUpdate 方法的执行// 这里的参数会自动传递给 onProgressUpdate// 注意:不能写成 onProgressUpdate(i * 10),因为这样会导致在错误的线程中执行publishProgress(i * 10);}return result;}@Overrideprotected void onProgressUpdate(Integer... values) {super.onProgressUpdate(values);// 更新 UI 进度progressBar.setProgress(values[0]);}@Overrideprotected void onPostExecute(Integer result) {super.onPostExecute(result);// 处理计算结果,并更新 UIprogressBar.setVisibility(View.GONE);calculateButton.setEnabled(true);resultTextView.setText("计算结果:" + result);}}
}
在这个示例中,当用户点击按钮时,会触发 AsyncTask 的执行。doInBackground
方法会在后台线程中执行计算任务,并使用 publishProgress
方法来更新进度。在 onProgressUpdate
中更新 UI 进度,最终在 onPostExecute
中更新计算结果。这样,整个计算过程在后台进行,不会阻塞主线程,并且在计算完成后更新了 UI。
参考资料
- Android 开发入门基础