您的位置:首页 > 移动开发 > Android开发

Android四大组件之Service全面学习

2017-11-18 12:50 736 查看

什么是服务

Android 的服务是 Android 四大组件之一,其他的三大组件是Activity、Broadcast Receiver、Content Provider。

它是程序运行在后台的解决方案。

服务依赖于应用程序进程,它创建它的进程被杀掉时,服务就会停止。

服务不会主动创建在子线程中,需要我们自己手动在服务内部创建子线程,否则主线程就有可能被阻塞住。

所以在学习服务之前,需要补充一点多线程编程的知识。

Android 多线程编程

线程的多种用法

Android 的多线程基本上跟 Java 是一致的。

创建一个类,继承自 Thread并重写父类的 run()方法:

class MyThread extends Thread{
@Override
public void run(){
// 处理具体的逻辑
}
}


调用这个线程:

new MyThread().start();


使用继承的方式耦合性较高,可以选择实现Runnable接口的方式来定义一个线程:

class MyThread implements Runnable{

@Override
public void run(){
// 处理具体的逻辑
}
}


实现 Runnable 接口的方式定义一个线程,调用它跟之前的继承的调用有些区别:

MyThread myThread = new MyThread();
new Thread(myThread).start();


如果连实现接口都觉得麻烦,可以使用匿名类的方式,这种方法更加普遍:

new Thread(new Runnable(){
@Override
public void run(){
// 处理具体的逻辑
}
}).start();


在子线程中更新 UI

Android 的 UI 也是线程不安全的,也就是说它的 UI 元素必须在主线程中进行。

我们可以通过一个例子,来演示 UI 元素如何在子线程中更新就是不安全的,会报错的。

这个例子是这样的,有两个控件,一个按钮,一个 textView,点击按钮改变textView的文字,不过改变文字是在子线程中进行的。

首先在res/layout/activity_main.xml中添加按钮和textView:

<Button
android:id="@+id/change_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Change Text"/>

<TextView
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="Hello world"
android:textSize="20sp" />


然后在主程序中开辟线程,在线程中更新UI

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

private TextView text;

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

text = (TextView)findViewById(R.id.text);
Button changeText = (Button)findViewById(R.id.change_text);
changeText.setOnClickListener(this);
}

@Override
public void onClick(View v){
switch (v.getId()){
case R.id.c
1601d
hange_text:
new Thread(new Runnable() {
@Override
public void run() {
text.setText("Nice to see you");
}
}).start();
break;
default:
break;
}
}
}


结果当然是,app崩溃。

但是我们确实需要在子线程中执行一些耗时任务,然后根据结果更新UI,应该怎么办?

Android提供了一套异步消息处理机制,完美地解决了在子线程中更新UI的问题。

我们将代码修改如下:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

public static final int UPDATA_TEXT = 1;        // 添加
private Handler handler = new Handler(){        // 添加
public void handleMessage(Message msg){     // 添加
switch(msg.what){
case UPDATA_TEXT:
text.setText("Nice to meet you");
break;
default:
break;
}
}
};
private TextView text;

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

text = (TextView)findViewById(R.id.text);
Button changeText = (Button)findViewById(R.id.change_text);
changeText.setOnClickListener(this);
}

@Override
public void onClick(View v){
switch (v.getId()){
case R.id.change_text:
new Thread(new Runnable() {
@Override
public void run() {
Message message = new Message();    // 添加
message.what = UPDATA_TEXT;         // 添加
handler.sendMessage(message);       // 添加
}
}).start();
break;
default:
break;
}
}
}


解析异步消息处理机制

通过异步消息的处理,可以成功的在子线程中更新UI了(本质上是传递给Handler的handleMessage()在主线程中进行)。

Android的异步消息处理包括4个部分:Message、Handler、MessageQueue和Looper。

其中Message和Handler在之前已经接触过了。

接下来详细解析这几个部分:

Message:之前已经接触了它的what字段,它还可以使用arg1和arg2携带整型,使用obj携带Object对象。

