首先我们看下项目结构

(1) 首先我们从扫描二维码Activity MipcaActivityCapture.Java 类入手该类主要是调用相机预览拍照内容,处理扫描后的结果,扫码成功震动,及扫描音效等。

首先我们看关键模块,相机拍摄预览用到为View控件SurfaceView 改控件提供了一个专用绘图面,嵌入在视图层次结构中。你可以控制整个表面的格式,它的大小;SurfaceView负责屏幕上正确的位置显示。

SurfaceView提供了 SurfaceHolder接口来设置控件的表面大小和格式编辑表面像素等,SurfaceHolder提供了Android.view.SurfaceHolder.Callback 接口来处理SurfaceView显示,渲染,销毁等回调监听,下面看关键代码

[java] view plaincopy
  1. @Override
  2. protected void onResume() {
  3. super.onResume();
  4. /**
  5. * 提供一个专用的绘图面,嵌入在视图层次结构中。你可以控制这个表面的格式,它的大小;
  6. * SurfaceView负责将面在屏幕上正确的位置显示。
  7. *
  8. * 表面是Z序是窗口举行SurfaceView落后;SurfaceView打出了一个洞在它的窗口,让其表面显示。
  9. * 视图层次结构将负责正确的合成与表面的任何兄弟SurfaceView通常会出现在它的上面
  10. * 。这可以用来放置覆盖如表面上的按钮,但注意,这可能会对性能产生影响因为完整的alpha混合复合材料将每一次表面的变化进行。
  11. *
  12. * 使表面可见的透明区域是基于视图层次结构中的布局位置的。如果布局后的变换属性用于在顶部的图形绘制一个兄弟视图,视图可能不正确的复合表面。
  13. *
  14. * 访问底层的表面通过SurfaceHolder接口提供,这可以通过调用getholder()检索。
  15. *
  16. * 表面将被创建为你而SurfaceView的窗口是可见的;你应该实现surfacecreated(SurfaceHolder)
  17. * 和surfacedestroyed(SurfaceHolder)发现当表面被创建和销毁窗口的显示和隐藏。
  18. *
  19. * 这个类的目的之一是提供一个表面,其中一个二级线程可以呈现到屏幕上。如果你要使用它,你需要知道一些线程的语义:
  20. *
  21. * 所有的图形和SurfaceHolder。回调方法将从线程运行的窗口叫SurfaceView(通常是应用程序的主线程)。因此,
  22. * 他们需要正确地与任何状态,也接触了绘图线程的同步。
  23. *
  24. * 你必须确保拉丝只触及表面,底层是有效的——SurfaceHolder.lockCanvas。回调。surfacecreated()
  25. * 和surfacedestroyed() SurfaceHolder。回调。
  26. */
  27. SurfaceView surfaceView = (SurfaceView) findViewById(R.id.preview_view);
  28. /**
  29. * SurfaceHolder 类解释
  30. *
  31. * 抽象接口,有人拿着一个显示面。允许你
  32. *
  33. * 控制的表面大小和格式,编辑在表面的像素,和
  34. *
  35. * *显示器更改为表面。此接口通常可用
  36. *
  37. * 通过SurfaceView类 {@link SurfaceView}
  38. *
  39. * 当使用该接口从一个线程以外的一个运行 {@link SurfaceView}, 你要仔细阅读
  40. *
  41. * 方法 {@link #lockCanvas} and {@link Callback#surfaceCreated
  42. * Callback.surfaceCreated()}.
  43. */
  44. /**
  45. * surfaceView.getHolder() 返回SurfaceHolder 对象
  46. */
  47. SurfaceHolder surfaceHolder = surfaceView.getHolder();
  48. if (hasSurface) { // 判断是否 有显示
  49. // 初始化相机
  50. initCamera(surfaceHolder);
  51. } else {
  52. // 添加回调监听
  53. surfaceHolder.addCallback(this);
  54. // 设置视图类型 这是被忽略的,这个值是在需要时自动设定的。
  55. surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
  56. }
  57. // 解码格式
  58. decodeFormats = null;
  59. // 字符集
  60. characterSet = null;
  61. playBeep = true;
  62. // 获取系统音频服务 AUDIO_SERVICE(音频服务)
  63. // AudioManager 提供了访问音量和振铃模式控制
  64. AudioManager audioService = (AudioManager) getSystemService(AUDIO_SERVICE);
  65. // 判断当前的模式 是否为 (铃声模式,可能是声音和振动。)
  66. if (audioService.getRingerMode() != AudioManager.RINGER_MODE_NORMAL) {
  67. // 设置 播放闹铃 为false
  68. playBeep = false;
  69. }
  70. //* 初始化 报警音频
  71. initBeepSound();
  72. // 设置震动状态为 true
  73. vibrate = true;
  74. }

接下来看下初始化媒体播放器,及震动模块代码,MediaPlayer 做过流媒体或音频相关开发都用过,这里是用文件流加载

raw目录下的文件。 Vibrator类 操作该设备上的振子的类,也就是让我们手机产生震动效果,请看一下代码块,注释有很多是自己理解和百度翻译。

[java] view plaincopy
  1. /**
  2. * 初始化 报警音频
  3. */
  4. private void initBeepSound() {
  5. if (playBeep && mediaPlayer == null) {
  6. // 在stream_system音量不可调的,用户发现它太大声,所以我们现在播放的音乐流。
  7. setVolumeControlStream(AudioManager.STREAM_MUSIC);
  8. // 初始化 媒体播放器
  9. mediaPlayer = new MediaPlayer();
  10. /*
  11. * 设置此播放器的音频流式。看到{@链接audiomanager }
  12. *
  13. * 对于一个流类型列表。必须调用这个方法之前,prepare() 或
  14. *
  15. * 为目标流式成为有效的为prepareasync()
  16. *
  17. * 此后。
  18. */
  19. mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
  20. /**
  21. *
  22. 当媒体源的结束时调用一个回调函数 已达到在播放。
  23. *
  24. * @param监听器回调将运行
  25. */
  26. mediaPlayer.setOnCompletionListener(beepListener);
  27. /**
  28. * 在资源管理入口文件描述符。这提供你自己的
  29. *
  30. * 打开FileDescriptor,可以用来读取数据,以及
  31. *
  32. * 该项数据在文件中的偏移和长度。
  33. */
  34. AssetFileDescriptor file = getResources().openRawResourceFd(
  35. R.raw.beep);
  36. try {
  37. /**
  38. * file.getFileDescriptor() 返回FileDescriptor,可以用来读取的数据文件。
  39. *
  40. * setDataSource() 设置数据源(FileDescriptor)使用。这是来电者的责任
  41. *
  42. * 关闭文件描述符。这是安全的,这样做,只要这个呼叫返回。
  43. */
  44. mediaPlayer.setDataSource(file.getFileDescriptor(),
  45. file.getStartOffset(), file.getLength());
  46. // 关闭 资源文件管理器
  47. file.close();
  48. /**
  49. * 设置该播放器的音量。
  50. *
  51. * 此接口建议用于平衡音频流的输出
  52. *
  53. * 在一个应用程序中。除非你正在写一个申请
  54. *
  55. * 控制用户设置时,应优先使用该原料药
  56. *
  57. * {@link AudioManager#setStreamVolume(int, int, int)}
  58. * 其中设置的所有流的体积
  59. *
  60. * 特定类型。请注意,通过量值是在范围0到1原标量。
  61. *
  62. * UI控件应该相应的对数。
  63. *
  64. * @param leftVolume
  65. *
  66. *            左量标量
  67. *
  68. * @param rightVolume
  69. *            对体积标量
  70. */
  71. mediaPlayer.setVolume(BEEP_VOLUME, BEEP_VOLUME);
  72. /**
  73. * 准备播放,同步播放。
  74. *
  75. * 在设置数据源和显示表面,你要么
  76. *
  77. * 电话prepare()或prepareasync()。文件,就可以prepare(),
  78. *
  79. * 块直到MediaPlayer准备播放。
  80. *
  81. * @throws IllegalStateException
  82. *             如果被称为无效状态
  83. */
  84. mediaPlayer.prepare();
  85. } catch (IOException e) {
  86. mediaPlayer = null; // 异常 释放播放器对象
  87. }
  88. }
  89. }
  90. // 震动持续时间
  91. private static final long VIBRATE_DURATION = 200L;
  92. /**
  93. * 打声音和振动
  94. */
  95. private void playBeepSoundAndVibrate() {
  96. if (playBeep && mediaPlayer != null) {
  97. /**
  98. * 开始或恢复播放。如果播放以前被暂停,
  99. *
  100. * 播放将继续从它被暂停的地方。如果播放了
  101. *
  102. * 被停止,或从未开始,播放将开始在
  103. *
  104. * 开始。
  105. *
  106. * @throws IllegalStateException
  107. *             如果被称为无效状态
  108. */
  109. mediaPlayer.start();
  110. }
  111. if (vibrate) {
  112. /**
  113. * getSystemService(VIBRATOR_SERVICE);
  114. *
  115. * 使用 {@link #getSystemService}检索{@link android.os.Vibrator}
  116. * 与振动硬件相互作用。
  117. *
  118. * @see #getSystemService
  119. * @see android.os.Vibrator
  120. *
  121. *
  122. *      Vibrator类 操作该设备上的振子的类。 如果你的进程存在,你开始的任何振动都将停止。
  123. *
  124. *      要获得系统振子的实例,调用 {@link Context#getSystemService}具有
  125. *      {@link Context#VIBRATOR_SERVICE} 作为参数。
  126. */
  127. Vibrator vibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE);
  128. /**
  129. * 为指定的时间周期振动。
  130. *
  131. * 此方法要求调用方持有权限
  132. *
  133. * {@link android.Manifest.permission#VIBRATE}.
  134. *
  135. * @param milliseconds
  136. *            振动的毫秒数。
  137. *
  138. *            VIBRATE_DURATION 震动持续时间
  139. */
  140. vibrator.vibrate(VIBRATE_DURATION);
  141. }
  142. }
  143. /**
  144. * 在播放时调用一个回调函数的接口定义媒体来源已完成
  145. */
  146. private final OnCompletionListener beepListener = new OnCompletionListener() {
  147. public void onCompletion(MediaPlayer mediaPlayer) {
  148. /**
  149. * 寻找指定的时间位置。
  150. *
  151. * @param 毫秒毫秒的偏移从开始寻求
  152. *
  153. * @抛出时,如果内部播放器引擎尚未初始化
  154. */
  155. mediaPlayer.seekTo(0);
  156. }
  157. };

接下来看相机初始化模块,及相机控制模块,这里用到了Activity生命周期函数,主要是关闭相机,终止线程等相关操作。

[java] view plaincopy
  1. /**
  2. * 初始化
  3. *
  4. * @param surfaceHolder
  5. */
  6. private void initCamera(SurfaceHolder surfaceHolder) {
  7. try {
  8. // 打开摄像头驱动和初始化硬件参数。
  9. CameraManager.get().openDriver(surfaceHolder);
  10. } catch (IOException ioe) {
  11. return;
  12. } catch (RuntimeException e) {
  13. return;
  14. }
  15. if (handler == null) {
  16. // 这个类处理所有的消息,包括为捕获的异常
  17. handler = new CaptureActivityHandler(this, decodeFormats,
  18. characterSet);
  19. }
  20. }
[java] view plaincopy
  1. /**
  2. * 当 Activity 失去焦点时调用
  3. */
  4. @Override
  5. protected void onPause() {
  6. super.onPause();
  7. if (handler != null) {
  8. // 退出同步
  9. handler.quitSynchronously();
  10. handler = null;
  11. }
  12. // 关闭摄像头驱动程序,如果仍在使用
  13. CameraManager.get().closeDriver();
  14. }
  15. /**
  16. * 销毁时调用
  17. */
  18. @Override
  19. protected void onDestroy() {
  20. /**
  21. * 启动一个有序的关机之前提交的
  22. *
  23. * 任务执行,但没有新任务将被接受。
  24. *
  25. * 如果已关闭,没有任何附加效果
  26. */
  27. inactivityTimer.shutdown();
  28. super.onDestroy();
  29. }

最后我们来看看,如何处理扫描结果的,这里用到了 CaptureActivityHandler 这个类继承 Handler,类中封装了解码线程类DecodeThread 这里我们先看 当前扫描Activity如何处理扫描后处理结果的函数 public void handleDecode(Result result, Bitmap barcode) ;这个函数主要是处理扫描成功后效果,拿到扫描后回传结果等

