Android Service 重难点解析

一、前言


Service 是Android 系统四大组件之一。Android 支持 Service 服务的原因主要目的有两个,一是简化后台任务的实现,二是实现在同一台设备当中跨进程的远程信息通信。下面我们一起来学习一下 Service。

 

二、Service 的用法


首先看看如何定义一个服务,代码如下:

public class MyService extends Service {
    //这个方法是 Service 中唯一的一个抽象方法,所以必须要在子类里实现
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    //onCreate()方法会在服务创建的时候调用
    @Override
    public void onCreate() {
        super.onCreate();
    }

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

    //onDestroy()方法会在服务销毁的时候调用
    @Override
    public void onDestroy() {
        super.onDestroy();
    }
}

onCreate() 方法是在服务第一次创建的时候调用,而 onStartCommand() 方法则在每次启动服务的时候都会调用。通常情况下,如果我们希望服务一旦启动就立刻去执行某个动作,就可以将逻辑写在 onStartCommand() 方法里。而当服务销毁时,我们又应该在 onDestroy() 方法中去回收那些不再使用的资源。另外需要注意,每一个服务都需要在 AndroidManifest.xml 文件中进行注册才能生效。

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

//android:enabled 是否可实例化,默认true。如设置false,Service将不可用
//android:exported 是否允许跨进程调用

定义好了服务之后,接下来就考虑如何启动以及停止这个服务。启动和停止服务的方法主要是借助 Intent 来实现。我们在布局文件里加入两个按钮,分别用于启动和停止服务。然后修改 MainActivity 的代码如下:

@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.stop_service:
            Intent stopIntent = new Intent(this, MyService.class);
            stopService(stopIntent); // 停止服务
            break;
        default:
            break;
    }
}

startService() 和 stopService() 方法都是定义在 Context 类中的,所以我们可以在 Activity 中直接调用这两个方法。另外,Service 也可以自己调用 stopSelf() 方法停掉服务。

在上述代码中,Service 启动后就一直处于运行状态,但具体运行的是什么逻辑,Activity 就控制不了了。如果想要 Activity 和 Service 的联系更紧密一些,可以通过绑定服务的方式,这就需要借助我们上面忽略的 onBind() 方法了。比如我们需要在 Service 里提供一个下载功能,然后在 Activity 中可以决定何时开始下载,以及随时查看下载进入,那么就需要创建一个专门的 Binder 对象来对下载功能进行管理,代码如下:

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

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

上面代码中,创建了一个 DownloadBinder 类并继承自 Binder,然后在它的内部提供了开始下载和查看下载进度的方法,当然这里只是两个模拟方法,没有实现真正的功能。接着,在 MyService 中创建了 DownloadBinder 的实例,然后在 onBind() 方法里返回这个 Binder 实例。

然后我们写两个按钮,分别用于在 Activity 中绑定服务和取消绑定服务,当一个 Activity 和 Service 绑定以后,就可以调用该 Service 里 Binder 提供的方法了,代码如下:

