Activity和Service交互方式探讨

目录

需求定义

方式一:广播(Broadcast)

方式二:bindService-接口回调

方式三:startService和bindService混合双打

方式四:单例传值

方式五:配合系统级别服务

其它方式


需求定义

假设一个设备具备装备了湿度传感器,可通过监测真实环境的湿度值来工作(如市面上的除湿设备),用户要求:除湿工作全自动化,无需人工干预,除非断电关机,否则永远在后台运行;有一个用户界面,打开可以查看当前环境的湿度值以及设备的工作状态(是否在进行除湿工作)。针对这些需求,得到写代码的思路:

  1. 无需人工干预全自动化永久运行 --> 定义service(前台服务)获得湿度传感器的值,当湿度值达到某个阈值,开启除湿功能,否则关闭除湿功能
  2. 用户界面呈现设备状态 --> 定义Activity,通过和service交互得到service的数据

这样就引出了文章的主题:Activity和Service交互的方式。

注:这个需求比较简单,实现方式也非常的多,可能有人会说Service可以拿到湿度传感器的值,Activity一样可以拿到啊,还交互个啥?确实是这样的,但是这里是为了探讨Activity和Service的交互行为,所以需要假定Activity的数据只能通过Service获取到


方式一:广播(Broadcast)

public class HumidityService extends Service {
    private static final String BROADCAST_KEY = "humidity_broadcast";
    private SensorManager sm;
    private Sensor mHumiditySensor;

    /**
     * 省略注册为前台服务的相关代码流程....
     */

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // 省略...
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onCreate() {
        super.onCreate();
        sm = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
        mHumiditySensor = sm.getDefaultSensor(Sensor.TYPE_RELATIVE_HUMIDITY);
        sm.registerListener(humidityEventListener, mHumiditySensor, SensorManager.SENSOR_DELAY_NORMAL);
    }

    // 监听湿度传感器,获取湿度值用广播发送出去
    SensorEventListener humidityEventListener = new SensorEventListener() {
        @Override
        public void onSensorChanged(SensorEvent sensorEvent) {
            float[] values = sensorEvent.values;
            sendBroadcast(values[0]);
        }

        @Override
        public void onAccuracyChanged(Sensor sensor, int i) {

        }
    };

    // 发送广播
    private void sendBroadcast(float data) {
        Intent intent = new Intent(BROADCAST_KEY);
        intent.putExtra("data", data);
        sendBroadcast(intent);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        sm.unregisterListener(humidityEventListener);
    }
}
public class HumidityActivity extends Activity {
    private static final String BROADCAST_KEY = "humidity_broadcast";
    private IntentFilter mFilter;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 省略相关代码...
        mFilter = new IntentFilter();
        mFilter.addAction(BROADCAST_KEY);
    }

    // 接收Service发过来的湿度值,界面显示等代码省略...
    BroadcastReceiver mReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            Log.d("test", "data: " + intent.getFloatExtra("data", 0));
        }
    };

    @Override
    protected void onResume() {
        super.onResume();
        registerReceiver(mReceiver, mFilter);
    }

    @Override
    protected void onPause() {
        super.onPause();
        unregisterReceiver(mReceiver);
    }
}

这个方法肯定是可以实现功能的,在界面显示的时候动态注册广播,接收service发送的数据,并呈现给用户。但是有一个问题,sensor的数据change是毫秒级的,也就是说一秒钟之内会发送多次广播,并且Broadcast的传输是通过binder间通信,本质上是用来方便跨进程通信的,这么频繁的发送广播必然是更耗性能的,不太友好,所以对于大量数据传输频繁的场景,我都不建议采用广播(不管是进程内通信还是跨进程通信)。尤其是对于那些对实时性要求高的数据,广播就显得有点不靠谱,因为发送广播的行为是提交到系统的,Android系统本身有很多系统级广播要发送,在framework层的实现是一个队列,将所有广播按照优先级等条件排排站,一个个发送的,所以,广播从发出去到接收到的时间是不固定的,快慢取决于系统当前的负载。


方式二:bindService-接口回调