Handler:它的使用方式是先在子线程中sendMessage(),最终传递到Handler的handleMessage()方法中。

MessageQueue:用于存放Handler发送的所有消息,这些消息放在队列中,等待被处理。每个线程中只有一个MessageQueue对象。

Looper:Looper就是MessageQueue的管家,调用Looper的loop()方法后,就会进行死循环中,然后发现MessageQueue中存在一条消息,就会将它取出,并传递到Handler的handleMessage()方法中,每个线程中同样也只有一个Looper对象。

异步处理机制可以很好的解决子线程中更新UI的问题,Android将它封装成了一个更简单的接口:

runOnUiThread()


使用AsyncTask

为了方便在子线程中对UI进行操作,Android还提供了另外一些好用的工具,比如AsyncTask。

借助AsyncTask,即使你对异步消息处理机制不了解,也可以很简单的从子线程切换到主线程。

AsyncTask的背后也是基于异步消息处理机制的,只是Android帮我们做了很好的封装。

AsyncTask基本用法

AsyncTask是一个抽象类,如果要使用它就要创建一个子类去继承它。

在继承时,可以为AsyncTask指定3个泛型参数。这3个参数的用途如下:

Params。执行AsyncTask时需要传入的参数,可用于在后台任务中使用。

Progress。后台任务执行时,如果需要在界面上显示进行,就使用这个参数作为进度单位。

Result。任务执行完毕时,如果需要结果返回,则可以使用这里指定的泛型作为返回值类型。

一个最简单的AsyncTask可以写成如下形式:

class DownloadTask extends AsyncTask<Void,Integer,Boolean>{

}


这只是一个空任务,需要重写几个方法才能完成对方法的定制。

// 在任务开始前执行,主要是对界面的一些初始化操作
onPreExecute()


// 这个方法中的所有代码都会在子线程中运行,应该在这里处理耗时任务。
// 如果AsyncTask的第三个参数是Void,可以不用返回。
// 这个方法是不可以进行UI操作的。
doInBackground(Params...)


// 在执行doInBackground(Params...)时,如果要更新UI元素,比如说任务进度,可以在这个方法中完成
publishProgress(Progress...)


// 如果在后台任务中调用了publishProgress(Progress...),这个方法很快就会被调用。
// 它的参数就是后台任务中传递过来的。
// 在这个方法中可以进行UI操作。
onProgressUpdate(Progress...)


// 当后台任务执行完毕后,这个方法很快就会被调用。
// 返回的参数会作为参数传递到这个方法中。
onPostExecute(Result)


// 如果要启动这个任务
new Download().execute();


服务的基本用法

定义一个服务

右键包名/New/Service/Service,就可以新建一个继承于Services的类了。

新建页上面有个属性Exported,是指是否允许给其他应用使用该服务,Enabled是指是否开启这个服务。

下面就是标准的服务代码:

public class MyService extends Service {
public MyService() {
}

@Override
// 这个方法是Service中唯一的抽象方法,必须在子类中实现。
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
@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");
}
}


每一个服务需要进行注册才能生效。

Android的四大组件都需要进行注册才能生效,不过Android Studio已经帮我们做好这一切了,只要我们是使用标准向导创建的四大组件。

打开AndroidManifest.xml,已经可以看到注册好了。

<service
android:name=".MyService"
android:enabled="true"
android:exported="true"></service>


启动和停止服务

启动和停止服务主要是借助Intent来实现的。

我们创建两个按钮,一个是启动Service的,一个是停止Service的。

在res/layout/activity_main.xml中添加两个按钮。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >

<Button
android:id="@+id/start_service"
android:text="Start Service"
android:layout_width="match_parent"
android:layout_height="wrap_content" />

<Button
android:id="@+id/end_service"
android:text="End Service"
android:layout_width="match_parent"
android:layout_height="wrap_content" />

</LinearLayout>


然后写点击按钮的逻辑。

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 endService = (Button)findViewById(R.id.end_service);
startService.setOnClickListener(this);
endService.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.end_service:
Intent endIntent = new Intent(this,MyService.class);
// 停止服务
// 在MyService类中的任何地方调用stopSelf()也可以停止服务
stopService(endIntent);
break;
default:
break;
}
}

}


