《第一行代码》读书笔记(九)----服务

什么是服务

服务(Service) 是 Android 中实现程序后台运行的解决方案. 非常适合不需要和用户交互但又要求长期运行的任务. 它的运行不依赖任何用户界面, 即使当程序被切换到后台, 或者用户打开了另外的应用程序, 服务仍然能够保持正常运行.
需要注意的是, 服务并不是运行在一个独立的进程之中, 而是依赖于创建这个服务时所在的程序进程. 当这个程序进程被杀掉时, 服务也会停止运行.
服务也不会自动开启线程, 所有的代码都是默认运行在主线程之中的. 我们需要在服务内部手机创建子线程, 并在这里执行具体的任务, 否则就有可能出现主线程被阻塞住的情况.

Android 多线程编程

当需要执行一些耗时操作, 比如发起一条网络请求, 如果不将这类操作放在子线程里, 可能会导致主线程被阻塞, 严重影响用户体验.

线程的基本用法

定义一个线程并启动有多种写法, 比较常见的有以下几种.
1, 新建一个类继承自 Thread, 然后重写父类的 run() 方法, 调用 start() 方法启动.

class MyThread extends Thread {

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

new MyThread.start();

使用继承的方式, 耦合性有点高, 更多时候采用实现 Runnable 接口的方式.
2, 实现 Runnable 接口, 再调用 Thread 的 start() 方法来启动.

class MyThread implements Runnable {

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

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

这种方式也可以使用匿名内部类的写法:

new Thread(new Runnable() {

    @Override
    public void run() {
        //具体逻辑
    }
}).start();

在子线程中更新UI

和许多其他的 GUI 库一样, Android中的 UI 也是线程不安全的. 如果想要更新程序的 UI 元素, 必须在主线程中进行, 否则会出现异常. 但是有时我们必须又要在子线程中改变 UI , Android 提供了一套异步消息处理机制, 完美地解决了这个问题.
新建一个项目, 修改 布局文件, 相对布局, 一个居中的 TextView, 一个按钮用来改变 TextView 显示的内容.
修改 MainActivity :

public class MainActivity extends Activity implements View.OnClickListener {

    //定义一个整型常量表示更新 TextView 的动作
    public static final int UPDATE_TEXT = 1;

    private TextView text;

    //这是运行在主线程的代码
    private android.os.Handler handler = new android.os.Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case UPDATE_TEXT:
                    //在这里 进行 UI 操作
                    text.setText("Nice to meet you ");
                    break;
                default:
                    break;
            }
        }
    };

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

        Button changeText = (Button) findViewById(R.id.change_text);
        text = (TextView) findViewById(R.id.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 = UPDATE_TEXT;
                        handler.sendMessage(message);//将Message对象发送出去
                    }
                }).start();
                break;
            default:
                break;
        }
    }
}

解析异步消息处理机制

1, Message
在线程之间传递的消息. 可以在内部携带少量信息, 用于在不同线程之间交换数据. 比如 what 字段, 还有 agr1 和 arg2 字段携带整型数据, obj 字段携带 Object 对象.

2, Handler
处理者. 用于发送和处理消息. 使用sendMessage() 方法发送消息, 经过一系列辗转处理, 最终会传递到 Handler 的 handleMessage() 方法中.

3, MessageQueue
消息队列. 用于存放所有通过 Handler 发送的消息. 这部分消息会一直存在于消息队列中, 等待被处理. 每个线程中只会有一个 MessageQueue 对象.

4, Looper
轮询器. 是每个线程中的 MessageQueue 的管家. 调用它的loop() 方法后, 就会进入到一个无限循环中, 每当发现队列中有一条消息, 就会将它取出, 并传递到 handleMessage() 方法中. 每个线程只会有一个 Looper 对象.

过程:
* 主线程中创建一个 Handler 对象 并 重写 handleMessage() 方法.
* 子线程需要 UI 操作时, 创建一个 Message 对象, 通过 Handler 发送这条消息.
* 消息被添加到 MessageQueue 队列中等待被处理.
* Looper 取出等待的消息, 分发会 Handler 的handleMessage() 方法中.
如下图所示:

使用 AsyncTask