定义一个回调接口

package com.xxx...

public interface SensorCallback {
    void callback(float data);
}
​
public class HumidityService extends Service {
    private static final String BROADCAST_KEY = "humidity_broadcast";
    private SensorManager sm;
    private Sensor mHumiditySensor;
    private SensorCallback mSensorCallback;

    public class MyBinder extends Binder {
        public HeaterService getService() {
            return HeaterService.this;
        }
    }
    private MyBinder binder = new MyBinder();

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.d(TAG, "onBind");
        return binder;
    }
    // 设置回调接口
    public void setCallback(SensorCallback callback) {
        this.mSensorCallback= callback;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // 省略...
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onCreate() {
        super.onCreate();
        sm = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
        mHumiditySensor = sm.getDefaultSensor(Sensor.TYPE_RELATIVE_HUMIDITY);
        sm.registerListener(humidityEventListener, mHumiditySensor, SensorManager.SENSOR_DELAY_NORMAL);
    }

    // 监听湿度传感器,获取湿度值调用回调接口
    SensorEventListener humidityEventListener = new SensorEventListener() {
        @Override
        public void onSensorChanged(SensorEvent sensorEvent) {
            float[] values = sensorEvent.values;
            if (mSensorCallback!= null) {
                mSensorCallback.callback(values[0]);
            }
        }

        @Override
        public void onAccuracyChanged(Sensor sensor, int i) {

        }
    };

    @Override
    public void onDestroy() {
        super.onDestroy();
        sm.unregisterListener(humidityEventListener);
    }
}

​
public class HumidityActivity extends Activity implements SensorCallback {
    private HumidityService mService;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 省略部分代码...
        bind();
    }

    // 通过bind方式可以得到Service的实例,这个实例的public方法都可以调用,当然包含设置回调函数的方法
    ServiceConnection conn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            HumidityService.MyBinder myBinder = (HumidityService.MyBinder) iBinder;
            mService = myBinder.getService();
            mService.setCallback(HumidityActivity.this);
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {
        }
    };

    // 回调湿度传感器数据
    @Override
    public void callback(float data) {
        Log.d(TAG, "callback data: " + data);
        // 省略显示UI代码...
    }

    private void bind() {
        Intent intent = new Intent(HumidityActivity.this, HumidityService.class);
        bindService(intent, conn, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbindService(conn);
    }

}

从代码流程可以看到,当打开了用户界面,会bindSerivce,并获取Service实例,设置回调函数获得传感器数据。但是通过bindService接口启动的服务有一个特点:Service的生命周期和UI界面会绑定在一起,也就是当用户退出界面,UI Destory后服务也会Destory。这样设备就停止工作了。前面的项目需求里要求设备24小时不停的工作的,这显然是不符合要求的。


方式三:startService和bindService混合双打

bindService和startService的显著区别:bindService启动的服务生命周期和UI绑定,同命运共存亡;startService启动的服务不会和其它东西绑定,如果声明为前台服务,除非在特殊情况下(用户主动杀掉进程或者系统内存不足查杀)会结束掉生命周期,否则会一直在后台运行

public class BootCompleteReceiver extends BroadcastReceiver {
    private static final String BOOT_COMPLETED = "android.intent.action.BOOT_COMPLETED";

    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if (TextUtils.isEmpty(action)) return;

        if (action.equals(BOOT_COMPLETED)) {
            Intent i = new Intent(context, HumidityService.class);
            context.startService(i);
        }
    }
}

其它代码和前面的流程大致相同,如何声明前台服务等也不再细说。先通过startService启动服务,使后台服务持续运行;当用户打开界面的时候通过bindService绑定服务,设置回调函数获得传感器数据更新UI,当用户退出界面则Unbind,但是后台服务不会消亡,还是在后台持续运行。通过startService和bindService的混合使用方式,可以改变Service的生命周期,得到预期的结果。


方式四:单例传值

public class DataTransManager {
    private static volatile DataTransManager mInstance = null;
    private SensorCallback callback;

    private DataTransManager() {
        // nothing...
    }

