美文网首页
2、Service

2、Service

作者: Leo_JiangZhiHao | 来源:发表于2023-09-09 16:59 被阅读0次

    2.1 Service的基本用法

    定义Service

    Service是Android系统中的四大组件之一,它是一种长生命周期的,没有可视化界面,运行于后台的一种服务程序。
    onBind()是Service类中唯一的抽象方法,必须在子类里实现。我们会在后面的小节中使用到onBind()方法。
    这里我们重写了OnCreate()、OnStartCommand()和OnDestroy()这3个方法,它们是每个Service最常用的3个方法了。

    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");
        }
    
        @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() {
            Log.d("onDestroy", "onStartCommand executed");
            super.onDestroy();
        }
    }
    

    在AndroidManifest.xml中注册

    每一个Service都需要在AndroidManifest.xml中文件中进行注册才能生效。这是Android四大组件共有的特点。

    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        package="com.example.myapplication">
    
        <application
            android:allowBackup="true"
            android:icon="@mipmap/ic_launcher"
            android:label="@string/app_name"
            android:roundIcon="@mipmap/ic_launcher_round"
            android:supportsRtl="true"
            android:theme="@style/Theme.MyApplication">
            <service
                android:name=".MyService"
                android:enabled="true"
                android:exported="true">
            </service>
        </application>
    
    </manifest>
    

    启动和停止Service

    创建MainActivity,并修改OnCreate的代码:

    public class MainActivity extends AppCompatActivity {
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            Button startServiceBtn = findViewById(R.id.button1);
            Button stopServiceBtn = findViewById(R.id.button2);
            startServiceBtn.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    Intent intent = new Intent(MainActivity.this, MyService.class);
                    startService(intent);
                }
            });
            stopServiceBtn.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    Intent intent = new Intent(MainActivity.this, MyService.class);
                    stopService(intent);
                }
            });
        }
    }
    

    startService()和stopService()方法都是定义在Context类中的,所以我们在Activity里可以直接调用这个方法。
    另外,Service也可以自我停止运行,只需要我们在Service内部调用stopSelf()方法即可。
    从Android 8.0系统开始,应用的后台功能被大幅削减。现在只有当应用保持在前台可见状态的情况下,Service才能保证稳定运行,一旦应用进入后台之后,Service随时都有可能被系统回收。之所以做这样的改动,是为了防止许多恶意的应用程序长期在后台占用手机资源,从而导致手机变得越来越卡。当然,如果需要长期在后台执行一些任务,可以使用前台Service或者WorkManager。

    Activity和Service进行通信

    我们希望在MyService里提供一个下载功能,然后在Activity中可以决定何时开始下载,以及随时查看下载进度。
    实现这个功能的思路是创建一个专门的Binder对象来对下载功能进行管理。修改MyService中的代码:

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

    当Activity和Service绑定了之后,就可以调用该Service里的Binder提供的方法了。修改MainActivity中的代码:

    public class MainActivity extends AppCompatActivity {
    
        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);
            Button bindServiceBtn = findViewById(R.id.button1);
            Button stopServiceBtn = findViewById(R.id.button2);
            bindServiceBtn.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    Intent intent = new Intent(MainActivity.this, MyService.class);
                    bindService(intent, connection, Context.BIND_AUTO_CREATE);
                }
            });
            stopServiceBtn.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    unbindService(connection);
                }
            });
        }
    }
    

    onServiceConnected()方法方法会在Activity与Service成功绑定的时候调用,而onServiceDisconnected()方法只有在Service的创建进程崩溃或者被杀掉的时候才会调用,这个方法不太常用。
    Activity和Service绑定:这里我们仍然构建了一个Intent对象,然后调用bindService()方法将MainActivity和MyService进行绑定。bindService()方法接收3个参数,第一个参数就是刚刚构建出的Intent对象,第二个参数是前面创建出的ServiceConnection的实例,第三个参数则是一个标志位,这里传入BIND_AUTO_CREATE表示在Activity和Service进行绑定后自动创建Service。这会使得MyService中的onCreate()方法得到执行,但 onStartCommand()方法不会执行。
    Activity和Service解绑:调用一下unbindService()方法就可以了。
    任何一个Service在整个应用程序范围内都是通用的,即MyService不仅可以和MainActivity绑定,还可以和任何一个其他的Activity进行绑定,而且在绑定完成后,它们都可以获取相同的DownloadBinder实例。

    2.2 Service的生命周期

    2.2.1.png

    一旦在项目的任何位置调用了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()方法也会执行,这两种情况都很好理解。但是需要注意,我们是完全有可能对一个Service既调用了startService()方法,又调用了bindService()方法的,根据Android系统的机制,一个Service只要被启动或者被绑定了之后,就会处于运行状态,必须要让以上两种条件同时不满足,Service才能被销毁。所以,这种情况下要同时调用stopService()和unbindService()方法,onDestroy()方法才会执行。

    2.3 使用前台Service

    前台Service概念

    从Android 8.0系统开始,只有当应用保持在前台可见状态的情况下,Service才能保证稳定运行,一旦应用进入后台之后,Service随时都有可能被系统回收。
    而如果你希望Service能够一直保持运行状态,就可以考虑使用前台Service。前台Service和普通Service最大的区别就在于,它一直会有一个正在运行的图标在系统的状态栏显示,下拉状态栏后可以看到更加详细的信息,非常类似于通知的效果。
    由于状态栏中一直有一个正在运行的图标,相当于我们的应用以另外一种形式保持在前台可见状态,所以系统不会倾向于回收前台Service。

    2.3.1.png

    定义前台Service

    PendingIntent和Intent有些类似,都可以去指明某一个“意图”,都可以用于启动活动、启动服务以及发送广播等。不同的是,Intent更加倾向于去立即执行某个动作,而PendingIntent更加倾向于在某个合适的时机去执行某个动作。所以,也可以把PendingIntent简单地理解为延迟执行的Intent。
    PendingIntent的用法同样很简单,它主要提供了几个静态方法用于获取PendingIntent的实例,可以根据需求来选择是使用getActivity()方法、getBroadcast()方法、还是getService() 方法。这几个方法所接收的参数都是相同的,第一个参数依旧是Context,不用多做解释。 第二个参数一般用不到,通常都是传入0即可。第三个参数是一个Intent对象,我们可以通过这个对象构建出PendingIntent的 “意图”。第四个参数用于确定PendingIntent的行为,FLAG_ONE_SHOT、FLAG_NO_CREATE、FLAG_CANCEL_CURRENT和FLAG_UPDATE_CURRENT这四种值可选,每种值的含义你可以查看文档,通常情况下这个参数传入0就可以了。

    public class MyService extends Service {
        @Override
        public void onCreate() {
            NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
            String channelId = "my_service";
            String channelName = "前台Service通知";
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                NotificationChannel channel = new NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_DEFAULT);
                manager.createNotificationChannel(channel);
            }
    
            Intent intent = new Intent(this, MainActivity.class);
            PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0);
            Notification notification = new NotificationCompat.Builder(this, channelId)
                    .setContentTitle("This is content title")
                    .setContentText("This is content text")
                    .setSmallIcon(R.drawable.small_icon)
                    .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.large_icon))
                    .setContentIntent(pendingIntent)
                    .build();
            startForeground(1, notification);
        }
    
        ......
    }
    

    可以看到,这里先是使用Intent表达出我们想要启动MainActivity的“意图”,然后将构建好的Intent对象传入PendingIntent的getActivity()方法里,以得到PendingIntent的实力,接着在NotificationCompat.Builder中调用setContentIntent()方法,把它作为参数传入即可。这样子点击一下该通知时,就会打开MainActivity的界面了。
    这部分代码和创建通知Notification的方法类似,只不过这次在构建Notification对象后并没有使用NotificationManager将通知显示出来,而是调用了StartForeground()方法。
    startForeground()方法接收两个参数:第一个参数是通知的id;第二个参数则是构建的Notification对象。调用startForeground()方法后就会让MyService变成一个前台Service,并在系统状态栏显示出来。

    在AndroidManifest.xml中声明权限

    另外,从Android 9.0系统开始,使用前台Service必须在AndroidManifest.xml中进行权限声明:

    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.example.myapplication" >
        <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
        ......
    </manifest>
    

    现在点击“Start Service”或者“Bind Service”按钮,MyService就会以前台Service的模式启动了,并且在系统状态栏会显示一个通知图标,下拉状态栏可以看到通知的详细内容:

    2.3.2.png

    2.3 使用IntentService

    Service处理耗时逻辑

    Service中的代码都是默认运行在主线程当中的,如果直接在Service里处理一些耗时的逻辑,就很容易出现ANR(Application Not Responding)的情况。
    所以这个时候就需要用到Android多线程编程的技术了,我们应该在每个Service的每个具体方法里开启一个子线程,然后在这里处理那些耗时的逻辑:

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

    但是,这种Service一旦启动,就会一直处于运行状态,必须调用stopService()或stopSelf()方法,或者被系统回收,Service才会停止:

    public class MyService extends Service {
        @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);
        }
    
        ......
    }
    

    定义IntentService

    总会有一些程序员忘记开启线程,或者忘记调用stopSelf()方法。为了可以简单地创建一个异步的、会自动停止的Service,Android专门提供了一个IntentService类:

    public class MyIntentService extends IntentService {
        public MyIntentService() {
            super("MyIntentService");
        }
    
        @Override
        protected void onHandleIntent(@Nullable Intent intent) {
            // 打印当前线程的id
            Log.d("MyIntentService", "Thread id is" + Thread.currentThread().getName());
        }
    
        @Override
        public void onDestroy() {
            super.onDestroy();
            Log.d("MyIntentService", "onDestroy");
        }
    }
    

    不要忘记,Service都是需要再AndroidManifest.xml里注册的。
    onHandleIntent()方法可以处理一些耗时的逻辑,而不用担心ANR的问题,因为这个方法已经是在子线程中运行的了。另外,这个Service在onHandleIntent()运行结束后会自动停止。

    相关文章

      网友评论

          本文标题:2、Service

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