如何优雅实现多类型View绑定传感器监听消除重复模板代码?
问题描述
SensorManager负责监听GPS、温度、亮度等多种传感器,对应Sensor类实现如下:
class Sensor{ public Sensor getInstance(){ } public register(int id,Listener listener){ } public unRegister(Listener listener){ } public interface Listener{ void onDataChange(int id, int value); } }
需求是用不同控件展示不同传感器的数据,最初的实现方式是为每类继承自不同View子类的控件单独编写抽象类,在控件onAttachToWindow时注册传感器监听、onDetachFromWindow时注销监听,收到数据后回调给控件的值更新方法,示例代码如下:
abstract class View1 extends TextView{ abstract int getSensorId(); abstract void onValueChange(int value); public void onAttachToWindow(){ Sensor.getInstance.register(getSensorId(),listener); } public void onDetachFromWindow() { Sensor.getInstance.unRegister(listener); } Listener listener = new Listener(){ void onDataChange(int id, int value){ onValueChange(value); } } } abstract class View2 extends Button{ abstract int getSensorId(); abstract void onValueChange(int value); public void onAttachToWindow(){ Sensor.getInstance.register(getSensorId(),listener); } public void onDetachFromWindow() { Sensor.getInstance.unRegister(listener); } Listener listener = new Listener(){ void onDataChange(int id, int value){ onValueChange(value); } } } abstract class View3 extends ImageView{ abstract int getSensorId(); abstract void onValueChange(int value); public void onAttachToWindow(){ Sensor.getInstance.register(getSensorId(),listener); } public void onDetachFromWindow() { Sensor.getInstance.unRegister(listener); } Listener listener = new Listener(){ void onDataChange(int id, int value){ onValueChange(value); } } } abstract class View4 extends LinearLayout { abstract int getSensorId(); abstract void onValueChange(int value); public void onAttachToWindow(){ Sensor.getInstance.register(getSensorId(),listener); } public void onDetachFromWindow() { Sensor.getInstance.unRegister(listener); } Listener listener = new Listener(){ void onDataChange(int id, int value){ onValueChange(value); } } } abstract class View5 extends RelativeLayout{ abstract int getSensorId(); abstract void onValueChange(int value); public void onAttachToWindow(){ Sensor.getInstance.register(getSensorId(),listener); } public void onDetachFromWindow() { Sensor.getInstance.unRegister(listener); } Listener listener = new Listener(){ void onDataChange(int id, int value){ onValueChange(value); } } }
这种实现存在大量重复代码,尝试编写泛型类统一封装时无法通过编译,错误写法如下:
abstract class AbsView<T extends View> extends T{ abstract int getSensorId(); abstract void onValueChange(int value); public void onAttachToWindow(){ Sensor.getInstance.register(getSensorId(),listener); } public void onDetachFromWindow() { Sensor.getInstance.unRegister(listener); } Listener listener = new Listener(){ void onDataChange(int id, int value){ onValueChange(value); } } }
期望的使用效果是只需要实现传感器ID获取、值更新逻辑即可,无需重复编写注册注销监听的模板代码,示例用法如下:
class View1 extends AbsView<TextView>{ int getSensorId(){ return gpsId; } void onValueChange(int value){ setText("xxxx"); } } class View2 extends AbsView<ImageView>{ int getSensorId(){ return gpsId; } void onValueChange(int value){ setImage(xxx); } } class View3 extends AbsView<LinearLayout>{ int getSensorId(){ return gpsId; } void onValueChange(int value){ addView(xxx); } }
原因说明
泛型写法编译失败的核心原因是Java不支持类继承泛型类型参数:泛型在编译阶段会做类型擦除,AbsView<T extends View> extends T 写法里,编译器在编译时无法确定父类T的具体类型,无法生成合法的类继承结构,属于语法层面的硬性限制。
推荐方案
遵循组合优于继承的设计原则,不需要自定义多套View子类,把传感器绑定、生命周期感知的逻辑抽成独立的委托类即可,适配所有View子类,零重复代码。
- 首先实现通用的传感器绑定委托类,复用所有生命周期监听、注册注销、数据回调逻辑:
import android.view.View; import java.util.function.Consumer; public class SensorBinder implements View.OnAttachStateChangeListener { private final int sensorId; private final Sensor.Listener sensorListener; public SensorBinder(View targetView, int sensorId, Consumer<Integer> onValueChange) { this.sensorId = sensorId; // 构造传感器回调,仅当View已附着窗口时才分发数据 this.sensorListener = (callbackId, value) -> { if (callbackId == sensorId && targetView.isAttachedToWindow()) { onValueChange.accept(value); } }; // 监听View的窗口附着生命周期 targetView.addOnAttachStateChangeListener(this); // 处理View已提前附着到窗口的场景 if (targetView.isAttachedToWindow()) { Sensor.getInstance().register(sensorId, sensorListener); } } @Override public void onViewAttachedToWindow(View v) { Sensor.getInstance().register(sensorId, sensorListener); } @Override public void onViewDetachedFromWindow(View v) { Sensor.getInstance().unRegister(sensorListener); } }
- 使用时不需要自定义任何View子类,不管是TextView、ImageView还是各类Layout,只要是View子类都可以直接绑定:
// 给TextView绑定GPS传感器 TextView gpsTv = findViewById(R.id.gps_text); new SensorBinder(gpsTv, SensorConst.GPS_ID, value -> { gpsTv.setText("当前GPS信号值:" + value); }); // 给ImageView绑定亮度传感器 ImageView brightnessIv = findViewById(R.id.brightness_img); new SensorBinder(brightnessIv, SensorConst.BRIGHTNESS_ID, value -> { brightnessIv.setImageAlpha(value * 255 / 100); }); // 给LinearLayout绑定温度传感器 LinearLayout tempLayout = findViewById(R.id.temp_container); new SensorBinder(tempLayout, SensorConst.TEMP_ID, value -> { // 温度变化时更新布局内容 updateTempTip(tempLayout, value); });
如果项目使用Kotlin,还可以通过扩展函数进一步简化调用,把绑定逻辑封装成View的扩展方法,调用时只需要一行代码即可完成传感器绑定。
内容的提问来源于stack exchange,提问作者NoBugNoError
相关产品推荐
相关产品推荐