[java] view plaincopy
  1. /**
  2. * 处理扫描结果
  3. *
  4. * @param result
  5. * @param barcode
  6. */
  7. public void handleDecode(Result result, Bitmap barcode) {
  8. // 试图取消此任务的执行 , 创建并执行将启用的一一个射击动作在给定的延迟。
  9. inactivityTimer.onActivity();
  10. // 打声音和振动
  11. playBeepSoundAndVibrate();
  12. // 获取扫描结果
  13. String resultString = result.getText();
  14. if (resultString.equals("")) {
  15. Toast.makeText(MipcaActivityCapture.this, "Scan failed!",
  16. Toast.LENGTH_SHORT).show();
  17. } else {
  18. // 回传扫描结果
  19. Intent resultIntent = new Intent();
  20. Bundle bundle = new Bundle();
  21. bundle.putString("result", resultString);
  22. bundle.putParcelable("bitmap", barcode);
  23. resultIntent.putExtras(bundle);
  24. this.setResult(RESULT_OK, resultIntent);
  25. }
  26. MipcaActivityCapture.this.finish();
  27. }

(2)以上只是浅谈如何调用相机,以及处理扫描效果等,接线来深入分析扫描线程,及处理扫描效果 handler 回调等,刚才有讲到CaptureActivityHandler 类这个类处理Handler消息下面就看相关代码。
   首先我们看下这个类的构造函数,在这个构造函数中,构造了解码线程类 DecodeThread类,这个类非常关键稍后会讲到,这里要注意,我们在构建中已经启用线程     decodeThread.start();

[java] view plaincopy
  1. /**
  2. *
  3. * @param activity  处理 Handler消息的Activity
  4. * @param decodeFormats 条形码格式结合
  5. * @param characterSet   字符集
  6. */
  7. public CaptureActivityHandler(MipcaActivityCapture activity,
  8. Vector<BarcodeFormat> decodeFormats, String characterSet) {
  9. this.activity = activity;
  10. decodeThread = new DecodeThread(activity, decodeFormats, characterSet,
  11. new ViewfinderResultPointCallback(activity.getViewfinderView()));
  12. decodeThread.start();
  13. state = State.SUCCESS;
  14. //开始自己捕捉预览解码。
  15. CameraManager.get().startPreview();
  16. restartPreviewAndDecode();
  17. }

看到了构造处理消息 Handler类代码块,那么还记得那个模块构造的该类对象不,我们刚才有讲到相机初始化模块请看一下代码。

[java] view plaincopy
  1. /**
  2. * 初始化
  3. *
  4. * @param surfaceHolder
  5. */
  6. private void initCamera(SurfaceHolder surfaceHolder) {
  7. try {
  8. // 打开摄像头驱动和初始化硬件参数。
  9. CameraManager.get().openDriver(surfaceHolder);
  10. } catch (IOException ioe) {
  11. return;
  12. } catch (RuntimeException e) {
  13. return;
  14. }
  15. if (handler == null) {
  16. // 这个类处理所有的消息,包括为捕获的异常
  17. handler = new CaptureActivityHandler(this, decodeFormats,
  18. characterSet);
  19. }
  20. }

接下来分析这个类的关键模块 Handler消息处理模块 handleMessage()消息处理函数,这里肯定大家会对一些用到的Id感到好奇其实这里的Id是定义在 下图中xml文件中,大家可以详细看下。

接下来言归正传,还是继续分析 handleMessage(),这里有用到枚举类,用来标记状态,public void handleMessage(Message message)函数都有注释,这里就不详解了。

[java] view plaincopy
  1. private enum State {
  2. PREVIEW, // 预览
  3. SUCCESS, // 成功
  4. DONE // 完成
  5. }
[java] view plaincopy
  1. @Override
  2. public void handleMessage(Message message) {
  3. switch (message.what) {
  4. case R.id.auto_focus:
  5. // Log.d(TAG, "Got auto-focus message");
  6. /**
  7. * 当一个自动对焦结束,开始另一个。这是
  8. *
  9. * 最接近的
  10. *
  11. * 连续AF似乎找了一点,但我不确定是什么
  12. *
  13. * 做其他的。
  14. */
  15. if (state == State.PREVIEW) {
  16. CameraManager.get().requestAutoFocus(this, R.id.auto_focus);
  17. }
  18. break;
  19. case R.id.restart_preview:
  20. Log.d(TAG, "Got restart preview message");
  21. //重新启动预览和解码
  22. restartPreviewAndDecode();
  23. break;
  24. case R.id.decode_succeeded:  //得到解码成功消息
  25. Log.d(TAG, "Got decode succeeded message");
  26. state = State.SUCCESS;
  27. Bundle bundle = message.getData();
  28. /***********************************************************************/
  29. Bitmap barcode = bundle == null ? null : (Bitmap) bundle
  30. .getParcelable(DecodeThread.BARCODE_BITMAP);
  31. activity.handleDecode((Result) message.obj, barcode);
  32. break;
  33. case R.id.decode_failed:
  34. /**
  35. * 我们尽可能快地解码,所以当一个解码失败,开始另一个。
  36. */
  37. state = State.PREVIEW;
  38. CameraManager.get().requestPreviewFrame(decodeThread.getHandler(),
  39. R.id.decode);
  40. break;
  41. case R.id.return_scan_result:
  42. Log.d(TAG, "返回扫描结果消息");
  43. activity.setResult(Activity.RESULT_OK, (Intent) message.obj);
  44. activity.finish();
  45. break;
  46. case R.id.launch_product_query:
  47. Log.d(TAG, "产品查询消息");
  48. String url = (String) message.obj;
  49. Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
  50. intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
  51. activity.startActivity(intent);
  52. break;
  53. }
  54. }

下面看下解码过程中,用到了两个关键函数 quitSynchronously()该函数主要是处理相机关闭相机预览帧,阻止线程,清空Handler消息队列。细心的同学会发现该函数是在 处理扫描的Activity 生命周期函数 onPause()函数中用到

[java] view plaincopy
  1. /**
  2. * 退出同步
  3. */
  4. public void quitSynchronously() {
  5. state = State.DONE;
  6. // 告诉相机停止绘制预览帧。
  7. CameraManager.get().stopPreview();
  8. Message quit = Message.obtain(decodeThread.getHandler(), R.id.quit);
  9. /**
  10. *
  11. * 将此消息发送到指定的处理程序 {@link #getTarget}. 如果该字段未被设置,则会引发一个空指针异常。
  12. */
  13. quit.sendToTarget();
  14. try {
  15. /**
  16. * 阻止当前线程 <code>Thread.currentThread()</code>) 接收器完成它的执行和死亡。
  17. *
  18. * @throws InterruptedException
  19. *  如果当前线程被中断。 当前线程的中断状态将在异常之前被清除
  20. *
  21. * @see Object#notifyAll
  22. * @see java.lang.ThreadDeath
  23. */
  24. decodeThread.join();
  25. } catch (InterruptedException e) {
  26. // continue
  27. }
  28. // 绝对肯定我们不会发送任何排队的消息
  29. removeMessages(R.id.decode_succeeded);
  30. removeMessages(R.id.decode_failed);
  31. }

下面还有一个关键模块,主要是处理,重新启动预览和解码函数 restartPreviewAndDecode() 这里有用到 CameraManager类 该类是相机管理类稍后会讲到

[java] view plaincopy
  1. /**
  2. * 重新启动预览和解码
  3. */
  4. private void restartPreviewAndDecode() {
  5. if (state == State.SUCCESS) {
  6. state = State.PREVIEW;
  7. CameraManager.get().requestPreviewFrame(decodeThread.getHandler(),
  8. R.id.decode);
  9. CameraManager.get().requestAutoFocus(this, R.id.auto_focus);
  10. activity.drawViewfinder();
  11. }
  12. }

(3)讲完了Handler消息回传可能大家还是不明白如何加码过程,接下来深入分析解码线程类 DecodeThread类,首先我们来看下这个类的构造函数,这里用到了 CountDownLatch 类这个类可能大家也不常用,我也是第一次接触,这里可以参考博客

http://blog.csdn.NET/shihuacai/article/details/8856370 讲的很详细,

CountDownLatch,一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。

构造函数中用到 Vector<BarcodeFormat> decodeFormats 集合,该集合主要是封装了解码格式,用到了DecodeFormatManager 解码格式管理类,稍后会讲到改类。

[java] view plaincopy
  1. /**
  2. *
  3. * @param activity
  4. * @param decodeFormats     条形码格式
  5. * @param characterSet      字符集
  6. * @param resultPointCallback  结果回调接口
  7. */
  8. DecodeThread(MipcaActivityCapture activity,
  9. Vector<BarcodeFormat> decodeFormats, String characterSet,
  10. ResultPointCallback resultPointCallback) {
  11. this.activity = activity;
  12. /**
  13. * 构建了一个countdownlatch与给定的计数初始化。
  14. *
  15. * * @param count 次数 {@link #countDown}  必须调用
  16. *
  17. * 在线程可以通过 {@link #await}
  18. *
  19. *  @throws IllegalArgumentException  如果 {@code count}  是负数引发异常
  20. */
  21. handlerInitLatch = new CountDownLatch(1);
  22. hints = new Hashtable<DecodeHintType, Object>(3);
  23. //DecodeFormatManager类   这里把之前添加好的几个常量类,添加到解码的方法里面去,这样解码方法里面就有了所有的解码格式了,包括一维码和二维码。
  24. if (decodeFormats == null || decodeFormats.isEmpty()) {
  25. decodeFormats = new Vector<BarcodeFormat>();
  26. decodeFormats.addAll(DecodeFormatManager.ONE_D_FORMATS);
  27. decodeFormats.addAll(DecodeFormatManager.QR_CODE_FORMATS);
  28. decodeFormats.addAll(DecodeFormatManager.DATA_MATRIX_FORMATS);
  29. }
  30. /**
  31. * DecodeHintType 解码提示类型
  32. *
  33. * DecodeHintType.POSSIBLE_FORMATS 枚举值
  34. */
  35. hints.put(DecodeHintType.POSSIBLE_FORMATS, decodeFormats);
  36. if (characterSet != null) {
  37. hints.put(DecodeHintType.CHARACTER_SET, characterSet);
  38. }
  39. hints.put(DecodeHintType.NEED_RESULT_POINT_CALLBACK,
  40. resultPointCallback);
  41. }

下面我们就看下线程类最关键模块线程体,这里是在线程中构造 DecodeHandler 类Handler ,下面还有一个函数处理Handler

[java] view plaincopy
  1. @Override
  2. public void run() {
  3. /**
  4. * 初始化当前线程作为一个活套。
  5. *
  6. * 这给了你一个机会来创建处理程序,然后引用
  7. *
  8. * 这活套,然后再开始循环。一定要打电话
  9. *
  10. * {@link #loop()} 调用此方法后,通过调用 {@link #quit()}.
  11. *
  12. * 如果当前计数等于零,则没有发生任何事情。
  13. */
  14. Looper.prepare();
  15. handler = new DecodeHandler(activity, hints);
  16. /**
  17. * decrements伯爵的闩锁释放所有等待的线程,如果在达到零计数。
  18. *
  19. * 如果当前计数大于零则递减。
  20. *
  21. * 如果新计数为零,则所有等待线程被重新启用
  22. *
  23. * 线程调度的目的。
  24. *
  25. * 如果当前计数等于零,则没有发生任何事情。
  26. */
  27. handlerInitLatch.countDown();
  28. /**
  29. * 调用此方法后,通过调用
  30. *
  31. * {@link #quit()} 结束循环。
  32. */
  33. Looper.loop();
  34. }
[java] view plaincopy
  1. Handler getHandler() {
  2. try {
  3. /**
  4. * 导致当前线程等待锁存已计数的零,除非该线程是 {@linkplain Thread#interrupt interrupted}.
  5. *
  6. *
  7. *
  8. * 如果当前计数为零,则此方法立即返回。 如果当前计数大于零,则电流
  9. *
  10. * 线程成为禁用线程调度的目的和谎言休眠,直到有两点发生:
  11. *
  12. * 计数达到零由于调用的 {@link #countDown} 法;或 其他线程 { @linkplain
  13. * Thread#interrupt interrupts}
  14. * 当前线程。
  15. */
  16. handlerInitLatch.await();
  17. } catch (InterruptedException ie) {
  18. // continue?
  19. }
  20. return handler;
  21. }