Android 还提供了 AsyncTask 机制来处理消息.
它是一个抽象类, 需要我们创建子类去继承它, 并重写它的某些方法.
在继承时, 为 AsyncTask 类指定三个泛型参数:
1, Params: 执行 AsyncTask 时需要传入的参数, 用于在后台任务中调用.
2, Progress: 后台任务执行时如果需要在界面上显示进度, 这里指定的泛型作为进度单位.
3, Result: 任务执行完毕后, 如果需要对结果返回, 这里的泛型作为返回值的类型.

经常需要重写的方法有:
1, onPreExecute()
在后台任务开始执行前调用, 用于进行一些界面上的初始化操作.

2, doInBackground(Params...)
在子线程中执行. 在这里去处理所有的耗时任务. 任务完成后通过 return 语句将结果返回. 注意, 在这个方法中, 不可以进行 UI 操作. 如需要更新UI元素, 要调用 publishProgress(Progress...) 方法来完成.

3, onProgressUpdate(Progress...)
后台任务调用了 publishProgress(Progress...) 方法后, 这个方法被调用. 所携带的参数就是后台任务中传过来的. 在这个方法中对 UI 进行操作.

4, onPostExecute(Result)
后台任务执行完毕并通过 return语句返回时, 这个方法调用. 返回的数据作为参数传递到此方法中, 可以利用返回的数据来进行一些 UI 操作.

简单来说, 使用 AsyncTask 的诀窍就是: 在 doInBackground() 方法中执行具体的耗时任务, 在 onProgressUpdate() 方法中进行UI操作, 在 onPostExecute() 方法中执行一些任务的收尾工作.

例子:

public 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("Downloaded" + values[0] + "%");
    }

    @Override
    protected void onPostExecute(Boolean aBoolean) {
        progressDialog.dismiss();//关闭进度对话框
        //在这里提示下载结果
        if (aBoolean) {
            Toast.makeText(context, "Download succeeded", Toast.LENGTH_SHORT).show();
        } else {
            Toast.makeText(context, "Download failed", Toast.LENGTH_SHORT).show();
        } 
    }
}

服务的基本用法

定义一个服务

新建一个类, 继承自 Service , 需要重写以下方法:
* onBind()
* Service 的唯一抽象方法, 所以必须在子类中实现.
* onCreate()
* 服务创建时调用.
* onStartCommand()
* 每次服务启动时调用, 把逻辑写在里面.
* onDestroy()
* 服务销毁时调用, 回收不再使用的资源.

最后别忘了在 清单文件 中注册服务.

例子:

public class MyService extends Service {

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

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

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return super.onStartCommand(intent, flags, startId);
    }

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

注册服务:

<service android:name=".MyService">
service>

启动和停止服务

修改布局文件, 添加两个按钮, 一个用来启动服务, 一个用来关闭服务.
修改 MainActivity :

public class MainActivity extends Activity implements View.OnClickListener {

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

        Button startService = (Button) findViewById(R.id.start);
        Button stopService = (Button) findViewById(R.id.stop);

        startService.setOnClickListener(this);
        stopService.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.start:
                Intent startIntent = new Intent(this, MyService.class);
                startService(startIntent);
                break;
            case R.id.stop:
                Intent stopIntent = new Intent(this, MyService.class);
                stopService(stopIntent);
                break;
            default:
                break;
        }
    }
}

tips:
stopSelf() 方法: 写在服务类的任何一个位置, 就可以让这个服务自己停下.
onCreate() 方法在第一次启动服务的时候调用, onStartCommand() 方法则是在每次启动服务的时候都调用.

活动和服务进行通信

继续在 布局文件 中再添加两个按钮, 用来绑定和解绑服务.
然后修改 MyService 类的代码:

public class MyService extends Service {

    private DownloadBinder mBinder = new DownloadBinder();

    class DownloadBinder extends Binder {

        public void startDownload() {
            Log.d("T", "startDownload");
        }

        public int getProgress() {
            Log.d("T", "getProgress");
            return 0;
        }
    }

