实现Activity和Service通信的几种方案
2016-06-10 20:31
585 查看
Service作为Android四大组件之一,一直默默无闻的在后台保证程序能正常运作。按照Google的定义,Service能在后台执行长时间的运行操作而不使用用户界面,它与后台线程的区别是它是整个程序的一部分,比如说Activity在运行的同时需要播放一些音乐,这完全可以通过开启一个后台线程来完成;但想要在应用进入后台运行时依然能播放音乐,则必须使用Service来完成了。
用户感知Service只能通过其弹出的Toast或者在通知栏的常驻通知,如果Service想向用户发送消息,除了以上这两项,就只能通过与Activity的通信来间接完成了。因此,我将平常看到的一些通信方式记录下来,以便以后学习和使用。
这里需要注意的就是绑定Service和解绑Service的时期,一般Activity只在用户可见时有与后台服务的通信需求,绑定和解绑可以放在onStart和onStop里;若在Activity的整个生命周期都需要与后台服务有联系,则可以放在onCreate和onDestroy里。然而,开发文档不建议将绑定和解绑放在onResume和onPause里,一是这两个方法调用频繁,如果绑定和解绑需要的操作比较多,容易造成界面卡顿;二是多个Activity绑定到Service时,Activity1执行 onPause(此时解绑,如果Service没有其它Activity绑定,系统可能销毁此Service) –>(Activity2)onCreate –>onStart –> onResume(此时绑定的Service已是系统重建的Service) –>onStop(如果Activity2完全覆盖Activity1)。 见下图:
Service和Activity是在不同的应用中的,调用Service需提供完整的包名和类名。在AndroidStudio中默认启动APP是需要一个Activity,如果APP只有一个Service,可以按图示修改。
问题就在于,当前的Activity是不确定的。我不知道服务端什么时候会发送UDP包过来,这时用户进入了哪个Activity也不确定。总不可能每个Activity都绑定到后台的Service。后来在开发者头条看到一篇文章,可以在Application中监听所有Activity的生命周期回调(需要API 14以上)。这样的话,只要监听到哪个Activity调用了onResume方法,那它就是当前Activity了。
只需要在Manifest文件中为添加Android:name=”.MyApplicaiton” 就可以使用了。这里使用了一个单例的MyActivityManager保存当前的Activity。为了防止影响Activity的正常回收,MyActivityManager只持有Activity的弱引用。代码如下:
这样,Service就能轻松在当前Activity中弹Dialog,且不需要绑定特定Activity。
以上例子在AndroidStudio 2.1 + Android 4.4.2 测试通过。
参考:
https://developer.android.com/guide/components/bound-services.html(推荐阅读)
http://toutiao.io/shares/252920/url
用户感知Service只能通过其弹出的Toast或者在通知栏的常驻通知,如果Service想向用户发送消息,除了以上这两项,就只能通过与Activity的通信来间接完成了。因此,我将平常看到的一些通信方式记录下来,以便以后学习和使用。
一、Activity绑定Service并调用Service中的方法
这是最简单最常用的方法,Activity通过Intent向服务发送消息并绑定,同时通过IBinder拿到Service的引用调用其公开的方法。public class MyService extends Service { private static final String TAG = "MyService"; public List<String> data = new ArrayList<>(); public MyService() { data.add("This is some msg from " + TAG); } public class LocalBinder extends Binder { MyService getService() { return MyService.this; } List<String> getData() { return data; } } private final IBinder localBinder = new LocalBinder(); @Override public IBinder onBind(Intent intent) { String s = intent.getStringExtra("data"); Log.i(TAG,"onBind: 接收 " + s); data.add(s); return localBinder; } }
public class MainActivity extends AppCompatActivity { private static final String TAG = "MainActivity"; private MyService mService; private List<String> mData; private boolean mBound = false; private TextView textView; private ServiceConnection serviceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name,IBinder service) { Log.i(TAG,"onServiceConnected: ComponentName = " + name); mService = ((MyService.LocalBinder) service).getService(); mData = ((MyService.LocalBinder) service).getData(); mBound = true; } @Override public void onServiceDisconnected(ComponentName name) { mService = null; mBound = false; } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); textView = (TextView) findViewById(R.id.textView); } public void getData(View v) { if (mBound) { //需要注意的是:绑定过程是异步的,bindService()立即返回。因此,mService和mData还有可能为null。因此需要通过mBound标志位判断下。 String text = mService.toString() + " data : " + mData.toString(); textView.setText(text); } } @Override protected void onStart() { super.onStart(); Intent intent = new Intent(this,MyService.class); //发送信息给Service intent.putExtra("data","msg from " + TAG); bindService(intent,serviceConnection,BIND_AUTO_CREATE); } @Override protected void onStop() { super.onStop(); unbindService(serviceConnection); 4000 } }
这里需要注意的就是绑定Service和解绑Service的时期,一般Activity只在用户可见时有与后台服务的通信需求,绑定和解绑可以放在onStart和onStop里;若在Activity的整个生命周期都需要与后台服务有联系,则可以放在onCreate和onDestroy里。然而,开发文档不建议将绑定和解绑放在onResume和onPause里,一是这两个方法调用频繁,如果绑定和解绑需要的操作比较多,容易造成界面卡顿;二是多个Activity绑定到Service时,Activity1执行 onPause(此时解绑,如果Service没有其它Activity绑定,系统可能销毁此Service) –>(Activity2)onCreate –>onStart –> onResume(此时绑定的Service已是系统重建的Service) –>onStop(如果Activity2完全覆盖Activity1)。 见下图:
二、Service向Activity发送消息
Service想向Activity发送信息,因为Activity已经拿到Service的引用,而它们又是在一个进程中,所以可以使用监听器模式,当Service完成某工作通知Activity。(然而,Service若是开了工作线程,还得使用Handler与其通信或者直接使用AsyncTask)//Activity中添加 private ServiceConnection serviceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name,IBinder service) { Log.i(TAG,"onServiceConnected: ComponentName = " + name); mService = ((MyService.LocalBinder) service).getService(); mData = ((MyService.LocalBinder) service).getData(); mService.setListener(MainActivity.this); mBound = true; } @Override public void onServiceDisconnected(ComponentName name) { mService = null; mBound = false; } }; @Override public void onComplete() { Toast.makeText(this,"任务完成",Toast.LENGTH_SHORT).show(); }
//Service中添加 public interface MissionCompleteListener { void onComplete(); } private MissionCompleteListener listener; public MyService() { data.add("This is some msg from " + TAG); } public class LocalBinder extends Binder { MyService getService() { return MyService.this; } List<String> getData() { return data; } } public MissionCompleteListener getListener() { return listener; } public void setListener(MissionCompleteListener listener) { this.listener = listener; } class MyAsyncTask extends AsyncTask<Object,Object,Object>{ @Override protected Object doInBackground(Object... params) { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } return null; } @Override protected void onPostExecute(Object o) { super.onPostExecute(o); listener.onComplete(); } }
三、Activity和Service的跨进程双向通信机制
对于不同进程中的Activity和Service(我觉得同一公司的不同应用之间用的比较多),要实现IPC(跨进程通信),其实也是通过IBinder接口,其中可能涉及到AIDL编程,和操作系统提供的进程通信接口这些底层c++知识。然而,Google已经为我们封装了一套Messenger机制,其底层也是使用AIDL实现的。要使用这套机制,必须为通信双方建立各自的Messenger,然后Service的Messenger依然是通过IBinder传递到Activity,Activity也可以将自己的Messenger通过Message的replyTo属性传递到Service。Service和Activity是在不同的应用中的,调用Service需提供完整的包名和类名。在AndroidStudio中默认启动APP是需要一个Activity,如果APP只有一个Service,可以按图示修改。
public class MyService extends Service { private static final String TAG = "MyService"; public List<String> data = new ArrayList<>(); private Messenger mClient; class MyHandler extends Handler { @Override public void handleMessage(Message msg) { super.handleMessage(msg); switch (msg.what){ case 101: Message message = Message.obtain(null,111,1,1); mClient = msg.replyTo; Log.i(TAG,"handleMessage: "+ mClient.toString()); try { mClient.send(message); } catch (RemoteException e) { e.printStackTrace(); } default: } } } private Messenger messenger = new Messenger(new MyHandler()); public MyService() { data.add("This is some msg from " + TAG); } @Override public IBinder onBind(Intent intent) { // TODO: Return the communication channel to the service. String s = intent.getStringExtra("data"); Log.i(TAG,"onBind: 接收 " + s); data.add(s); return messenger.getBinder(); } }
public class MainActivity extends AppCompatActivity { private static final String TAG = "MainActivity"; private Messenger mService; class MyHandler extends Handler{ @Override public void handleMessage(Message msg) { super.handleMessage(msg); switch (msg.what){ case 111: int i = msg.arg1; if(i == 1){ Toast.makeText(MainActivity.this,"接收到Service消息",Toast.LENGTH_SHORT).show(); } default: } } } private Messenger messenger = new Messenger(new MyHandler()); private boolean mBound = false; private TextView textView; private ServiceConnection serviceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name,IBinder service) { Log.i(TAG,"onServiceConnected: ComponentName = " + name); mService = new Messenger(service); textView.setText("连接成功"); Message message = Message.obtain(null,101); message.replyTo = messenger; try { mService.send(message); } catch (RemoteException e) { e.printStackTrace(); } mBound = true; } @Override public void onServiceDisconnected(ComponentName name) { mBound = false; } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); textView = (TextView) findViewById(R.id.textView); } public void getData(View v){ if(mService != null){ Log.i(TAG,"getData: "+ mService.toString()); } } @Override protected void onStart() { super.onStart(); Intent intent = new Intent(); ComponentName name = new ComponentName("com.app.feng.activityandservicedemo2", "com.app.feng.activityandservicedemo2.MyService"); intent.setComponent(name); //发送信息 intent.putExtra("data","msg from " + TAG); bindService(intent,serviceConnection,BIND_AUTO_CREATE); } @Override protected void onStop() { super.onStop(); unbindService(serviceConnection); } }
四、Service实现让前台界面弹出Dialog的需求
说说我最近写的一个应用的需求。应用是一个摇一摇功能,后台开启了一个Service检测线性加速计的参数,达到一定加速度即发送一个UDP包给服务端,服务端拿到各个设备发来的UDP包,检测其中的摇动时间是否存在相等的,若有,则为每个检测到的设备发送一个UDP,包含与其同时摇动的所有设备名。然后用Service开一个任务线程监听服务端发的UDP包,监听到就在当前Activity界面弹出Dialog。问题就在于,当前的Activity是不确定的。我不知道服务端什么时候会发送UDP包过来,这时用户进入了哪个Activity也不确定。总不可能每个Activity都绑定到后台的Service。后来在开发者头条看到一篇文章,可以在Application中监听所有Activity的生命周期回调(需要API 14以上)。这样的话,只要监听到哪个Activity调用了onResume方法,那它就是当前Activity了。
public class MyApplication extends Application { @Override public void onCreate() { super.onCreate(); registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() { @Override public void onActivityCreated(Activity activity b001 ,Bundle savedInstanceState) { } @Override public void onActivityStarted(Activity activity) { } @Override public void onActivityResumed(Activity activity) { MyActivityManager.getInstance().setCurrentActivity(activity); } @Override public void onActivityPaused(Activity activity) { } @Override public void onActivityStopped(Activity activity) { } @Override public void onActivitySaveInstanceState(Activity activity,Bundle outState) { } @Override public void onActivityDestroyed(Activity activity) { } }); } }
只需要在Manifest文件中为添加Android:name=”.MyApplicaiton” 就可以使用了。这里使用了一个单例的MyActivityManager保存当前的Activity。为了防止影响Activity的正常回收,MyActivityManager只持有Activity的弱引用。代码如下:
public class MyActivityManager { private static MyActivityManager sInstance = new MyActivityManager(); private WeakReference<Activity> sCurrentActivityWeakRef; private MyActivityManager(){ } public static MyActivityManager getInstance(){ return sInstance; } public Activity getCurrentActivity(){ Activity a = null; if(sCurrentActivityWeakRef != null){ a = sCurrentActivityWeakRef.get(); } return a; } public void setCurrentActivity(Activity activity){ sCurrentActivityWeakRef = new WeakReference<>(activity); } }
这样,Service就能轻松在当前Activity中弹Dialog,且不需要绑定特定Activity。
以上例子在AndroidStudio 2.1 + Android 4.4.2 测试通过。
参考:
https://developer.android.com/guide/components/bound-services.html(推荐阅读)
http://toutiao.io/shares/252920/url
相关文章推荐
- 使用C++实现JNI接口需要注意的事项
- Android IPC进程间通讯机制
- Android Manifest 用法
- [转载]Activity中ConfigChanges属性的用法
- Android之获取手机上的图片和视频缩略图thumbnails
- Android之使用Http协议实现文件上传功能
- Android学习笔记(二九):嵌入浏览器
- android string.xml文件中的整型和string型代替
- i-jetty环境搭配与编译
- android之定时器AlarmManager
- android wifi 无线调试
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- android 代码实现控件之间的间距
- android FragmentPagerAdapter的“标准”配置
- Android"解决"onTouch和onClick的冲突问题
- android:installLocation简析
- android searchView的关闭事件
- SourceProvider.getJniDirectories