(4)这些Handler和线程在哪里发挥它的价值呢,接下来请看 CameraManager 相机管理类,在CaptureActivityHandler 构造类中有提到CameraManager类的函数调用,接下了深入了解这个类,这个类被封装成了单例类,那么在哪里初始化的呢,请看代码 CameraManager.init(getApplication()); 这行代码肯定很熟悉,在扫描二维码Activity的 onCreate()函数中出现过。 CameraManager 的get()函数提供了当前类的对象。

构造函数中提供了三个类 CameraConfigurationManager相机配置管理器类和 AutoFocusCallback 类回调接口用来通知自动对焦完成,PreviewCallback类 用于提供预览帧的副本的回调接口,稍后会讲到这些类。

[java] view plaincopy
  1. /**
  2. * 随着调用活动的上下文初始化静态对象。
  3. *
  4. * @param context
  5. *            The Activity which wants to use the camera.
  6. */
  7. public static void init(Context context) {
  8. if (cameraManager == null) {
  9. cameraManager = new CameraManager(context);
  10. }
  11. }
  12. /**
  13. * 得到cameramanager singleton实例。
  14. *
  15. * @return 返回一个参考的cameramanager 单例
  16. */
  17. public static CameraManager get() {
  18. return cameraManager;
  19. }
  20. private CameraManager(Context context) {
  21. this.context = context;
  22. this.configManager = new CameraConfigurationManager(context);
  23. // 摄像机。setoneshotpreviewcallback() 在 Android (蛋糕版本)的竞争条件,所以我们使用旧的
  24. // 摄像机。setpreviewcallback() 1.5和更早。 在 Android (甜甜圈)和后,我们需要使用
  25. // 一次打回的球越打越高,因为年纪越大,就可以淹没系统,导致它
  26. // 从内存中耗尽。我们不能用sdk_int由于引入的Donut SDK。
  27. // useoneshotpreviewcallback =整数。parseInt(版本。版本。SDK)>
  28. // build.version_codes.cupcake;
  29. useOneShotPreviewCallback = Integer.parseInt(Build.VERSION.SDK) > 3; // 3
  30. // =
  31. // Cupcake
  32. previewCallback = new PreviewCallback(configManager,
  33. useOneShotPreviewCallback);
  34. autoFocusCallback = new AutoFocusCallback();
  35. }

接下来我们就看下如何打开相机的,SurfaceHolder这个接口肯定不陌生,这个函数在相机初始化函数中有调用

initCamera(SurfaceHolder surfaceHolder),其实相机说拍摄的到的东西都是在SurfaceView上呈现在你眼前的,这里对 SurfaceView最关键的操作类SurfaceHolder 。这里有用到 CameraConfigurationManager相机配置管理类对象,稍后会讲到。

[java] view plaincopy
  1. /**
  2. * 打开摄像头驱动和初始化硬件参数。
  3. *
  4. * @param holder
  5. *            相机将绘制预览帧的表面对象。
  6. *
  7. * @throws IOException
  8. *             异常表示相机驱动程序未能打开。
  9. *
  10. */
  11. public void openDriver(SurfaceHolder holder) throws IOException {
  12. if (camera == null) {
  13. camera = Camera.open();
  14. if (camera == null) {
  15. throw new IOException();
  16. }
  17. /**
  18. * *设置用于实时预览的 {@link Surface}
  19. *
  20. * *表面或表面纹理是必要的预览,和
  21. *
  22. * 预览是必要的拍照。相同的表面可以重新设定
  23. *
  24. * 没有伤害。设置一个预览面将不设置任何预览表面
  25. *
  26. * 纹理是通过 {@link #setPreviewTexture}.。
  27. *
  28. *
  29. *
  30. * <P>
  31. * 的{@link #setPreviewTexture必须已经包含一个表面时,这
  32. *
  33. * 方法被称为。如果你使用的是Android {@link android.view.SurfaceView},
  34. *
  35. * 你需要登记一个{@link android.view.SurfaceView},用。
  36. *
  37. * {@link SurfaceHolder#addCallback(SurfaceHolder.Callback)} 和
  38. *
  39. * {@link SurfaceHolder.Callback#surfaceCreated(SurfaceHolder)} 之前
  40. *
  41. * 通知 setpreviewdisplay()或启动预览。
  42. *
  43. * <P>
  44. * 方法必须调用之前{@link #startPreview()}. 。这个
  45. *
  46. * 一个例外是,如果预览表面没有设置(或设置为空)
  47. *
  48. * 在startpreview()叫,那么这种方法可以调用一次
  49. *
  50. * 与非空参数设置预览表面。(这让
  51. *
  52. * 相机设置和表面创建发生在平行,节省时间。)
  53. *
  54. * 预览版在运行时可能没有其他更改。
  55. *
  56. *
  57. *
  58. * @param夹含表面上放置预览,
  59. *
  60. *                  或空删除预览表面
  61. *
  62. * @抛出IOException如果方法失败(例如,如果表面
  63. *
  64. *                              不可用或不适合)。
  65. */
  66. camera.setPreviewDisplay(holder);
  67. if (!initialized) {
  68. initialized = true;
  69. configManager.initFromCameraParameters(camera);
  70. }
  71. configManager.setDesiredCameraParameters(camera);
  72. // FIXME
  73. // SharedPreferences prefs =
  74. // PreferenceManager.getDefaultSharedPreferences(context);
  75. // 是否使用前灯
  76. // if (prefs.getBoolean(PreferencesActivity.KEY_FRONT_LIGHT, false))
  77. // {
  78. // FlashlightManager.enableFlashlight();
  79. // }
  80. FlashlightManager.enableFlashlight();
  81. }
  82. }
[java] view plaincopy
  1. /**
  2. * 关闭摄像头驱动程序,如果仍在使用
  3. */
  4. public void closeDriver() {
  5. if (camera != null) {
  6. FlashlightManager.disableFlashlight();
  7. camera.release();
  8. camera = null;
  9. }
  10. }

接下来看相机的启用和关闭,这里主要是对相机进行操作,相机的绘制预览帧,及监听等

[java] view plaincopy
  1. /**
  2. * 关闭摄像头驱动程序,如果仍在使用
  3. */
  4. public void closeDriver() {
  5. if (camera != null) {
  6. FlashlightManager.disableFlashlight();
  7. camera.release();
  8. camera = null;
  9. }
  10. }
  11. /**
  12. * 要求摄像机硬件开始绘制预览帧到屏幕上。
  13. */
  14. public void startPreview() {
  15. if (camera != null && !previewing) {
  16. /**
  17. * 开始捕获并绘制预览帧到屏幕。
  18. *
  19. * 预览将不会真正开始,直到提供一个表面
  20. *
  21. * {@link #setPreviewDisplay(SurfaceHolder)} or
  22. * {@link #setPreviewTexture(SurfaceTexture)}.
  23. *
  24. *
  25. * 如果 {@link #setPreviewCallback(Camera.PreviewCallback)},
  26. * {@link #setOneShotPreviewCallback(Camera.PreviewCallback)}, or
  27. * {@link #setPreviewCallbackWithBuffer(Camera.PreviewCallback)}
  28. *
  29. * 是称{@link Camera.PreviewCallback#onPreviewFrame(byte[], Camera)}
  30. *
  31. ** 预览数据将成为可用。
  32. */
  33. camera.startPreview();
  34. previewing = true;
  35. }
  36. }
  37. /**
  38. * 告诉相机停止绘制预览帧。
  39. */
  40. public void stopPreview() {
  41. if (camera != null && previewing) {
  42. if (!useOneShotPreviewCallback) {
  43. /**
  44. * 除此之外,还安装了一个回调函数来调用每个预览帧
  45. *
  46. * 在屏幕上显示。这个回调将被反复调用
  47. *
  48. * 只要预览是活动的。这种方法可以随时调用,
  49. *
  50. * 即使预览是活的。其他预览回调
  51. *
  52. * 重写。
  53. *
  54. * 如果你使用的是预览数据来创建视频或静止图像,
  55. *
  56. * 强烈考虑使用 {@link android.media.MediaActionSound}
  57. *
  58. * 到
  59. *
  60. * 正确地显示图像捕捉或记录开始/停止给用户
  61. */
  62. camera.setPreviewCallback(null);
  63. }
  64. camera.stopPreview();
  65. previewCallback.setHandler(null, 0);
  66. autoFocusCallback.setHandler(null, 0);
  67. previewing = false;
  68. }
  69. }

下面看相机,执行对焦等相关函数 requestPreviewFrame() 一个单独的预览框将返回给处理程序提供的处理。在CaptureActivityHandler类的handleMessage()函数和 restartPreviewAndDecode()函数中有调用,用户解码失败后的重新对焦,和重新启动预览和解码时有调用。

requestAutoFocus()请求相机的硬件来执行自动对焦。与requestPreviewFrame()出现的位置同样有调用。

这里有讲到两个重要的监听类 PreviewCallback类:用于提供预览帧的副本的回调接口,AutoFocusCallback类: 回调接口用来通知自动对焦完成,这两个类是相机回调监听接口,提供了设置Handler和,回调函数。requestPreviewFramerequestPreviewFramerequestPreviewFramerequestPreviewFrame

[java] view plaincopy
  1. /**
  2. * 一个单独的预览框将返回给处理程序提供的处理。数据将作为字节到达
  3. * 在message.obj场,宽度和高度编码为message.arg1和message.arg2, 分别。
  4. *
  5. * @param handler
  6. *            发送消息的处理程序
  7. *
  8. * @param message
  9. *            要发送的消息的字段。
  10. *
  11. */
  12. public void requestPreviewFrame(Handler handler, int message) {
  13. if (camera != null && previewing) {
  14. previewCallback.setHandler(handler, message);
  15. if (useOneShotPreviewCallback) {
  16. /**
  17. * 安装在下一个预览帧中调用的回调函数
  18. *
  19. * 除了在屏幕上显示。一次调用之后
  20. *
  21. * 回调被清除。这种方法可以称为任何时间,甚至当
  22. *
  23. * 预览是活的。其他预览回调重写
  24. *
  25. * 如果你使用的是预览数据来创建视频或静止图像,
  26. *
  27. * 强烈考虑使用 {@link android.media.MediaActionSound}
  28. *
  29. * 正确地显示图像捕捉或记录开始/停止给用户。
  30. */
  31. camera.setOneShotPreviewCallback(previewCallback);
  32. } else {
  33. /**
  34. * 安装一个回调以供每个预览帧调用
  35. *
  36. * 在屏幕上显示。这个回调将被反复调用
  37. *
  38. * 只要预览是活动的。这种方法可以随时调用,
  39. *
  40. * 即使预览是活的。其他预览回调
  41. *
  42. * 重写。
  43. *
  44. * 如果你使用的是预览数据来创建视频或静止图像,
  45. *
  46. * 强烈考虑使用 {@link android.media.MediaActionSound}
  47. *
  48. * 正确地显示图像捕捉或记录开始/停止给用户
  49. *
  50. ** @param 可接收每个预览帧的副本的回调对象
  51. *            ,
  52. *
  53. * @see看 android.media.MediaActionSound
  54. *
  55. */
  56. camera.setPreviewCallback(previewCallback);
  57. }
  58. }
  59. }
  60. /**
  61. * 请求相机的硬件来执行自动对焦。
  62. *
  63. * @param处理器处理通知时,自动对焦完成。
  64. * @param消息的消息传递。
  65. */
  66. public void requestAutoFocus(Handler handler, int message) {
  67. if (camera != null && previewing) {
  68. autoFocusCallback.setHandler(handler, message);
  69. // Log.d(TAG, "Requesting auto-focus callback");
  70. /**
  71. * 启动相机自动对焦,并注册一个回调函数来运行
  72. *
  73. * 相机聚焦。此方法仅在预览时有效
  74. *
  75. * (之间 {@link #startPreview()} and before {@link #stopPreview()})
  76. *
  77. * 来电者应检查 {@link android.hardware.Camera.Parameters#getFocusMode()}
  78. *
  79. * 这种方法应该被称为。如果摄像头不支持自动对焦,
  80. *
  81. * 这是一个没有OP和 {@link AutoFocusCallback#onAutoFocus(boolean, Camera)}
  82. *
  83. * 回调将立即调用。
  84. *
  85. * 如果你的申请不应该被安装
  86. *
  87. * 在设备没有自动对焦,您必须声明,您的应用程序
  88. *
  89. * 使用自动对焦
  90. *
  91. * <a href="{@docRoot}
  92. * guide/topics/manifest/uses-feature-element.html
  93. * "><uses-feature></a>
  94. *
  95. * manifest element.
  96. *
  97. * 如果当前闪光模式不
  98. * {@link android.hardware.Camera.Parameters#FLASH_MODE_OFF},
  99. *
  100. * 在自动对焦时,根据驾驶和相机的硬件
  101. *
  102. * 自动曝光锁定
  103. * {@link android.hardware.Camera.Parameters#getAutoExposureLock()}
  104. *
  105. * 不要在自动对焦和之后的变化。但自动对焦程序可能会停止
  106. *
  107. * 自动曝光和自动白平衡在聚焦过程中瞬时。
  108. *
  109. * 停止预览 {@link #stopPreview()}
  110. *
  111. * 或触发仍然
  112. *
  113. * 图像捕捉
  114. * {@link #takePicture(Camera.ShutterCallback, Camera.PictureCallback, Camera.PictureCallback)}
  115. * ,
  116. *
  117. * 不会改变
  118. *
  119. * 焦点位置。应用程序必须调用cancelautofocus重置
  120. *
  121. *
  122. * 如果对焦成功,可以考虑使用 * {@link android.media.MediaActionSound} 正确播放自动对焦
  123. *
  124. * 成功的声音给用户。
  125. *
  126. *
  127. ** @param CB回调运行
  128. *
  129. * @看 cancelautofocus()
  130. *
  131. * @看
  132. *    android.hardware.Camera.Parameters#setAutoExposureLock(boolean)
  133. *
  134. * @看 android.hardware.Camera.Parameters#setAutoWhiteBalanceLock(
  135. *    boolean)
  136. *
  137. *    看 android.media.MediaActionSound
  138. */
  139. camera.autoFocus(autoFocusCallback);
  140. }
  141. }