    @Override
    public IBinder onBind(Intent intent) {

        return mBinder;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Log.d("T", "Create");
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d("T", "Start");

        return super.onStartCommand(intent, flags, startId);
    }

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

可以看到, 定义了一个内部类继承自 Binder, 在 onBind() 方法中返回了这个类的实例.(IBinder 是一个接口, 创建实例的话去继承它的子类 Binder)

然后修改 MainActivity 的代码:

public class MainActivity extends Activity implements View.OnClickListener {

    private Button startService;
    private Button stopService;
    private Button bindService;
    private Button unbindService;

    private MyService.DownloadBinder downloadBinder;

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

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

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

        startService = (Button) findViewById(R.id.start);
        stopService = (Button) findViewById(R.id.stop);
        bindService = (Button) findViewById(R.id.bind);
        unbindService = (Button) findViewById(R.id.unbind);

        startService.setOnClickListener(this);
        stopService.setOnClickListener(this);
        bindService.setOnClickListener(this);
        unbindService.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.start:
                Intent startIntent = new Intent(this, MyService.class);
                startService(startIntent);
                break;
            case R.id.stop:
                Intent stopIntent = new Intent(this, MyService.class);
                stopService(stopIntent);
                break;
            case R.id.bind:
                Intent bindIntent = new Intent(this, MyService.class);
                bindService(bindIntent, connection, BIND_AUTO_CREATE);
                break;
            case R.id.unbind:
                unbindService(connection);
                break;
            default:
                break;
        }
    }
}

服务的生命周期

在项目的任何位置调用 Context 的 startService() 方法, 服务就会启动, 并回调 onStartCommand() 方法(如果该服务之前没被创建过, 还会首先调用 onCreate() 方法). 服务启动后一直保持运行状态, 直到 stopService() 或者 stopSelf() 方法被调用. 注意, 每个服务都只会存在一个实例, 所以不管调用多少次 startService() 方法, 只要调用一次 stopService() 或者 stopSelf() 方法, 服务就会停止.

另外, 还可以调用 Context 的 bindService() 方法来获取一个服务的持久连接, 这时会回调 onBind() 方法(如果该服务之前没被创建过, 还会首先调用 onCreate() 方法). 之后, 调用方获取到 onBind() 方法里返回的 IBinder 对象的实例, 来与服务自由通信.

调用过 startService() 方法后, 又调用 stopService() 方法, 这时服务的 onDestroy() 方法就会执行, 服务销毁. 类似地, 当调用了 bindService() 方法后, 再调用 unbindService() 方法, onDestroy() 方法也会执行. 需要注意的是, 对一个服务既调用了 startService() 方法又调用了 bindService() 方法, 必须使服务被启动和被绑定两种条件同时不满足, 才能销毁这个服务. 也就是说, 这种情况要同时调用 stopService() 方法和 unbindService() 方法, onDestroy() 方法才会执行.

服务的更多技巧

使用前台服务

普通服务的系统优先级比较低, 当内存不足时, 可能回收掉正在后台运行的服务. 如果希望服务一直保持运行状态, 可以使用前台服务. 它会一直有一个正在运行的图标在系统的状态栏显示, 下拉状态栏可以看到更加详细的信息, 非常类似通知的效果.

创建一个前台服务很简单, 修改我们创建的服务类的 onCreate() 方法:

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

    Notification notification = new NotificationCompat.Builder(this)
          .setSmallIcon(R.mipmap.ic_launcher)
          .setTicker("Notification comes")
          .setWhen(System.currentTimeMillis())
          .setContentTitle("This is title")
          .setContentText("This is content")
          .build();

    PendingIntent pi = PendingIntent.getActivity(this, 0,
               new Intent(this, MainActivity.class), 0);

    startForeground(1, notification);

    Log.d("MyService", "onCreate");
}

使用 IntentService

为了更简单地创建一个 异步的并且会自动停止的 服务, Android提供了一个 IntentService 类.
修改 布局文件, 添加一个按钮, 用来启动 MyIntentService.
新建 MyIntentService , 继承自 IntentService:

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

在 清单文件 中注册服务:



修改 MainActivity 的代码:

public class MainActivity extends Activity implements View.OnClickListener {

