0%

Android 详解 Service

1. 怎样理解 Android 中的异步消息处理机制

  • Message: Message 是在线程之间传递的消息。它可以在内部携带少量的信息,用于在不同线程之间交换数据。内部有一个 target 属性可以和 Handler 实现绑定。除了 Messagewhat 字段,还可以使用 arg1arg2 字段来携带一些整型数据,使用 obj 字段携带一个 Object 对象
  • Handler: Handler 主要用于发送和处理消息。发送消息一般是使用 HandlersendMessage() 方法,发出的消息经过一系列地辗转处理后,最终会传递到 HandlerhandlerMessage() 方法中
  • MessageQueue: MessageQueue 是消息队列。主要用于存放所有通过 Handler 发送的消息。这部分消息会一直存在于消息队列中,等待被处理。每个线程中只会有一个 MessageQueue 对象
  • Looper: Looper 是每个线程中 MessageQueue 的管家。调用 Looperloop() 方法,就会进入到一个无限循环当中,然后每当发现 MessageQueue 中存在一条消息,就会将它取出,并传递到 HandlerhandleMessage() 方法中。每个线程中也只会有一个 Looper 对象

2. 写一个简单的使用 Handler 的 Demo

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
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
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 changeText = (Button) findViewById(R.id.change_text);
changeText.setOnClickListener(this);
}

private Handler handler = new Handler() {
public void handleMessage() {
switch (msg.what) {
case UPDATE_TEXT:
// 在这里可以进行 UI 操作
text.setText("Nice to meet you");
break;
default:
break;
}
}
};

@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 = UPDATE_TEXT;
handler.sendMessage(message); // 将 Message 对象发送出去
}
}).start();
break;
default:
break;
}
}
}

3. 怎样理解 AsyncTask

  • AsyncTask 是 Android 提供的用于多线程的工具类,背后实现的原理也是基于异步消息处理机制

  • AsyncTask 是一个抽象类,创建子类继承 AsyncTask 时,可以为 AsyncTask 指定 3 个泛型参数

    • Params:在执行 AsyncTask 时需要传入的参数,可用于在后台任务中使用
    • Progress:后台任务执行时,如果需要在界面上显示当前的进度,则使用这里指定的泛型作为进度单位
    • Result:当任务执行完毕后,如果需要对结果进行返回,则使用这里指定的泛型作为返回值类型
  • AsyncTask 中经常需要重写的方法有 4 个

    • onPreExecute(): 这个方法会在后台任务开始之前调用,用于进行一些界面上的初始化操作,比如显示一个进度条对话框等
    • doInBackground(Params...): 这个方法中的所有代码都会在子线程中运行,我们应该在这个方法内部处理所有的耗时任务。任务一旦完成就可以通过 return 语句将任务的执行结果返回,如果 AsyncTask 的第三个泛型参数指定的是 Void,就可以不返回任务执行结果。需要注意的是,这个方法中是不可以进行 UI 操作的,如果需要跟新 UI 元素,比如说反馈当前任务的执行进度,可以调用 publishProgress(Progress...) 方法来完成
    • onProgressUpdate(Progress...): 当在后台任务中调用了 publishProgress(Progress...) 方法后,该方法就会被调用,方法中携带的参数就是在后台任务中传递过来的。在这个方法中可以对 UI 进行操作,利用参数中的数值就可以对界面元素进行相应的更新
    • onPostExecute(Result): 当后台任务执行完毕并通过 return 语句进行返回时,这个方法就会被调用。返回的数据会作为参数传递到该方法中,可以利用返回的数据来进行一些 UI 操作,比如说提醒任务执行的结果,以及关闭掉进度条对话框等

4. 写一个简单的使用 AsynTask 的 Demo

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
class DownloadTask extends AsyncTask<Void, Integer, Boolean> {
@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("Download " + 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();
}
}
}

// 启动这个任务
new DownloadTask().execute();

5. 怎样理解服务 Service

  • Service 是 Android 中实现程序后台运行的解决方案,非常适合执行不需要和用户交互且要求长期运行的服务
  • Service 的运行不依赖于任何用户界面,即使程序被切换到后台,或者用户打开了另外一个应用程序,服务仍然能够保持正常运行
  • Service 并不是运行在一个独立的进程当中的,而是依赖于创建服务时所在的应用程序进程。当某个应用程序进程被杀掉时,所有依赖于该进程的服务也会停止运行
  • Service 并不会自动开启线程,所有的代码都是默认运行在主线程当中的。我们通常在服务的内部手动创建子程序,在子线程里执行具体的耗时任务,否则直接在 Service 里执行耗时任务可能出现主线程被阻塞,即 ANR(Activity 5 秒;Broadcast 10 秒;后台 Service 20 秒;前台 Service 200 秒

6. 怎样理解 Service 的生命周期

  • 一旦在项目的任何位置调用了 ContextstartService() 方法,方法参数中相应的服务就会启动起来,并回调 onStartCommand() 方法

    • 如果这个服务之前还没有创建过,ServiceonCreate() 方法会先于 onStartCommand() 方法执行
    • Service 启动了之后会一直保持运行状态,直到 stopService()stopSelf() 被调用
    • 虽然每调用一次 startService() 方法,onStartCommand() 方法就会执行一次,但实际上每个服务都只会存在一个实例。所以不管调用了多少次 startService() 方法,只需调用一次 stopService()stopSelf(),服务就会停止下来
  • 还可以调用 ContextbindService() 方法来获取一个服务的持久连接,这时就会回调服务中的 onBind() 方法

    • 类似地,如果这个服务之前还没有创建过,onCreate() 方法会先于 onBind() 方法执行
    • 之后,调用方可以获取到 onBind() 方法里返回的 IBinder 对象的实例,这样就能自由地和服务进行通信了
    • 只要调用方和服务之间的连接没有断开,服务就会一直保持运行状态
  • 当调用了 startService() 方法后,又去调用 stopService() 方法,这时服务中的 onDestroy() 方法就会执行

    • 类似地,当调用了 bindService() 方法后,又去调用 unbindService() 方法,onDestroy() 方法也会执行
    • 需要注意的是,我们完全有可能对一个服务既调用了 startService() 方法,又调用了 bindService() 方法,这种情况下该怎样销毁服务呢
    • 根据 Android 系统的机制,一个服务只要被启动或者被绑定之后,就会一直处于运行状态,必须要让以上两种条件同时不满足,服务才能被销毁。所以,这种情况下要同时调用 stopService() 方法和 unbindService() 方法,这样 onDestroy() 方法才会执行
  • 特殊情况(参考 Android 中 Service 生命周期

    • 当服务被系统自动或手动(人为地在设置里停止)停止时,Service 仍然会正常走完生命周期,即会回调到 onDestroy() 方法
    • 当使用类似“腾讯手机管家”一键加速时,直接 kill 掉该进程,Service 不会走正常的生命周期,即 onDestroy() 方法不会被调用

7. Service 生命周期流程图

Service 生命周期图

8. 写一个简单的使用 Service 的 Demo

  • MainActivity

    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
    public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private MyService.DownloadBinder downloadBinder;

    private ServiceConnection connection = new ServiceConnection() {
    @Override
    public void onServiceDisconnected(ComponentName name) { // Activity 与 Service 解除绑定时调用
    }

    @Override
    public void onServiceConnected(ComponentName name, IBinder service) { // Activity 与 Service 成功绑定时调用
    downloadBinder = (MyService.DownloadBinder) service;
    downloadBinder.startDownload();
    downloadBinder.getProgress();
    }
    };

    @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);

    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.start_service:
    Intent startIntent = new Intent(this, MyService.class);
    startService(startIntent); // 启动服务,这个方法是定义在 Context 中的
    break;
    case R.id.stop_service:
    Intent stopIntent = new Intent(this, MyService.class);
    stopService(stopIntent); // 停止服务,这个方法是定义在 Context 中的。也可以在 MyService 中调用 stopSelf() 方法停止服务
    break;
    case R.id.bind_service:
    Intent bindIntent = new Intent(this, MyService.class);
    // 绑定服务
    // 第三个参数是一个标志位,这里传入 BIND_AUTO_CREATE,表示在活动和服务进行绑定后自动创建服务。这会使得 MyService 中的 onCreate() 方法得到执行,但 onStartCommand() 方法不会执行
    bindService(bindIntent, connection, BIND_AUTO_CREATE);
    break;
    case R.id.unbind_service:
    // 解绑服务
    unbindService(connection);
    break;
    default:
    break;
    }
    }
    }
  • MyService

    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
    //需要在 AndroidManifest.xml 文件中注册,属性 enabled 和 exported 都设为 true
    //服务启动之后,还可以在手机的 Settings --> Developer options --> Running services 中找到它
    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;
    }
    }

    // 任何一个服务在整个应用程序范围内都是通用的
    // 即 MyService 不仅可以和 MainActivity 绑定,还可以和任何一个其他的活动进行绑定,而且在绑定完成后它们都可以获取到相同的 DownloadBinder 实例

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

    @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");
    }
    }

9. 什么是前台服务?前台服务和普通服务的区别是什么?使用前台服务的场景有哪些

  • 定义

    • 前台服务是系统优先级比较高的一种服务
  • 区别

    • 前台服务的系统优先级比普通服务要高,在系统内存不足时被回收的概率比普通服务的要小
    • 前台服务会有一个正在运行的图标在系统的状态栏显示,下拉状态栏后可以看到更加详细的信息,非常类似于通知的效果
  • 场景

    • 防止服务被系统回收
    • 由于特殊的需求会要求必须使用前台服务,比如墨迹天气应用在后台更新数据前台展示、ofo小黄车应用在用户骑行过程中显示用车详情等

10. 写一个简单的使用前台服务的 Demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class MyService extends Service {
...
@Override
public void onCreate() {
super.onCreate();
Log.d("MyService", "onCreate executed");
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();

// 这里没有使用 NotificationManager 显示通知,而是调用了 startForeground() 方法
// 第一个参数是通知的 id,类似于 notify() 方法的第一个参数,第二个参数就是构建的 Notification 对象
// 调用 startForeground() 方法就会让 MyService 变成一个前台服务,并在系统状态栏显示
startForeground(1, notification);
}
}

11. 怎样理解 IntentService

  • 程序员的尴尬

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public class MyService extends Service {
    ...
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
    new Thread(new Runnable() { // 尴尬1:程序员总是容易忘记开启子线程去执行耗时逻辑
    @Override
    public void run() {
    // 处理具体的耗时逻辑
    stopSelf(); // 尴尬2:程序员总是容易忘记调用 stopService() 或者 stopSelf() 方法停止服务
    }
    }).start();
    }
    }
  • IntentService 是 Android 提供的一个异步的、会自动停止的服务,可以很好地解决程序员的尴尬

12. 写一个简单的使用 IntentService 的 Demo

  • MainActivity

    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
    public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    ...
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    ...
    Button startIntentService = (Button) findViewById(R.id.start_intent_service);
    startIntentService.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
    switch (v.getId()) {
    ...
    case R.id.start_intent_service:
    // 打印主线程的 id
    Log.d("MainActivity", "Thread id is " + Thread.currentThread().getId());
    Intent intentService = new Intent(this, MyIntentService.class);
    startService(intentService);
    break;
    default:
    break;
    }
    }
    }
  • MyIntentService

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    // 服务都是需要在 AndroidManifest.xml 文件中注册
    public class MyIntentService extends IntentService {

    public MyIntentService() { // 首先提供一个无参的构造方法
    super("MyIntentService"); //调用父类的有参构造方法
    }

    @Override
    protected void onHandleIntent(Intent intent) {
    // 打印当前线程的 id
    Log.d("MyIntentService", "Thread id is " + Thread.currentThread().getId());
    }

    @Override
    public void onDestroy() {
    super.onDestroy();
    Log.d("MyIntentService", "onDestroy executed");
    }
    }

13. 写一个在服务中下载文件的 Demo

  • 添加依赖库。在 app/build.gradle 文件的 dependencies 闭包中添加依赖:compile 'com.squareup.okhttp3:okhttp:3.4.1'

  • 定义一个回调接口 DownloadListener

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public interface DownloadListener {

    void onProgress(int progress);

    void onSuccess();

    void onFailed();

    void onPaused();

    void onCanceled();
    }
  • 新建一个下载任务 DownloadTask

    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
    // 第一个泛型参数 String 表示在执行 AsyncTask 的时候需要传入一个字符串参数给后台任务
    // 第二个泛型参数 Integer 表示使用整型数据来作为进度显示单位
    // 第三个泛型参数 Integer 表示使用整型数据来反馈执行结果
    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;

    public DownloadTask(DownloadListener listener) {
    this.listener = listener;
    }

    @Override
    protected Integer doInBackground(String... params) { // 在后台执行具体的下载逻辑
    InputStream is = null;
    RandomAccessFile savedFile = null;
    try {
    long downloadedLength = 0; // 纪录已下载的文件长度
    String downloadUrl = params[0]; // 从参数中获取下载的 URL 地址
    String fileName = downloadUrl.substring(downloadUrl.lastIndexOf("/")); // 根据 URL 地址解析下载的文件名
    // 指定将文件下载到 Environment.DIRECTORY_DOWNLOADS 目录下,即 SD 卡的 Download 目录
    String directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath();
    file = new File(directory + fileName);
    if(file.exist()) {
    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 + "-") // header 用于告诉服务器我们想要从哪个字节开始下载,因为已下载过的部分就不需要重新下载了
    .url(downloadUrl)
    .build();
    Response response = client.newCall(request).execute();
    if(response != null) {
    is = response.body().byteStream();
    savedFile = new RandomAccessFile(file, "rw");
    savedFile.seek(downloadedLength); // 跳过已下载的字节
    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
    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();
    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;
    }
    }
  • 创建下载服务 DownloadService

    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
    public class DownloadService extends Service {
    private DownloadTask downloadTask;
    private String downloadUrl;

    private DownloadListener listener = new DownloadListener() { // 创建匿名类实例
    @Override
    public void onProgress(int progress) {
    getNotificationManager().notify(1, getNotification("Downloading...", progress));
    }

    @Override
    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
    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
    public void onPaused() {
    downloadTask = null;
    Toast.makeText(DownloadService.this, "Paused", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onCanceled() {
    downloadTask = null;
    stopForeground(ture);
    Toast.makeText(DownloadService.this, "Canceled", Toast.LENGTH_SHORT).show();
    }
    };

    private DownloadBinder mBinder = new DownloadBinder(); // 让 DownloadService 可以和活动通信

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

    class DownloadBinder extends Binder {
    public void startDownload(String url) {
    if(downloadTask == null) {
    downloadUrl = url;
    downloadTask = new DownloadTask(listener);
    downloadTask.execute(downloadUrl);
    startForeground(1, getNotification("Downloading...", 0)); // 让这个下载服务成为一个前台服务
    Toast.makeText(DownloadService.this, "Downloading...", Toast.LENGTH_SHORT).show();
    }
    }

    public void pausedDownload() {
    if(downloadTask != null) {
    downloadTask.pausedDownload();
    }
    }

    public void cancelDownload() {
    if(downloadTask != null) {
    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.exist()) {
    file.delete();
    }
    getNotificationManager().cancel(1);
    stopForeground(true);
    Toast.makeText(DownloadService.this, "Canceled", Toast.LENGTH_SHORT).show();
    }
    }
    }
    }

    private NotificationMangaer 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) {
    // 当 progress 大于或等于 0 时才需显示下载进度
    builder.setContentText(progress + "%");
    // 第一个参数传入通知的最大进度,第二个参数传入通知单当前进度,第三个参数表示是否使用模糊进度条
    builder.setProgress(100, progress, false);
    }
    return builder.build();
    }
    }
  • MainActivity

    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
    // 需要在 AndroidManifest.xml 文件中声明 INTERNET 和 WRITE_EXTERNAL_STORAGE 权限
    // MainActivity 和 DownloadService 也需要声明(四大组件都需要在清单文件中声明)
    public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private DownloadService.DownloadService.DownloadBinder downloadBinder;
    private ServiceConnection connection = new ServiceConnection() { // 创建 ServiceConnection 匿名类
    @Override
    public void onServiceDisconnectioned(ComponentName name) {
    }

    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
    downloadBinder = (DownloadService.DownloadBinder) service; //获取到 DownloadBinder 实例,用于在活动中调用服务的各种方法
    }
    };

    @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.R.id.cancel_download);

    startDownload.setOnClickListener(this);
    pauseDownload.setOnClickListener(this);
    cancelDownload.setOnClickListener(this);

    Intent intent = new Intent(this, DownloadService.class);
    startService(intent); // 启动服务,保证 DownloadService 一直在后台运行
    bindService(intent, connection, BIND_AUTO_CREATE); // 绑定服务,让 MainActivity 和 DownloadService 进行通信
    // WRITE_EXTERNAL_STORAGE 运行时权限申请,因为下载文件要下载到 SD 卡的 Download 目录下
    if(ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORATE) != PackageManager.PERMISSION_GRANTED) {
    ActivityCompat.requestPermissions(MainActivity.this, new String[] { Manifest.permission.WRITE_EXTERNAL_STORAGE }, 1);
    }
    }

    @Override
    public void onClick(View v) {
    if(downloadBinder == null) {
    return;
    }
    switch (v.getId()) {
    case R.id.start_download:
    String url = "https://raw.githubusercontent.com/guolindev/eclipse/master/eclipse-inst-win64.exe";
    downloadBinder.startDownload(url); // 传入一个 Eclipse 地址,以此向这个 Android 平台上曾经最出色的开发工具致敬
    break;
    case R.id.pause_download:
    downloadBinder.pauseDownload();
    break;
    case R.id.cancel_download:
    downloadBinder.cancelDownload();
    break;
    default:
    break;
    }
    }

    @Override
    public void onRequestPermissionResult(int requestCode, String[] permissions int[] grantResults) {
    switch (requestCode) {
    case 1:
    if(grantResults.length > 0 && grantResults[0] != PackageManager.PERMISSION_GRANTED) {
    Toast.makeText(this, "拒绝权限将无法使用程序", Toast.LENGTH_SHORT).show();
    finish();
    }
    break;
    default:
    break;
    }
    }

    @Override
    protected void onDestroy() {
    super.onDestroy();
    unbindService(connection); // 如果活动被销毁了,一定要对服务进行解绑,否则会造成内存泄露
    }
    }

14. Android 系统启动流程

-------------------- 本文结束感谢您的阅读 --------------------