继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

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

星箜下
关注TA
已关注
手记 7
粉丝 8
获赞 739

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

这里需要注意的就是绑定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,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文件中为<application>添加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 测试通过。

打开App,阅读手记
14人推荐
发表评论
随时随地看视频慕课网APP