    ...
    private Button startIntentService;

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

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

服务的最佳实践—-后台执行的定时任务

Android 中的定时任务有两种实现方式. 一种是使用 Java API 里提供的 Timer 类, 一种是使用 Android 的 Alarm 机制. 但是 Timer 不太适用于长期运行在后台的定时任务(因为长时间不操作手机会自动让CPU进入到睡眠状态). 而 Alarm 机制具有唤醒 CPU 的功能, 比较适合.

使用 Alarm 机制, 是需要借助 AlarmManager 类. 通过调用 Context 的 getSystemService() 方法来获取实例, 需要传入的参数是 Context.ALARM_SERVICE.

AlarmManager manager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);

然后调用 AlarmManager 的 set() 方法就可以设置一个定时任务了. 比如要设置一个10秒后执行的任务, 可以写成:

long triggerAtTime = SystemClock.elapseRealtime() + 10 * 1000;
manager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerAtTime, pendingIntent);

set() 方法需要三个参数:
* 第一个是整型, 用于指定 AlarmManager 的工作类型, 有四种值可以选:
* ELAPSED_REALTIME: 让定时任务的触发时间从系统开机开始算起, 但不会唤醒 CPU.
* ELAPSED_REALTIME_WAKEUP: 触发时间从系统开机开始算起, 会唤醒 CPU.
* RTC: 触发时间从1970年1月1日0点开始算起, 不会唤醒 CPU.
* RTC_WAKEUP: ……, 会唤醒CPU.
使用SystemClock.elapsedRealtime()方法获取到系统开机到现在的毫秒数, 使用System.currentTimeMillis()方法获取到1970年1月1日0点到现在的毫秒数.
* 第二个是定时任务的触发时间, 以毫秒为单位.
* 第三个是一个 PendingIntent , 调用 getBroadcast() 方法获取一个能执行广播的 PendingIntent. 当任务被触发时, 广播接收器的 onReceive() 方法就可以得到执行.
另外, 在 Android 4.4 版本开始, 由于系统在耗电性方面做的优化, 任务触发时间可能会有些许延迟. 系统会自动检测有多少 Alarm 任务存在, 将触发时间相近的几个任务放在一起执行, 以便延迟电池使用时间. 如果仍然要精确指定执行时间, 可以使用 setExact() 方法替代 set() 方法.

设置一个10秒后执行的任务, 还可以写成:

long trggerAtTime = System.currentTimeMillis() + 10 * 1000;
manager.set(AlarmManager.RTC_WAKEUP, triggerAtTime, pendingIntent);

新建一个项目, 创建一个可以长期在后台执行定时任务的服务.
添加一个 LongRunningService 类:

public class LongRunningService extends Service {

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

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        //在子线程中执行具体逻辑操作
        new Thread(new Runnable() {
            @Override
            public void run() {
                Log.d("TAG", "executed at " + new Date().toString());
            }
        }).start();

        AlarmManager manager = (AlarmManager) getSystemService(ALARM_SERVICE);
        int anHour = 60 * 60 * 1000;
        long triggerAtTime = SystemClock.elapsedRealtime() + anHour;

        Intent i = new Intent(this, AlarmReceiver.class);
        PendingIntent pi = PendingIntent.getBroadcast(this, 0, i, 0);

        manager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerAtTime, pi);

        return super.onStartCommand(intent, flags, startId);
    }
}

新建一个 AlarmReceiver 类:

public class AlarmReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        Intent i = new Intent(context, LongRunningService.class);
        context.startService(i);
    }
}

这样, 一旦启动 LongRunningService , 就会设置一个定时任务, 一个小时以后 AlarmReceiver 收到广播, 执行 onReceive() 方法, 再启动 LongRunningService, 形成一个永久的循环. 保证 LongRunningService 每隔一个小时就启动一次.

修改MainActivity的代码, 保证 LongRunningService 在程序启动时就执行:

public class MainActivity extends Activity {

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

        Intent intent = new Intent(this, LongRunningService.class);
        startService(intent);
    }
}

最后在 AndroidManifest.xml 文件中注册所用到的服务和广播接收器.

<service android:name=".LongRunningService">
service>
<receiver android:name=".AlarmReceiver">
receiver>

你可能感兴趣的:(Android)