public class MainActivity extends Activity implements OnClickListener {
……
    private MyService.DownloadBinder downloadBinder;

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

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

……

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

可以看到,我们首先创建了一个 ServiceConnection 的匿名类,在里面重写了 onServiceConnected() 和 onServiceDisconnected() 方法,这两个方法分别会在 Activity 与 Service 成功绑定和解除绑定的时候调用。在 onServiceConnected() 方法中,我们又通过向下转型得到了 DownloadBinder 的实例,有了这个实例,就可以在 Activity 中根据具体的场景来调用 DownloadBinder 中的任何 public 方法了。

BindService() 方法将 Activity 于 Service 进行绑定,这个方法接收三个参数,第一个参数是 Intent 对象,第二个参数是前面创建出的 ServiceConnection 的实例,第三个参数则是一个标志位,这里传入 BIND_AUTO_CREATE 表示在 Activity 和 Service 进行绑定后自动创建服务,这会使得 Service 中的 onCreate() 方法得到执行,但 onStartCommand() 方法不会执行。解除绑定的话可以调用 unBindService() 方法。另外,任何一个 Service 在整个应用程序范围内都是通用的,可以和任何一个 Activity 进行绑定,而且绑定完成后它们都可以获取到相同的 DownloadBinder 实例。

和 Activity 一样,Service 也有自己的生命周期,前面我们使用到的 onCreate()、onStartCommand()、onBind()和 onDestroy() 等方法都是在服务的生命周期内可能回调的方法。一旦在项目的任何位置调用了 Context 的 startService()方法,相应的 Service 就会启动起来,并回调 onStartCommand() 方法。如果这个 Service 之前还没有创建过,onCreate() 方法会先于
onStartCommand() 方法执行。Service 启动了之后会一直保持运行状态,直到 stopService() 或 stopSelf() 方法被调用。注意虽然每调用一次 startService() 方法,onStartCommand() 就会执行一次,但实际上每个 Service 都只会存在一个实例。所以不管你调用了多少次 startService() 方法,只需调用一次 stopService()或 stopSelf()方法,Service 就会停止下来了。另外,还可以调用 Context 的 bindService() 来获取一个 Service 的持久连接,这时就会回调 Service 中的 onBind()方法。类似地,如果这个 Service 之前还没有创建过,onCreate() 方法会先于 onBind() 方法执行。之后,调用方可以获取到 onBind() 方法里返回的 IBinder 对象的实例,这样就能自由地和 Service 进行通信了。只要调用方和 Service 之间的连接没有断开, Service 就会一直保持运行状态。当调用了 startService()方法后,又去调用 stopService()方法,这时 Service 中的 onDestroy() 方法就会执行,表示 Service 已经销毁了。类似地,当调用了 bindService() 方法后,又去调用 unbindService() 方法,onDestroy() 方法也会执行,并且 unbindService() 方法只能调用一次,多次调用应用会抛出异常。使用时也要注意调用 unbindService() 一定要确保服务已经开启,否则应用会抛出异常。但是需要注意,我们是完全有可能对一个 Service 既调用了 startService()方法,又调用了 bindService()方法的,这种情况下该如何才能让服务销毁掉呢?根据 Android 系统的机制,一个服务只要被启动或者被绑定了之后,就会一直处于运行状态,必须要让以上两种条件同时不满足,服务才能被销毁。所以,这种情况下要同时调用 stopService() 和 unbindService() 方法,onDestroy() 方法才会执行。

Service的onCreate、onStartCommand、onDestory等全部生命周期方法都运行在UI线程,ServiceConnection里面的回调方法也是运行在UI线程。

关于 Service 还有些难点,这里我说的可能不够清晰,推荐大家可以看看其他博客:Service: onStartCommand 诡异的返回值Android中startService和bindService的区别

 

三、前台服务


Service 几乎都是在后台运行的,一直以来它都是默默地做着辛苦的工作。但是 Service 的系统优先级还是比较低的,当系统出现内存不足的情况时,就有可能会回收掉正在后台运行的 Service 。如果你希望 Service 可以一直保持运行状态,而不会由于系统内存不足的原因导致被回收,就可以考虑使用前台 Service 。前台 Service 和普通 Service 最大的区别就在于,它会一直有一个正在运行的图标在系统的状态栏显示,下拉状态栏后可以看到更加详细的信息,非常类似于通知的效果。当然有时候你也可能不仅仅是为了防止 Service 被回收掉才使用前台 Service 的,有些项目由于特殊的需求会要求必须使用前台 Service ,比如说天气类应用,它的 Service 在后台更新天气数据的同时,还会在系统状态栏一直显示当前的天气信息,如下所示:

创建一个前台服务并不复杂,代码如下:

public class MyService extends Service {
……
    @Override
    public void onCreate() {
        super.onCreate();
        Notification notification = new Notification(R.drawable.ic_launcher,"Notification comes",
System.currentTimeMillis());
        Intent notificationIntent = new Intent(this, MainActivity.class);
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,notificationIntent, 0);
        notification.setLatestEventInfo(this, "This is title", "This iscontent", pendingIntent);
        startForeground(1, notification);
        Log.d("MyService", "onCreate executed");
    }
……
}