下面   下

AutoFocusCallback 相机监听接口,对焦完成发送Handler消息是通知

[java] view plaincopy
  1. /**
  2. *
  3. * 回调接口用来通知自动对焦完成
  4. *
  5. * 不支持自动对焦的设备 将返回 boolean 类型的值假
  6. *
  7. * 回调到这个接口。如果您的应用程序需要自动对焦和 不应安装在设备没有自动对焦,您必须 声明你的应用程序使用
  8. *
  9. * Android 相机自动对焦源码请参考 <a href="{@docRoot}
  10. * guide/topics/manifest/uses-feature-element.html"><uses-feature></a>
  11. * manifest element.</p>
  12. *
  13. * 看#自动对焦 AutoFocusCallback
  14. *
  15. * 不建议使用新{@link android.hardware.camera2} API的新硬件应用。
  16. *
  17. */
  18. final class AutoFocusCallback implements Camera.AutoFocusCallback {
  19. private static final String TAG = AutoFocusCallback.class.getSimpleName();
  20. // 自动对焦区间MS
  21. private static final long AUTOFOCUS_INTERVAL_MS = 1500L;
  22. private Handler autoFocusHandler;
  23. private int autoFocusMessage;
  24. void setHandler(Handler autoFocusHandler, int autoFocusMessage) {
  25. this.autoFocusHandler = autoFocusHandler;
  26. this.autoFocusMessage = autoFocusMessage;
  27. }
  28. /**
  29. * 当你把摄像机自动对焦完成时。如果相机
  30. *
  31. * 如果相机不支持自动对焦和自动对焦,将会调用  onautofocus 将值立即回传
  32. *
  33. * <code>成功< /code>设置为<code>真< /code> 否则为假
  34. * 。
  35. *
  36. * 自动对焦程序不会自动曝光和自动白色 平衡完成后。
  37. *
  38. * @param成功真正的病灶是否成功,如果不假
  39. *
  40. * @param相机相机服务对象
  41. *
  42. * @看到Android的硬件。相机参数# setautoexposurelock(布尔)。
  43. *
  44. * @看到Android的硬件。相机参数# setautowhitebalancelock(布尔)。
  45. */
  46. @Override
  47. public void onAutoFocus(boolean success, Camera camera) {
  48. if (autoFocusHandler != null) {
  49. Message message = autoFocusHandler.obtainMessage(autoFocusMessage,
  50. success);
  51. autoFocusHandler.sendMessageDelayed(message, AUTOFOCUS_INTERVAL_MS);
  52. autoFocusHandler = null;
  53. } else {
  54. Log.d(TAG, "自动对焦回调,但没有处理程序");
  55. }
  56. }
  57. }

PreviewCallback类监听接口,onPreviewFrame()函数:预览帧显示,拿到相机捕捉的画面和返回的byte[]字节数据。

[java] view plaincopy
  1. /**
  2. * 用于提供预览帧的副本的回调接口
  3. *
  4. * 他们被显示。
  5. *
  6. *
  7. * “看# PreviewCallback(Camera.PreviewCallback)
  8. *
  9. * “看# OneShotPreviewCallback(Camera.PreviewCallback)
  10. *
  11. * “看# PreviewCallbackWithBuffer(Camera.PreviewCallback)
  12. *
  13. * “看# startPreview()
  14. *
  15. *
  16. *
  17. * @deprecated 我们建议使用新的 {@link android.hardware.camera2} 新应用程序接口。
  18. *
  19. */
  20. final class PreviewCallback implements Camera.PreviewCallback {
  21. private static final String TAG = PreviewCallback.class.getSimpleName();
  22. private final CameraConfigurationManager configManager;
  23. // 使用一次预览回调
  24. private final boolean useOneShotPreviewCallback;
  25. private Handler previewHandler;
  26. private int previewMessage;
  27. PreviewCallback(CameraConfigurationManager configManager,
  28. boolean useOneShotPreviewCallback) {
  29. this.configManager = configManager;
  30. this.useOneShotPreviewCallback = useOneShotPreviewCallback;
  31. }
  32. /**
  33. *
  34. * @param previewHandler
  35. *            预览处理程序
  36. * @param previewMessage
  37. *            预览信息
  38. */
  39. void setHandler(Handler previewHandler, int previewMessage) {
  40. this.previewHandler = previewHandler;
  41. this.previewMessage = previewMessage;
  42. }
  43. /**
  44. * 称为预览帧显示。调用这个回调在事件线程 {@link #open(int)}被称为。
  45. *
  46. * 如果使用 {@link android.graphics.ImageFormat#YV12}
  47. *
  48. * 格式的图形,参见方程 {@link Camera.Parameters#setPreviewFormat}
  49. *
  50. * 在预览回拨中的像素数据的安排
  51. *
  52. * 缓冲区
  53. *
  54. * @param 数据定义的格式的预览帧的内容
  55. *            通过 {@link android.graphics.ImageFormat},可以查询 具有
  56. *            {@link android.hardware.Camera.Parameters#getPreviewFormat()}.
  57. *
  58. *            如果
  59. *            {@link android.hardware.Camera.Parameters#setPreviewFormat(int)}
  60. *            永远不会被调用,默认的是 YCbCr_420_SP (NV21) .format
  61. * @param camera
  62. *            相机服务对象。
  63. */
  64. public void onPreviewFrame(byte[] data, Camera camera) {
  65. // 获取相机分辨率
  66. Point cameraResolution = configManager.getCameraResolution();
  67. if (!useOneShotPreviewCallback) {
  68. camera.setPreviewCallback(null);
  69. }
  70. if (previewHandler != null) {
  71. Message message = previewHandler.obtainMessage(previewMessage,
  72. cameraResolution.x, cameraResolution.y, data);
  73. message.sendToTarget();
  74. previewHandler = null;
  75. } else {
  76. Log.d(TAG, "预览回调,但没有处理程序");
  77. Log.d(TAG, "Got preview callback, but no handler for it");
  78. }
  79. }
  80. }

接下来可以通过相机拿到屏幕相关参数,来处理捕捉到的数据,getFramingRect()通过计算屏幕分辨率啦计算坐标位置,

getFramingRectInPreview()函数还是在计算坐标,buildLuminanceSource()函数非常重要,功能就是拿到YUV预览框宽高。在指定的Rect坐标内进行剪裁,拿到预览字符串判断剪裁大小,最后生成 PlanarYUVLuminanceSource
类对象,这个类会将结果生成 Bitmap,该函数在 DecodeHandler类中调用,用于计算要扫描成功后要捕捉的图片。

[java] view plaincopy
  1. /**
  2. * 计算框架矩形的界面应显示用户的位置 条码。这个目标有助于调整以及迫使用户持有该设备 足够远,以确保图像将集中。
  3. *
  4. * @return “返回”矩形在窗口坐标中绘制。
  5. */
  6. public Rect getFramingRect() {
  7. //获取屏幕分辨率
  8. Point screenResolution = configManager.getScreenResolution();
  9. //Rect framingRect 直接适用于矩形四整数坐标。矩形
  10. if (framingRect == null) {
  11. if (camera == null) {
  12. return null;
  13. }
  14. int width = screenResolution.x * 3 / 4;
  15. //当宽度最小框宽度
  16. if (width < MIN_FRAME_WIDTH) {
  17. width = MIN_FRAME_WIDTH;
  18. } else if (width > MAX_FRAME_WIDTH) {
  19. width = MAX_FRAME_WIDTH;
  20. }
  21. int height = screenResolution.y * 3 / 4;
  22. //当高度小于最小高度
  23. if (height < MIN_FRAME_HEIGHT) {
  24. height = MIN_FRAME_HEIGHT;
  25. } else if (height > MAX_FRAME_HEIGHT) {
  26. height = MAX_FRAME_HEIGHT;
  27. }
  28. int leftOffset = (screenResolution.x - width) / 2;
  29. int topOffset = (screenResolution.y - height) / 2;
  30. //重构一个坐标
  31. framingRect = new Rect(leftOffset, topOffset, leftOffset + width,
  32. topOffset + height);
  33. Log.d(TAG, "Calculated framing rect: " + framingRect);
  34. }
  35. return framingRect;
  36. }
[java] view plaincopy
  1. /**
  2. * 像 {@link #getFramingRect} 但坐标从预览
  3. *
  4. * 帧,而不是用户界面/屏幕。
  5. */
  6. public Rect getFramingRectInPreview() {
  7. if (framingRectInPreview == null) {
  8. Rect rect = new Rect(getFramingRect());
  9. // 获取相机分辨率
  10. Point cameraResolution = configManager.getCameraResolution();
  11. // 获取屏幕分辨率
  12. Point screenResolution = configManager.getScreenResolution();
  13. rect.left = rect.left * cameraResolution.y / screenResolution.x;
  14. rect.right = rect.right * cameraResolution.y / screenResolution.x;
  15. rect.top = rect.top * cameraResolution.x / screenResolution.y;
  16. rect.bottom = rect.bottom * cameraResolution.x / screenResolution.y;
  17. framingRectInPreview = rect;
  18. }
  19. return framingRectInPreview;
  20. }
[java] view plaincopy
  1. /**
  2. * 一个建立在适当的luminancesource对象工厂方法
  3. *
  4. * 预览缓冲区的格式,被描述为camera.parameters。
  5. *
  6. * @param data
  7. *            数据预览框。
  8. * @param width
  9. *            宽度图像的宽度。
  10. * @param height
  11. *            高度图像的高度。
  12. * @返回 planaryuvluminancesource 实例。
  13. */
  14. public PlanarYUVLuminanceSource buildLuminanceSource(byte[] data,
  15. int width, int height) {
  16. Rect rect = getFramingRectInPreview();
  17. int previewFormat = configManager.getPreviewFormat();
  18. String previewFormatString = configManager.getPreviewFormatString();
  19. switch (previewFormat) {
  20. /**
  21. * 这是标准的安卓格式,所有设备都需要支持。
  22. *
  23. * 在理论上,这是我们唯一关心的。
  24. */
  25. case PixelFormat.YCbCr_420_SP:
  26. /**
  27. * 这种格式从未在野外见过,但兼容
  28. *
  29. * 我们只关心
  30. *
  31. * 关于“关于”的,所以允许它。
  32. */
  33. case PixelFormat.YCbCr_422_SP:
  34. return new PlanarYUVLuminanceSource(data, width, height, rect.left,
  35. rect.top, rect.width(), rect.height());
  36. default:
  37. /**
  38. * 三星的时刻不正确地使用这个变量,而不是
  39. *
  40. * “文本”版本。
  41. *
  42. * 幸运的是,它也有所有的数据前,所以我们可以阅读
  43. *
  44. * 它
  45. */
  46. if ("yuv420p".equals(previewFormatString)) {
  47. return new PlanarYUVLuminanceSource(data, width, height,
  48. rect.left, rect.top, rect.width(), rect.height());
  49. }
  50. }
  51. throw new IllegalArgumentException("Unsupported picture format: "
  52. + previewFormat + '/' + previewFormatString);
  53. }

