四大组件学习:服务Service
文章目录
服务的定义
Service是Android中实现后台运行的解决方案,他非常适合执行那些不需要和用户交互且需要长期运行的任务。服务的运行不依赖任何的用户界面,即使程序被切换到后台,或者用户打开另外的应用程序,服务仍可以保持运行。
不过需要注意的是,服务并不是运行在一个独立的进程中的,二十依赖创建服务时所在的应用程序进程。当某个应用程序进程被杀死,所有依赖该进程的服务都会停止。
服务并不会开启线程,所有的代码都是默认运行在主线程当中的。也就是说,需要在服务的内部手动创建子线程,并在这里执行具体的任务,否则就有可能出现主线程被阻塞住的情况。
Android多线程编程
### 1.线程用法
1.定义线程
class MyThread extends Thread{
@Override
public void run(){
//具体工作
}
}//定义线程,1.继承Thread 2.重写run方法
启动线程
new Mythread().start();
new 出实例,调用start方法,run就会在子线程当中运行
2.更多的时候都不是使用继承的方法,而是选择使用Runnable接口来定义一个线程
class MyThread implements Runnable{
@Override
public void run(){
//具体工作
}
}
启动线程
MyThread myThread = new MyThread();
new Thread(myThread).start();
Thread的给v偶早函数接受一个Runnable 参数,而我们new出的MyThread正式一个实现了Runnable接口的对象,所以就可以直接将他方法Thread的构造函数里面。调用Thread的start方法就可以启动
3.使用匿名类的方法,这种方法更加常见
new Thread (new Runnable){
@Override
public void run(){
//处理具体的逻辑
}
}.start();
2.在子线程中更新Ui
因为Android应用当中,UI是线程不安全的,也就是说要想更新应用程序的UI内容,就必须在主线程中进行。
但是有时需要在子线程里去执行一些耗时任务,然后根据任务的接过来更新对应的UI,Android提供了一套异步消息处理机制,完美解决了在子线程中执行UI操作的问题。
异步消息处理的使用方法
package com.example.androidthreadtest;
import android.os.Handler;
import android.os.Message;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
public final int changeText = R.id.change_text;
public static final int UPDATE_TEXT = 1;
private TextView text;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
text = (TextView) findViewById(R.id.text);
Button button = (Button) findViewById(R.id.change_text);
button.setOnClickListener(this);
}
private Handler handler = new Handler() {
public void handleMessage(Message msg) {
switch (msg.what) {
case UPDATE_TEXT:
// 在这里可以进行 UI 操作
text.setText("Nice to meet you");
break;
default:
break;
}
}
};
@Override
public void onClick(View v) {
if (v.getId() == R.id.change_text) {
new Thread(new Runnable() {
@Override
public void run() {
Message message = new Message();
message.what = UPDATE_TEXT;
handler.sendMessage(message);
}
}).start();
}
}
}
先定义一个整型常量UPDATE_TEXT,用来表示更新TextView这个动作,然后新增一个Handler对象,并重写父类的handleMessage()方法,在这里对具体的Message进行处理。如果发现Message的what字段的值等于UPDATE_TEXT,就将TextView的显示内容更改。
下面再来看一下Change Text按钮的点击事件中的代码。可以看到并没有在子线程中直接进行UI操作,而是创建一个Message(android.os.Message)对象,并将它的what设置为UPDATE_TEXT,然后调用Handler的sendMessage方法将这条Message发送出去。很快,Handler就收到这条Message,并在handleMessage中对他处理,此时handleMessage方法中的代码就是在主线程中运行的了,所以可以放心在这里进行UI操作。
3.解析异步消息处理机制
一、异步消息处理组成部分
Android中的异步消息处理主要有4各部分组成。
- Message:在线程间传递的消息,可携带少量信息(
what
、arg1
、arg2
整型字段,obj
携带Object
对象 ),用于不同线程交换数据。 - Handler:用于发送和处理消息。通过
sendMessage()
发消息,消息经处理后传递到handleMessage()
处理 。 - MessageQueue:消息队列,存放所有经
Handler
发送的消息,等待被处理,每个线程仅有一个MessageQueue
对象。 - Looper:每个线程中
MessageQueue
的管家,调用loop()
进入无限循环,从MessageQueue
取出消息并传递到Handler
的handleMessage()
,每个线程仅有一个Looper
对象。
二、异步消息处理流程
- 主线程创建
Handler
并复写handleMessage()
。 - 子线程需 UI 操作时,创建
Message
,经Handler
发送到MessageQueue
等待处理。 Looper
从MessageQueue
取消息分发回Handler
的handleMessage()
,因Handler
主线程创建,handleMessage()
代码在主线程运行,可安全进行 UI 操作 。
三、补充说明
runOnUiThread()
是异步消息处理机制的接口封装,原理与上述流程一致,简化了使用 。
使用AsyncTask
Android还提供了另外一些好用的工具AsyncTask。
**AsyncTask的基本用法:**由于AsyncTask是一个抽象类,在继承时我们可以为AsyncTask指定三个泛型参数,这三个参数的用途如下。
**1.Params。**在执行AsyncTask是需要传入的参数,可以用于在后台任务中使用
**2.Progress。**后台执行任务时,如果需要在界面上显示当前的进度,则使用这里指定的泛型作为返回值类型
**3.Result。**当任务执行完成后,如果需要对结果进行返回,就是用这里指定的反省作为返回值类型。
一个最简单的自定义AsyncTask
class DownloadTask extends AsyncTask<Void,Integer,Bolean>{
}
这里把AsyncTask的第一个泛型指定成Void,表示在执行AsyncTask的时候不要传入参数给后台任务,第二个Integer用来使用整形数据来显示进度。第三个Boolean表示使用布尔值来反馈执行结果。
下面需要对DownloadTask来重写AsyncTask的几个方法就可以完成对任务的定制。
1.onPreExecute()
此方法会在后台任务开始执行之前调用,可以做一些UI初始化,比如显示一个进度条对话框。
2。doInBackground(Params …)
这个方法的所有代码都会在子线程中运行,所以应该在这里取处理所有的好事任务,任务一旦通过就可以return语句来将任务执行的结果返回。如果第三个参数是Void就可以不反悔。
**注意:**这个方法不可以进行UI操作,如果需要更新UI,如返回当前任务进度,可以调用publishProgress(Progress…)完成
3.onProgressUpdate(Progress …)
当后台调用return进行返回时,这个方法就会被调用,返回的数据作为参数传递到此方法中,可以利用来进行一些UI操作,如提示结果,以及显示关闭进度条对话框。
class DownloadTask extends AsyncTask<Void, Integer, Boolean> {
// 假设这些成员已在合适地方初始化,实际使用需根据情况处理
private ProgressDialog progressDialog;
private Context context;
@Override
protected void onPreExecute() {
progressDialog.show(); // 显示进度对话框
}
@Override
protected Boolean doInBackground(Void... params) {
try {
while (true) {
int downloadPercent = doDownload(); // 虚构的计算下载进度方法
publishProgress(downloadPercent);
if (downloadPercent >= 100) {
break;
}
}
} catch (Exception e) {
return false;
}
return true;
}
@Override
protected void onProgressUpdate(Integer... values) {
// 更新下载进度显示
progressDialog.setMessage("Downloaded " + values[0] + "%");
}
@Override
protected void onPostExecute(Boolean result) {
progressDialog.dismiss(); // 关闭进度对话框
// 根据结果提示下载结果
if (result) {
Toast.makeText(context, "Download succeeded", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(context, "Download failed", Toast.LENGTH_SHORT).show();
}
}
// 虚构的计算下载进度方法,需实际实现或替换逻辑
private int doDownload() {
// 这里应编写真实计算下载进度的代码,比如结合网络请求、文件读写等
// 此处简单模拟,实际需完善
return 0;
}
}
上述代码定义了一个继承自 AsyncTask
的 DownloadTask
,用于模拟下载任务,涵盖了 AsyncTask
核心的四个回调方法:onPreExecute
(准备工作,如显示进度框 )、doInBackground
(后台执行耗时任务 )、onProgressUpdate
(更新进度,可进行 UI 操作 )、onPostExecute
(任务结束,处理结果、收尾,可进行 UI 操作 ) 。不过代码里 progressDialog
、context
等成员需在合适地方(像构造方法、外部传入等 )正确初始化,doDownload
方法也需按实际下载逻辑完善 。
如果要启动这个任务只需要
new DownloadTask().execute();
服务的基本用法
1.定义一个服务
- 服务名为
MyService
,Exported
属性控制是否允许当前程序外其他程序访问,Enabled
属性控制服务是否启用,创建时可勾选这两个属性。
service的代码
package com.example.servicetest;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
public class MyService extends Service {
public MyService() {
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
}
MyService
继承自Service
类,onBind()
是Service
中唯一抽象方法,需在子类实现,当前可暂忽略 。
Service 核心重写方法及作用
-
onCreate()
:服务创建时调用,可做初始化操作,重写需调用super.onCreate()
。 -
onStartCommand()
:每次服务启动时调用,若服务启动后要立即执行动作,逻辑写在此,重写需调用super.onStartCommand(intent, flags, startId)
。 -
onDestroy()
:服务销毁时调用,用于回收不再使用的资源,重写需调用super.onDestroy()
。public class MyService extends Service { @Override public void onCreate() { super.onCreate(); } @Override public int onStartCommand(Intent intent, int flags, int startId) { return super.onStartCommand(intent, flags, startId); } @Override public void onDestroy() { super.onDestroy(); } }
-
Service 生效条件:需在
AndroidManifest.xml
中注册,Android Studio 一般会自动完成。注册时通过``标签配置,如android:name
指定服务类名,android:enabled
控制是否启用,android:exported
控制是否允许其他程序访问 。
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.servicetest">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<service
android:name=".MyService"
android:enabled="true"
android:exported="true">
</service>
</application>
</manifest>
这样就完全定义好一个服务了
2.启动和停止
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/start_service"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Start Service" />
<Button
android:id="@+id/stop_service"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Stop Service" />
</LinearLayout>
主活动布局加两个按钮启动和停止
MainActivity
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button startService = (Button) findViewById(R.id.start_service);
Button stopService = (Button) findViewById(R.id.stop_service);
startService.setOnClickListener(this);
stopService.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.start_service:
Intent startIntent = new Intent(this, MyService.class);
startService(startIntent); // 启动服务
break;
case R.id.stop_service:
Intent stopIntent = new Intent(this, MyService.class);
stopService(stopIntent); // 停止服务
break;
default:
break;
}
}
}
MainActivity
实现 View.OnClickListener
接口,在 onCreate
中获取按钮实例并设置点击监听器;onClick
方法里通过 switch
语句,依据按钮 id
分别构建 Intent
,调用 startService
(启动服务 )和 stopService
(停止服务 )方法 。
注意 startService
和 stopService
方法定义在 Context
类中,活动可直接调用;服务也可通过 stopSelf
方法自行停止 。
package com.example.servicetest;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
import androidx.annotation.Nullable;
public class MyService extends Service {
@Override
public void onCreate() {
super.onCreate();
Log.d("MyService", "onCreate executed");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d("MyService", "onStartCommand executed");
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d("MyService", "onDestroy executed");
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
修改service
在 MyService
的 onCreate()
、onStartCommand()
、onDestroy()
等生命周期方法中加入日志打印(如 Log.d
),通过观察日志判断服务是否成功启动、停止 。
3.服务和活动的通信
- 服务与活动的关系问题:启动服务后,活动与服务基本脱离关联,活动无法控制服务具体逻辑及知晓执行情况,想让关系更紧密需用
onBind()
方法。 onBind()
方法作用:可让活动指挥服务执行任务,通过创建继承Binder
的类(如DownloadBinder
),在服务中返回该类实例,活动绑定服务后可调用其方法。- 实现步骤
- 服务端:创建
DownloadBinder
类,含业务方法(如模拟下载的startDownload
、getProgress
);在服务的onBind
方法返回DownloadBinder
实例。 - 活动端:布局添加绑定(
bind_service
)、解绑(unbind_service
)按钮;活动中通过ServiceConnection
建立与服务的连接,连接成功后获取DownloadBinder
实例,进而调用服务内方法 。
- 服务端:创建
服务端代码(MyService.java
)
public class MyService extends Service {
private DownloadBinder mBinder = new DownloadBinder();
class DownloadBinder extends Binder {
public void startDownload() {
Log.d("MyService", "startDownload executed");
}
public int getProgress() {
Log.d("MyService", "getProgress executed");
return 0;
}
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
}
布局文件代码(activity_main.xml
)
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/bind_service"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Bind Service" />
<Button
android:id="@+id/unbind_service"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Unbind Service" />
</LinearLayout>
活动端代码(MainActivity.java
)
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private MyService.DownloadBinder downloadBinder;
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
downloadBinder = (MyService.DownloadBinder) service;
downloadBinder.startDownload();
downloadBinder.getProgress();
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button bindService = findViewById(R.id.bind_service);
Button unbindService = findViewById(R.id.unbind_service);
bindService.setOnClickListener(this);
unbindService.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.bind_service:
Intent bindIntent = new Intent(this, MyService.class);
bindService(bindIntent, connection, BIND_AUTO_CREATE);
break;
case R.id.unbind_service:
unbindService(connection);
break;
default:
break;
}
}
}
服务的生命周期
一、服务生命周期相关方法
服务生命周期涉及的关键回调方法有 onCreate()
、onStartCommand()
、onBind()
、onDestroy()
,这些方法在服务不同阶段被调用 。
二、startService()
相关流程
- 调用
Context
的startService()
方法会启动服务,若服务未创建,先执行onCreate()
,再执行onStartCommand()
;若已创建,直接执行onStartCommand()
,且每次调用startService()
都会触发onStartCommand()
,但服务仅一个实例 。 - 服务启动后持续运行,直到调用
stopService()
(外部调用 )或stopSelf()
(服务自身调用 ),此时会执行onDestroy()
销毁服务 。
三、bindService()
相关流程
- 调用
Context
的bindService()
可获取服务持久连接,若服务未创建,先执行onCreate()
,再执行onBind()
;已创建则直接执行onBind()
,调用方可获取onBind()
返回的IBinder
对象与服务通信 。 - 只要连接未断开(未调用
unbindService()
),服务就保持运行,调用unbindService()
会触发onDestroy()
(若同时满足停止条件 )。
四、混合调用(startService()
+ bindService()
)的销毁条件
若服务既通过 startService()
启动,又通过 bindService()
绑定,需同时调用 stopService()
和 unbindService()
,让服务的启动和绑定条件都不满足,才会执行 onDestroy()
销毁服务 。