可以看到,这里只是修改了 onCreate()方法中的代码,构建出 Notification 对象后并没有使用 NotificationManager 来将通知显示出来,而是调用了 startForeground()方法。这个方法接收两个参数,第一个参数是通知的 id,类似于 notify()方法的第一个参数,第二个参数则是构建出的 Notification 对象。调用 startForeground()方法后就会让 MyService 变成一个前台服务,并在系统状态栏显示出来。现在重新运行一下程序,并点击 Start Service 或 Bind Service 按钮,MyService 就会以前台服务的模式启动了,并且在系统状态栏会显示一个通知图标,下拉状态栏后可以看到该通知的详细内容。
 

四、IntentService


4.1 IntentService 使用

Service 中的代码都是默认运行在主线程当中的,如果直接在 Service 里去处理一些耗时的逻辑,就很容易出现 ANR(Application Not Responding)的情况。所以这个时候就需要手动去开启一个子线程,然后在这里去处理那些耗时的逻辑。虽说这种写法并不复杂,但是总会有一些程序员忘记开启线程,或者忘记调用 stopSelf() 方法。为了可以简单地创建一个异步的、会自动停止的服务,Android 专门提供了一个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");
    }
}

这里首先是要提供一个无参的构造函数,并且必须在其内部调用父类的有参构造函数。然后要在子类中去实现 onHandleIntent() 这个抽象方法,在这个方法中可以去处理一些具体的逻辑,不用担心 ANR 的问题,因为这个方法已经是在子线程中运行的了。另外根据 IntentService 的特性,这个服务在运行结束后应该是会自动停止的。

 

4.2 IntentService 源码解析

 我们先看一下它的源码:

public abstract class IntentService extends Service {
    private volatile Looper mServiceLooper;
    private volatile ServiceHandler mServiceHandler;
    private String mName;
    private boolean mRedelivery;

    private final class ServiceHandler extends Handler {
        public ServiceHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            onHandleIntent((Intent)msg.obj);
            stopSelf(msg.arg1);
        }
    }

    public IntentService(String name) {
        super();
        mName = name;
    }

    public void setIntentRedelivery(boolean enabled) {
        mRedelivery = enabled;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
        thread.start();

        mServiceLooper = thread.getLooper();
        mServiceHandler = new ServiceHandler(mServiceLooper);
    }

    @Override
    public void onStart(@Nullable Intent intent, int startId) {
        Message msg = mServiceHandler.obtainMessage();
        msg.arg1 = startId;
        msg.obj = intent;
        mServiceHandler.sendMessage(msg);
    }

    @Override
    public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
        onStart(intent, startId);
        return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
    }

    @Override
    public void onDestroy() {
        mServiceLooper.quit();
    }
    
    @Override
    @Nullable
    public IBinder onBind(Intent intent) {
        return null;
    }

    @WorkerThread
    protected abstract void onHandleIntent(@Nullable Intent intent);
}

 可以看到它在 onCreate 里面初始化了一个 HandlerThread,我们再进一步看看 HandlerThread 源码:

public class HandlerThread extends Thread {
    int mPriority;
    int mTid = -1;
    Looper mLooper;

    public HandlerThread(String name) {
        super(name);
        mPriority = Process.THREAD_PRIORITY_DEFAULT;
    }
    
    public HandlerThread(String name, int priority) {
        super(name);
        mPriority = priority;
    }
    
    protected void onLooperPrepared() {
    }

    @Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }
    
    public Looper getLooper() {
        if (!isAlive()) {
            return null;
        }
        
        // If the thread has been started, wait until the looper has been created.
        synchronized (this) {
            while (isAlive() && mLooper == null) {
                try {
                    wait();
                } catch (InterruptedException e) {
                }
            }
        }
        return mLooper;
    }

    public boolean quit() {
        Looper looper = getLooper();
        if (looper != null) {
            looper.quit();
            return true;
        }
        return false;
    }

    public boolean quitSafely() {
        Looper looper = getLooper();
        if (looper != null) {
            looper.quitSafely();
            return true;
        }
        return false;
    }

    public int getThreadId() {
        return mTid;
    }
}

我们发现 HandlerThread 其实就是初始化和启动了一个线程,然后在 run() 方法中调用了 Looper.prepare() 创建了一个 Looper 对象,并且把该对象放到了该线程范围内变量中(sThreadLocal),在 Looper 对象的构造过程中,初始化了一个 MessageQueue,作为该 Looper 对象成员变量。然后调用 Loop.loop() 方法开启循环,不断地从 MessageQueue 中获取消息。以上其实就是消息机制的一些过程,更详细的说明可以看我的另一篇文章 Android 消息机制解析