活动和服务进行通信

前面服务开启以后就会一直运行,似乎跟活动没啥关系了。也不需要活动去控制什么,一切都是自动化的。

那么要在活动中指挥服务去干什么,应该怎么办呢?

还记得前面重写的onBind()抽象方法吗?

它就是干这个的!

onBind()使用的一个小例子

假如服务提供了一个下载的功能,在活动中我想随时开始,以及查看进度,这时候就可以在服务中创建一个专门的Binder对象来对下载功能进行管理。

public class MyService extends Service {
public MyService() {
}
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;     // 添加
}
@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();
}
}


那么如何在活动中去调用服务里的这些方法呢?

首先要创建两个按钮用来绑定活动和服务

<Button
android:id="@+id/bind_service"
android:text="Bind Service"
android:layout_width="match_parent"
android:layout_height="wrap_content" />

<Button
android:id="@+id/unbind_service"
android:text="UnBind Service"
android:layout_width="match_parent"
android:layout_height="wrap_content" />


在主窗口中点击绑定按钮,就会绑定服务和活动,点击解绑按钮,就会解绑服务和活动

public class MainActivity extends AppCompatActivity implements View.OnClickListener{

private MyService.DownloadBinder downloadBinder;

private ServiceConnection connection = new ServiceConnection(){
@Override
public void onServiceConnected(ComponentName componentName, IBinder service) {
downloadBinder = (MyService.DownloadBinder)service;
downloadBinder.startDownload();
downloadBinder.getProgress();
}

@Override
public void onServiceDisconnected(ComponentName componentName) {

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

Button bindService = (Button)findViewById(R.id.bind_service);
Button unbindService = (Button)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())开始,以stopService()或stopSelf()结束。

一个服务只要被启动或者被绑定以后,就会一直运行,除非两者都停止或解绑,服务才会被销毁,才会调用onDestory()方法。

服务的更多技巧

使用前台服务

后台服务的优先级比较低,当内存不足的时候,有可能就被释放了。

前台服务就不会被任意释放掉。

前台服务会有一个图标显示在系统的状态栏上。

下拉通知栏还会有详细的信息。

public class MyService extends Service {
public MyService() {
}

@Override
public void onCreate(){
super.onCreate();

Intent intent = new Intent(this,MainActivity.class);
PendingIntent pi = PendingIntent.getActivity(this,0,intent,0);
Notification notification = new NotificationCompat.Builder(this)
.setContentTitle("This is content title")
.setContentText("This is content text")
.setWhen(System.currentTimeMillis())
.setSmallIcon(R.mipmap.ic_launcher)
.setLargeIcon(BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher))
.setContentIntent(pi)
.build();
startForeground(1,notification);

}

@Override
// 它会在服务每次启动时调用?
public int onStartCommand(Intent intent,int flags,int startId){
return super.onStartCommand(intent,flags,startId);
}

@Override
public void onDestroy(){
super.onDestroy();
}

@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
}


使用IntentService

服务默认是在主线程中运行的,所以我们要在服务的每个具体方法中开启一个子线程。

为了创建一个异步的、会自动停止的服务,我们可能要写如下代码:

public int onStartCommand(Intent intent,int flags,int startId){

new Thread(new Runnable() {
@Override
public void run() {
// 处理具体的逻辑
stopSelf();
}
});
return super.onStartCommand(intent,flags,startId);
}


但是这样写的问题的是,如果忘了开辟子线程或是忘记stopSelf(),服务就会产生问题。

会了更简单的处理这样的事,Android提供了一个IntentService()。

新建一个服务继承自IntentService:

public class MyIntentService extends IntentService {

public MyIntentService() {
super("MyIntentService");
}
@Override
// 这个方法已经在子线程中运行了。
protected void onHandleIntent(Intent intent) {

Log.d("MyIntentService","Thread id is" + Thread.currentThread().getId());

}
@Override
// 当子线程中的代码执行完,就会调用它。
public void onDestroy(){
super.onDestroy();
Log.d("MyIntentService","onDestroy executed");
}
}


为了证明它会自动在子线程中运行,我们在onHandleIntent()方法里打印了当前线程号。

为了证明它会自动销毁服务,我们没有使用停止服务按钮,而是在onDestroy()方法中打印了一下,证明它在服务运行结束后,会自动停止。

使用向导创建服务会自动生成注册服务的代码:

<service android:name=".MyIntentService" />


但是使用向导创建IntentService会生成一大堆不需要的代码,所以采用手动的方式,记住要注册服务才会生效。

服务的最佳实践

这一节会写一个基于服务的下载器,里面会用到之前学过的内容,由于代码较多,我做好了注释,一起贴出来。

这个小 demo 里会用到okhttp3,它是用于Java和 Android 的下载框架。

在app/build.gradle里面的dependencies添加:

dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:24.2.1'
compile 'com.squareup.okhttp3:okhttp:3.4.1'
}