    public static DataTransManager getInstance() {
        if (mInstance == null) {
            synchronized (DataTransManager.class) {
                if (mInstance == null) {
                    mInstance = new DataTransManager();
                }
            }
        }
        return mInstance;
    }

    public SensorCallback getCallback() {
        return callback;
    }

    public void setCallback(SensorCallback callback) {
        this.callback = callback;
    }

    public void dataTrans(float data) {
        if (callback != null) {
            callback.callback(data);
        }
    }
}

上面定义了一个单例类,SensorCallback 回调接口还是前面定义的那个。

​
public class HumidityActivity extends Activity implements SensorCallback {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 省略部分代码...
    }

    // 回调湿度传感器数据
    @Override
    public void callback(float data) {
        Log.d(TAG, "callback data: " + data);
        // 省略显示UI代码...
    }

    @Override
    protected void onResume() {
        super.onResume();
        // 通过单例设置回调接口
        if (DataTransManager.getInstance().getCallback() == null) {
            DataTransManager.getInstance().setCallback(this);
        }
    }

    @Override
    protected void onPause() {
        super.onPause();
        // 反注册回调接口
        DataTransManager.getInstance().setCallback(null);
    }

}

​
​
​
public class HumidityService extends Service {
    private SensorManager sm;
    private Sensor mHumiditySensor;

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // 省略...
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onCreate() {
        super.onCreate();
        sm = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
        mHumiditySensor = sm.getDefaultSensor(Sensor.TYPE_RELATIVE_HUMIDITY);
        sm.registerListener(humidityEventListener, mHumiditySensor, SensorManager.SENSOR_DELAY_NORMAL);
    }

    // 监听湿度传感器,获取湿度值通过单例的方式调用回调接口
    SensorEventListener humidityEventListener = new SensorEventListener() {
        @Override
        public void onSensorChanged(SensorEvent sensorEvent) {
            float[] values = sensorEvent.values;
            DataTransManager.getInstance().dataTrans(values[0]);
        }

        @Override
        public void onAccuracyChanged(Sensor sensor, int i) {

        }
    };

    @Override
    public void onDestroy() {
        super.onDestroy();
        sm.unregisterListener(humidityEventListener);
    }
}

​

​

通过单例传值的方式可以很方便的实现预定义的功能,后台服务一直在运行监听环境湿度,并传递到单例中,如果有监听者,则把数据发送给它。当用户打开了前台界面,立即将自己作为监听者传递给单例,当用户离开前台界面,则解除监听者。


方式五:配合系统级别服务

这种方式只适合有源代码的方案公司。我们可以添加一个系统级服务,在后台监听环境湿度,如何添加系统服务参考我之前的文章:Android原生系统开发如何优雅的提供系统级的API供第三方程序调用?,或者 AIDL在android系统中的作用

值得一提的是,使用这种方式开发的服务会变的及其稳定,因其实集成在framework中的,所以生命周期是和Android系统共存亡的,用户无法终止其运行,除非断电关机,否则会一直运行。并且可以很方便的交互,只要定义好对应的xxxManager类,设置回调接口或者存取值都是非常的便捷的。当然也是有缺点的,用户界面是一个独立的apk,它显示的数据全部来自于framework层的系统级服务,这样一来本来一个进程能完成的事情现在变成了两个进程。不过好在这种设计方式耦合性不大,因为这套服务一旦设计完成,就和系统其它服务一样,例如WifiManager等,都是对外接口,任何上层应用进程都可以调用,不存在和谁耦合的问题。

因这个方案涉及到源代码定制开发,具体代码不详细赘述,有兴趣的参考上面两篇文章链接。


其它方式

就这个简单的功能来说,可实现的方式还有很多,比如共享文件、Handler+Message等方式,对于我们定义的这个需求来说,这些方式都不太适合,或多或少都有各自的缺陷,这里也不在展开。个人觉得最强大的实现方式是方式五,当然开发条件也最严苛,代码量最大。最方便的是方式四单例传值。

本文旨在大家一起探讨,如果有更好的性能更佳的实现方式,欢迎留言评论。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值