(4)讲到这里可能还是没有明白到底是如何解码的,接下进入解码DecodeHandler类,该类主要是提供了捕捉,二维码截图后的矩形图像,生成Bitmap图像。首先来看下构造函数,在哪里构造的,可能你并未发现,我告诉你在解码线程DecodeThread类 run()方法体中构造的,那在哪里调用的呢,就要看getHandler()函数在哪里有调用,看下图会发现我们有三处用到它,接下来细看每个位置。

[java] view plaincopy
  1. DecodeHandler(MipcaActivityCapture activity,
  2. Hashtable<DecodeHintType, Object> hints) {
  3. multiFormatReader = new MultiFormatReader();
  4. multiFormatReader.setHints(hints);
  5. this.activity = activity;
  6. }
[java] view plaincopy
  1. @Override
  2. public void run() {
  3. /**
  4. * 初始化当前线程作为一个活套。
  5. *
  6. * 这给了你一个机会来创建处理程序,然后引用
  7. *
  8. * 这活套,然后再开始循环。一定要打电话
  9. *
  10. * {@link #loop()} 调用此方法后,通过调用 {@link #quit()}.
  11. *
  12. * 如果当前计数等于零,则没有发生任何事情。
  13. */
  14. Looper.prepare();
  15. handler = new DecodeHandler(activity, hints);
  16. /**
  17. * decrements伯爵的闩锁释放所有等待的线程,如果在达到零计数。
  18. *
  19. * 如果当前计数大于零则递减。
  20. *
  21. * 如果新计数为零,则所有等待线程被重新启用
  22. *
  23. * 线程调度的目的。
  24. *
  25. * 如果当前计数等于零,则没有发生任何事情。
  26. */
  27. handlerInitLatch.countDown();
  28. /**
  29. * 调用此方法后,通过调用
  30. *
  31. * {@link #quit()} 结束循环。
  32. */
  33. Looper.loop();
  34. }
[java] view plaincopy
  1. Handler getHandler() {
  2. try {
  3. /**
  4. * 导致当前线程等待锁存已计数的零,除非该线程是 {@linkplain Thread#interrupt interrupted}.
  5. *
  6. *
  7. *
  8. * 如果当前计数为零,则此方法立即返回。 如果当前计数大于零,则电流
  9. *
  10. * 线程成为禁用线程调度的目的和谎言休眠,直到有两点发生:
  11. *
  12. * 计数达到零由于调用的 {@link #countDown} 法;或 其他线程 { @linkplain
  13. * Thread#interrupt interrupts}
  14. * 当前线程。
  15. */
  16. handlerInitLatch.await();
  17. } catch (InterruptedException ie) {
  18. // continue?
  19. }
  20. return handler;
  21. }

(4.1)我们来看第一处调用 handleMessage(Message message),代码块,这里调用了     CameraManager.get().requestPreviewFrame()类函数,接下来进入这个函数,看到这个代码块是不是很惊讶发现这是之前看到的模块,现在知道这个Handler是被谁调用的,被谁发消息的了吧,被PreviewCallback类监听接口发出的消息。

[java] view plaincopy
  1. case R.id.decode_failed:
  2. /**
  3. * 我们尽可能快地解码,所以当一个解码失败,开始另一个。
  4. */
  5. state = State.PREVIEW;
  6. CameraManager.get().requestPreviewFrame(decodeThread.getHandler(),
  7. R.id.decode);
  8. break;
[java] view plaincopy
  1. /**
  2. * 一个单独的预览框将返回给处理程序提供的处理程序。数据将作为字节到达
  3. * 在message.obj场,宽度和高度编码为message.arg1和message.arg2, 分别。
  4. *
  5. * @param handler
  6. *            发送消息的处理程序
  7. *
  8. * @param message
  9. *            要发送的消息的字段。
  10. *
  11. */
  12. public void requestPreviewFrame(Handler handler, int message) {
  13. if (camera != null && previewing) {
  14. previewCallback.setHandler(handler, message);
  15. if (useOneShotPreviewCallback) {
  16. /**
  17. * 安装在下一个预览帧中调用的回调函数
  18. *
  19. * 除了在屏幕上显示。一次调用之后
  20. *
  21. * 回调被清除。这种方法可以称为任何时间,甚至当
  22. *
  23. * 预览是活的。其他预览回调重写
  24. *
  25. * 如果你使用的是预览数据来创建视频或静止图像,
  26. *
  27. * 强烈考虑使用 {@link android.media.MediaActionSound}
  28. *
  29. * 正确地显示图像捕捉或记录开始/停止给用户。
  30. */
  31. camera.setOneShotPreviewCallback(previewCallback);
  32. } else {
  33. /**
  34. * 安装一个回调以供每个预览帧调用
  35. *
  36. * 在屏幕上显示。这个回调将被反复调用
  37. *
  38. * 只要预览是活动的。这种方法可以随时调用,
  39. *
  40. * 即使预览是活的。其他预览回调
  41. *
  42. * 重写。
  43. *
  44. * 如果你使用的是预览数据来创建视频或静止图像,
  45. *
  46. * 强烈考虑使用 {@link android.media.MediaActionSound}
  47. *
  48. * 正确地显示图像捕捉或记录开始/停止给用户
  49. *
  50. ** @param 可接收每个预览帧的副本的回调对象
  51. *            ,
  52. *
  53. * @see看 android.media.MediaActionSound
  54. *
  55. */
  56. camera.setPreviewCallback(previewCallback);
  57. }
  58. }
  59. }

(4.2)我们来看第二处调用,quitSynchronously()这个函数也不陌生,这是退出时调用的

[java] view plaincopy
  1. /**
  2. * 退出同步
  3. */
  4. public void quitSynchronously() {
  5. state = State.DONE;
  6. // 告诉相机停止绘制预览帧。
  7. CameraManager.get().stopPreview();
  8. Message quit = Message.obtain(decodeThread.getHandler(), R.id.quit);
  9. /**
  10. *
  11. * 将此消息发送到指定的处理程序 {@link #getTarget}. 如果该字段未被设置,则会引发一个空指针异常。
  12. */
  13. quit.sendToTarget();
  14. try {
  15. /**
  16. * 阻止当前线程 <code>Thread.currentThread()</code>) 接收器完成它的执行和死亡。
  17. *
  18. * @throws InterruptedException
  19. *  如果当前线程被中断。 当前线程的中断状态将在异常之前被清除
  20. *
  21. * @see Object#notifyAll
  22. * @see java.lang.ThreadDeath
  23. */
  24. decodeThread.join();
  25. } catch (InterruptedException e) {
  26. // continue
  27. }
  28. // 绝对肯定我们不会发送任何排队的消息
  29. removeMessages(R.id.decode_succeeded);
  30. removeMessages(R.id.decode_failed);
  31. }

(4.3)我们来看第三处调用,restartPreviewAndDecode()重新启动预览和解码函数,其实执行的代码还是,(4.1)中讲到的 PreviewCallback监听接口,发送的Handler消息。

[java] view plaincopy
  1. /**
  2. * 重新启动预览和解码
  3. */
  4. private void restartPreviewAndDecode() {
  5. if (state == State.SUCCESS) {
  6. state = State.PREVIEW;
  7. CameraManager.get().requestPreviewFrame(decodeThread.getHandler(),
  8. R.id.decode);
  9. CameraManager.get().requestAutoFocus(this, R.id.auto_focus);
  10. activity.drawViewfinder();
  11. }
  12. }

(5)看完了相机解码流程,接下来看解码格式管理类 DecodeFormatManager类,该类封装了常用的一些条形码,二维码,商品码等一些格式结合。该类的其他几个函数并未使用,这里不做讲解。

[java] view plaincopy
  1. // Pattern.compile(",") 返回一个编译的形式给定正则表达式}
  2. private static final Pattern COMMA_PATTERN = Pattern.compile(",");
  3. // 产品格式
  4. static final Vector<BarcodeFormat> PRODUCT_FORMATS;
  5. // 一维码
  6. static final Vector<BarcodeFormat> ONE_D_FORMATS;
  7. // QR码格式
  8. static final Vector<BarcodeFormat> QR_CODE_FORMATS;
  9. // 数据矩阵格式
  10. static final Vector<BarcodeFormat> DATA_MATRIX_FORMATS;
  11. static {
  12. PRODUCT_FORMATS = new Vector<BarcodeFormat>(5);
  13. PRODUCT_FORMATS.add(BarcodeFormat.UPC_A); // UPC标准码(通用商品)
  14. PRODUCT_FORMATS.add(BarcodeFormat.UPC_E); // UPC缩短码(商品短码)
  15. PRODUCT_FORMATS.add(BarcodeFormat.EAN_13);
  16. PRODUCT_FORMATS.add(BarcodeFormat.EAN_8);
  17. PRODUCT_FORMATS.add(BarcodeFormat.RSS14);
  18. ONE_D_FORMATS = new Vector<BarcodeFormat>(PRODUCT_FORMATS.size() + 4);
  19. ONE_D_FORMATS.addAll(PRODUCT_FORMATS); // 此处将PRODUCT_FORMATS中添加的码加入
  20. ONE_D_FORMATS.add(BarcodeFormat.CODE_39);
  21. ONE_D_FORMATS.add(BarcodeFormat.CODE_93);
  22. ONE_D_FORMATS.add(BarcodeFormat.CODE_128);
  23. ONE_D_FORMATS.add(BarcodeFormat.ITF);
  24. QR_CODE_FORMATS = new Vector<BarcodeFormat>(1);// QR_CODE即二维码
  25. QR_CODE_FORMATS.add(BarcodeFormat.QR_CODE);
  26. DATA_MATRIX_FORMATS = new Vector<BarcodeFormat>(1); // 也属于一种二维码
  27. DATA_MATRIX_FORMATS.add(BarcodeFormat.DATA_MATRIX);
  28. }

(6)最后讲下相机如何配置 CameraConfigurationManager 相机配置管理器,改类中用到相机服务类 Camera.Parameters,这个类提供了相机设置,变焦等相关功能,注意这里用到很多parameters.get()函数,因为Parameters类中封装了一个字典,用于配置相机相关数据。改类提供了诸多函数和算法,但是最主要的功能是对外提供了相机分辨率 getCameraResolution()函数,屏幕分辨率getScreenResolution() 函数,预览格式getPreviewFormat()函数,获取预览格式字符串 getPreviewFormatString()函数等。其余函数是相关算法可以看代码理解