res/layout/activity_main里面添加三个按钮,分别用于下载、暂停、取消。

<Button
android:id="@+id/start_download"
android:text="start download"
android:layout_width="match_parent"
android:layout_height="wrap_content" />

<Button
android:id="@+id/pause_download"
android:text="pause_download"
android:layout_width="match_parent"
android:layout_height="wrap_content" />

<Button
android:text="cancel download"
android:id="@+id/cancel_download"
android:layout_width="match_parent"
android:layout_height="wrap_content" />


MainActivity:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

// Binder:用于在活动中操作服务的对象
private DownloadService.DownloadBinder downloadBinder;
// 通过binder连接服务和活动
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
downloadBinder = (DownloadService.DownloadBinder) iBinder;
}

@Override
public void onServiceDisconnected(ComponentName componentName) {

}
};

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

// 三个按钮,开始下载,暂停下载,取消下载
Button startDownload = (Button)findViewById(R.id.start_download);
Button pauseDownload = (Button)findViewById(R.id.pause_download);
Button cancelDownload = (Button)findViewById(R.id.cancel_download);
// 按钮响应
startDownload.setOnClickListener(this);
pauseDownload.setOnClickListener(this);
cancelDownload.setOnClickListener(this);

// 开启服务
Intent intent  = new Intent(this,DownloadService.class);
startService(intent);

// 绑定服务和活动
bindService(intent,connection,BIND_AUTO_CREATE);

// 请求外部存储器权限
if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) !=
PackageManager.PERMISSION_GRANTED){
ActivityCompat.requestPermissions(MainActivity.this,new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},1);
}
}

@Override
// 请求外部存储器权限结果
public void onRequestPermissionsResult(int requestCode,String[] permisson,int[] grantResult){
switch (requestCode){
case 1:
if (grantResult.length > 0 && grantResult[0] != PackageManager.PERMISSION_GRANTED){
Toast.makeText(this,"拒绝权限将无法使用程序",Toast.LENGTH_SHORT).show();
finish();
}
break;
default:
}
}
@Override
public void onClick(View v){

if (downloadBinder == null) return;

switch (v.getId()){
case R.id.start_download:
// 调用服务进行下载
String url = "http://sw.bos.baidu.com/sw-search-sp/software/6c864ea1874b3/setup_bd.exe";
downloadBinder.startDownload(url);
break;
case R.id.pause_download:
// 调用服务暂停下载
downloadBinder.pauseDownload();
break;
case R.id.cancel_download:
// 调用服务取消下载
downloadBinder.cancelDownload();
break;
default:
break;
}
}

@Override
protected void onDestroy(){
super.onDestroy();
// 销毁时取消服务和活动的绑定
unbindService(connection);
}
}


DownloadListener,接口,用于在服务中回调:

public interface DownloadListener {

void onProgress(int progress);

void onSuccess();

void onFailed();

void onPaused();

void onCanceled();

}


DownloadService 继承自Service:

