您的位置:首页 > 移动开发 > Android开发

实现Activity和Service通信的几种方案

2016-06-10 20:31 585 查看
Service作为Android四大组件之一,一直默默无闻的在后台保证程序能正常运作。按照Google的定义,Service能在后台执行长时间的运行操作而不使用用户界面,它与后台线程的区别是它是整个程序的一部分,比如说Activity在运行的同时需要播放一些音乐,这完全可以通过开启一个后台线程来完成;但想要在应用进入后台运行时依然能播放音乐,则必须使用Service来完成了。

用户感知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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  android