我们再回到 IntentService 源码中,在onCreate里面初始化了一个HandlerThread,然后在 onStartCommand 中回调了 onStart,onStart 中通过 mServiceHandler 发送消息到该 handler 的 handleMessage 中去。最后 handleMessage 中回调 onHandleIntent(intent)。回调完成后会调用 stopSelf(msg.arg1),注意这个 msg.arg1 是个 int 值,相当于一个请求的唯一标识。每发送一个请求,会生成一个唯一的标识,然后将请求放入队列,当全部执行完成(最后一个请求也就相当于 getLastStartId == startId),或者当前发送的标识是最近发出的那一个(getLastStartId == startId),则会销毁我们的Service.如果传入的是 -1 则直接销毁。当任务完成销毁Service回调onDestory,可以看到在onDestroy中释放了我们的 Looper:mServiceLooper.quit()。

可以看到,设计得是非常好的,如果你的需求可以使用IntentService来做,可以尽可能的使用。当然了,如果你需要考虑并发等等需求,那么可能需要自己去扩展创建线程池等。

 

4.3 IntentService 实践

 这里我们写一个APK文件下载的后台服务,代码如下:

1、先写一个 IntentService

/**
 * APK文件下载的后台服务
 */
public class DownLoadFileService extends IntentService {

    public static final String SERVICE_NAME = "com.lerendan.download.file.service";

    public static final String DOWNLOAD_FILE_INTENT_KEY = "mDownLoadFileIntentKey";

    public static final String DOWNLOAD_URL_INTENT_KEY = "mDownLoadUrlIntentKey";

    public static final String RESULT_RECEIVER_CLASS_INTENT_KEY = "mResultReceiverClassIntentKey";

    public static final int CONNECT_TIME_OUT = 10000;

    public static final int READ_TIME_OUT = 10000;

    public static final int PROGRESS_CODE = 500;

    public static final int SUCCESS_CODE = 501;

    public static final int ERROR_CODE = 502;

    public DownLoadFileService() {
        super(SERVICE_NAME);
    }

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

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

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

    @Override
    protected void onHandleIntent(Intent intent) {
        String downLoadUrl = intent.getStringExtra(DOWNLOAD_URL_INTENT_KEY);
        File downLoadFile = (File) intent.getSerializableExtra(DOWNLOAD_FILE_INTENT_KEY);
        ResultReceiver receiver = intent.getParcelableExtra(RESULT_RECEIVER_CLASS_INTENT_KEY);
        if (TextUtils.isEmpty(downLoadUrl) || downLoadFile == null || receiver == null)
            return;


        FileOutputStream fos = null;
        InputStream is = null;
        try {
            URL myFileUrl;
            myFileUrl = new URL(downLoadUrl);
            HttpURLConnection conn = (HttpURLConnection) myFileUrl.openConnection();
            conn.setRequestMethod("GET");
            conn.setConnectTimeout(CONNECT_TIME_OUT);
            conn.setReadTimeout(READ_TIME_OUT);
            if (conn.getResponseCode() == 200) {
                long apkLength = conn.getContentLength();
                is = conn.getInputStream();
                fos = new FileOutputStream(downLoadFile);
                byte[] temp = new byte[2048];
                int readLen;
                long total = 0;
                long count = 0;
                while ((readLen = is.read(temp)) > 0) {
                    fos.write(temp, 0, readLen);
                    total += readLen;
                    // 防止频繁刷新UI
                    if (count % 40 == 0) {
                        // 获取进度值
                        float progress = (((float) total) / apkLength) * 100;
                        sendProgressMessage(receiver, progress, total, apkLength);
                    }
                    count++;
                }
                fos.flush();
                // 发送成功的消息
                Bundle resultBundle = new Bundle();
                resultBundle.putSerializable("downLoadFile", downLoadFile);
                receiver.send(SUCCESS_CODE, resultBundle);
            } else {
                receiver.send(ERROR_CODE, null);
            }
        } catch (IOException e) {
            receiver.send(ERROR_CODE, null);
        } finally {
            try {
                if (fos != null) {
                    fos.close();
                }
            } catch (IOException e) {
            }
            try {
                if (is != null) {
                    is.close();
                }
            } catch (IOException e) {
            }
        }
    }