[java] view plaincopy
  1. /**
  2. *
  3. * 相机配置管理器
  4. *
  5. * @author ZQY
  6. *
  7. */
  8. /**
  9. * @author Administrator
  10. *
  11. */
  12. final class CameraConfigurationManager {
  13. private static final String TAG = CameraConfigurationManager.class
  14. .getSimpleName();
  15. // 所需的变焦
  16. private static final int TEN_DESIRED_ZOOM = 27;
  17. // 所需的锐度
  18. private static final int DESIRED_SHARPNESS = 30;
  19. /**
  20. * 返回一个编译的形式给定正则表达式
  21. *
  22. * @throws PatternSyntaxException
  23. *             如果正则表达式语法不正确,将会引发 PatternSyntaxException 异常。
  24. *
  25. *             compile(String regularExpression, int flags) 此方法被重载
  26. *             (regularExpression)正则表达式 可参考一下常量值设置 flags // *@see CANON_EQ
  27. *             // * @see CASE_INSENSITIVE // * @see COMMENTS // * @see
  28. *             #DOTALL // * @see #LITERAL // * @see #MULTILINE // * @see
  29. *             #UNICODE_CASE // * @see #UNIX_LINES
  30. */
  31. private static final Pattern COMMA_PATTERN = Pattern.compile(",");
  32. private final Context context;
  33. // 屏幕分辨率
  34. private Point screenResolution;
  35. // 相机的分辨率
  36. private Point cameraResolution;
  37. // 预览格式
  38. private int previewFormat;
  39. // 预览格式字符串
  40. private String previewFormatString;
  41. CameraConfigurationManager(Context context) {
  42. this.context = context;
  43. }
  44. /**
  45. * 读,一时间,从应用程序所需要的相机的值。
  46. */
  47. void initFromCameraParameters(Camera camera) {
  48. /**
  49. * 返回此相机服务的当前设置。 如果对返回的参数进行修改,则必须通过 to
  50. * {@link #setParameters(Camera.Parameters)} 设置
  51. *
  52. * see #setParameters(Camera.Parameters) 调用此方法设置
  53. */
  54. Camera.Parameters parameters = camera.getParameters();
  55. /**
  56. * 返回预览帧的图像格式
  57. *
  58. * {@链接previewcallback }。
  59. *
  60. * @return 返回预览格式。 看android.graphics.imageformat 看# setpreviewformat
  61. */
  62. previewFormat = parameters.getPreviewFormat();
  63. /**
  64. * 返回字符串参数的值。
  65. *
  66. * @param参数名的关键的关键 返回参数的字符串值
  67. */
  68. previewFormatString = parameters.get("preview-format");
  69. // “默认预览格式:”+“预览格式”+“/”预览格式字符串 b
  70. Log.d(TAG, "Default preview format: " + previewFormat + '/'
  71. + previewFormatString);
  72. // 获取 应用程序的界面和窗口管理器对话。
  73. WindowManager manager = (WindowManager) context
  74. .getSystemService(Context.WINDOW_SERVICE);
  75. /**
  76. * Display 类解释
  77. *
  78. * 提供逻辑显示的大小和密度的信息。
  79. *
  80. * 显示区域以不同的方式描述。
  81. *
  82. * 用显示区域指定可能包含的显示的部分
  83. *
  84. * 一个应用程序窗口,不包括系统装饰。应用显示区域可以
  85. *
  86. * 小于真实的显示区域由于系统中减去所需空间
  87. *
  88. * 为装饰元素,如状态栏。使用下面的方法来查询
  89. *
  90. * *应用展示区 {@link #getSize}, {@link #getRectSize} and {@link #getMetrics}
  91. *
  92. * 真正显示区域指定包含内容的显示部分
  93. *
  94. * 包括系统装饰。即使如此,真正的显示区域可能比
  95. *
  96. * 物理尺寸的显示如果窗口管理器是模拟一个较小的显示
  97. *
  98. * 使用 (adb shell am display-size)
  99. *
  100. * 使用下面的方法来查询
  101. *
  102. * 真正的展示区: {@link #getRealSize}, {@link #getRealMetrics}.
  103. *
  104. * 逻辑显示并不一定代表一个特定的物理显示设备
  105. *
  106. * 如内置屏幕或外部显示器。逻辑的内容
  107. *
  108. * 显示可根据设备的一个或多个物理显示
  109. *
  110. * 这是当前连接的,以及是否已启用镜像。
  111. */
  112. Display display = manager.getDefaultDisplay();
  113. // 屏幕分辨率
  114. screenResolution = new Point(display.getWidth(), display.getHeight());
  115. // 打印屏幕分辨率值:screenResolution
  116. Log.d(TAG, "Screen resolution: " + screenResolution);
  117. // 相机的分辨率
  118. cameraResolution = getCameraResolution(parameters, screenResolution);
  119. // 相机分辨率:screenResolution
  120. Log.d(TAG, "Camera resolution: " + screenResolution);
  121. }
  122. /**
  123. * 设置相机拍摄的图像,用于预览
  124. *
  125. * 解码。我们在这里检测到预览格式
  126. *
  127. * buildluminancesource()可以建立一个适当的luminancesource类。
  128. *
  129. * 在未来,我们可能想力yuv420sp因为它是最小的,和
  130. *
  131. * 在某些情况下,可以使用平面的条形码扫描而无需复制。
  132. */
  133. void setDesiredCameraParameters(Camera camera) {
  134. /**
  135. * 返回此相机服务的当前设置。
  136. *
  137. * 如果对返回的参数进行修改,则必须通过
  138. *
  139. * {@link #setParameters(Camera.Parameters)} 设置生效
  140. *
  141. * 看 see #setParameters(Camera.Parameters) 的参数是 Camera.Parameters
  142. */
  143. Camera.Parameters parameters = camera.getParameters();
  144. // 设置预览大小 cameraResolution
  145. Log.d(TAG, "Setting preview size: " + cameraResolution);
  146. /**
  147. * 设置预览图片的尺寸。如果预览已经
  148. *
  149. * 开始,应用程序应在更改前先停止预览
  150. *
  151. * 预览大小。 宽度和高度的双方都是以相机为基础的。那
  152. *
  153. * 是,预览大小是在它被显示旋转之前的大小
  154. *
  155. * 定位。所以应用程序需要考虑显示方向
  156. *
  157. * 设置预览大小。例如,假设相机支持
  158. *
  159. * 尺寸320x480 480x320和预览。应用程序需要一个3:2
  160. *
  161. * 预览比。如果显示方向设置为0或180,则预览
  162. *
  163. * 大小应设置为480x320。如果显示方向被设置为
  164. *
  165. * 90或270,预览大小应设置为320x480。显示
  166. *
  167. * 设置图片大小时也应考虑*方向
  168. *
  169. * 缩略图大小。
  170. *
  171. * * @param宽度的图片,像素
  172. *
  173. * @param高度像素的图片,
  174. *
  175. *                “看# setdisplayorientation(int)
  176. *
  177. *                “看# getcamerainfo(int,camerainfo)
  178. *
  179. *                “看# setpicturesize(int,int)
  180. *
  181. *                “看# setjpegthumbnailsize(int,int)
  182. */
  183. parameters.setPreviewSize(cameraResolution.x, cameraResolution.y);
  184. setFlash(parameters);
  185. setZoom(parameters);
  186. // setSharpness(parameters);
  187. // modify here
  188. // camera.setDisplayOrientation(90);
  189. // 兼容2.1
  190. setDisplayOrientation(camera, 90);
  191. camera.setParameters(parameters);
  192. }
  193. /**
  194. * 获取相机分辨率
  195. *
  196. * @return
  197. */
  198. Point getCameraResolution() {
  199. return cameraResolution;
  200. }
  201. /**
  202. *
  203. 获取屏幕分辨率
  204. *
  205. * @return
  206. */
  207. Point getScreenResolution() {
  208. return screenResolution;
  209. }
  210. /**
  211. * 获得预览格式
  212. *
  213. * @return
  214. */
  215. int getPreviewFormat() {
  216. return previewFormat;
  217. }
  218. /**
  219. * 获取预览格式字符串
  220. *
  221. * @return
  222. */
  223. String getPreviewFormatString() {
  224. return previewFormatString;
  225. }
  226. /**
  227. * 获取相机分辨率
  228. *
  229. * @param parameters
  230. *            相机服务设置 即相机参数设置类
  231. * @param screenResolution
  232. *            屏幕分辨率
  233. * @return
  234. */
  235. private static Point getCameraResolution(Camera.Parameters parameters,
  236. Point screenResolution) {
  237. // 获取预览大小值
  238. String previewSizeValueString = parameters.get("preview-size-values");
  239. // 如果值为空 重新获取
  240. if (previewSizeValueString == null) {
  241. previewSizeValueString = parameters.get("preview-size-value");
  242. }
  243. // 相机的分辨率
  244. Point cameraResolution = null;
  245. if (previewSizeValueString != null) {
  246. // 打印 预览值参数
  247. Log.d(TAG, "preview-size-values parameter: "
  248. + previewSizeValueString);
  249. // 相机的分辨率
  250. cameraResolution = findBestPreviewSizeValue(previewSizeValueString,
  251. screenResolution);
  252. }
  253. if (cameraResolution == null) {
  254. /**
  255. * 确保相机分辨率为8,为屏幕可能不。
  256. */
  257. cameraResolution = new Point((screenResolution.x >> 3) << 3,
  258. (screenResolution.y >> 3) << 3);
  259. }
  260. return cameraResolution;
  261. }
  262. /**
  263. * 找到最佳的预览大小值
  264. *
  265. * @param previewSizeValueString
  266. *            预览大小值字符串
  267. * @param screenResolution
  268. *            屏幕分辨率
  269. * @return
  270. */
  271. private static Point findBestPreviewSizeValue(
  272. CharSequence previewSizeValueString, Point screenResolution) {
  273. int bestX = 0; // 最好的X
  274. int bestY = 0; // 最好的Y
  275. int diff = Integer.MAX_VALUE; // 最大值
  276. // 已 (逗号,) 拆分预览大小值字符串
  277. for (String previewSize : COMMA_PATTERN.split(previewSizeValueString)) {
  278. previewSize = previewSize.trim();
  279. /**
  280. * 返回给定代码点的第一个索引,或- 1。
  281. *
  282. * 搜索开始时并向移动 该字符串的结束。
  283. */
  284. int dimPosition = previewSize.indexOf('x');
  285. if (dimPosition < 0) {
  286. // 如果值小于零 打印 坏的预览大小
  287. Log.w(TAG, "Bad preview-size: " + previewSize);
  288. continue;
  289. }
  290. int newX;
  291. int newY;
  292. try {
  293. // 拿到新的 X值 和 Y 值
  294. newX = Integer.parseInt(previewSize.substring(0, dimPosition));
  295. newY = Integer.parseInt(previewSize.substring(dimPosition + 1));
  296. } catch (NumberFormatException nfe) {
  297. // 如果异常 打印 坏的预览大小
  298. Log.w(TAG, "Bad preview-size: " + previewSize);
  299. continue;
  300. }
  301. /**
  302. * Math.abs(int i) 返回参数的绝对值
  303. *
  304. * 如果参数是 {@code Integer.MIN_VALUE}, {@code Integer.MIN_VALUE} 返回
  305. */
  306. int newDiff = Math.abs(newX - screenResolution.x)
  307. + Math.abs(newY - screenResolution.y);
  308. if (newDiff == 0) {
  309. bestX = newX;
  310. bestY = newY;
  311. break;
  312. } else if (newDiff < diff) {
  313. bestX = newX;
  314. bestY = newY;
  315. diff = newDiff;
  316. }
  317. }
  318. /**
  319. * 如果 最好的X 最好的Y 都大于零 从新绘制
  320. */
  321. if (bestX > 0 && bestY > 0) {
  322. return new Point(bestX, bestY);
  323. }
  324. return null;
  325. }
  326. /**
  327. * 找到最好的MOT缩放值
  328. *
  329. * @param stringValues
  330. *            字符串值
  331. * @param tenDesiredZoom
  332. *            所需的变焦
  333. * @return
  334. */
  335. private static int findBestMotZoomValue(CharSequence stringValues,
  336. int tenDesiredZoom) {
  337. int tenBestValue = 0;
  338. // 以 (逗号,) 拆分字符串值
  339. for (String stringValue : COMMA_PATTERN.split(stringValues)) {
  340. stringValue = stringValue.trim();
  341. double value;
  342. try {
  343. // 得到整数值
  344. value = Double.parseDouble(stringValue);
  345. } catch (NumberFormatException nfe) {
  346. return tenDesiredZoom;
  347. }
  348. // 计算 改值得 十倍值
  349. int tenValue = (int) (10.0 * value);
  350. // 计算绝对值 得到最好的值
  351. if (Math.abs(tenDesiredZoom - value) < Math.abs(tenDesiredZoom
  352. - tenBestValue)) {
  353. tenBestValue = tenValue;
  354. }
  355. }
  356. return tenBestValue;
  357. }
  358. /**
  359. * 设置闪光
  360. *
  361. * @param parameters
  362. *            相机配置参数设置类
  363. */
  364. private void setFlash(Camera.Parameters parameters) {
  365. // FIXME:这是一个黑客把闪光灯关掉了三星Galaxy。
  366. // 这是一个黑客攻击,以解决不同的价值
  367. // 看一看
  368. // 限制看二检查蛋糕,每三星的建议
  369. // if (Build.MODEL.contains("Behold II") &&
  370. // CameraManager.SDK_INT == Build.VERSION_CODES.CUPCAKE) {
  371. if (Build.MODEL.contains("Behold II") && CameraManager.SDK_INT == 3) { // 3
  372. // =
  373. // Cupcake
  374. parameters.set("flash-value", 1);
  375. } else {
  376. parameters.set("flash-value", 2);
  377. }
  378. /**
  379. * 这是标准的设置,把所有的设备应该遵守
  380. */
  381. parameters.set("flash-mode", "off");
  382. }
  383. /**
  384. * 设定缩放等级
  385. *
  386. * @param parameters
  387. *            相机配置参数设置类
  388. */
  389. private void setZoom(Camera.Parameters parameters) {
  390. // 拿到 变焦支持 值
  391. String zoomSupportedString = parameters.get("zoom-supported");
  392. // 判断 zoomSupportedString 值不为空 且 字符串不为 boolean 类型的值
  393. if (zoomSupportedString != null
  394. && !Boolean.parseBoolean(zoomSupportedString)) {
  395. return;
  396. }
  397. // 所需的变焦
  398. int tenDesiredZoom = TEN_DESIRED_ZOOM;
  399. // 得到 最大变焦
  400. String maxZoomString = parameters.get("max-zoom");
  401. if (maxZoomString != null) {
  402. try {
  403. // 得到最大变焦值 10 倍值
  404. int tenMaxZoom = (int) (10.0 * Double
  405. .parseDouble(maxZoomString));
  406. // 如果所需变焦值 大于 最大变焦值
  407. if (tenDesiredZoom > tenMaxZoom) {
  408. tenDesiredZoom = tenMaxZoom;
  409. }
  410. } catch (NumberFormatException nfe) {
  411. // 打印异常的变焦值
  412. Log.w(TAG, "Bad max-zoom: " + maxZoomString);
  413. }
  414. }
  415. // 图片缩放最大
  416. String takingPictureZoomMaxString = parameters
  417. .get("taking-picture-zoom-max");
  418. if (takingPictureZoomMaxString != null) {
  419. try {
  420. // 最大缩放
  421. int tenMaxZoom = Integer.parseInt(takingPictureZoomMaxString);
  422. // 所需变焦 大于 图片最大缩放值
  423. if (tenDesiredZoom > tenMaxZoom) {
  424. tenDesiredZoom = tenMaxZoom;
  425. }
  426. } catch (NumberFormatException nfe) {
  427. // 异常的 图片缩放最大值
  428. Log.w(TAG, "Bad taking-picture-zoom-max: "
  429. + takingPictureZoomMaxString);
  430. }
  431. }
  432. // MOT缩放值
  433. String motZoomValuesString = parameters.get("mot-zoom-values");
  434. if (motZoomValuesString != null) {
  435. tenDesiredZoom = findBestMotZoomValue(motZoomValuesString,
  436. tenDesiredZoom);
  437. }
  438. // mot 变焦步骤
  439. String motZoomStepString = parameters.get("mot-zoom-step");
  440. if (motZoomStepString != null) {
  441. try {
  442. // MOT缩放值
  443. double motZoomStep = Double.parseDouble(motZoomStepString
  444. .trim());
  445. int tenZoomStep = (int) (10.0 * motZoomStep);
  446. if (tenZoomStep > 1) {
  447. tenDesiredZoom -= tenDesiredZoom % tenZoomStep;
  448. }
  449. } catch (NumberFormatException nfe) {
  450. // continue
  451. }
  452. }
  453. // 设置缩放。这有助于鼓励用户拉回来。
  454. // 一些设备,如有一个变焦参数
  455. if (maxZoomString != null || motZoomValuesString != null) {
  456. parameters.set("zoom", String.valueOf(tenDesiredZoom / 10.0));
  457. }
  458. // 大多数设备,像英雄,似乎暴露这个变焦参数。
  459. // 它的值“27”,似乎意味着2.7倍变焦
  460. if (takingPictureZoomMaxString != null) {
  461. parameters.set("taking-picture-zoom", tenDesiredZoom);
  462. }
  463. }
  464. /**
  465. * 获得理想的清晰度
  466. *
  467. * @return
  468. */
  469. public static int getDesiredSharpness() {
  470. return DESIRED_SHARPNESS;
  471. }
  472. /**
  473. *
  474. * 设置显示方向
  475. *
  476. * compatible 1.6
  477. *
  478. * @param camera
  479. * @param angle
  480. */
  481. protected void setDisplayOrientation(Camera camera, int angle) {
  482. Method downPolymorphic;
  483. try {
  484. /**
  485. * 返回一个表示公共方法的 {@code Method}对象
  486. *
  487. * 指定的名称和参数类型。
  488. *
  489. * {@code (Class[]) null} 等于空数组
  490. *
  491. * 该方法首先搜索 C类的代码 {@code Class} 最后C的父类
  492. *
  493. * 为匹配名称的方法。
  494. *
  495. * 将调用不存在方法异常将会引发 @throws NoSuchMethodException
  496. *
  497. * 看 see #getDeclaredMethod(String, Class[])
  498. */
  499. downPolymorphic = camera.getClass().getMethod(
  500. "setDisplayOrientation", new Class[] { int.class });
  501. if (downPolymorphic != null)
  502. /**
  503. * 返回动态调用此方法的结果。相当于
  504. * {@code receiver.methodName(arg1, arg2, ... , argN)}.
  505. *
  506. * 如果该方法是静态的,则忽略接收器参数(可能是空的)
  507. *
  508. * 如果该方法没有任何参数,您可以通过 {@code (Object[]) null} 来代替 分配一个空数组。
  509. *
  510. * 如果你调用一个可变参数的方法,你需要传递一个{@code Object[]}做,不是虚拟机,和
  511. *
  512. * 反射机制不会为您做此。(它不能,因为它会
  513. *
  514. * 暧昧。) 反射方法调用遵循通常的方法查找方法。
  515. *
  516. * 如果在调用过程中抛出异常,则该异常被捕获和
  517. *
  518. * 包装在一个invocationtargetexception。然后抛出此异常。
  519. *
  520. *
  521. *
  522. * 如果调用完成的话,返回值本身是
  523. *
  524. * 返回。如果该方法被声明为返回原始类型,则
  525. *
  526. * 返回值被装箱。如果返回类型无效,返回空
  527. */
  528. downPolymorphic.invoke(camera, new Object[] { angle });
  529. } catch (Exception e1) {
  530. }
  531. }
  532. }

