Android双屏异显实战:从收银机到广告机,用Presentation和WindowManager搞定多屏适配

张开发
2026/4/8 9:10:50 15 分钟阅读

分享文章

Android双屏异显实战:从收银机到广告机,用Presentation和WindowManager搞定多屏适配
Android双屏异显商业场景实战Presentation与WindowManager的进阶应用在数字化商业环境中多屏交互正成为提升用户体验的关键技术。从餐厅收银台到商场广告机再到博物馆的互动展板Android双屏异显技术正在重塑商业交互模式。本文将深入探讨如何基于不同硬件特性和业务需求选择最优实现方案并解决实际开发中的核心挑战。1. 商业场景下的技术选型策略商业场景对双屏异显的需求远不止简单的显示扩展。以零售行业为例主屏需要处理订单录入等高交互操作而副屏则要实时展示促销内容这对技术方案的稳定性和灵活性提出了更高要求。关键决策因素对比表评估维度Presentation方案优势WindowManager方案优势开发复杂度官方API集成简单需要手动管理窗口生命周期性能消耗每个屏幕独立Activity单窗口多视图资源占用低交互同步跨进程通信实现复杂同一进程内直接控制版本兼容性Android 4.2支持需要处理不同API级别的窗口类型差异动态内容更新需要重建Presentation实例直接操作View树实时更新实际项目中选择方案时建议先明确三个核心问题屏幕间是否需要数据同步副屏内容更新频率如何目标设备的Android版本分布情况对于广告机这类静态内容展示场景Presentation的隔离特性反而是优势。而需要实时同步的互动展览则更适合采用WindowManager方案。最近在为连锁餐厅部署自助点餐系统时我们就因为需要主副屏菜单实时同步最终选择了WindowManager方案。2. Presentation的工业级实现方案Android官方提供的Presentation类确实为多屏开发提供了基础支持但在商业项目中直接使用原始实现会遇到诸多限制。以下是经过实战检验的增强方案public class CommercialPresentation extends Presentation { private static final String TAG CommercialPresentation; private DisplayMetrics secondaryMetrics; public CommercialPresentation(Context context, Display display) { super(context, display); initDisplayMetrics(display); } private void initDisplayMetrics(Display display) { secondaryMetrics new DisplayMetrics(); display.getMetrics(secondaryMetrics); Log.d(TAG, Secondary screen: secondaryMetrics.widthPixels x secondaryMetrics.heightPixels); } Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); adjustLayoutParams(); setContentView(R.layout.commercial_layout); } private void adjustLayoutParams() { Window window getWindow(); WindowManager.LayoutParams params window.getAttributes(); params.flags | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON; if (Build.VERSION.SDK_INT Build.VERSION_CODES.O) { params.type WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; } window.setAttributes(params); } }商业项目中必须处理的细节问题DPI适配陷阱不同厂商的副屏可能采用非常规DPI设置val config Configuration(context.resources.configuration) config.densityDpi secondaryMetrics.densityDpi context.createConfigurationContext(config)输入焦点冲突主副屏同时接收触摸事件会导致操作混乱activity android:name.SecondaryActivity android:noHistorytrue android:excludeFromRecentstrue android:themestyle/PresentationTheme/内存泄漏预防Presentation实例必须随主Activity销毁Override protected void onDestroy() { if (mPresentation ! null mPresentation.isShowing()) { mPresentation.dismiss(); } super.onDestroy(); }在最近实施的智能售货机项目中我们就因为忽略DPI适配导致副屏UI严重错位。后来通过动态获取DisplayMetrics并重设Configuration才解决问题。3. WindowManager的高阶应用技巧当项目需要更精细的窗口控制时直接使用WindowManager会提供更大的灵活性。以下是经过多个商业项目验证的增强型窗口管理方案class AdvancedWindowManager( private val context: Context, private val display: Display ) { private val windowManager: WindowManager private var contentView: View? null init { val displayContext context.createDisplayContext(display) windowManager displayContext.getSystemService(Context.WINDOW_SERVICE) as WindowManager } fun showContent(layoutId: Int): View { val inflater LayoutInflater.from( context.createDisplayContext(display) ) contentView inflater.inflate(layoutId, null) val params buildLayoutParams() windowManager.addView(contentView, params) return contentView!! } private fun buildLayoutParams(): WindowManager.LayoutParams { return WindowManager.LayoutParams( WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT, if (Build.VERSION.SDK_INT Build.VERSION_CODES.O) { WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY } else { WindowManager.LayoutParams.TYPE_SYSTEM_ALERT }, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE, PixelFormat.TRANSLUCENT ).apply { gravity Gravity.TOP or Gravity.START token context.activity?.window?.attributes?.token } } fun updateContent(layoutId: Int) { contentView?.let { windowManager.removeView(it) showContent(layoutId) } } fun release() { contentView?.let { windowManager.removeView(it) contentView null } } }商业项目中的典型问题解决方案窗口层级控制多窗口场景下的z-order管理params.flags | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS; params.flags | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;跨屏触摸事件处理实现类似POS机的防误触机制View android:layout_widthmatch_parent android:layout_heightmatch_parent android:clickabletrue android:focusabletrue/动态分辨率适配应对旋转屏幕等场景fun onConfigurationChanged(newConfig: Configuration) { contentView?.layoutParams?.apply { width newConfig.screenWidthDp height newConfig.screenHeightDp windowManager.updateViewLayout(contentView, this) } }在医疗行业的双屏问诊系统开发中我们利用WindowManager实现了主屏病历编辑、副屏检查报告实时预览的复杂交互。关键是在合适的时机更新窗口属性避免界面闪烁。4. 性能优化与异常处理商业项目往往需要7×24小时稳定运行这对双屏应用的健壮性提出了极高要求。以下是关键的性能保障策略内存优化检查清单使用LeakCanary监控Presentation内存泄漏副屏Bitmap采用RGB_565格式降低内存占用定期调用WindowManager.removeView()释放资源避免在副屏使用硬件加速的复杂动画跨进程通信方案对比通信方式延迟测试(ms)适用场景实现复杂度LocalSocket12±2高频小数据量同步★★★☆☆ContentProvider35±5结构化数据共享★★☆☆☆Broadcast50±10低频事件通知★☆☆☆☆AIDL18±3需要RPC调用的复杂交互★★★★☆// 高效的跨屏事件总线实现示例 public class DisplayEventBus { private static final String CHANNEL display_events; private final LocalServerSocket serverSocket; public DisplayEventBus(int port) throws IOException { serverSocket new LocalServerSocket(CHANNEL port); new Thread(this::acceptConnections).start(); } private void acceptConnections() { while (!Thread.interrupted()) { try (LocalSocket socket serverSocket.accept(); InputStream input socket.getInputStream()) { byte[] buffer new byte[1024]; int bytes input.read(buffer); handleEvent(new String(buffer, 0, bytes)); } catch (IOException e) { Log.e(TAG, Connection error, e); } } } public void sendEvent(String event) { try (LocalSocket client new LocalSocket(); OutputStream output client.getOutputStream()) { client.connect(new LocalSocketAddress(CHANNEL port)); output.write(event.getBytes()); } catch (IOException e) { Log.e(TAG, Send event failed, e); } } }商业环境中的特殊问题处理长时间运行的内存增长建议每24小时重启一次Presentation屏幕热插拔支持注册DisplayManager.DisplayListenerdisplayManager.registerDisplayListener(object : DisplayManager.DisplayListener { override fun onDisplayAdded(displayId: Int) { // 处理新增屏幕 } override fun onDisplayRemoved(displayId: Int) { // 清理对应资源 } override fun onDisplayChanged(displayId: Int) { // 适应参数变化 } }, null)厂商定制ROM的兼容性需要特别处理MIUI、EMUI等系统的权限问题!-- 针对华为设备的特殊权限声明 -- uses-permission android:namecom.huawei.permission.SECURE_SCREEN /在实施机场信息发布系统时我们就遇到了某厂商设备在连续运行72小时后出现SurfaceFlinger崩溃的问题。最终通过定时回收重建Presentation实例的方案解决了这一隐患。

更多文章