欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Android實現(xiàn)后臺服務拍照功能

 更新時間:2018年05月29日 14:06:34   作者:wurensen  
這篇文章主要為大家詳細介紹了Android實現(xiàn)后臺服務拍照功能,具有一定的參考價值,感興趣的小伙伴們可以參考一下

一、背景介紹

最近在項目中遇到一個需求,實現(xiàn)一個后臺拍照的功能。一開始在網上尋找解決方案,也嘗試了很多種實現(xiàn)方式,都沒有滿意的方案。不過確定了難點:即拍照要先預覽,然后再調用拍照方法。問題也隨之而來,既然是要實現(xiàn)后臺拍照,就希望能在Service中或者是異步的線程中進行,這和預覽這個步驟有點相矛盾。那有什么方式能夠既能正常的實現(xiàn)預覽、拍照,又不讓使用者察覺呢?想必大家也會想到一個取巧的辦法:隱藏預覽界面。

說明一下,這只是我在摸索中想到的一種解決方案,能很好的解決業(yè)務上的需求。對于像很多手機廠商提供的“找回手機”功能時提供的拍照,我不確定他們的實現(xiàn)方式。如果大家有更好的實現(xiàn)方案,不妨交流一下。

關于這個功能是否侵犯了用戶的隱私,影響用戶的安全等等問題,不在我們的考慮和討論范圍之內。

二、方案介紹

方案實現(xiàn)步驟大致如下:

1.初始化拍照的預覽界面(核心部分);
2.在需要拍照時獲取相機Camera,并給Camera設置預覽界面;
3.打開預覽,完成拍照,釋放Camera資源(重要)
4.保存、旋轉、上傳.......(由業(yè)務決定)

先大概介紹下業(yè)務需求:從用戶登錄到注銷這段時間內,收到后臺拍照的指令后完成拍照、保存、上傳。以下會基于這個業(yè)務場景來詳細介紹各步驟的實現(xiàn)。

1.初始化拍照的預覽界面

在測試的過程中發(fā)現(xiàn),拍照的預覽界面需要在可顯示的情況下生成,才能正常拍照,假如是直接創(chuàng)建SurfaceView實例作為預覽界面,然后直接調用拍照時會拋出native層的異常:take_failed。想過看源碼尋找問題的原因,發(fā)現(xiàn)相機核心的功能代碼都在native層上面,所以暫且放下,假定的認為該在拍照時該預覽界面一定得在最上面一層顯示。

由于應用不管是在前臺還是按home回到桌面,都需要滿足該條件,那這個預覽界面應該是全局的,很容易的聯(lián)想到使用一個全局窗口來作為預覽界面的載體。這個全局窗口要是不可見的,不影響后面的界面正常交互。所以,就想到用全局的context來獲取WindowManager對象管理這個全局窗口。接下來直接看代碼:

package com.yuexunit.zjjk.service; 
 
import com.yuexunit.zjjk.util.Logger; 
 
import android.content.Context; 
import android.view.SurfaceView; 
import android.view.WindowManager; 
import android.view.WindowManager.LayoutParams; 
 
/** 
 * 隱藏的全局窗口,用于后臺拍照 
 * 
 * @author WuRS 
 */ 
public class CameraWindow { 
 
  private static final String TAG = CameraWindow.class.getSimpleName(); 
 
  private static WindowManager windowManager; 
 
  private static Context applicationContext; 
 
  private static SurfaceView dummyCameraView; 
 
  /** 
   * 顯示全局窗口 
   * 
   * @param context 
   */ 
  public static void show(Context context) { 
    if (applicationContext == null) { 
      applicationContext = context.getApplicationContext(); 
      windowManager = (WindowManager) applicationContext 
          .getSystemService(Context.WINDOW_SERVICE); 
      dummyCameraView = new SurfaceView(applicationContext); 
      LayoutParams params = new LayoutParams(); 
      params.width = 1; 
      params.height = 1; 
      params.alpha = 0; 
      params.type = LayoutParams.TYPE_SYSTEM_ALERT; 
      // 屏蔽點擊事件 
      params.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL 
          | LayoutParams.FLAG_NOT_FOCUSABLE 
          | LayoutParams.FLAG_NOT_TOUCHABLE; 
      windowManager.addView(dummyCameraView, params); 
      Logger.d(TAG, TAG + " showing"); 
    } 
  } 
 