(7)最后讲下相机中散光灯如何使用,FlashlightManager类控制散光灯,该类提供了多种反射技术,拿到封装的对象和方法,来实现硬件功能,请看相关注解

[java] view plaincopy
  1. /**
  2. *
  3. 这个类是用来激活弱光的一些相机的手机(不是闪光灯)
  4. *
  5. * 为了照亮扫描的表面。没有官方的方法来做这件事,
  6. *
  7. * 但是,允许访问此功能的类仍然存在于某些设备上。
  8. *
  9. * 因此通过大量的思考。
  10. *
  11. * 看 <a href=
  12. * "http://almondmendoza.com/2009/01/05/changing-the-screen-brightness-programatically/"
  13. * > http://almondmendoza.com/2009/01/05/changing-the-screen-brightness-
  14. * programatically/</a> and <a href=
  15. * "http://code.google.com/p/droidled/source/browse/trunk/src/com/droidled/demo/DroidLED.java"
  16. * > http://code.google.com/p/droidled/source/browse/trunk/src/com/droidled/demo
  17. * /DroidLED.java</a>.
  18. *
  19. * 感谢Ryan Alford指出该类的可用性。
  20. */
  21. final class FlashlightManager {
  22. private static final String TAG = FlashlightManager.class.getSimpleName();
  23. // 硬件服务
  24. private static final Object iHardwareService;
  25. // 设置闪光功能的方法
  26. private static final Method setFlashEnabledMethod;
  27. static {
  28. iHardwareService = getHardwareService();
  29. setFlashEnabledMethod = getSetFlashEnabledMethod(iHardwareService);
  30. if (iHardwareService == null) {
  31. Log.v(TAG, "该设备支持一个手电筒的控制");
  32. } else {
  33. Log.v(TAG, "该设备不支持控制手电筒");
  34. }
  35. }
  36. private FlashlightManager() {
  37. }
  38. /**
  39. * 控制相机闪光灯开关
  40. */
  41. // FIXME
  42. static void enableFlashlight() {
  43. setFlashlight(false);
  44. }
  45. /**
  46. *
  47. 禁用闪光灯
  48. */
  49. static void disableFlashlight() {
  50. setFlashlight(false);
  51. }
  52. private static Object getHardwareService() {
  53. // 反向映射 得到指定 类
  54. Class<?> serviceManagerClass = maybeForName("android.os.ServiceManager");
  55. if (serviceManagerClass == null) {
  56. return null;
  57. }
  58. Method getServiceMethod = maybeGetMethod(serviceManagerClass,
  59. "getService", String.class);
  60. if (getServiceMethod == null) {
  61. return null;
  62. }
  63. Object hardwareService = invoke(getServiceMethod, null, "hardware");
  64. if (hardwareService == null) {
  65. return null;
  66. }
  67. Class<?> iHardwareServiceStubClass = maybeForName("android.os.IHardwareService$Stub");
  68. if (iHardwareServiceStubClass == null) {
  69. return null;
  70. }
  71. Method asInterfaceMethod = maybeGetMethod(iHardwareServiceStubClass,
  72. "asInterface", IBinder.class);
  73. if (asInterfaceMethod == null) {
  74. return null;
  75. }
  76. return invoke(asInterfaceMethod, null, hardwareService);
  77. }
  78. private static Method getSetFlashEnabledMethod(Object iHardwareService) {
  79. if (iHardwareService == null) {
  80. return null;
  81. }
  82. Class<?> proxyClass = iHardwareService.getClass();
  83. return maybeGetMethod(proxyClass, "setFlashlightEnabled", boolean.class);
  84. }
  85. private static Class<?> maybeForName(String name) {
  86. try {
  87. /**
  88. * 返回一个代表类的{ @码类}对象
  89. *
  90. * 给定的名称。名称应该是非本原的名称
  91. *
  92. * 类,如在{ @链接类定义}中所描述的。
  93. *
  94. * 原始类型不能使用此方法来找到;使用{ @代码
  95. *
  96. * int.class }或{ @代码类型}而不是整数。
  97. *
  98. *
  99. *
  100. * 如果尚未加载该类,则加载和初始化
  101. *
  102. * 第一。这是通过调用类的类装入器来完成的
  103. *
  104. * 或其母类装入器中的一个。这可能是一个静态初始化运行
  105. *
  106. * 这一呼叫的结果。
  107. *
  108. *
  109. *
  110. * @抛出ClassNotFoundException
  111. *
  112. *                           如果无法找到所需的类。
  113. *
  114. * @抛出连接失败错误
  115. *
  116. *           如果在连接过程中出现错误
  117. *
  118. * @投exceptionininitializererror
  119. *
  120. *                               如果在静态初始化期间发生异常
  121. *
  122. *                               类。
  123. */
  124. return Class.forName(name);
  125. } catch (ClassNotFoundException cnfe) {
  126. // OK
  127. return null;
  128. } catch (RuntimeException re) {
  129. Log.w(TAG, "Unexpected error while finding class " + name, re);
  130. return null;
  131. }
  132. }
  133. private static Method maybeGetMethod(Class<?> clazz, String name,
  134. Class<?>... argClasses) {
  135. try {
  136. return clazz.getMethod(name, argClasses);
  137. } catch (NoSuchMethodException nsme) {
  138. // OK
  139. return null;
  140. } catch (RuntimeException re) {
  141. Log.w(TAG, "Unexpected error while finding method " + name, re);
  142. return null;
  143. }
  144. }
  145. private static Object invoke(Method method, Object instance, Object... args) {
  146. try {
  147. /**
  148. *
  149. *
  150. * 返回动态调用此方法的结果。相当于
  151. *
  152. * { @代码语句(arg1,arg2接收器,…argn)}。
  153. *
  154. *
  155. *
  156. * 如果该方法是静态的,则忽略接收器参数(可能是空的)。
  157. *
  158. *
  159. *
  160. * 如果该方法没有任何参数,您可以通过{ @代码(对象[)]空}来代替
  161. *
  162. * 分配一个空数组。
  163. *
  164. *
  165. *
  166. * <BR>
  167. * 如果你调用一个可变参数的方法,你需要传递一个{ } [ ] @代码对象的
  168. *
  169. * 变参数:转换通常是在{ @代码javac }做,不是虚拟机,和
  170. *
  171. * 反射机制不会为您做此。(它不能,因为它会
  172. *
  173. * 暧昧。)
  174. *
  175. *
  176. *
  177. * *反射方法调用遵循通常的方法查找方法。
  178. *
  179. *
  180. *
  181. * 如果在调用过程中抛出异常,则该异常被捕获和
  182. *
  183. * 包装在一个invocationtargetexception。然后抛出此异常。
  184. *
  185. *
  186. *
  187. * 如果调用完成的话,返回值本身是
  188. *
  189. * 返回。如果该方法被声明为返回原始类型,则
  190. *
  191. * 返回值被装箱。如果返回类型无效,返回空。
  192. *
  193. *
  194. *
  195. * @param接收机
  196. *
  197. *           将调用该方法的对象(或静态方法为空)
  198. *
  199. * @param参数
  200. *
  201. *          参数的方法
  202. *
  203. *          返回结果
  204. *
  205. *
  206. *
  207. * @抛出NullPointerException异常
  208. *
  209. *                           如果{“代码”接收器=空}为非静态方法
  210. *
  211. * @抛出非法存取异常
  212. *
  213. *           如果这个方法不容易(参阅{@链接AccessibleObject })
  214. *
  215. * @抛出时
  216. *
  217. *      如果参数的数目与参数的数目不匹配,该接收器
  218. *
  219. *      与声明类不相容,或争论不能拆箱
  220. *
  221. *      或转换为相应的参数类型的拉宽转换
  222. *
  223. * @投invocationtargetexception
  224. *
  225. *                             如果被调用的方法引发异常
  226. *
  227. *
  228. */
  229. return method.invoke(instance, args);
  230. } catch (IllegalAccessException e) {
  231. Log.w(TAG, "Unexpected error while invoking " + method, e);
  232. return null;
  233. } catch (InvocationTargetException e) {
  234. Log.w(TAG, "Unexpected error while invoking " + method,
  235. e.getCause());
  236. return null;
  237. } catch (RuntimeException re) {
  238. Log.w(TAG, "Unexpected error while invoking " + method, re);
  239. return null;
  240. }
  241. }
  242. private static void setFlashlight(boolean active) {
  243. if (iHardwareService != null) {
  244. invoke(setFlashEnabledMethod, iHardwareService, active);
  245. }
  246. }
  247. }