    /**
     * 发送进度消息
     */
    private void sendProgressMessage(ResultReceiver receiver, float progress, long currentLength, long totalLength) {
        Bundle resultBundle = new Bundle();
        resultBundle.putFloat("progress", progress);
        resultBundle.putLong("currentLength", currentLength);
        resultBundle.putLong("totalLength", totalLength);
        receiver.send(PROGRESS_CODE, resultBundle);
    }

}

可以看到,无论是下载过程中还是下载成功或是下载失败,都会给 ResultReceiver 发送指令,接下来看看我们 ResultReceiver 的代码:

/**
 * APK下载回调
 */
public class ApkDownLoadReceiver extends ResultReceiver {

    private Context mContext;

    private NotificationManager mNotificationManager;

    public static final int NOTIFICATION_ID = 7649831;

    private ApkDownLoadReceiver(Handler handler) {
        super(handler);
    }

    public ApkDownLoadReceiver(Context context, Handler handler) {
        this(handler);
        this.mContext = context;
        this.mNotificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
    }

    @Override
    protected void onReceiveResult(int resultCode, Bundle resultData) {
        super.onReceiveResult(resultCode, resultData);
        switch (resultCode) {
            case DownLoadFileService.PROGRESS_CODE:
                if (resultData != null) {
                    float progress = resultData.getFloat("progress");
                    Notification notification = ApkDownLoadNotification.getNotification(mContext, (int) progress, R.mipmap.app_launch);
                    mNotificationManager.notify(NOTIFICATION_ID, notification);
                }
                break;
            case DownLoadFileService.SUCCESS_CODE:
                mNotificationManager.cancel(NOTIFICATION_ID);
                if (resultData != null) {
                    File downLoadFile = (File) resultData.getSerializable("downLoadFile");
                    if (downLoadFile != null && downLoadFile.exists() && downLoadFile.isFile()) {
                        // 下载成功,安装apk
                        AppInfoUtils.installApkFile(mContext, downLoadFile);
                    } else {
                        ToastUtils.show(mContext.getString(R.string.download_app_failure));
                    }
                }
                break;
            case DownLoadFileService.ERROR_CODE:
                mNotificationManager.cancel(NOTIFICATION_ID);
                ToastUtils.show(mContext.getString(R.string.download_app_failure));
                break;
        }
    }

}

接下来就是调用的代码了:

    /**
     * 开启后台服务去下载
     */
    private void startServiceToDownLoadApk(String apkPath, String newVersion) {
        File apkFile = new File(FileUtils.getDownLoadApkPath(mContext), "VVIC_" + newVersion + "_" + System.currentTimeMillis() + ".apk");
        Intent intent = new Intent(mContext, DownLoadFileService.class);
        intent.putExtra(DownLoadFileService.DOWNLOAD_URL_INTENT_KEY, apkPath);
        intent.putExtra(DownLoadFileService.DOWNLOAD_FILE_INTENT_KEY, apkFile);
        ResultReceiver receiver = new ApkDownLoadReceiver(mContext, new Handler());
        intent.putExtra(DownLoadFileService.RESULT_RECEIVER_CLASS_INTENT_KEY, receiver);
        mContext.startService(intent);
    }

 

五、远程 Service


以上章节所举的例子都是使用 Local Service 技术,Serivce 服务端与 Client 客户端都是在于同一进程当中,当APP被卸御,Service 也被同时卸御。要是想把服务端与客户端分别放在不同的进程当中进行跨进程信息交换的话,就需要使用远程通信服务 Remote Service。使用 Remote Service 可以把服务端与客户端分离,当一方被卸御,另一方不会被影响。

将一个普通的 Service 转换成远程 Service 其实非常简单,只需要在注册 Service 的时候将它的 android:process 属性指定成 :remote 就可以了,代码如下所示:

<service
    android:name="com.example.servicetest.MyService"
    android:process=":remote" >
</service>

那么如何才能让Activity与一个远程Service建立关联呢?这就要使用AIDL来进行跨进程通信了(IPC)。这个不是本篇文章的中点,具体的可以通过我的另一篇博客学习 — Android IPC 之 AIDL 使用与解析

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值