  /** 
   * @return 獲取窗口視圖 
   */ 
  public static SurfaceView getDummyCameraView() { 
    return dummyCameraView; 
  } 
 
  /** 
   * 隱藏窗口 
   */ 
  public static void dismiss() { 
    try { 
      if (windowManager != null && dummyCameraView != null) { 
        windowManager.removeView(dummyCameraView); 
        Logger.d(TAG, TAG + " dismissed"); 
      } 
    } catch (Exception e) { 
      e.printStackTrace(); 
    } 
  } 
} 

代碼很簡單,主要功能就是顯示這個窗口、獲取用于預覽的SurfaceView以及關閉窗口。

在這個業(yè)務中,show方法可以直接在自定義的Application類中調用。這樣,在應用啟動后,窗口就在了,只有在應用銷毀(注意,結束所有Activity不會關閉,因為它初始化在Application中,它的生命周期就為應用級的,除非主動調用dismiss方法主動關閉)。

完成了預覽界面的初始化,整個實現(xiàn)其實已經非常簡單了。可能許多人遇到的問題就是卡在沒有預覽界面該如何拍照這里,希望這樣一種取巧的方式可以幫助大家在以后的項目中遇到無法直接解決問題時,可以考慮從另外的角度切入去解決問題。

2.完成Service拍照功能

這里將對上面的后續(xù)步驟進行合并。先上代碼:

package com.yuexunit.zjjk.service; 
 
import java.io.File; 
import java.io.FileNotFoundException; 
import java.io.FileOutputStream; 
import java.io.IOException; 
 
import android.app.Service; 
import android.content.Intent; 
import android.graphics.Bitmap; 
import android.graphics.BitmapFactory; 
import android.graphics.BitmapFactory.Options; 
import android.hardware.Camera; 
import android.hardware.Camera.CameraInfo; 
import android.hardware.Camera.PictureCallback; 
import android.os.IBinder; 
import android.os.Message; 
import android.text.TextUtils; 
import android.view.SurfaceView; 
 
import com.yuexunit.sortnetwork.android4task.UiHandler; 
import com.yuexunit.sortnetwork.task.TaskStatus; 
import com.yuexunit.zjjk.network.RequestHttp; 
import com.yuexunit.zjjk.util.FilePathUtil; 
import com.yuexunit.zjjk.util.ImageCompressUtil; 
import com.yuexunit.zjjk.util.Logger; 
import com.yuexunit.zjjk.util.WakeLockManager; 
 
/** 
 * 后臺拍照服務,配合全局窗口使用 
 * 
 * @author WuRS 
 */ 
public class CameraService extends Service implements PictureCallback { 
 
  private static final String TAG = CameraService.class.getSimpleName(); 
 
  private Camera mCamera; 
 
  private boolean isRunning; // 是否已在監(jiān)控拍照 
 
  private String commandId; // 指令ID 
 
  @Override 
  public void onCreate() { 
    Logger.d(TAG, "onCreate..."); 
    super.onCreate(); 
  } 
 
  @Override 
  public int onStartCommand(Intent intent, int flags, int startId) { 
    WakeLockManager.acquire(this); 
    Logger.d(TAG, "onStartCommand..."); 
    startTakePic(intent); 
    return START_NOT_STICKY; 
  } 
 
  private void startTakePic(Intent intent) { 
    if (!isRunning) { 
      commandId = intent.getStringExtra("commandId"); 
      SurfaceView preview = CameraWindow.getDummyCameraView(); 
      if (!TextUtils.isEmpty(commandId) && preview != null) { 
        autoTakePic(preview); 
      } else { 
        stopSelf(); 
      } 
    } 
  } 
 
  private void autoTakePic(SurfaceView preview) { 
    Logger.d(TAG, "autoTakePic..."); 
    isRunning = true; 
    mCamera = getFacingFrontCamera(); 
    if (mCamera == null) { 
      Logger.w(TAG, "getFacingFrontCamera return null"); 
      stopSelf(); 
      return; 
    } 
    try { 
      mCamera.setPreviewDisplay(preview.getHolder()); 
      mCamera.startPreview();// 開始預覽 
      // 防止某些手機拍攝的照片亮度不夠 
      Thread.sleep(200); 
      takePicture(); 
    } catch (Exception e) { 
      e.printStackTrace(); 
      releaseCamera(); 
      stopSelf(); 
    } 
  } 
 