public class DownloadService extends Service {
public DownloadService() {
}

// 实现回调接口
// downloadtask下载、暂停等状态会发送消息给服务\
// 这里的实现就是收到状态信息后的处理
private DownloadListener listener = new DownloadListener() {
@Override
// downloadtask正在下载,通知栏显示downloading
public void onProgress(int progress) {
getNotificationManager().notify(1,getNotification("Downloading...",progress));
}

@Override
// downloadtask下载完毕,关闭task,停止前台服务,通知栏显示download success等
public void onSuccess() {
downloadTask = null;
stopForeground(true);
getNotificationManager().notify(1,getNotification("Download Success",-1));
Toast.makeText(DownloadService.this,"Download Success",Toast.LENGTH_SHORT).show();
}

@Override
// downloadtask下载失败,关闭task,停止前台服务,通知栏显示download failed等
public void onFailed() {
downloadTask = null;
stopForeground(true);
getNotificationManager().notify(1,getNotification("Download Failed",-1));
Toast.makeText(DownloadService.this,"Download Failed",Toast.LENGTH_SHORT).show();
}

@Override
// downloadtask下载暂停,关闭task等
public void onPaused() {
downloadTask = null;
Toast.makeText(DownloadService.this,"Paused",Toast.LENGTH_SHORT).show();
}

@Override
// downloadtask下载取消,关闭task、停止前台服务等
public void onCanceled() {
downloadTask = null;
stopForeground(true);
Toast.makeText(DownloadService.this,"Canceld",Toast.LENGTH_SHORT).show();
}
};

// 继承于AsyncTask,用于处理下载任务
private DownloadTask downloadTask;

private String downloadUrl;

// binder对象,用于连接活动和服务,用于在活动中操作服务
private DownloadBinder mBinder = new DownloadBinder();
class DownloadBinder extends Binder {
// 开始下载
public void startDownload(String url){
if (downloadTask == null){
downloadUrl = url;
// 传递监听对象,用于在task状态更新时通知回调
downloadTask = new DownloadTask(listener);
// task开始执行,并在task中的doInBackground函数的子线程中执行代码
downloadTask.execute(downloadUrl);
// 开启前台服务,并在通知栏显示downloading
startForeground(1,getNotification("Downloading...",0));
Toast.makeText(DownloadService.this,"Downloading...",Toast.LENGTH_SHORT).show();
}
}

public void pauseDownload(){
if (downloadTask != null){
// 标记改为pause,使doInBackground中的下载暂停
downloadTask.pauseDownload();
}
}

public void cancelDownload(){
if (downloadTask != null){
// 标记改为cancel,使doInBackground中的下载取消
downloadTask.cancelDownload();
}else{
// 取消下载、删除已经下载的文件
if (downloadUrl != null){
String fileName = downloadUrl.substring(downloadUrl.lastIndexOf("/"));
String directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath();
File file = new File(directory + fileName);
if (file.exists()){
file.delete();
}
// 取消通知栏的显示
getNotificationManager().cancel(1);
// 停止前台服务
stopForeground(true);
Toast.makeText(DownloadService.this,"Canceld",Toast.LENGTH_SHORT).show();
}
}
}
}
@Override
public IBinder onBind(Intent intent) {

return mBinder;
}

private NotificationManager getNotificationManager(){
// 取得系统通知栏服务
return (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
}
private Notification getNotification(String title,int progress){
// 用于在通知栏中显示图片和标题
Intent intent = new Intent(this,MainActivity.class);
PendingIntent pi = PendingIntent.getActivity(this,0,intent,0);
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
builder.setSmallIcon(R.mipmap.ic_launcher);
builder.setLargeIcon(BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher));
builder.setContentIntent(pi);
builder.setContentTitle(title);

// 通知栏下载进度条
if (progress >= 0){
builder.setContentText(progress + "%");
builder.setProgress(100,progress,false);
}
return builder.build();
}


DownloadTask,继承于AsyncTask:

public class DownloadTask extends AsyncTask<String,Integer,Integer>{

public static final int TYPE_SUCCESS = 0;
public static final int TYPE_FAILED = 1;
public static final int TYPE_PAUSED = 2;
public static final int TYPE_CANCELED = 3;

private boolean isCanceled = false;

private boolean isPaused = false;

private int lastProgress;

private DownloadListener listener;

// 接收一个listener对象,listener会在服务中实现接口,用于回调
public DownloadTask(DownloadListener listener){this.listener = listener;}

@Override
protected  Integer doInBackground(String... params){
InputStream is = null;
RandomAccessFile savedFile = null;
File file = null;
try{
long downloadedLength = 0;
String downloadUrl = params[0];
String fileName = downloadUrl.substring(downloadUrl.lastIndexOf("/"));
String directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath();
// 创建一个文件
file = new File(directory + fileName);

// 如果是续传,更新它的长度
if (file.exists()){
downloadedLength = file.length();
}
// 整个文件的大小
long contentLength = getContentLength(downloadUrl);
if (contentLength == 0){
return TYPE_FAILED;
}else if(contentLength == downloadedLength){
// 已下载字节和文件总字节相等,说明下载完毕。
return TYPE_SUCCESS;
}

OkHttpClient client = new OkHttpClient();

Request request = new Request.Builder()
// 断点下载,指定从哪个字节开始下载
.addHeader("RANGE","bytes=" + downloadedLength + "-")
.url(downloadUrl)
.build();
// 下载结果
Response response = client.newCall(request).execute();
if (response != null){
// 跳过已下载的字节
is = response.body().byteStream();
savedFile = new RandomAccessFile(file,"rw");
savedFile.seek(downloadedLength);
// 每次读1024个字节,然后不断接到下载的文件上
byte[] b = new byte[1024];
int total = 0;
int len;
while((len = is.read(b)) != -1){
if (isCanceled){
return TYPE_CANCELED;
}else if(isPaused){
return TYPE_PAUSED;
}else{

total += len;
savedFile.write(b,0,len);
int progress = (int)((total + downloadedLength) * 100 / contentLength);
// 更新进度
publishProgress(progress);
}
}
response.body().close();
return TYPE_SUCCESS;
}

}catch (Exception e){
e.printStackTrace();
}finally {
try{
if(is != null){
is.close();
}
if (savedFile != null){
savedFile.close();
}
if (isCanceled && file != null){
file.delete();
}
}catch (Exception e){
e.printStackTrace();
}
}
return TYPE_FAILED;
}

@Override
protected void onProgressUpdate(Integer... values){
int progress = values[0];
if (progress > lastProgress){
// 回调:更新进度
listener.onProgress(progress);
lastProgress = progress;
}
}

@Override
// static来自于doInBackground的返回值
protected void onPostExecute(Integer status){
switch (status){
case TYPE_SUCCESS:
// 回调:下载成功
listener.onSuccess();
break;
case TYPE_FAILED:
// 回调:下载失败
listener.onFailed();
break;
case TYPE_PAUSED:
// 回调:下载暂停
listener.onPaused();
break;
case TYPE_CANCELED:
// 回调:下载取消
listener.onCanceled();
break;
default:
break;
}
}

public void pauseDownload(){
isPaused = true;
}

public void cancelDownload(){
isCanceled = true;
}

// 获取要下载的文件的大小
private long getContentLength(String downloadUrl) throws IOException {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url(downloadUrl)
.build();
Response response = client.newCall(request).execute();
if (response != null && response.isSuccessful()){
long contentLength = response.body().contentLength();
response.close();
return contentLength;
}
return 0;
}
}


给这个示例程序作一个总结:

活动与服务之间有一个连接者binder,用于在活动中控制服务要做的事情。

服务处理任务时交给AsyncTask的子类,这个子类会在子线程中异步的处理任务。服务与这个子类之间有若干个接口方法实现监听子类的状态。

监听到的状态实时的反应到通知栏里。

就是这么简单。~

参考:

《第一行代码 Android 第2版》.郭霖.人民邮电出版社.2016.12
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: