服务

标签: Android


1 服务基本概念

服务可以实现程序后台运行,适合长期运行且不与用户交互的任务。 服务不是运行在一个独立的进程中,而是依赖于创建服务的应用程序进程。当它被kill掉后,依赖该进程的服务也会停止运行。 服务的代码运行在主线程中,但是执行具体任务的时候需要在服务内部创建新线程,否则会阻塞主线程

1.1 多线程

继承,实现Runnable接口以及匿名类,3种方式

更新UI 子线程中不能直接更新UI元素,但可以利用异步消息处理机制,解决在子线程中进行UI操作

异步消息处理机制:

子线程中发送消息,主线程接收消息并处理。

  1. Message

    Message是线程间传递的消息,携带信息,如Message的what字段,arg1\arg2携带整形数据,obj携带Object对象

  2. Handler

    处理者,用于发送消息和处理消息。 发送消息:

    Handler.sendMessage()
    

    处理消息:

    handleMessage
    
  3. MessageQueue

    消息队列,存放通过Handler发送的消息,每个线程只有一个MessageQueue对象

  4. Looper

    管理MessageQueue的对象,每个线程只有一个Looper对象,调用Looper的loop方法后,就能从MessageQueue取出消息当存在一条消息时。

    public class MainActivity extends AppCompatActivity implements View.ONClickListener {
    public static final int Update_Text = 1;
    private TextView text;
    
    
    private Handler handler = new Handler() {
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case Update_Text:
                    text.setText("Nice to meet you");
                    break;
                default:
                    break;
            }
        }
    };
    ...
    @Override
    public void onClick(View v) {
        switch (v.getId(){
            case R.id.text:
                new Thread(new Runnable(){
                    @Override
                    public void run() {
                        Message message = new Message();
                        message.what = Update_Text;
                        handler.sendMessage(message);
                    }
                }).start();
                break;
            default:
                break;
            }
        }
    }
    

主线程中创建一个Handler对象,重写handleMessage方法 子线程创建一个Message对象,通过Handler发送消息 消息进入到消息队列中并被取出分发回Handler的handleMessage方法中

runOnUIThread是异步消息处理机制的接口封装

使用AsyncTask

AsyncTask是抽象类,需要继承它,指定3个泛型参数,分别为

  • Params:传入参数,用于后台任务
  • Progress:任务进度
  • Result:返回结果的返回值类型

    class DownloadTask extends AsyncTask<void, Integer, Boolean> {
    ...
    }
    

重写方法

  • onPreExecute()

    后台任务开始执行前调用,初始化界面,如显示一个进度条对话框

  • doInBackground(Params...)

    在子线程中运行,此方法中不能进行UI操作,如果需要更新UI元素,如反馈当前任务的执行进度,可以调用publicProgress(Progress...)方法

  • onProgressUpdate(Pogress...)

    当调用pullishProgress(Progress...)方法后,onProgressUpdate(Progress...)被调用。可以对UI进行操作,利用参数中携带的数值对界面元素进行相应的更新

  • onPostExecute(Result)

    后台任务执行完毕后通过return语句返回,会调用此方法。可以对UI进行操作,如提醒任务执行结果,以及关闭进度条对话框等

    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();
                publicProgress(downloadPercent);
                if (downloadPercent&gt;=100)
                    break;
            }
        }catch(Exception e){
            return false;
        }
        return true;
    }
    
    
    @Override
    protected void onPostExecute(Boolean result) {
        progressDialog.dismiss();
        if (result){
            Toast.makeText(context, "Succeed", Toast.LENGTH_SHORT).show();
        else
            Toast.makeText(context, "Failed", Toast.LENGTH_SHORT).show();
        }
    }
    
    }

启动任务

new DownloadTask().execute();

1.2 服务基本用法

类似其它组件,利用AS快速新建一个服务并重写方法

public class MyService extends Service {

    public MyService() {
    }
    @Override
    public IBinder onBind(Intent intent) {
        //
    }

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

启动和停止服务

使用Intent实现

Intent startIntent = new Intent(this, MyService.class);
startService(startintent);//启动服务

Intent stopIntent = new Intent(this, MyService.class);
stopService(stopIntent);//停止服务

当需要让服务自己停止,需要在MyService的任一位置调用stopSelf方法

活动和服务进行通信

比如在活动中可以决定何时开始下载,以及随时查看下载进度。通过创建一个Binder对象对下载功能进行管理

public class MyService extends Service {
    private DonwloadBinder mBinder = ne DownloadBinder();

    class DownloadBinder extends Binder {
        public void startDownload() {
            //
        }
        public int getProgress() {
            //
            return 0;
        }
    }

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

在活动中调用服务中的方法

public class MainActivity extends AppCompatActivity implements View.onClickListener {

    private MyService.DownlloadBinder downloadBinder;

    private ServiceConnection connectio = new ServiceConnection() {
        @Override
        public void onServiceDisconnected(ComponentName name) {
        }

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            downloadBinder = (MyService.DownloadBinder) service;
            downloadBinder.startDownload();
            downloadBinder.getProgress();
        }
    };
    ...
}

活动与服务绑定以及解除绑定

Intent bindIntent = new Intent(this, MyService.class);
bindService(bindIntent, connection, BIND_AUTO_CREATE);
...
unbindService(connection);

当活动和服务成功绑定后,调用onServiceConnected方法,解除绑定调用onServiceDisconnected方法。

1.3 服务的高级技巧

前台服务

前台服务与普通服务不同的是它会在系统的状态栏显示,下拉会显示,类似通知。

在服务中的onCreate中指定创建一个前台服务

public class MyService extends Service {
    ...

    @Override
    public void onCreate() {
        ...
        Intent intent = new Intent(this, MainActivity.class);
        PendingIntent pi = PendingIntent.getActivity(this, 0, intent, 0);
        Notification notification = new NotificationCompat.Builder(this)
                    .setContentTitle("title")
                    .setContentText("conent text")
                    .setWhen(System.currentTimeMills())
                    .setSmallIcon(R.mipmap.ic_launcher)
                    .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))
                    .setConetentIntnet(pi)
                    .builde();
        startForeground(1, notification);
    }
}

IntentService 为了避免ANR,需要在服务的每个具体方法中开启线程,当处理完毕时,调用stopSelf或者stopService停止服务。IntentService可以解决忘记开启线程以及调用stopSelf方法,创建一个服务

public class MyIntentService extends IntnetService {
    public MyIntentService() {
        super("MIntentService");
    }
    @Override
    protected void onHandleIntent(Intent intent) {
        //
    }
    @Override
    public void onDestroy(){
        super.onDestroy();
    }

在活动内启动

Intent intent = new Intent(this, MyIntentService.class);
startService(intentService);

1.4 下载功能实现

OkHttp实现网络请求

compile 'com.squareup.okhttp3:okhttp:3.7.0'

定义会调接口,监听下载过程的状态和回调

public interface DownloadListener {
    void onProgress(int progress);
    void onSuccess();
    void onFaild();
    void onPaused();
    void onCanceled();
}

使用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 DownloadListener listener;

private boolean isCanceled = false;

private boolean isPaused = false;

private int lastProgress;

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); // 跳过已下载的字节
            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;
}

}

新建一个服务用于下载

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(true);
        Toast.makeText(DownloadService.this, "Canceled", Toast.LENGTH_SHORT).show();
    }

};

private DownloadBinder mBinder = new DownloadBinder();

@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 pauseDownload() {
        if (downloadTask != null) {
            downloadTask.pauseDownload();
        }
    }

    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.exists()) {
                    file.delete();
                }
                getNotificationManager().cancel(1);
                stopForeground(true);
                Toast.makeText(DownloadService.this, "Canceled", Toast.LENGTH_SHORT).show();
            }
        }
    }

}

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) {
        // 当progress大于或等于0时才需显示下载进度
        builder.setContentText(progress + "%");
        builder.setProgress(100, progress, false);
    }
    return builder.build();
}

}

DownloadListener匿名类实例中实现了接口方法
DownloadBinder用于让服务和活动通信
其它细节东西参考<<第一行代码>>P375