告别扫描方向限制:zxing-android-embedded横竖屏适配全指南
你是否还在为 barcode scanner 只能固定方向扫描而烦恼?用户横屏使用时扫码窗口颠倒、自定义界面适配各种屏幕方向时布局错乱、Manifest 配置与代码逻辑冲突导致方向切换失效?本文将系统解决 zxing-android-embedded 库的屏幕方向控制难题,通过 3 种核心实现方案、5 个实战案例和 8 项优化技巧,让你的扫码功能完美支持任意方向,适配从手机到平板的全场景使用。
读完本文你将掌握:
- 横屏/竖屏/自动旋转的 Manifest 配置方案
- 自定义 Activity 实现动态方向切换的完整代码
- 方向变更时的布局适配与相机参数调整技巧
- 常见方向问题的诊断与解决方案
- 含 Toolbar/自定义 UI 的复杂场景适配方案
一、屏幕方向控制的核心实现方案
1.1 Manifest 静态配置方案(基础版)
AndroidManifest.xml 中通过 android:screenOrientation 属性直接指定 Activity 方向,这是最简单直接的实现方式,适合固定方向需求。
| 配置值 | 适用场景 | 优势 | 局限 |
|---|---|---|---|
portrait | 仅竖屏扫描 | 实现简单,无需代码 | 无法横屏使用 |
landscape | 仅横屏扫描 | 适合平板设备 | 手机竖屏体验差 |
fullSensor | 自动旋转 | 跟随设备物理方向 | 布局需适配各方向 |
unspecified | 默认行为 | 继承父 Activity 设置 | 行为不可控 |
竖屏固定实现示例:
<activity
android:name=".ToolbarCaptureActivity"
android:screenOrientation="portrait" <!-- 强制竖屏 -->
android:theme="@style/AppCompatCaptureTheme">
</activity>
自动旋转实现示例:
<activity
android:name=".AnyOrientationCaptureActivity"
android:screenOrientation="fullSensor" <!-- 跟随传感器旋转 -->
android:stateNotNeeded="true"> <!-- 方向变化不保留状态 -->
</activity>
⚠️ 注意:
stateNotNeeded="true"表示方向变化时无需保存 Activity 状态,适合扫码场景减少资源消耗。
1.2 代码动态控制方案(进阶版)
通过 Activity 的 setRequestedOrientation() 方法动态控制方向,适合需要根据业务逻辑切换方向的场景。
// 在需要切换方向的时机调用(如按钮点击)
public void switchOrientation() {
int currentOrientation = getResources().getConfiguration().orientation;
if (currentOrientation == Configuration.ORIENTATION_PORTRAIT) {
// 切换到横屏
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
} else {
// 切换到竖屏
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}
}
动态方向控制的完整 Activity 实现:
public class DynamicOrientationActivity extends AppCompatActivity {
private DecoratedBarcodeView barcodeView;
private CaptureManager captureManager;
private boolean isLandscape = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_dynamic_orientation);
barcodeView = findViewById(R.id.zxing_barcode_scanner);
captureManager = new CaptureManager(this, barcodeView);
// 方向切换按钮
Button switchBtn = findViewById(R.id.switch_orientation);
switchBtn.setOnClickListener(v -> toggleOrientation());
}
private void toggleOrientation() {
isLandscape = !isLandscape;
setRequestedOrientation(isLandscape ?
ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE :
ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}
@Override
protected void onResume() {
super.onResume();
captureManager.onResume();
}
@Override
protected void onPause() {
super.onPause();
captureManager.onPause();
}
// 其他生命周期方法...
}
1.3 自定义 CaptureActivity 方案(高级版)
通过继承 CaptureActivity 或自定义实现 CaptureManager,完全掌控扫描流程中的方向控制逻辑,适合复杂场景。
任意方向捕获 Activity 实现:
public class AnyOrientationCaptureActivity extends CaptureActivity {
// 无需额外代码,完全继承 CaptureActivity 功能
// 方向控制通过 Manifest 的 fullSensor 实现
}
带方向感知的自定义实现:
public class OrientationAwareCaptureActivity extends AppCompatActivity implements RotationListener {
private CaptureManager captureManager;
private DecoratedBarcodeView barcodeView;
private int currentRotation = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.custom_capture_layout);
barcodeView = findViewById(R.id.zxing_barcode_scanner);
barcodeView.addRotationListener(this); // 注册旋转监听器
captureManager = new CaptureManager(this, barcodeView);
captureManager.initializeFromIntent(getIntent(), savedInstanceState);
captureManager.decode();
}
@Override
public void onRotationChanged(int rotation) {
currentRotation = rotation;
// 根据旋转角度调整 UI 元素
adjustUIForRotation(rotation);
}
private void adjustUIForRotation(int rotation) {
// 示例:根据旋转角度调整扫码框位置和大小
ViewfinderView viewfinder = findViewById(R.id.zxing_viewfinder_view);
ViewGroup.LayoutParams params = viewfinder.getLayoutParams();
if (rotation == 0 || rotation == 180) { // 竖屏方向
params.width = ViewGroup.LayoutParams.MATCH_PARENT;
params.height = getResources().getDimensionPixelSize(R.dimen.portrait_viewfinder_height);
} else { // 横屏方向
params.width = getResources().getDimensionPixelSize(R.dimen.landscape_viewfinder_width);
params.height = ViewGroup.LayoutParams.MATCH_PARENT;
}
viewfinder.setLayoutParams(params);
}
// 其他生命周期方法...
}
二、实战案例:从基础到复杂场景的实现
2.1 案例一:基础竖屏扫描(固定方向)
实现目标:无论设备如何旋转,始终保持竖屏扫描界面。
1. Activity 定义:
public class VerticalCaptureActivity extends CaptureActivity {
// 直接继承基础扫描功能,无需额外代码
}
2. Manifest 配置:
<activity
android:name=".VerticalCaptureActivity"
android:screenOrientation="portrait" <!-- 强制竖屏 -->
android:windowSoftInputMode="stateAlwaysHidden">
</activity>
3. 启动扫描代码:
new IntentIntegrator(this)
.setCaptureActivity(VerticalCaptureActivity.class)
.setOrientationLocked(true) // 锁定方向
.initiateScan();
2.2 案例二:全方向自动旋转扫描
实现目标:扫描界面跟随设备物理方向自动旋转,适配竖屏和横屏两种模式。
1. Activity 定义:
public class AutoRotateCaptureActivity extends CaptureActivity {
// 此类在 sample 中实际存在,仅作为示例展示
// 无额外代码,方向控制完全通过 Manifest 实现
}
2. Manifest 配置:
<activity
android:name=".AutoRotateCaptureActivity"
android:screenOrientation="fullSensor" <!-- 跟随传感器 -->
android:stateNotNeeded="true" <!-- 旋转时不保存状态 -->
android:theme="@style/zxing_CaptureTheme">
</activity>
3. 布局文件(res/layout/zxing_capture.xml):
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.journeyapps.barcodescanner.BarcodeView
android:id="@+id/zxing_barcode_surface"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<com.journeyapps.barcodescanner.ViewfinderView
android:id="@+id/zxing_viewfinder_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
关键要点:使用 FrameLayout 作为根布局,让相机预览和扫描框能自适应不同方向的尺寸变化。
2.3 案例三:带 Toolbar 的自定义方向扫描
实现目标:在扫描界面顶部添加 Toolbar,并支持方向切换,解决 ActionBar 与扫描界面共存问题。
1. Activity 实现:
public class ToolbarCaptureActivity extends AppCompatActivity {
private CaptureManager capture;
private DecoratedBarcodeView barcodeScannerView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.capture_appcompat);
// 配置 Toolbar
Toolbar toolbar = findViewById(R.id.my_awesome_toolbar);
toolbar.setTitle("扫码");
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
// 初始化扫描组件
barcodeScannerView = findViewById(R.id.zxing_barcode_scanner);
capture = new CaptureManager(this, barcodeScannerView);
capture.initializeFromIntent(getIntent(), savedInstanceState);
capture.decode();
}
// 方向切换处理
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
// 方向变化时重新布局
barcodeScannerView.onConfigurationChanged(newConfig);
}
// Toolbar 返回按钮处理
@Override
public boolean onSupportNavigateUp() {
onBackPressed();
return true;
}
// 相机按键处理
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
return barcodeScannerView.onKeyDown(keyCode, event) || super.onKeyDown(keyCode, event);
}
// 生命周期方法必须调用 CaptureManager 对应方法
@Override
protected void onResume() {
super.onResume();
capture.onResume();
}
@Override
protected void onPause() {
super.onPause();
capture.onPause();
}
@Override
protected void onDestroy() {
super.onDestroy();
capture.onDestroy();
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
capture.onSaveInstanceState(outState);
}
}
2. 布局文件(capture_appcompat.xml):
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.appcompat.widget.Toolbar
android:id="@+id/my_awesome_toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorPrimary"
android:minHeight="?attr/actionBarSize" />
<com.journeyapps.barcodescanner.DecoratedBarcodeView
android:id="@+id/zxing_barcode_scanner"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
</LinearLayout>
3. Manifest 配置:
<activity
android:name=".ToolbarCaptureActivity"
android:screenOrientation="fullSensor"
android:theme="@style/AppCompatCaptureTheme"
android:configChanges="orientation|screenSize"> <!-- 方向变化不重建 Activity -->
</activity>
4. 主题定义(styles.xml):
<style name="AppCompatCaptureTheme" parent="Theme.AppCompat.Light.NoActionBar">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
<item name="android:windowContentOverlay">@null</item>
<item name="android:windowBackground">@android:color/black</item>
</style>
三、方向适配的进阶技巧与最佳实践
3.1 方向变更时的布局适配
当屏幕方向变化时,不仅是视觉布局需要调整,相机参数也需要重新配置。以下是关键的适配技巧:
1. 多方向布局资源准备: 在 res 目录下创建不同方向的布局文件夹:
layout/:默认布局(竖屏)layout-land/:横屏布局layout-port/:竖屏布局(明确指定)
2. 动态调整扫描框大小:
private void adjustViewfinderSize(int orientation) {
ViewfinderView viewfinder = findViewById(R.id.zxing_viewfinder_view);
if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
// 横屏:宽大于高
viewfinder.setFramingRectSize(600, 400); // 宽, 高
} else {
// 竖屏:高大于宽
viewfinder.setFramingRectSize(400, 600); // 宽, 高
}
}
3. 相机预览尺寸优化: 方向变更时,选择最适合当前分辨率的相机预览尺寸:
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
BarcodeView barcodeView = findViewById(R.id.zxing_barcode_surface);
// 获取当前屏幕尺寸
DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);
int screenWidth = metrics.widthPixels;
int screenHeight = metrics.heightPixels;
// 根据新方向选择最佳预览尺寸
List<Camera.Size> sizes = barcodeView.getCameraPreview().getSupportedPreviewSizes();
Camera.Size optimalSize = getOptimalPreviewSize(sizes, screenWidth, screenHeight);
// 应用新的预览尺寸
barcodeView.getCameraPreview().setPreviewSize(optimalSize);
}
// 计算最佳预览尺寸
private Camera.Size getOptimalPreviewSize(List<Camera.Size> sizes, int w, int h) {
final double ASPECT_TOLERANCE = 0.1;
double targetRatio = (double) w / h;
if (sizes == null) return null;
Camera.Size optimalSize = null;
double minDiff = Double.MAX_VALUE;
int targetHeight = h;
for (Camera.Size size : sizes) {
double ratio = (double) size.width / size.height;
if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue;
if (Math.abs(size.height - targetHeight) < minDiff) {
optimalSize = size;
minDiff = Math.abs(size.height - targetHeight);
}
}
if (optimalSize == null) {
minDiff = Double.MAX_VALUE;
for (Camera.Size size : sizes) {
if (Math.abs(size.height - targetHeight) < minDiff) {
optimalSize = size;
minDiff = Math.abs(size.height - targetHeight);
}
}
}
return optimalSize;
}
3.2 常见问题诊断与解决方案
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 方向变化后黑屏 | Activity 重建导致相机资源释放 | 添加 configChanges 配置 |
| 扫描框位置偏移 | 未适配方向变化的坐标系统 | 重写 getFramingRect() 方法 |
| 预览画面拉伸变形 | 预览尺寸与屏幕比例不匹配 | 优化预览尺寸选择算法 |
| 方向变化后无法扫描 | 解码线程未重启 | 方向变化后重启解码线程 |
| 横屏时扫描区域过小 | 扫描框尺寸未适配 | 为横屏单独设置扫描框大小 |
问题案例:方向变化后黑屏
症状:旋转设备后,扫描界面变为黑屏,但有扫描线动画。
诊断:Activity 因方向变化重建,但相机资源未正确重新初始化。
解决方案:
- 在 Manifest 中添加 configChanges 属性:
<activity
android:name=".MyCaptureActivity"
android:configChanges="orientation|screenSize|keyboardHidden">
</activity>
- 在 Activity 中重写 onConfigurationChanged 方法:
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
// 通知 BarcodeView 配置变化
barcodeView.onConfigurationChanged(newConfig);
// 重新开始扫描
if (captureManager != null) {
captureManager.onConfigurationChanged(newConfig);
}
}
3.3 含 ViewPager/TabLayout 的复杂场景适配
在包含滑动标签页的界面中集成扫描功能时,方向控制需要特别处理:
1. Fragment 中的扫描实现:
public class ScanFragment extends Fragment {
private DecoratedBarcodeView barcodeView;
private CaptureManager captureManager;
private boolean isScanning = false;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_scan, container, false);
barcodeView = view.findViewById(R.id.zxing_barcode_scanner);
return view;
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
captureManager = new CaptureManager(getActivity(), barcodeView);
captureManager.initializeFromIntent(getActivity().getIntent(), savedInstanceState);
}
@Override
public void onResume() {
super.onResume();
isScanning = true;
captureManager.onResume();
}
@Override
public void onPause() {
super.onPause();
isScanning = false;
captureManager.onPause();
}
@Override
public void onDestroy() {
super.onDestroy();
captureManager.onDestroy();
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
captureManager.onSaveInstanceState(outState);
}
// 处理返回键事件
public boolean onBackPressed() {
if (isScanning) {
// 停止扫描
isScanning = false;
return true;
}
return false;
}
}
2. ViewPager 中的方向控制: 当 ViewPager 包含多个 Fragment 时,需要在切换到扫描 Fragment 时才启动相机:
viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageSelected(int position) {
if (position == SCAN_FRAGMENT_POSITION) {
// 切换到扫描 Fragment,启动相机
ScanFragment fragment = (ScanFragment) adapter.getItem(position);
fragment.startScan();
} else {
// 离开扫描 Fragment,停止相机
ScanFragment fragment = (ScanFragment) adapter.getItem(SCAN_FRAGMENT_POSITION);
fragment.stopScan();
}
}
// 其他方法...
});
四、完整的方向控制工具类与测试
4.1 方向控制工具类封装
为简化多个扫描 Activity 的方向控制实现,封装一个工具类:
public class OrientationUtils {
private static final String TAG = "OrientationUtils";
private Activity activity;
private DecoratedBarcodeView barcodeView;
private int lastOrientation = -1;
public OrientationUtils(Activity activity, DecoratedBarcodeView barcodeView) {
this.activity = activity;
this.barcodeView = barcodeView;
lastOrientation = activity.getResources().getConfiguration().orientation;
}
/**
* 检查并处理方向变化
*/
public void checkOrientation() {
int currentOrientation = activity.getResources().getConfiguration().orientation;
if (currentOrientation != lastOrientation) {
onOrientationChanged(currentOrientation);
lastOrientation = currentOrientation;
}
}
/**
* 方向变化处理
*/
private void onOrientationChanged(int orientation) {
Log.d(TAG, "Orientation changed to: " + (orientation == Configuration.ORIENTATION_LANDSCAPE ? "Landscape" : "Portrait"));
// 调整扫描框大小
adjustViewfinderSize(orientation);
// 调整相机参数
adjustCameraParameters(orientation);
// 通知应用其他部分方向变化
if (orientationChangeListener != null) {
orientationChangeListener.onOrientationChanged(orientation);
}
}
/**
* 调整扫描框大小
*/
private void adjustViewfinderSize(int orientation) {
if (barcodeView == null) return;
ViewfinderView viewfinder = (ViewfinderView) barcodeView.getChildAt(1);
if (viewfinder != null) {
if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
viewfinder.setFramingRectSize(600, 400);
} else {
viewfinder.setFramingRectSize(400, 600);
}
}
}
/**
* 调整相机参数
*/
private void adjustCameraParameters(int orientation) {
// 可以在这里调整曝光、对焦等相机参数
}
// 方向变化监听器
public interface OnOrientationChangeListener {
void onOrientationChanged(int orientation);
}
private OnOrientationChangeListener orientationChangeListener;
public void setOrientationChangeListener(OnOrientationChangeListener listener) {
this.orientationChangeListener = listener;
}
// 生命周期方法
public void onResume() {
// 启动方向检查
startOrientationCheck();
}
public void onPause() {
// 停止方向检查
stopOrientationCheck();
}
// 方向检查实现
private Runnable orientationCheckRunnable = new Runnable() {
@Override
public void run() {
checkOrientation();
handler.postDelayed(this, 500); // 每500ms检查一次
}
};
private Handler handler = new Handler();
private void startOrientationCheck() {
handler.post(orientationCheckRunnable);
}
private void stopOrientationCheck() {
handler.removeCallbacks(orientationCheckRunnable);
}
}
工具类使用示例:
public class AdvancedCaptureActivity extends AppCompatActivity {
private OrientationUtils orientationUtils;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_advanced_capture);
DecoratedBarcodeView barcodeView = findViewById(R.id.zxing_barcode_scanner);
orientationUtils = new OrientationUtils(this, barcodeView);
// 设置方向变化监听器
orientationUtils.setOrientationChangeListener(orientation -> {
Toast.makeText(this,
orientation == Configuration.ORIENTATION_LANDSCAPE ?
"横屏模式" : "竖屏模式",
Toast.LENGTH_SHORT).show();
});
}
@Override
protected void onResume() {
super.onResume();
orientationUtils.onResume();
}
@Override
protected void onPause() {
super.onPause();
orientationUtils.onPause();
}
}
4.2 方向适配测试清单
为确保方向控制在各种场景下都能正常工作,需要进行全面测试:
基础功能测试:
- 竖屏扫描正常识别
- 横屏扫描正常识别
- 自动旋转时界面平滑过渡
- 各方向下扫描成功率一致
边界情况测试:
- 快速旋转设备时的稳定性
- 扫描过程中旋转设备
- 充电状态下的方向控制
- 锁定系统旋转时的表现
- 多任务切换后的方向恢复
兼容性测试:
- 不同 Android 版本(API 16+)
- 不同屏幕尺寸(手机、平板)
- 不同相机配置(单摄、双摄、前置)
性能测试:
- 方向变化时的响应时间(<500ms)
- 方向变化后的首次扫描时间(<1s)
- 多次方向变化后的内存使用情况
五、总结与未来展望
5.1 关键知识点回顾
本文系统介绍了 zxing-android-embedded 库的屏幕方向控制方案,从基础的 Manifest 配置到复杂的自定义实现,涵盖了以下核心知识点:
-
方向控制的三种实现方式:
- Manifest 静态配置:适合简单固定方向需求
- 代码动态控制:适合需要业务逻辑干预的场景
- 自定义 Activity:适合复杂 UI 和完全控制需求
-
核心 API 与类:
CaptureActivity:基础扫描 ActivityCaptureManager:扫描流程管理器DecoratedBarcodeView:带装饰的扫描视图OrientationUtils:方向控制工具类(自定义)
-
最佳实践总结:
- 始终在 Manifest 中设置
configChanges属性 - 方向变化时必须处理相机资源重新配置
- 为不同方向提供优化的布局和扫描框尺寸
- 在 Fragment 中使用时注意生命周期管理
- 复杂场景下使用方向监听器动态调整 UI
- 始终在 Manifest 中设置
5.2 未来技术趋势与优化方向
随着 Android 系统的不断演进,条码扫描的方向控制也将面临新的挑战和机遇:
-
Jetpack Compose 适配: 未来的 UI 开发将更多转向 Compose,需要探索在声明式 UI 中实现灵活的方向控制。
-
CameraX 集成: 谷歌推荐的 CameraX API 提供了更好的设备兼容性和生命周期管理,未来版本的 zxing-android-embedded 可能会基于 CameraX 重构,方向控制将更加简化。
-
AI 辅助的智能方向适应: 通过分析条码位置和方向,自动旋转扫描线或调整解码算法,实现任意方向的条码识别,减少对物理方向的依赖。
-
折叠屏设备适配: 折叠屏手机带来了更多的屏幕形态变化,需要开发更智能的方向感知和布局适配方案。
5.3 扩展学习资源
要深入掌握条码扫描的方向控制和高级定制,以下资源值得推荐:
-
官方文档与示例:
- zxing-android-embedded 官方 GitHub 仓库
- Android 开发者文档:屏幕方向与配置变化
-
进阶学习项目:
- journeyapps/zxing-android-embedded(本文项目)
- Google CameraX 示例项目
-
技术文章:
- 《Android 屏幕方向全解析》
- 《Camera 预览尺寸选择与优化》
- 《处理 Android 配置变化的最佳实践》
通过本文的学习,你已经掌握了 zxing-android-embedded 库的各种方向控制方案和最佳实践。无论是简单的固定方向扫描,还是复杂的带 Toolbar/ViewPager 的动态方向适配,都能游刃有余地实现。记住,优秀的扫码体验不仅需要正确的功能实现,还需要充分考虑各种设备和使用场景下的适应性,让用户在任何姿势下都能轻松完成扫描操作。
希望本文能帮助你构建更出色的条码扫描功能,如果你有任何问题或发现更好的实现方案,欢迎在评论区交流分享。别忘了点赞收藏,关注作者获取更多 Android 高级开发技巧!
下一篇预告:《zxing-android-embedded 性能优化实战:从 300ms 到 50ms 的扫描速度提升》
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



