Android总结篇系列:Android Service 一时失言乱红尘 2021-06-26 16:06 315阅读 0赞 `Service`通常总是称之为“后台服务”,其中“后台”一词是相对于前台而言的,具体是指其本身的运行并不依赖于用户可视的UI界面,因此,从实际业务需求上来理解,Service的适用场景应该具备以下条件: 1.并不依赖于用户可视的UI界面(当然,这一条其实也不是绝对的,如前台Service就是与Notification界面结合使用的); 2.具有较长时间的运行特性。 ## 1.Service AndroidManifest.xml 声明 ## 一般而言,从`Service`的启动方式上,可以将`Service`分为`Started Service`和`Bound Service`。无论哪种具体的`Service`启动类型,都是通过继承Service基类自定义而来。在使用`Service`时,要想系统能够找到此自定义`Service`,无论哪种类型,都需要在`AndroidManifest.xml`中声明,语法格式如下: <service android:enabled=["true" | "false"] android:exported=["true" | "false"] android:icon="drawable resource" android:isolatedProcess=["true" | "false"] android:label="string resource" android:name="string" android:permission="string" android:process="string" > . . . </service> 其中,`android:exported`属性上一篇博文中对此已进行详尽描述,`android:name`对应`Service`类名,`android:permission`是权限声明,`android:process`设置具体的进程名称。需要注意的是`Service`能否单独使用一个进程与其启动方式有关,本后下面会给出具体说明。其他的属性此处与其他组件基本相同,不再过多描述。 注:如果自定义`Service`没有在`AndroidManifest.xml`中声明,当具体使用时,不会像`Activity`那样直接崩溃报错,对于显式`Intent`启动的`Service`,此时也会给出`waring`信息“`IllegalArgumentException: Service not registered`”,有时候不容易发现忘了声明而一时定位不到问题。 ## 2.Started Service ## `Started Service`相对比较简单,通过`context.startService(Intent serviceIntent)`启动`Service`,`context.stopService(Intent serviceIntent)`停止此`Service`。当然,在`Service`内部,也可以通过`stopSelf(...)`方式停止其本身。 ### 1)Started Service自定义 ### 下面代码片段显示的是一个最基本的`Started Service`的自定义方式: public class MyService extends Service { public static final String TAG = "MyService"; @Override public IBinder onBind(Intent intent) { return null; } @Override public void onCreate() { super.onCreate(); Log.w(TAG, "in onCreate"); } @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.w(TAG, "in onStartCommand"); Log.w(TAG, "MyService:" + this); String name = intent.getStringExtra("name"); Log.w(TAG, "name:" + name); return START_STICKY; } @Override public void onDestroy() { super.onDestroy(); Log.w(TAG, "in onDestroy"); } } 其中,`onBind(...)`函数是`Service`基类中的唯一抽象方法,子类都必须重写实现,此函数的返回值是针对`Bound Service`类型的`Service`才有用的,在`Started Service`类型中,此函数直接返回 `null` 即可。`onCreate(...)、onStartCommand(...)`和`onDestroy()`都是`Started Service`相应生命周期阶段的回调函数。 ### 2) Started Service使用 ### public class MainActivity extends Activity { public static final String TAG = "MainActivity"; private Button startServiceBtn; private Button stopServideBtn; private Button goBtn; private Intent serviceIntent; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); startServiceBtn = (Button) findViewById(R.id.start_service); stopServideBtn = (Button) findViewById(R.id.stop_service); goBtn = (Button) findViewById(R.id.go); startServiceBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { serviceIntent = new Intent(MainActivity.this, MyService.class); startService(serviceIntent); } }); stopServideBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { stopService(serviceIntent); } }); goBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(MainActivity.this, BActivity.class); startActivity(intent); } }); } @Override protected void onDestroy() { super.onDestroy(); Log.w(TAG, "in onDestroy"); } } 如上代码片段, 当`Client`调用`startService(Intent serviceIntent)`后,如果`MyService`是第一次启动,首先会执行 `onCreate()`回调,然后再执行`onStartCommand(Intent intent, int flags, int startId)`,当Client再次调用`startService(Intent serviceIntent)`,将只执行`onStartCommand(Intent intent, int flags, int startId)`,因为此时Service已经创建了,无需执行`onCreate()`回调。无论多少次的`startService`,只需要一次`stopService()`即可将此Service终止,执行`onDestroy()`函数(其实很好理解,因为`onDestroy()`与`onCreate()`回调是相对的)。 下面重点关注下`onStartCommand(Intent intent, int flags, int startId)`方法。 其中参数flags默认情况下是0,对应的常量名为`START_STICKY_COMPATIBILITY`。startId是一个唯一的整型,用于表示此次Client执行startService(…)的请求请求标识,在多次startService(…)的情况下,呈现0,1,2…递增。另外,此函数具有一个int型的返回值,具体的可选值及含义如下: * START\_NOT\_STICKY:当Service因为内存不足而被系统kill后,接下来未来的某个时间内,即使系统内存足够可用,系统也不会尝试重新创建此Service。除非程序中Client明确再次调用startService(…)启动此Service。 * START\_STICKY:当Service因为内存不足而被系统kill后,接下来未来的某个时间内,当系统内存足够可用的情况下,系统将会尝试重新创建此Service,一旦创建成功后将回调onStartCommand(…)方法,但其中的Intent将是null,pendingintent除外。 * START\_REDELIVER\_INTENT:与START\_STICKY唯一不同的是,回调onStartCommand(…)方法时,其中的Intent将是非空,将是最后一次调用startService(…)中的intent。 * START\_STICKY\_COMPATIBILITY:compatibility version of \{@link \#START\_STICKY\} that does not guarantee that \{@link \#onStartCommand\} will be called again after being killed。此值一般不会使用,所以注意前面三种情形就好。 以上的描述中,”当Service因为内存不足而被系统kill后“一定要非常注意,因为此函数的返回值设定只是针对此种情况才有意义的,换言之,当认为的kill掉Service进程,此函数返回值无论怎么设定,接下来未来的某个时间内,即使系统内存足够可用,Service也不会重启。 小米手机针对此处做了变更: 另外,需要注意的是,小米手机针对此处做了一定的修改。在“自启动管理”中有一个自启动应用列表,默认情况下,只有少应用(如微信、QQ、YY、360等)默认是可以自启动的,其他应用默认都是禁止的。用户可以手动添加自启动应用,添加后的应用中如果Started Service onStartCommand(…)回调返回值是START\_STICKY或START\_REDELIVER\_INTENT,当用户在小米手机上长按Home键结束App后,接下来未来的某个时间内,当系统内存足够可用时,Service依然可以按照上述规定重启。当然,如果用户在 设置 >> 应用 >> 强制kill掉App进程,此时Service是不会重启的。 注:以上实验结论基于小米2S亲测。 ### 3) Started Service生命周期及进程相关 ### 1.onCreate(Client首次startService(…)) >> onStartCommand >> onStartCommand - optional … >> onDestroy(Client调用stopService(…)) 注:onStartCommand(…)可以多次被调用,`onDestroy()`与`onCreate()`想匹配,当用户强制kill掉进程时,`onDestroy()`是不会执行的。 2.对于同一类型的Service,Service实例一次永远只存在一个,而不管Client是否是相同的组件,也不管Client是否处于相同的进程中。 3.Service通过startService(…)启动Service后,此时Service的生命周期与Client本身的什么周期是没有任何关系的,只有Client调用stopService(…)或Service本身调用stopSelf(…)才能停止此Service。当然,当用户强制kill掉Service进程或系统因内存不足也可能kill掉此Service。 4.Client A 通过startService(…)启动Service后,可以在其他Client(如Client B、Client C)通过调用stopService(…)结束此Service。 5.Client调用stopService(…)时,如果当前Service没有启动,也不会出现任何报错或问题,也就是说,stopService(…)无需做当前Service是否有效的判断。 6.startService(Intent serviceIntent),其中的intent既可以是显式Intent,也可以是隐式Intent,当Client与Service同处于一个App时,一般推荐使用显示Intent。当处于不同App时,只能使用隐式Intent。 当Service需要运行在单独的进程中,AndroidManifest.xml声明时需要通过android:process指明此进程名称,当此Service需要对其他App开放时,android:exported属性值需要设置为true(当然,在有intent-filter时默认值就是true)。 <service android:name=".MyService" android:exported="true" android:process=":MyCorn" > <intent-filter> <action android:name="com.example.androidtest.myservice" /> </intent-filter> </service> ### 4)`Started Service Client`与`Service`通信相关 ### 当`Client`调用`startService(Intent serviceIntent)`启动`Service`时,`Client`可以将参数通过`Intent`直接传递给`Service`。`Service`执行过程中,如果需要将参数传递给`Client`,一般可以通过借助于发送广播的方式(此时,`Client`需要注册此广播)。 ## 3.Bound Service ## 相对于`Started Service`,`Bound Service`具有更多的知识点。`Bound Service`的主要特性在于`Service`的生命周期是依附于`Client`的生命周期的,当Client不存在时,`Bound Service`将执行`onDestroy`,同时通过`Service`中的`Binder`对象可以较为方便进行Client-Service通信。Bound Service一般使用过程如下: 1.自定义Service继承基类Service,并重写onBind(Intent intent)方法,此方法中需要返回具体的Binder对象; 2.Client通过实现ServiceConnection接口来自定义ServiceConnection,并通过bindService (Intent service, ServiceConnection sc, int flags)方法将Service绑定到此Client上; 3.自定义的ServiceConnection中实现onServiceConnected(ComponentName name, IBinder binder)方法,获取Service端Binder实例; 4.通过获取的Binder实例进行Service端其他公共方法的调用,以完成Client-Service通信; 5.当Client在恰当的生命周期(如onDestroy等)时,此时需要解绑之前已经绑定的Service,通过调用函数unbindService(ServiceConnection sc)。 在Bound Service具体使用过程中,根据onBind(Intent intent)方法放回的Binder对象的定义方式不同,又可以将其分为以下三种方式,且每种方式具有不同的特点和适用场景: ### 1).Extending the Binder class ### 这是Bound Service中最常见的一种使用方式,也是Bound Service中最简单的一种。 **局限:Clinet与Service必须同属于同一个进程,不能实现进程间通信(IPC)。否则则会出现类似于“android.os.BinderProxy cannot be cast to xxx”错误。** 下面通过代码片段看下具体的使用: public class MyBindService extends Service { public static final String TAG = "MyBindService"; private MyBinder mBinder = new MyBinder(); public class MyBinder extends Binder { MyBindService getService() { return MyBindService.this; } } @Override public void onCreate() { super.onCreate(); Log.w(TAG, "in onCreate"); } @Override public IBinder onBind(Intent intent) { Log.w(TAG, "in onBind"); return mBinder; } @Override public boolean onUnbind(Intent intent) { Log.w(TAG, "in onUnbind"); return super.onUnbind(intent); } @Override public void onDestroy() { super.onDestroy(); Log.w(TAG, "in onDestroy"); } } public class BActivity extends Activity { public static final String TAG = "BActivity"; private Button bindServiceBtn; private Button unbindServiceBtn; private Button startIntentService; private Intent serviceIntent; private ServiceConnection sc = new MyServiceConnection(); private MyBinder mBinder; private MyBindService mBindService; private boolean mBound; private class MyServiceConnection implements ServiceConnection { @Override public void onServiceConnected(ComponentName name, IBinder binder) { Log.w(TAG, "in MyServiceConnection onServiceConnected"); mBinder = (MyBinder) binder; mBindService = mBinder.getService(); mBound = true; } @Override public void onServiceDisconnected(ComponentName name) { // This is called when the connection with the service has been // unexpectedly disconnected -- that is, its process crashed. Log.w(TAG, "in MyServiceConnection onServiceDisconnected"); mBound = false; } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.b); bindServiceBtn = (Button) findViewById(R.id.bind_service); unbindServiceBtn = (Button) findViewById(R.id.unbind_service); startIntentService = (Button) findViewById(R.id.start_intentservice); bindServiceBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(BActivity.this, MyBindService.class); bindService(intent, sc, Context.BIND_AUTO_CREATE); } }); unbindServiceBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { excuteUnbindService(); } }); startIntentService.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(BActivity.this, MyIntentService.class); startService(intent); } }); } private void excuteUnbindService() { if (mBound) { unbindService(sc); mBound = false; } } @Override protected void onDestroy() { super.onDestroy(); Log.w(TAG, "in onDestroy"); excuteUnbindService(); } } 首次点击`bindServiceBtn`进行`bindService(..)`时,依次回调顺序如下: MyBindService(13457): in onCreate MyBindService(13457): in onBind BActivity(13457): in MyServiceConnection onServiceConnected 再次点击`bindServiceBtn`按钮时,发现没有任何输出,说明`MyBindService`没有进行任何回调。 点击`unbindServiceBtn`进行`unbindService(..)`时,回调顺序为: MyBindService(13457): in onUnbind MyBindService(13457): in onDestroy 注:在四大基本组件中,需要注意的的是`BroadcastReceiver`不能作为B`ound Service`的`Client`,因为`BroadcastReceiver`的生命周期很短,当执行完`onReceive(..)`回调时,`BroadcastReceiver`生命周期完结。而`Bound Service`又与`Client`本身的生命周期相关,因此,Android中不允许`BroadcastReceiver`去`bindService(..)`,当有此类需求时,可以考虑通过`startService(..)`替代。 ### 2)Using a Messenger ### `Messenger`,在此可以理解成”信使“,通过`Messenger`方式返回Binder对象可以不用考虑`Clinet - Service`是否属于同一个进程的问题,并且,可以实现`Client - Service`之间的双向通信。极大方便了此类业务需求的实现。 **局限:不支持严格意义上的多线程并发处理,实际上是以队列去处理** 下面直接看下具体的使用: public class MyMessengerService extends Service { public static final String TAG = "MyMessengerService"; public static final int MSG_FROM_CLIENT_TO_SERVER = 1; public static final int MSG_FROM_SERVER_TO_CLIENT = 2; private Messenger mClientMessenger; private Messenger mServerMessenger = new Messenger(new ServerHandler()); @Override public IBinder onBind(Intent intent) { Log.w(TAG, "in onBind"); return mServerMessenger.getBinder(); } class ServerHandler extends Handler { @Override public void handleMessage(Message msg) { Log.w(TAG, "thread name:" + Thread.currentThread().getName()); switch (msg.what) { case MSG_FROM_CLIENT_TO_SERVER: Log.w(TAG, "receive msg from client"); mClientMessenger = msg.replyTo; // service发送消息给client Message toClientMsg = Message.obtain(null, MSG_FROM_SERVER_TO_CLIENT); try { Log.w(TAG, "server begin send msg to client"); mClientMessenger.send(toClientMsg); } catch (RemoteException e) { e.printStackTrace(); } break; default: super.handleMessage(msg); } } } @Override public boolean onUnbind(Intent intent) { Log.w(TAG, "in onUnbind"); return super.onUnbind(intent); } @Override public void onDestroy() { Log.w(TAG, "in onDestroy"); super.onDestroy(); } } public class CActivity extends Activity { public static final String TAG = "CActivity"; private Button bindServiceBtn; private Button unbindServiceBtn; private Button sendMsgToServerBtn; private ServiceConnection sc = new MyServiceConnection(); private boolean mBound; private Messenger mServerMessenger; private Handler mClientHandler = new MyClientHandler(); private Messenger mClientMessenger = new Messenger(mClientHandler); private class MyClientHandler extends Handler { @Override public void handleMessage(Message msg) { if (msg.what == MyMessengerService.MSG_FROM_SERVER_TO_CLIENT) { Log.w(TAG, "reveive msg from server"); } } } private class MyServiceConnection implements ServiceConnection { @Override public void onServiceConnected(ComponentName name, IBinder binder) { Log.w(TAG, "in MyServiceConnection onServiceConnected"); mServerMessenger = new Messenger(binder); mBound = true; } @Override public void onServiceDisconnected(ComponentName name) { // This is called when the connection with the service has been // unexpectedly disconnected -- that is, its process crashed. Log.w(TAG, "in MyServiceConnection onServiceDisconnected"); mBound = false; } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.c); bindServiceBtn = (Button) findViewById(R.id.bind_service); unbindServiceBtn = (Button) findViewById(R.id.unbind_service); sendMsgToServerBtn = (Button) findViewById(R.id.send_msg_to_server); bindServiceBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(CActivity.this, MyMessengerService.class); bindService(intent, sc, Context.BIND_AUTO_CREATE); } }); unbindServiceBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { excuteUnbindService(); } }); sendMsgToServerBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { sayHello(); } }); new Handler().postDelayed(new Runnable() { @Override public void run() { Intent intent = new Intent(CActivity.this, MyAlarmBroadcastReceiver.class); sendBroadcast(intent); } }, 3 * 1000); } public void sayHello() { if (!mBound) return; // Create and send a message to the service, using a supported 'what' value Message msg = Message.obtain(null, MyMessengerService.MSG_FROM_CLIENT_TO_SERVER, 0, 0); // 通过replyTo把client端的Messenger(信使)传递给service msg.replyTo = mClientMessenger; try { mServerMessenger.send(msg); } catch (RemoteException e) { e.printStackTrace(); } } private void excuteUnbindService() { if (mBound) { unbindService(sc); mBound = false; } } @Override protected void onDestroy() { super.onDestroy(); Log.w(TAG, "in onDestroy"); excuteUnbindService(); } } 其中,需要注意的几点是: 1.`MyMessengerService`自定中,通过`new Messenger(new ServerHandler())`创建`Messenger`对象,在`onBind(..)`回调中,通过调用`Messenger`对象的`getBinder()`方法,将`Binder`返回; 2.Client在`ServiceConnection`的`onServiceConnected(..)`的回调中,通过`new Messenger(binder)`获取到`Service`传递过来的`mServerMessenger`; 3.接下来,就可以通过`mServerMessenger.send(msg)`方法向`Service`发送`message`,`Service`中的`Messenger`构造器中的`Handler`即可接收到此信息,在`handleMessage(..)`回调中处理; 4.至此只是完成了从Client发送消息到Service,同样的道理,想实现Service发送消息到Client,可以在客户端定义一个Handler,并得到相应的Messenger,在Clinet发送消息给Service时,通过msg.replyTo = mClientMessenger方式将Client信使传递给Service; 5.Service接收到Client信使后,获取此信使,并通过mClientMessenger.send(toClientMsg)方式将Service消息发送给Client。 至此,完成了Client - Service之间的双向通信流程。 ### 3).AIDL(Android Interface Definition Language) ### 一般情况下,Messenger这种方式都是可以满足需求的,当然,通过自定义AIDL方式相对更加灵活。 这种方式需要自己在项目中自定义xxx.aidl文件,然后系统会自动在gen目录下生成相应的接口类文件,接下来整个的流程与`Messenger`方式差别不大,网上也有不少实例,在此不再具体给出。 **注:无论哪种方式的Bound Service,在进行unbind(…)操作时,都需要注意当前Service是否处于已经绑定状态,否则可能会因为当前Service已经解绑后继续执行unbind(…)会导致崩溃。这点与Started Service区别很大(如前文所述:stopService(…)无需做当前Service是否有效的判断)。** ## 4.Local Service VS Remote Service ## Local Service:不少人又称之为”本地服务“,是指Client - Service同处于一个进程; Remote Service:又称之为”远程服务“,一般是指Service处于单独的一个进程中。 其他使用上上文中基本上都有所述。 ## 5.Service特性 ## 1.Service本身都是运行在其所在进程的主线程(如果Service与Clinet同属于一个进程,则是运行于UI线程),但Service一般都是需要进行”长期“操作,所以经常写法是在自定义Service中处理”长期“操作时需要新建线程,以免阻塞UI线程或导致ANR; 2.Service一旦创建,需要停止时都需要显示调用相应的方法(Started Service需要调用stopService(…)或Service本身调用stopSelf(…), Bound Service需要调用unbindService(…)),否则对于Started Service将处于一直运行状态,对于Bound Service,当Client生命周期结束时也将因此问题。也就是说,Service执行完毕后,必须人为的去停止它。 ## 6.`IntentService` ## IntentService是系统提供给我们的一个已经继承自Service类的特殊类,IntentService特殊性是相对于Service本身的特性而言的: 1.默认直接实现了`onBind(..)`方法,直接返回`null`,并定义了抽象方法`onHandlerIntent(..)`,用户自定义子类时,需要实现此方法; 2.`onHandlerIntent(..)`主要就是用来处于相应的”长期“任务的,并且已经自动在新的线程中,用户无语自定义新线程; 3.当”长期“任务执行完毕后(也就是`onHandlerIntent(..)`执行完毕后),此`IntentService`将自动结束,无需人为调用方法使其结束; 4.`IntentService`处于任务时,也是按照队列的方式一个个去处理,而非真正意义上的多线程并发方式。 下面是一个基本的继承自`IntentService`的自定义`Service`: public class MyIntentService extends IntentService { public static final String TAG = "MyIntentService"; public MyIntentService() { super(TAG); } public MyIntentService(String name) { super(name); } @Override protected void onHandleIntent(Intent intent) { Log.w(TAG, "in onHandleIntent"); Log.w(TAG, "thread name:" + Thread.currentThread().getName()); } } ## 7.前台Service ## Android中Service接口中还提供了一个称之为”前台Service“的概念。通过`Service.startForeground (int id, Notification notification)`方法可以将此Service设置为前台Service。在UI显示上,`notification`将是一个处于`onGoing`状态的通知,使得前台`Service`拥有更高的进程优先级,并且`Service`可以直接`notification`通信。 下面是一个简单的前台Service使用实例: public class MyService extends Service { public static final String TAG = "MyService"; @Override public IBinder onBind(Intent intent) { return null; } @Override public void onCreate() { super.onCreate(); Log.w(TAG, "in onCreate"); } @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.w(TAG, "in onStartCommand"); Log.w(TAG, "MyService:" + this); String name = intent.getStringExtra("name"); Log.w(TAG, "name:" + name); Notification notification = new Notification(R.drawable.ic_launcher, "test", System.currentTimeMillis()); Intent notificationIntent = new Intent(this, DActivity.class); PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntesnt, 0); notification.setLatestEventInfo(this, "title", "content", pendingIntent); startForeground(1, notification); return START_REDELIVER_INTENT; } @Override public void onDestroy() { super.onDestroy(); Log.w(TAG, "in onDestroy"); } }
还没有评论,来说两句吧...