  private void takePicture() throws Exception { 
    Logger.d(TAG, "takePicture..."); 
    try { 
      mCamera.takePicture(null, null, this); 
    } catch (Exception e) { 
      Logger.d(TAG, "takePicture failed!"); 
      e.printStackTrace(); 
      throw e; 
    } 
  } 
 
  private Camera getFacingFrontCamera() { 
    CameraInfo cameraInfo = new CameraInfo(); 
    int numberOfCameras = Camera.getNumberOfCameras(); 
    for (int i = 0; i < numberOfCameras; i++) { 
      Camera.getCameraInfo(i, cameraInfo); 
      if (cameraInfo.facing == CameraInfo.CAMERA_FACING_FRONT) { 
        try { 
          return Camera.open(i); 
        } catch (Exception e) { 
          e.printStackTrace(); 
        } 
      } 
    } 
    return null; 
  } 
 
  @Override 
  public void onPictureTaken(byte[] data, Camera camera) { 
    Logger.d(TAG, "onPictureTaken..."); 
    releaseCamera(); 
    try { 
      // 大于500K,壓縮預防內存溢出 
      Options opts = null; 
      if (data.length > 500 * 1024) { 
        opts = new Options(); 
        opts.inSampleSize = 2; 
      } 
      Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, 
          opts); 
      // 旋轉270度 
      Bitmap newBitmap = ImageCompressUtil.rotateBitmap(bitmap, 270); 
      // 保存 
      String fullFileName = FilePathUtil.getMonitorPicPath() 
          + System.currentTimeMillis() + ".jpeg"; 
      File saveFile = ImageCompressUtil.convertBmpToFile(newBitmap, 
          fullFileName); 
      ImageCompressUtil.recyleBitmap(newBitmap); 
      if (saveFile != null) { 
        // 上傳 
        RequestHttp.uploadMonitorPic(callbackHandler, commandId, 
            saveFile); 
      } else { 
        // 保存失敗,關閉 
        stopSelf(); 
      } 
    } catch (Exception e) { 
      e.printStackTrace(); 
      stopSelf(); 
    } 
  } 
 
  private UiHandler callbackHandler = new UiHandler() { 
 
    @Override 
    public void receiverMessage(Message msg) { 
      switch (msg.arg1) { 
      case TaskStatus.LISTENNERTIMEOUT: 
      case TaskStatus.ERROR: 
      case TaskStatus.FINISHED: 
        // 請求結束,關閉服務 
        stopSelf(); 
        break; 
      } 
    } 
  }; 
 
  // 保存照片 
  private boolean savePic(byte[] data, File savefile) { 
    FileOutputStream fos = null; 
    try { 
      fos = new FileOutputStream(savefile); 
      fos.write(data); 
      fos.flush(); 
      fos.close(); 
      return true; 
    } catch (FileNotFoundException e) { 
      e.printStackTrace(); 
    } catch (IOException e) { 
      e.printStackTrace(); 
    } finally { 
      if (fos != null) { 
        try { 
          fos.close(); 
        } catch (IOException e) { 
          e.printStackTrace(); 
        } 
      } 
    } 
    return false; 
  } 
 
  private void releaseCamera() { 
    if (mCamera != null) { 
      Logger.d(TAG, "releaseCamera..."); 
      mCamera.stopPreview(); 
      mCamera.release(); 
      mCamera = null; 
    } 
  } 
 
  @Override 
  public void onDestroy() { 
    super.onDestroy(); 
    Logger.d(TAG, "onDestroy..."); 
    commandId = null; 
    isRunning = false; 
    FilePathUtil.deleteMonitorUploadFiles(); 
    releaseCamera(); 
    WakeLockManager.release(); 
  } 
 
  @Override 
  public IBinder onBind(Intent intent) { 
    return null; 
  } 
} 

代碼也不多,不過有幾個點需要特別注意下,

1.相機在通話時是用不了的,或者別的應用持有該相機時也是獲取不到相機的,所以需要捕獲camera.Open()的異常,防止獲取不到相機時應用出錯;

2.在用華為相機測試時,開始預覽立馬拍照,發(fā)現(xiàn)獲取的照片亮度很低,原因只是猜測,具體需要去查資料。所以暫且的解決方案是讓線程休眠200ms,然后再調用拍照。

