安卓学习(5)——Service

本文深入讲解Android服务的概念及其在后台运行的机制,涵盖服务的生命周期、通信方式、前台服务的使用及最佳实践案例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >



什么是服务

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

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

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

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

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

Android 多线程编程

线程的多种用法

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

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

class MyThread extends Thread{
  @Override
    public void run(){
     // 处理具体的逻辑
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

调用这个线程:

new MyThread().start();
  • 1

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

class MyThread implements Runnable{

  @Override
  public void run(){
     // 处理具体的逻辑
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

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

MyThread myThread = new MyThread();
new Thread(myThread).start();
  • 1
  • 2

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

new Thread(new Runnable(){
  @Override
  public void run(){
    // 处理具体的逻辑
  }
}).start();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

在子线程中更新 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" />
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

然后在主程序中开辟线程,在线程中更新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.change_text:
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        text.setText("Nice to see you");
                    }
                }).start();
                break;
            default:
                break;
        }
    }
}
  • 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

结果当然是,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;
        }
    }
}
  • 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
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44

解析异步消息处理机制

通过异步消息的处理,可以成功的在子线程中更新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()
  • 1

使用AsyncTask

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

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

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

AsyncTask基本用法

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

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

  • Params。执行AsyncTask时需要传入的参数,可用于在后台任务中使用。
  • Progress。后台任务执行时,如果需要在界面上显示进行,就使用这个参数作为进度单位。
  • Result。任务执行完毕时,如果需要结果返回,则可以使用这里指定的泛型作为返回值类型。

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

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

}
  • 1
  • 2
  • 3

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

// 在任务开始前执行,主要是对界面的一些初始化操作
onPreExecute() 
  • 1
  • 2
// 这个方法中的所有代码都会在子线程中运行,应该在这里处理耗时任务。
// 如果AsyncTask的第三个参数是Void,可以不用返回。
// 这个方法是不可以进行UI操作的。
doInBackground(Params...)
  • 1
  • 2
  • 3
  • 4
// 在执行doInBackground(Params...)时,如果要更新UI元素,比如说任务进度,可以在这个方法中完成
publishProgress(Progress...)
  • 1
  • 2
// 如果在后台任务中调用了publishProgress(Progress...),这个方法很快就会被调用。
// 它的参数就是后台任务中传递过来的。
// 在这个方法中可以进行UI操作。
onProgressUpdate(Progress...)
  • 1
  • 2
  • 3
  • 4
// 当后台任务执行完毕后,这个方法很快就会被调用。
// 返回的参数会作为参数传递到这个方法中。
onPostExecute(Result)
  • 1
  • 2
  • 3
// 如果要启动这个任务
new Download().execute();
  • 1
  • 2

服务的基本用法

定义一个服务

右键包名/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");
    }
}
  • 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

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

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

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

 <service
            android:name=".MyService"
            android:enabled="true"
            android:exported="true"></service>
  • 1
  • 2
  • 3
  • 4

启动和停止服务

启动和停止服务主要是借助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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

然后写点击按钮的逻辑。

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;
        }
    }

}
  • 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

活动和服务进行通信

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

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

还记得前面重写的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();
    }
}
  • 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

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

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

 <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" />
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

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

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;
        }
    }

}
  • 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
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45

服务的生命周期

以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;
    }
}
  • 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
  • 38
  • 39
  • 40

使用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);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

但是这样写的问题的是,如果忘了开辟子线程或是忘记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");
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

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

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

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

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

但是使用向导创建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'
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

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" />
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

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);
    }
}
  • 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
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89

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

public interface DownloadListener {

    void onProgress(int progress);

    void onSuccess();

    void onFailed();

    void onPaused();

    void onCanceled();

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

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();
    }
  • 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
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126

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;
    }
}
  • 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
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158

给这个示例程序作一个总结:
活动与服务之间有一个连接者binder,用于在活动中控制服务要做的事情。
服务处理任务时交给AsyncTask的子类,这个子类会在子线程中异步的处理任务。服务与这个子类之间有若干个接口方法实现监听子类的状态。
监听到的状态实时的反应到通知栏里。
就是这么简单。~

参考:

《第一行代码 Android 第2版》.郭霖.人民邮电出版社.2016.12

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值