安卓速通(其实是期末复习~)(第八章+第九章)
ZealSinger 发布于 阅读:122 期末复习
概述
服务Service是四大组件之一,是一个长期运行在后台得用户组件,没有用户界面
Service可以和其他组件之间进行交互,一般由Activity启动但是不依赖于Acitivy,当Activity的生命周期结束的时候也不会结束Service,直至Service自己的生命周期结束,也通常被称之为后台服务,所谓后台是一个想对于前台的概念,具体是指其本身的运行不需要依靠用户可视化UI界面,其两大应用场景:后台运行+跨进程访问(当service被其它应用组件启动的时候,即使用户切换到别的应用程序,服务仍然在后台继续运行)
Service的特点
(1)无用户界面,后台运行
(2)长时间运行
(3)有自己的生命周期管理,存在onCreate(),onStartCommand(),onBind(),onUnbind(),onDestory()等回调方法
Service的生命周期
Service有自己的生命周期,但是特别的是,因为启动服务的方式有两种:startService()和bindService()两种方法,不同方式启动的Service生命周期不太一样
-
如果是startService()的方式启动Service
首先是onCreate()方法,然后是onStartCommand()方法,执行完毕后Service就会处于运行态,然后当Sewrvice要结束的时候就会执行onDestroy()方法,最后销毁
-
如果是bindService()的方式启动Service
首先是onCreate()方法,然后是onBind()方法,执行完毕后Service进入运行态,然后运行完毕之后,首先需要进行onunbindService()方法然后再执行onDestory()方法,最后被销毁
服务的创建
Service服务本质上就是一个Service的类,所以无论是New->Service在IDE中快速创建服务,还是手动创建一个Service,都是创建了一个Service的子类
public class MyService extends Service {
public void onCreate() {
super.onCreate();
// 初始化代码
}
public int onStartCommand(Intent intent, int flags, int startId) {
// 执行任务
return START_STICKY; // 返回 START_STICKY 表示如果服务被意外终止,系统会尝试重启它
}
public IBinder onBind(Intent intent) {
// 返回一个 IBinder 实例,用于与服务交互
return null; // 如果不支持绑定,则返回 null
}
public void onDestroy() {
super.onDestroy();
// 清理资源
}
}
需要注意的是,也需要在全局主配置文件中声明<service>标签
<service android:name=".MyService"
android:enbale="true" //表示系统是否能实例化该服务
android:exported="true" //表示该服务是否能被其他应用程序中的组件或者进行交互
/>
服务的启动
上面也有说到,启动的方式有两种startService()和bindService()
startService()
程序中通过startService()方法启动的服务将会在后台长期运行,并且启动服务的组件和服务之间没有关联,即使启动服务的组件被销毁,Service服务只要还没结束就不会被销毁,主要就是通过startService()方法启动服务和stopService()方法停止服务
这种方式一般是通过意图启动服务
//启动服务
public void startService() {
//通过意图启动服务
Intent intent = new Intent(this, MyService.class);
startService(intent);
}
bindService()
当一个组件通过bindService()的方式启动Service服务,服务会和组件进行绑定,程序允许组件与服务交互且组件一旦退出/销毁/调用unbindService()方法 ,服务就会进行销毁,多个组件可以绑定一个服务 ,函数原型如下
// 第一个参数service 代表要绑定的service
// 第二个参数用于监听调用者和服务之间的连接状态,是一个服务连接类ServiceConnection对象。当调用者与Service连接成功的时候,会回调调用者的onServiceConnected()方法 ; 当调用者和服务之间的连接断开的时候,就会回调调用者的onServiceDisconnected()方法
// 第三个参数表示组件绑定服务时,如果Service还没被创建,是否自动创建Service,0表示不自动创建,BIND_AUTO_CREATE表示自动创建
bindService(Intent service,ServiceConnection conn , int flag)
这种方式一般是通过服务连接类调用bindService方法
//服务连接类
private class MyServiceConnection implements ServiceConnection {
public void onServiceConnected(ComponentName name, IBinder service) {
//连接服务时为其中的方法赋值
myBinder = (MyService.MyServiceProxy) service;
Log.i("MyService", "onServiceConnected连接服务" + service.toString());
}
public void onServiceDisconnected(ComponentName name) {
}
}
//绑定服务
public void bindMyService() {
Intent intent = new Intent(this, MyService.class);
//没有绑定服务时才绑定服务连接
if (myServiceConnection == null) {
//创建服务并且绑定
myServiceConnection = new MyServiceConnection();
//绑定服务
bindService(intent, myServiceConnection, BIND_AUTO_CREATE);
Toast.makeText(this, "绑定服务", Toast.LENGTH_SHORT).show();
Log.i("MyService", "绑定服务");
return ;
}
Toast.makeText(this, "服务已经连接", Toast.LENGTH_SHORT).show();
}
服务的通信
Service之间的通信分为了本地服务通信和远程服务通信
本地服务通信
本地服务通信是指应用程序内部的通信
这种方式我们其实上面说到了,也就是bindService()启动Service的时候,调用者组件和Service之间是可以通信的。
其主要原因是,在service被bindService()启动之后,onBInd()方法时候会返回一个IBinder对象,这个对象也会被传入给我们bindService()的三个参数中的第二个参数即ServiceConnection对象,我们上面的案例代码也可以看到MyServiceConnection实现ServiceConnection接口后重写的onserviceConnected()方法的入参中第二个参数就是IBinder对象
调用者(绑定服务的组件)就会通过IBinder对象和Service进行通信,组件可以通过ServiceConnection对象中的IBinder对象调用service中的方法从而执行对应的操作
private class MyServiceConnection implements ServiceConnection {
// 第二个参数就是IBinder对象
public void onServiceConnected(ComponentName name, IBinder service) {
myBinder = (MyService.MyServiceProxy) service;
Log.i("MyService", "onServiceConnected连接服务" + service.toString());
}
public void onServiceDisconnected(ComponentName name) {
}
}
远程服务通信
所谓远程服务通信,就是两个应用程序之间的通讯。Android中每个应用程序本质上就是一个运行在手机上的进程,所以远程服务通信其实就是进程之间的通信,通过AIDL实现
AIDL是一种接口定义语言,语法格式简单,与Java中的接口定义类似,有如下两个特点
-
AIDL定义接口的源码必须与 .aidl 作为拓展名结尾
-
AIDL接口中用到的数据类型,除了基础数据类型String,List,Map,CharSequence之外,其他类型都需要进行导入包,即使他们在同一个包下
Service和Activity的区别
特性 | Activity | Service |
---|---|---|
主要用途 | 提供用户界面(UI),处理用户交互(如点击、滑动)。 | 在后台执行长时间运行的任务(如下载、音乐播放),无需直接用户交互。 |
生命周期 | 受用户操作直接影响(如启动、切换应用、屏幕旋转)。 | 独立于界面,可长期运行(即使应用退到后台)。 |
用户可见性 | 必须在前台显示界面。 | 默认无界面;可通过前台服务(Foreground Service)在通知栏显示状态。 |
启动方式 | 通过 Intent 显式或隐式启动。 |
通过 startService() (启动服务)或 bindService() (绑定服务)。 |
交互方式 | 直接与用户交互(如按钮点击、输入数据)。 | 通过回调、广播、绑定接口等方式与应用其他组件通信。 |
资源优先级 | 高优先级(系统优先保留可见的 Activity)。 | 默认低优先级;前台服务优先级与 Activity 相当。 |
典型场景 | 登录界面、设置页、商品详情页。 | 后台下载、音乐播放、位置跟踪、数据同步。 |
销毁条件 | 用户主动退出、系统回收内存(如应用进入后台)。 | 启动服务需手动调用 stopSelf() 或 stopService() ;绑定服务在所有绑定解除后销毁。 |
网络编程
Android的网络编程个人感觉就是计网+Java Socket编程 + IO知识 + 特殊组件的杂合,有如上知识体系的其实看起来应该不会很难
通过HTTP访问网络
Android对于HTTP通讯就是通过标准的Java的HttpURLConnection实现基于URL的请求和响应
HTTP协议通讯简介
这个考虑到计网大家都学过,书上也没写多少,就简单写几点
-
基于TCP
-
请求-响应模型,HHTP请求+HTTP响应
-
中文名为超文本传输协议
HttpURLConnection的使用
先看一段案例代码,应该很熟悉了
URL url = new URL("http://example.com"); // 创建URL对象
// 利用url对象的openConnection()方法获取HttpURLConnection连接对象
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
// 设置请求方式 常见的有GET POST DELETE PUT
connection.setRequestMethod("GET");
// 设置响应超时时间
connection.setConnectionTimeout(5000);
// 获取HTTP响应状态码
int responseCode = connection.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
// 如果响应为200即成功 则获取输入流 准备将结果读取到内存中
InputStream inputStream = connection.getInputStream();
// 处理输入流
}
connection.disconnect();
这里需要注意GET请求和POST请求的区别
//GTE请求,参数信息一般放在URL后面,作为路径参数携带,所以GET请求一般比较小,不超过1024字节
private static void sendGetRequest(String baseUrl) {
try {
// 定义参数并编码
Map<String, String> params = new HashMap<>();
params.put("name", "John Doe");
params.put("age", "30");
// 拼接URL和编码后的参数 直接采用URL路径后面拼接的方式
String queryString = buildQueryString(params);
URL url = new URL(baseUrl + "?" + queryString);
// 创建连接并设置请求方法
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(5000);
conn.setReadTimeout(5000);
// 获取响应
int responseCode = conn.getResponseCode();
System.out.println("GET Response Code: " + responseCode);
// 读取响应内容
try (BufferedReader in = new BufferedReader(
new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8))) {
String line;
StringBuilder response = new StringBuilder();
while ((line = in.readLine()) != null) {
response.append(line);
}
System.out.println("GET Response Body: " + response);
}
conn.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
}
// POST请求会将参数信息放到请求体中,以键值对的形式存在,可携带量比GET请求大且相对安全
private static void sendPostRequest(String baseUrl) {
try {
// 定义参数并编码
// 利用map采用key-value的结构进行保存参数
Map<String, String> params = new HashMap<>();
params.put("name", "John Doe");
params.put("age", "30");
String postData = buildQueryString(params);
// 创建连接并设置请求方法
URL url = new URL(baseUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setDoOutput(true);
conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
conn.setConnectTimeout(5000);
conn.setReadTimeout(5000);
// 写入请求体
try (OutputStream os = conn.getOutputStream()) {
byte[] input = postData.getBytes(StandardCharsets.UTF_8);
os.write(input, 0, input.length);
}
// 获取响应
int responseCode = conn.getResponseCode();
System.out.println("POST Response Code: " + responseCode);
// 读取响应内容
try (BufferedReader in = new BufferedReader(
new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8))) {
String line;
StringBuilder response = new StringBuilder();
while ((line = in.readLine()) != null) {
response.append(line);
}
System.out.println("POST Response Body: " + response);
}
conn.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
}
WebView控件的使用
Android默认提供了内置浏览器,使用的是开源的WebKit协议,可以进行搜索和发送邮件,播放视频等等,对于内置浏览器的使用,配套的存在WebView控件进行操作,该控件可以指定URL,还可以加载HTML代码,且支持JS代码,可以利用WBView控件进行网络编程
和其他的控件一样,既可以在XML布局文件中进行编写也可以通过Java/Kotlin对象的方式创建
<WebView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/webview"
android:layout_width="match_parent"
android:layout_height="match_parent">
</WebView>
常规使用
定义好WebView之后,可以在利用其提供的方法进行网络编程操作,常用的方法如下
方法名称 | 功能描述 |
---|---|
loadUrl(String url) | 加载指定的URL对应的页面 |
loadData(String data,String mimeType,String encoding) | 用指定的字符串数据加载到浏览器中 |
loadDtaWithBaseURL(String baseURL,String data,String mimeType,String encoding,String historyUrl) | 基于URL加载指定的数据 |
capturePicture() | 创建当前屏幕的快照 |
goBack() | 用于执行后退操作,相当于浏览器的回退按钮 |
goForward() | 用于执行前进操作,相当于浏览器的前进按钮 |
stopLoading() | 用于停止加载当前页面 |
reload() | 用于刷新当前页面 |
但是需要注意,Androio默认的初始项目是不允许网络访问的,所以需要我们在AndroidManifest.xml全局配置文件中<application>标签中添加一个属性允许网络访问
<application
android:usesCleartextTraffic="true"
.........
/>
展示一下部分方法
// 运行如下代码 我们就能很愉快的在Android Studio中快乐的学习安卓
class MainActivity : ComponentActivity() {
private val TAG = "MainActivityLifecycle"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.d(TAG, "onCreate called")
enableEdgeToEdge()
setContent {
KtAndroisTheme {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
Greeting(
name = "Android",
modifier = Modifier.padding(innerPadding)
)
}
}
}
}
}
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
Column(modifier = modifier) {
// 新增 XML 布局
AndroidView(
factory = { context ->
// 加载 XML 布局
val view = View.inflate(context, R.layout.webviewtext, null)
val activity = context as MainActivity
val webView = view.findViewById<WebView>(R.id.webview)
Toast.makeText(activity,"准备进入帅哥の博客站,骚等~",Toast.LENGTH_LONG).show()
webView.loadUrl("http://8.154.40.120:24180/")
view
},
modifier = Modifier.padding(20.dp)
)
}
}
@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
KtAndroisTheme {
Greeting("Android")
}
}
然后是加载本地HTML代码和直接解析HTML代码段
// 这是直接加载HTML代码段
// 运行如下代码 我们就能很愉快的在Android Studio中快乐的学习安卓
class MainActivity : ComponentActivity() {
private val TAG = "MainActivityLifecycle"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.d(TAG, "onCreate called")
enableEdgeToEdge()
setContent {
KtAndroisTheme {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
Greeting(
name = "Android",
modifier = Modifier.padding(innerPadding)
)
}
}
}
}
}
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
Column(modifier = modifier) {
// 新增 XML 布局
AndroidView(
factory = { context ->
// 加载 XML 布局
val view = View.inflate(context, R.layout.webviewtext, null)
val activity = context as MainActivity
val webView = view.findViewById<WebView>(R.id.webview)
Toast.makeText(activity,"加载一下我们的HTML代码",Toast.LENGTH_LONG).show()
val html = "<!DOCTYPE html>\n" +
"<html lang=\"en\">\n" +
"<head>\n" +
" <meta charset=\"UTF-8\">\n" +
" <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n" +
" <title>WebView Study</title>\n" +
"</head>\n" +
"<body>\n" +
" <h1>这是H1</h1>\n" +
" <h2>这是H2</h2>\n" +
" <div>content</div>\n" +
"</body>\n" +
"</html>";
webView.loadData(html, "text/html", "UTF-8");
view
},
modifier = Modifier.padding(20.dp)
)
}
}
@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
KtAndroisTheme {
Greeting("Android")
}
}
// 这是直接加载HTML文件 只有Greeting不一样 所以这里只贴这一部分的代码
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
Column(modifier = modifier) {
// 新增 XML 布局
AndroidView(
factory = { context ->
// 加载 XML 布局
val view = View.inflate(context, R.layout.webviewtext, null)
val activity = context as MainActivity
val webView = view.findViewById<WebView>(R.id.webview)
Toast.makeText(activity,"加载一下我们的本地HTML代码",Toast.LENGTH_LONG).show()
// 查阅资料发现访问本地HTML和HTML文件位置有关 这里就先没有展示了 使用loadUrl方法,传入文件路径即可
view
},
modifier = Modifier.padding(20.dp)
)
}
}
支持JS代码
默认情况下,WebView是不支持JS代码的,为了使其支持,需要对webView对象进行一定的配置,这里需要用到WebSettings对象
WebSettings setttings = webview.getSettings() // 获取webview的WebSettings对象
settings.setJavaScriptEnable() // 设置支持JS代码
这样配置之后,能支持绝大部分的JS代码,但是还有一些类似弹窗JS代码 window.alert()这种还是无法支持的,所以还是需要再加一个配置,这个配置是webview对象进行的配置,而不是webSettings对象
webview.setWebChromeClient(new WebChromeClient());
JSON数据解析
网络传输中的数据传输形式,尤其是HTTP网络传输,数据绝大部分是以JSO的格式进行传输的,JSON数据格式不可直接展示在程序界面上,所以我们需要对JSON格式的数据进行解析从而获得我们需要的样式(考试无关知识:这个过程称之为JSON的反序列化)
如下是一个标准的大部分数据类型都覆盖的JSON格式如下 整体采用 key-value 的形式 key需要打双引发 ,value可以是number数字类型,String字符串类型,array数组类型等等
{
// 用户信息对象
"user_info": {
// 基础信息
"basic": {
"username": "Alice", // 字符串类型 - 用户名
"age": 28, // 数字类型 - 年龄
"is_member": true, // 布尔类型 - 是否是会员
"registration_date": "2023-10-01T15:30:00Z" // 字符串类型 - ISO 8601日期格式
},
// 地址信息(嵌套对象)
"address": {
"street": "123 Tech Valley Rd", // 字符串类型 - 街道
"city": "San Francisco", // 字符串类型 - 城市
"coordinates": [37.7749, -122.4194] // 数组类型 - 经纬度坐标(数字)
},
// 联系方式(混合类型)
"contact": {
"email": "alice@example.com", // 字符串类型 - 邮箱
"phone_numbers": [ // 数组类型 - 电话号码
"+1-555-0100",
"+1-555-0101"
],
"emergency_contact": null // null类型 - 未设置紧急联系人
},
// 高级属性
"metadata": {
"premium_level": 3, // 数字类型 - 会员等级
"subscription_ids": [101, 205, 307], // 数组类型 - 订阅ID集合
"preferences": { // 嵌套对象 - 用户偏好设置
"theme": "dark", // 字符串类型 - 主题偏好
"notifications_enabled": false // 布尔类型 - 是否启用通知
}
}
},
// 系统元数据
"system": {
"api_version": 2.1, // 浮点数类型 - API版本号
"environment": "production" // 字符串类型 - 运行环境
}
}
对于JSON数据的解析,市面上已经有了很多方案了,例如Java自带的JSONObject和JSONArray ; 阿里开源的Fast-Json 和 Fast-Json2 ;谷歌开源的Gson等等,书上主要讲的是Java自带的JSONObject和JSONArray 以及谷歌开源的Gson
直接看代码吧
/*
分别采用Java自带的方式 和 GSON 统一对 数据 [{\"name\": \"lisi\", \"age\": 25}, {\"name\": \"jason\", \"age\": 20}] 进行解析
*/
// Java自带方式
public class OrgJsonExample {
public static void main(String[] args) {
String jsonStr = "[{\"name\": \"lisi\", \"age\": 25}, {\"name\": \"jason\", \"age\": 20}]";
try {
// 将其解析为两个JSON数组格式 即{\"name\": \"lisi\", \"age\": 25} 和 {\"name\": \"jason\", \"age\": 20}
JSONArray jsonArray = new JSONArray(jsonStr);
// 对上述两个Json分别解析
for (int i = 0; i < jsonArray.length(); i++) {
JSONObject personObj = jsonArray.getJSONObject(i); // 拿出第i个对象
String name = personObj.getString("name"); // getString(key)和getInt(key)通过key获取对应的value
int age = personObj.getInt("age");
System.out.println("Name: " + name + ", Age: " + age);
}
} catch (Exception e) {
System.err.println("解析失败: " + e.getMessage());
}
}
}
//GSON方式
// 将json封装为一个对象 这个是GSON库必须要求的
public class Person {
private String name;
private int age;
// 必须有无参构造方法
public Person() {}
// Getter/Setter 方法(或使用 Lombok)
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
}
public class GsonExample {
public static void main(String[] args) {
String jsonStr = "[{\"name\": \"lisi\", \"age\": 25}, {\"name\": \"jason\", \"age\": 20}]";
Gson gson = new Gson();
try {
// 直接将JSON对应的类封装,fromJson第一个参数为要解析的JSON数据,第二个为内部定义的一个TypeToken对象,需要一个泛型,泛型即JSON对应的类对象类型
List<Person> people = gson.fromJson(jsonStr, new TypeToken<List<Person>>(){}.getType());
for (Person person : people) {
System.out.println("Name: " + person.getName() + ", Age: " + person.getAge());
}
} catch (Exception e) {
System.err.println("解析失败: " + e.getMessage());
}
}
}
Handler机制
在Android中,应用程序启动的时候,主程序其实就是UI程序,负责管理UI界面中的程序和事件分发,例如当我们界面上有一个Button按钮的时候,点击发送一个网络IO事件等耗时操作,将操作结果回显到UI界面上,这样其实会导致UI界面必须等着操作完成,程序阻塞出现假死现象,在正常的web服务中,我们可以开一个子线程完成这个事情,但是在Android中是不允许的,因为Androdi的UI更新操作只能由主线程执行
所以这里需要一个回调机制,子线程等待耗时操作完成,完成之后回调告知主线程,从而委托主线程完成UI的更新,Android提供了这种回调机制即Handler
Handler中包括四个主要的对象:Message,Handler,MessageQueue,Looper
-
Message:线程之间传递的消息,在内部携带少量信息,用于在不同线程之间交换信息,内部的what字段可以携带int类型的数据,obj字段可以携带object类型的数据
-
Handler:处理者,用于发送消息和接收消息,一般使用Handler对象的sendMessage发送消息,发出的消息经过一系列处理之后会落到handler的handlerMessge()方法中
-
MessageQueue:消息队列,用于存储通过Handler发送的消息,Handler发出去的消息都会先经过MessageQueue,当MessageQueue空闲就会快速处理完毕然后发送出去 ; 如果MessageQueue比较繁忙,Message就会排队等待MessageQueue处理,一个线程中只会有一个MessageQueue
-
Looper:Looper负责管理MessageQueue,当MessageQueue中存在消息的时候,Looper就会从中取出信息(实际上就是一个无限循环 轮询查询并且拿取MessageQueue中的数据)并且传递到Handler对象中的handlerMessage方法中,每个线程中也只会有一个Looper,并且在主线程中创建Handler对象的时候默认就会创建一个Looper对象,无需手动创建;如果是子线程中需要创建使用Looper,就需要在子线程的Handler对象中通过Looper.loop()方法启动,从而开启消息循环。
可以将将Handler相关的原理机制形象的描述为以下情景:
Handler 快递员兼配送员 1. 接收用户寄件请求(发送消息) 2. 将包裹派送到目的地(处理消息)
Message 包裹 包含寄件人信息(目标 Handler)、内容(数据)和标签(what、arg 等)
MessageQueue 传送带 按顺序存放包裹(消息队列),先进先出
Looper 传送带操作员 持续从传送带上取包裹,并分发给对应的配送员(Handler)处理
Thread 快递公司分站点 主线程站点默认有操作员(Looper)和传送带;子线程站点需手动配置操作员和传送带
子线程更新主线程 UI
用户寄件(子线程发送消息)
你在子线程(分站点 A)想更新主线程(分站点 B)的 UI。
你打包一个 Message(包裹),填写收件地址为主线程的 Handler,并放入传送带(MessageQueue)。
传送带运作(Looper 循环处理)
主线程的传送带操作员(Looper)不断检查传送带(MessageQueue)。
发现新包裹后,操作员将其交给收件地址指定的配送员(主线程的 Handler)。
包裹派送(消息处理)
配送员(Handler)拆开包裹(解析 Message),根据内容更新 UI(如刷新 TextView)。
文章标题:安卓速通(其实是期末复习~)(第八章+第九章)
文章链接:https://zealsinger.xyz/?post=13
本站所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明来自ZealSinger !
如果觉得文章对您有用,请随意打赏。
您的支持是我们继续创作的动力!

微信扫一扫

支付宝扫一扫