3.在不使用Camera資源或者發(fā)生任何異常時,請記得釋放Camera資源,否則為導致相機被一直持有,別的應用包括系統(tǒng)的相機也用不了,只能重啟手機解決。代碼大家可以優(yōu)化下, 把非正常業(yè)務邏輯統(tǒng)一處理掉?;蛘呤?,使用自定義的UncaughtExceptionHandler去處理未捕獲的異常。

4.關于代碼中WakeLocaManager類,是我自己封裝的喚醒鎖管理類,這也是大家在處理后臺關鍵業(yè)務時需要特別關注的一點,保證業(yè)務邏輯在處理時,系統(tǒng)不會進入休眠。等業(yè)務邏輯處理完,釋放喚醒鎖,讓系統(tǒng)進入休眠。

三、總結

該方案問題也比較多,只是提供一種思路。全局窗口才是這個方案的核心。相機的操作需要謹慎,獲取的時候需要捕獲異常(native異常,連接相機錯誤,相信大家也遇到過),不使用或異常時及時釋放(可以把相機對象寫成static,然后在全局的異常捕獲中對相機做釋放,防止在持有相機這段時間內應用異常時導致相機被異常持有),不然別的相機應用使用不了。
代碼大家稍作修改就可以使用,記得添加相關的權限。以下是系統(tǒng)窗口、喚醒鎖、相機的權限。如果用到自動對焦再拍照,記得聲明以下uses-feature標簽。其它常用權限這里就不贅述。

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.CAMERA" />

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。

相關文章

  • Android中自定義控件的declare-styleable屬性重用方案

    Android中自定義控件的declare-styleable屬性重用方案

    這篇文章主要介紹了Android中自定義控件的declare-styleable屬性重用方案,本文給出了一個終極重用解決方案,需要的朋友可以參考下
    2015-01-01
  • SurfaceView播放視頻發(fā)送彈幕并實現(xiàn)滾動歌詞

    SurfaceView播放視頻發(fā)送彈幕并實現(xiàn)滾動歌詞

    這篇文章主要為大家詳細介紹了SurfaceView播放視頻發(fā)送彈幕并實現(xiàn)滾動歌詞,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2019-11-11
  • android 獲取APP的唯一標識applicationId的實例

    android 獲取APP的唯一標識applicationId的實例

    下面小編就為大家分享一篇android 獲取APP的唯一標識applicationId的實例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-02-02
  • Android自定義View實現(xiàn)水波紋引導動畫

    Android自定義View實現(xiàn)水波紋引導動畫

    這篇文章主要為大家詳細介紹了Android自定義View實現(xiàn)水波紋動畫引導,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-01-01
  • 基于Android代碼實現(xiàn)常用布局

    基于Android代碼實現(xiàn)常用布局

    大家在日常中經常見到用xml文件實現(xiàn)android常用布局,但是大家知道如何用代碼實現(xiàn)呢?使用代碼實現(xiàn)可以幫助我們學習sdk api,所以小編把我日常整理些關于android常用布局代碼實現(xiàn)分享給大家
    2015-11-11
  • Android RecyclerView網格布局示例解析

    Android RecyclerView網格布局示例解析

    這篇文章主要介紹了Android RecyclerView網格布局示例解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-12-12
  • Android軟鍵盤狀態(tài)彈出與消失的示例

    Android軟鍵盤狀態(tài)彈出與消失的示例

    這篇文章主要介紹了本篇文章主要介紹了Android軟鍵盤狀態(tài)彈出與消失的示例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-02-02
  • Android優(yōu)質索尼滾動相冊

    Android優(yōu)質索尼滾動相冊

    這篇文章主要介紹了Android優(yōu)質索尼滾動相冊,桌面小部件滾動相冊,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2016-09-09
  • Android ListView實現(xiàn)下拉加載功能

    Android ListView實現(xiàn)下拉加載功能

    這篇文章主要為大家詳細介紹了Android ListView實現(xiàn)下拉加載功能,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-08-08
  • Android開發(fā)之Parcel機制實例分析

    Android開發(fā)之Parcel機制實例分析

    這篇文章主要介紹了Android開發(fā)之Parcel機制,實例分析了Parcel機制的原理與實現(xiàn)技巧,需要的朋友可以參考下
    2015-05-05

最新評論