(8)最后你会想如何实现扫描效果那个识别二位码控件如何实现,请看 ViewfinderView类,该类继承了View类,提供了绘制扫描控件 onDraw(Canvas canvas)函数

[java] view plaincopy
  1. @Override
  2. public void onDraw(Canvas canvas) {
  3. // 中间的扫描框,你要修改扫描框的大小,去CameraManager里面修改
  4. Rect frame = CameraManager.get().getFramingRect();
  5. if (frame == null) {
  6. return;
  7. }
  8. // 初始化中间线滑动的最上边和最下边
  9. if (!isFirst) {
  10. isFirst = true;
  11. slideTop = frame.top;
  12. slideBottom = frame.bottom;
  13. }
  14. // 获取屏幕的宽和高
  15. int width = canvas.getWidth();
  16. int height = canvas.getHeight();
  17. paint.setColor(resultBitmap != null ? resultColor : maskColor);
  18. // 画出扫描框外面的阴影部分,共四个部分,扫描框的上面到屏幕上面,扫描框的下面到屏幕下面
  19. // 扫描框的左边面到屏幕左边,扫描框的右边到屏幕右边
  20. canvas.drawRect(0, 0, width, frame.top, paint);
  21. canvas.drawRect(0, frame.top, frame.left, frame.bottom + 1, paint);
  22. canvas.drawRect(frame.right + 1, frame.top, width, frame.bottom + 1,
  23. paint);
  24. canvas.drawRect(0, frame.bottom + 1, width, height, paint);
  25. if (resultBitmap != null) {
  26. // Draw the opaque result bitmap over the scanning rectangle
  27. paint.setAlpha(OPAQUE);
  28. canvas.drawBitmap(resultBitmap, frame.left, frame.top, paint);
  29. } else {
  30. // 画扫描框边上的角,总共8个部分
  31. paint.setColor(Color.GREEN);
  32. canvas.drawRect(frame.left, frame.top, frame.left + ScreenRate,
  33. frame.top + CORNER_WIDTH, paint);
  34. canvas.drawRect(frame.left, frame.top, frame.left + CORNER_WIDTH,
  35. frame.top + ScreenRate, paint);
  36. canvas.drawRect(frame.right - ScreenRate, frame.top, frame.right,
  37. frame.top + CORNER_WIDTH, paint);
  38. canvas.drawRect(frame.right - CORNER_WIDTH, frame.top, frame.right,
  39. frame.top + ScreenRate, paint);
  40. canvas.drawRect(frame.left, frame.bottom - CORNER_WIDTH, frame.left
  41. + ScreenRate, frame.bottom, paint);
  42. canvas.drawRect(frame.left, frame.bottom - ScreenRate, frame.left
  43. + CORNER_WIDTH, frame.bottom, paint);
  44. canvas.drawRect(frame.right - ScreenRate, frame.bottom
  45. - CORNER_WIDTH, frame.right, frame.bottom, paint);
  46. canvas.drawRect(frame.right - CORNER_WIDTH, frame.bottom
  47. - ScreenRate, frame.right, frame.bottom, paint);
  48. // 绘制中间的线,每次刷新界面,中间的线往下移动SPEEN_DISTANCE
  49. slideTop += SPEEN_DISTANCE;
  50. if (slideTop >= frame.bottom) {
  51. slideTop = frame.top;
  52. }
  53. canvas.drawRect(frame.left + MIDDLE_LINE_PADDING, slideTop
  54. - MIDDLE_LINE_WIDTH / 2, frame.right - MIDDLE_LINE_PADDING,
  55. slideTop + MIDDLE_LINE_WIDTH / 2, paint);
  56. // 画扫描框下面的字
  57. paint.setColor(Color.WHITE);
  58. paint.setTextSize(TEXT_SIZE * density);
  59. paint.setAlpha(0x40);
  60. paint.setTypeface(Typeface.create("System", Typeface.BOLD));
  61. canvas.drawText(
  62. getResources().getString(R.string.scan_text),
  63. frame.left,
  64. (float) (frame.bottom + (float) TEXT_PADDING_TOP * density),
  65. paint);
  66. Collection<ResultPoint> currentPossible = possibleResultPoints;
  67. Collection<ResultPoint> currentLast = lastPossibleResultPoints;
  68. if (currentPossible.isEmpty()) {
  69. lastPossibleResultPoints = null;
  70. } else {
  71. possibleResultPoints = new HashSet<ResultPoint>(5);
  72. lastPossibleResultPoints = currentPossible;
  73. paint.setAlpha(OPAQUE);
  74. paint.setColor(resultPointColor);
  75. for (ResultPoint point : currentPossible) {
  76. canvas.drawCircle(frame.left + point.getX(), frame.top
  77. + point.getY(), 6.0f, paint);
  78. }
  79. }
  80. if (currentLast != null) {
  81. paint.setAlpha(OPAQUE / 2);
  82. paint.setColor(resultPointColor);
  83. for (ResultPoint point : currentLast) {
  84. canvas.drawCircle(frame.left + point.getX(), frame.top
  85. + point.getY(), 3.0f, paint);
  86. }
  87. }
  88. // 只刷新扫描框的内容,其他地方不刷新
  89. postInvalidateDelayed(ANIMATION_DELAY, frame.left, frame.top,
  90. frame.right, frame.bottom);
  91. }
  92. }

(9)其中还有几个相关辅助类没有贴上,ViewfinderResultPointCallback类,PlanarYUVLuminanceSource类,InactivityTimer类,FinishListener类,Intents类 这些可以详细看代码

Android 基于google Zxing实现二维码、条形码扫描相关推荐

  1. Android 基于google Zxing实现二维码 条形码扫描,仿微信二维码扫描效果

    分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow 也欢迎大家转载本篇文章.分享知识,造福人民,实现我们中华民族伟大复兴! 转载请注 ...

  2. Android基于Google Zxing实现二维码/条形码扫描、生成二维码/条形码

     二维码/条形码生成器 二维码/条形码扫描器 一.二维码与条形码工作原理 目前的很多应用上都有扫码功能,当时微信推出二维码扫码功能时,觉得imagine,通过一张简单的图片就能扫描添加还有,还有分 ...

  3. Android 基于google Zxing实现二维码、条形码扫描,仿微信二维码扫描效果

    转载请注明出处:http://blog.csdn.net/xiaanming/article/details/10163203 了解二维码这个东西还是从微信中,当时微信推出二维码扫描功能,自己感觉挺新 ...

  4. 基于google Zxing实现二维码、条形码扫描,仿微信二维码扫描效果

    转载请注明出处:http://blog.csdn.net/xiaanming/article/details/10163203 了解二维码这个东西还是从微信中,当时微信推出二维码扫描功能,自己感觉挺新 ...

  5. Java 生成二维码 zxing生成二维码 条形码 服务端生成二维码 Java生成条形码

    Java 生成二维码 zxing生成二维码 条形码 服务端生成二维码 Java生成条形码 一.关于ZXing 1.ZXing是谷歌开源的支持二维码.条形码 等图形的生成类库:支持生成.和解码功能. G ...

  6. Android开发之google Zxing实现二维码扫描的代码分析

    1.技术简介    在Android中实现二维码的扫描主要是通过第三方框架来实现的,主要框架是google的Zxing.现在就用该框架来实现二维码及条形码的扫描及识别,同时对于手机中存储的图片也进行识 ...

  7. Android:使用ZXing生成二维码(支持添加Logo图案)

    ZXing是谷歌的一个开源库,可以用来生成二维码.扫描二维码.本文所介绍的是第一部分. 首先上效果图: ZXing相关各种文件官方下载地址:https://github.com/zxing/zxing ...

  8. Android 比Zing 更快的二维码 条形码扫描Zbar

    之前项目要用做二维码 条形码的扫描 找资料 大多都是Zing 的,但是 zing 扫描比较慢 而且经常扫描半天不出来 ,如果是近距离的人话根本不行, 体验很差 ,最主要还可能是对zing 的理解不够透 ...

  9. Google Zxing 生成二维码

    Net Zxing 源码地址 http://zxingnet.codeplex.com/ github 地址 https://github.com/zxing/zxing 新建一个Winform 项目 ...

最新文章

  1. 可构建AI的「AI」诞生:几分之一秒内,就能预测新网络的参数
  2. DateChooser控件发布ASP.NET 2.0新版(我的ASP.NET 2.0控件开发书的第二个阶段项目)[请大家一定注意版本的更新,下载最新版]...
  3. KingShard MySQL中间件快速入门
  4. 学习jQuery顺便学习下CSS选择器:奇偶匹配nth-child(even)
  5. ZooKeeper编程
  6. 企业:怎样的渗透测试频率是合适的?
  7. PopWindow弹出在任意位置。
  8. SpringBoot使用JdbcTemplate案例(学习笔记)
  9. ReactNative手势解锁(react-native-ok-gesture-password)
  10. 谈.Net委托与线程——解决窗体假死
  11. *【CodeForces - 1150D】Three Religions(dp,预处理,思维)
  12. Linux系统编程19:基础IO之了解Linux中的标准输入和输出以及相关的系统调用接口(如write,read等)
  13. Ubuntu安装Beyond Compare(crack)
  14. SQL 查看SQL语句的执行时间 直接有效的方法
  15. cognos报表导出excel_Cognos制作报表常见问题
  16. linux打开mid格式音乐,mid文件扩展名,mid文件怎么打开?
  17. 漫画算法python篇pdf_漫画算法:小灰的算法之旅(Python篇)(全彩)
  18. 乔布斯一生中的3次阴差阳错、柳暗花明
  19. H5拖拽方法drag在VUE中的应用实例
  20. AOSP ~ 默认开启开发者模式

热门文章

  1. 抠图软件哪个最好用?这三款亲测简单、有效、功能强
  2. Leetcode面试热题(九)
  3. PAC模式和全局代理模式
  4. 人脉存折 五个朋友决定你的富贵
  5. 发展前景好、薪资高,计算机行业成为许多人改变命运的首选!
  6. Arthas 的 sc 命令和 sm 命令实战
  7. 吾儿秘史第一季--趣事糗事大杂烩(2011.7.11-2014.6.1)
  8. 【福利】送编程日历,GitChat 祝你新年无 BUG
  9. C语言代码的头文件与源文件有什么区别?
  10. 信号集中监测网站服务器,信号集中监测系统