第一行代码读书笔记 10 -- 探究服务(中)

作者: 开心wonderful | 来源:发表于2017-01-22 22:38 被阅读62次

    本篇文章主要介绍以下几个知识点:

    • 服务的基本用法
    • 服务的生命周期
    • 服务的其他用法
    图片来源于网络

    10.2 服务的基本用法

    服务(Service)是 Android 中实现程序后台运行的解决方案,它非常适合去执行那些不需要和用户交互而且还要求长期运行的任务。
      服务并不是运行在一个独立的进程当中的,而是依赖于创建服务时所在的应用程序进程。
      服务并不会自动开启线程,所有代码默认运行在主线程中。

    10.2.1 定义一个服务

    在项目中定义一个服务:在 Android Studio 中右击 com.wonderful.myfirstcode.chapter10.service包(你项目所在的包名)→New→Service→Service,会弹出如下窗口:

    创建服务的窗口

    上面将服务命名为 MyService,Exported 表示是否允许除了当前程序之外的其他程序访问这个服务,Enabled 表示是否启用这个服务。完成创建后的 MyService 如下:

    public class MyService extends Service {
        public MyService() {
        }
    
        @Override
        public IBinder onBind(Intent intent) {
            // TODO: Return the communication channel to the service.
            throw new UnsupportedOperationException("Not yet implemented");
        }
    }
    

    可以看到,onBind() 方法是 Service 中唯一的一个抽象方法,必须在子类中实现。在服务中添加处理事情的逻辑还要重写 Service 中的另外一些方法,如下:

    public class MyService extends Service {
    
        . . .
    
        /**
         * 在服务创建时调用
         */
        @Override
        public void onCreate() {
            super.onCreate();
            Log.d("------MyService------", "onCreate: ");
        }
    
        /**
         * 在每次服务启动时调用
         * 若服务一旦启动就立刻执行某个动作,可以将逻辑写在此方法中
         */
        @Override
        public int onStartCommand(Intent intent, int flags, int startId) {
            Log.d("------MyService------", "onStartCommand: ");
            return super.onStartCommand(intent, flags, startId);
        }
    
        /**
         * 在服务销毁时调用,回收不再使用的资源
         */
        @Override
        public void onDestroy() {
            super.onDestroy();
            Log.d("------MyService------", "onDestroy: ");
        }
    }
    

    另外需注意的是,每一个服务都需要在 AndroidManifest.xml 文件中进行注册才能生效(安卓四大组件的共有特点)。当然,刚才创建服务时 AS 已经自动帮我们注册好了:

    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.wonderful.myfirstcode">
    
        <application
            android:name=".MyApplication"
            android:allowBackup="true"
            android:icon="@mipmap/ic_launcher"
            android:label="@string/app_name"
            android:supportsRtl="true"
            android:theme="@style/AppTheme">
            . . .
    
            <service
                android:name=".chapter10.service.MyService"
                android:enabled="true"
                android:exported="true">
            </service>
        </application>
    
    </manifest>
    

    以上,就将一个服务定义好了。

    10.2.2 启动和停止服务

    启动和停止服务的方法主要是借助 Intent 来实现的。下面就在项目中尝试去启动和停止服务。

    在布局中添加两个按钮,分别用于启动和停止服务:

    public class MyServiceActivity extends AppCompatActivity implements View.OnClickListener{
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_my_service);
    
            Button start_service = (Button) findViewById(R.id.start_service);
            Button stop_service = (Button) findViewById(R.id.stop_service);
            start_service.setOnClickListener(this);
            stop_service.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.stop_service:
                    Intent stopIntent = new Intent(this,MyService.class);
                    stopService(stopIntent);// 停止服务
                    break;
                default:
                    break;
            }
        }
    }
    

    上述代码,启动服务 startService() 和停止服务 stopService() 方法都是定义在 Context 类中的,在活动里可以直接调用。当然,停止服务也可以在 MyService 的任何一个位置调用 stopSelf() 方法,让服务自己停止下来。

    运行程序,点击启动服务,打印日志如下:

    启动服务时打印日志

    点击停止服务,打印日志如下:

    停止服务时打印日志

    值得注意的是,onCreate() 在服务第一次创建时调用,onStartCommand() 在每次启动服务时都会调用,上面第一次点击启动服务时两个方法都会执行,之后再点击启动服务按钮就只有 onStartCommant() 方法执行了。

    10.2.3 活动和服务进行通信

    上面一节中,虽然服务在活动里启动,但启动之后活动与服务就没什么关系了。若要在活动中指定服务做什么,就要借助服务里面的 onBind() 方法了。

    下面举个例子,若在 MyService 里提供一个下载功能,然后在活动中可以决定何时开始下载,以及随时查看下载进度。实现这个功能的思路是创建一个专门的 Binder 对象来对下载功能进行管理,如下:

    public class MyService extends Service {
           
        private DownloadBinder mBinder = new DownloadBinder();
        
        class DownloadBinder extends Binder{
            // 模拟开始下载方法
            public void startDownload(){
                Log.d("------MyService------", "startDownload: ");
            }
            // 模拟查看下载进度方法
            public int getProgress(){
                Log.d("------MyService------", "getProgress: ");
                return 0;
            }
        }
    
        @Override
        public IBinder onBind(Intent intent) {
            return mBinder;
        }
    
        . . .
    }
    

    接着在布局中添加两个按钮,用于绑定服务和取消绑定服务:

    public class MyServiceActivity extends AppCompatActivity implements View.OnClickListener{
    
        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_my_service);
    
            Button bind_service = (Button) findViewById(R.id.bind_service);
            Button unbind_service = (Button) findViewById(R.id.unbind_service);
            bind_service.setOnClickListener(this);
            unbind_service.setOnClickListener(this);
        }
    
        @Override
        public void onClick(View v) {
            switch (v.getId()){        
                case R.id.bind_service:
                    Intent bindIntent = new Intent(this,MyService.class);
                    // 绑定服务,三个参数:Intent对象、ServiceConnection实例、标志位
                    // (BIND_AUTO_CREATE 表示活动和服务绑定后自动创建服务)
                    bindService(bindIntent,connection,BIND_AUTO_CREATE);
                    break;
                case R.id.unbind_service:
                    // 解绑服务
                    unbindService(connection);
                    break;
                default:
                    break;
            }
        }
    }
    

    上述代码,首先创建了一个 ServiceConnection 的匿名类,里面重写两个方法,在 onServiceConnected() 中获取 DownloadBinder 的实例,接下来就根据具体场景来调用 DownloadBinder 中的任何公共方法。

    运行程序,点击绑定服务,打印日志如下:

    绑定服务时打印日志

    点击解绑服务,打印日志如下:

    解绑服务时打印日志

    值得注意的是,任何一个服务在整个应用程序内都是通用的,即 MyService 可以和任何一个活动进行绑定,而且绑定完成后都可以获取到相同的 DownloadBinder 实例。

    10.3 服务的生命周期

    前面使用到的 onCreate()、onStartCommand()、onBind()、onDestroy() 等方法都是在服务的生命周期内可能回调的方法,具体如下图所示:

    服务的生命周期

    值得注意的是,当我们对一个服务既调用了 startService() 方法,又调用了 bindService() 方法时,要同时调用 stopService() 和 unbindService() 方法,onDestroy() 方法才会执行。

    10.4 服务的更多技巧

    10.4.1 使用前台服务

    服务的优先级较低,当系统内存不足时,可能会回收正在后台运行的服务,若要避免被回收,可以考虑使用前台服务。

    前台服务和普通服务的区别在于,它会一直有一个正在运行的图标在系统的状态栏显示,下拉状态栏可以看到更加详细的信息,类似于通知的效果。

    创建一个前台服务如下:

    public class MyService extends Service {
        . . .
    
       /**
         * 在服务创建时调用
         */
        @Override
        public void onCreate() {
            super.onCreate();
            Log.d("------MyService------", "onCreate: ");
            Intent intent = new Intent(this,MyServiceActivity.class);
            PendingIntent pi = PendingIntent.getActivity(this,0,intent,0);
            Notification notification = new NotificationCompat.Builder(this)
                    .setContentTitle("这是标题")
                    .setContentText("这是内容")
                    .setWhen(System.currentTimeMillis())
                    .setSmallIcon(R.mipmap.ic_launcher)
                    .setLargeIcon(BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher))
                    .setContentIntent(pi)
                    .build();
            //让MyService变成一个前台服务,并在系统状态栏显示出来
            startForeground(1,notification);
        }
    
        . . .
    }
    

    前台服务的用法就这么简单,和创建通知的方法类似。运行程序,点击开启服务或绑定服务,效果如下:

    前台服务的状态栏效果

    10.4.2 使用 IntentService

    之前提到过服务中的代码都是默认运行在主线程当中,若直接在服务里处理耗时操作,容易出现 ANR(Application Not Responding)的情况。

    为避免上述情况,应该在服务的每个具体的方法里开启一个子线程,在子线程里处理耗时操作。因此一个比较标准的服务可以写成如下形式:

    public class MyService extends Service {
        . . .
    
       /**
         * 在每次服务启动时调用
         */
        @Override
        public int onStartCommand(Intent intent, int flags, int startId) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    // 处理具体的逻辑        
                }
            }).start();
            return super.onStartCommand(intent, flags, startId);
        }
    }
    

    服务开启后会一直处于运行状态,必须调用 stopService() 或者 stopSelf() 才能停止服务,所以要实现一个服务在执行完毕后自动停止,可以这样写:

     @Override
     public int onStartCommand(Intent intent, int flags, int startId) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    // 处理具体的逻辑        
                    stopSelf();
                }
            }).start();
            return super.onStartCommand(intent, flags, startId);
     }
    

    当然,为了可以简单地创建一个异步地、会自动停止地服务,Android 专门提供了一个** IntentService **类。

    下面介绍下 IntentService 类的用法,创建一个 MyIntentService 类继承自 IntentService,如下:

    public class MyIntentService extends IntentService {
    
        public MyIntentService() {
            super("MyIntentService");//调用父类的有参构造函数
        }
    
        /**
         * 此方法在子线程中运行,可以处理一些具体的逻辑,且不用担心 ANR 问题
         * @param intent
         */
        @Override
        protected void onHandleIntent(Intent intent) {
            // 打印当前线程的 id
            Log.d("MyIntentService", "onHandleIntent: 线程id是 "+ Thread.currentThread().getId());
        }
    
        @Override
        public void onDestroy() {
            super.onDestroy();
            Log.d("MyIntentService", "onDestroy: 服务停止");
        }
    }
    

    下面举个例子来证实下,在布局中添加个按钮用于启动 MyIntentService 这个服务,如下:

    public class MyServiceActivity extends AppCompatActivity implements View.OnClickListener{
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_my_service);
    
            Button start_intent_service = (Button) findViewById(R.id.start_intent_service);
            start_intent_service.setOnClickListener(this);
        }
    
        @Override
        public void onClick(View v) {
            switch (v.getId()){
                case R.id.start_intent_service:
                    // 打印主线程的 id
                    Log.d("MyServiceActivity", "onClick: 主线程id:"+ Thread.currentThread().getId());
                    Intent intentService = new Intent(this,MyIntentService.class);
                    startService(intentService);
                    break;
            }
        }
    }
    

    不要忘了在 AndroidManifest.xml 里注册服务(当然也可以用 AS 提供的快捷方式创建服务):

    <service android:name=".chapter10.service.MyIntentService" />
    

    运行程序,点击按钮,打印日志如下:

    启动IntentService时打印日志

    可以看到,MyIntentService 在运行完毕后自动停止了。

    本篇文章介绍到这,下一小节进入服务的最佳实践。

    相关文章

      网友评论

        本文标题:第一行代码读书笔记 10 -- 探究服务(中)

        本文链接:https://www.haomeiwen.com/subject/ninwbttx.html