這篇文章給大家分享的是有關(guān)如何使用Android實(shí)現(xiàn)后臺服務(wù)拍照功能的內(nèi)容。小編覺得挺實(shí)用的,因此分享給大家做個參考,一起跟隨小編過來看看吧。

創(chuàng)新互聯(lián)10多年企業(yè)網(wǎng)站制作服務(wù);為您提供網(wǎng)站建設(shè),網(wǎng)站制作,網(wǎng)頁設(shè)計及高端網(wǎng)站定制服務(wù),企業(yè)網(wǎng)站制作及推廣,對成都電動窗簾等多個方面擁有豐富的網(wǎng)站制作經(jīng)驗的網(wǎng)站建設(shè)公司。
一、背景介紹
最近在項目中遇到一個需求,實(shí)現(xiàn)一個后臺拍照的功能。一開始在網(wǎng)上尋找解決方案,也嘗試了很多種實(shí)現(xiàn)方式,都沒有滿意的方案。不過確定了難點(diǎn):即拍照要先預(yù)覽,然后再調(diào)用拍照方法。問題也隨之而來,既然是要實(shí)現(xiàn)后臺拍照,就希望能在Service中或者是異步的線程中進(jìn)行,這和預(yù)覽這個步驟有點(diǎn)相矛盾。那有什么方式能夠既能正常的實(shí)現(xiàn)預(yù)覽、拍照,又不讓使用者察覺呢?想必大家也會想到一個取巧的辦法:隱藏預(yù)覽界面。
說明一下,這只是我在摸索中想到的一種解決方案,能很好的解決業(yè)務(wù)上的需求。對于像很多手機(jī)廠商提供的“找回手機(jī)”功能時提供的拍照,我不確定他們的實(shí)現(xiàn)方式。如果大家有更好的實(shí)現(xiàn)方案,不妨交流一下。
關(guān)于這個功能是否侵犯了用戶的隱私,影響用戶的安全等等問題,不在我們的考慮和討論范圍之內(nèi)。
二、方案介紹
方案實(shí)現(xiàn)步驟大致如下:
1.初始化拍照的預(yù)覽界面(核心部分);
2.在需要拍照時獲取相機(jī)Camera,并給Camera設(shè)置預(yù)覽界面;
3.打開預(yù)覽,完成拍照,釋放Camera資源(重要)
4.保存、旋轉(zhuǎn)、上傳.......(由業(yè)務(wù)決定)
先大概介紹下業(yè)務(wù)需求:從用戶登錄到注銷這段時間內(nèi),收到后臺拍照的指令后完成拍照、保存、上傳。以下會基于這個業(yè)務(wù)場景來詳細(xì)介紹各步驟的實(shí)現(xiàn)。
1.初始化拍照的預(yù)覽界面
在測試的過程中發(fā)現(xiàn),拍照的預(yù)覽界面需要在可顯示的情況下生成,才能正常拍照,假如是直接創(chuàng)建SurfaceView實(shí)例作為預(yù)覽界面,然后直接調(diào)用拍照時會拋出native層的異常:take_failed。想過看源碼尋找問題的原因,發(fā)現(xiàn)相機(jī)核心的功能代碼都在native層上面,所以暫且放下,假定的認(rèn)為該在拍照時該預(yù)覽界面一定得在最上面一層顯示。
由于應(yīng)用不管是在前臺還是按home回到桌面,都需要滿足該條件,那這個預(yù)覽界面應(yīng)該是全局的,很容易的聯(lián)想到使用一個全局窗口來作為預(yù)覽界面的載體。這個全局窗口要是不可見的,不影響后面的界面正常交互。所以,就想到用全局的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;
// 屏蔽點(diǎn)擊事件
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();
}
}
}代碼很簡單,主要功能就是顯示這個窗口、獲取用于預(yù)覽的SurfaceView以及關(guān)閉窗口。
在這個業(yè)務(wù)中,show方法可以直接在自定義的Application類中調(diào)用。這樣,在應(yīng)用啟動后,窗口就在了,只有在應(yīng)用銷毀(注意,結(jié)束所有Activity不會關(guān)閉,因為它初始化在Application中,它的生命周期就為應(yīng)用級的,除非主動調(diào)用dismiss方法主動關(guān)閉)。
完成了預(yù)覽界面的初始化,整個實(shí)現(xiàn)其實(shí)已經(jīng)非常簡單了。可能許多人遇到的問題就是卡在沒有預(yù)覽界面該如何拍照這里,希望這樣一種取巧的方式可以幫助大家在以后的項目中遇到無法直接解決問題時,可以考慮從另外的角度切入去解決問題。
2.完成Service拍照功能
這里將對上面的后續(xù)步驟進(jìn)行合并。先上代碼:
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;
/**
* 后臺拍照服務(wù),配合全局窗口使用
*
* @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; // 指令I(lǐng)D
@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();// 開始預(yù)覽
// 防止某些手機(jī)拍攝的照片亮度不夠
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,壓縮預(yù)防內(nèi)存溢出
Options opts = null;
if (data.length > 500 * 1024) {
opts = new Options();
opts.inSampleSize = 2;
}
Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length,
opts);
// 旋轉(zhuǎn)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 {
// 保存失敗,關(guān)閉
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:
// 請求結(jié)束,關(guān)閉服務(wù)
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;
}
}代碼也不多,不過有幾個點(diǎn)需要特別注意下,
1.相機(jī)在通話時是用不了的,或者別的應(yīng)用持有該相機(jī)時也是獲取不到相機(jī)的,所以需要捕獲camera.Open()的異常,防止獲取不到相機(jī)時應(yīng)用出錯;
2.在用華為相機(jī)測試時,開始預(yù)覽立馬拍照,發(fā)現(xiàn)獲取的照片亮度很低,原因只是猜測,具體需要去查資料。所以暫且的解決方案是讓線程休眠200ms,然后再調(diào)用拍照。
3.在不使用Camera資源或者發(fā)生任何異常時,請記得釋放Camera資源,否則為導(dǎo)致相機(jī)被一直持有,別的應(yīng)用包括系統(tǒng)的相機(jī)也用不了,只能重啟手機(jī)解決。代碼大家可以優(yōu)化下, 把非正常業(yè)務(wù)邏輯統(tǒng)一處理掉。或者是,使用自定義的UncaughtExceptionHandler去處理未捕獲的異常。
4.關(guān)于代碼中WakeLocaManager類,是我自己封裝的喚醒鎖管理類,這也是大家在處理后臺關(guān)鍵業(yè)務(wù)時需要特別關(guān)注的一點(diǎn),保證業(yè)務(wù)邏輯在處理時,系統(tǒng)不會進(jìn)入休眠。等業(yè)務(wù)邏輯處理完,釋放喚醒鎖,讓系統(tǒng)進(jìn)入休眠。
三、總結(jié)
該方案問題也比較多,只是提供一種思路。全局窗口才是這個方案的核心。相機(jī)的操作需要謹(jǐn)慎,獲取的時候需要捕獲異常(native異常,連接相機(jī)錯誤,相信大家也遇到過),不使用或異常時及時釋放(可以把相機(jī)對象寫成static,然后在全局的異常捕獲中對相機(jī)做釋放,防止在持有相機(jī)這段時間內(nèi)應(yīng)用異常時導(dǎo)致相機(jī)被異常持有),不然別的相機(jī)應(yīng)用使用不了。
代碼大家稍作修改就可以使用,記得添加相關(guān)的權(quán)限。以下是系統(tǒng)窗口、喚醒鎖、相機(jī)的權(quán)限。如果用到自動對焦再拍照,記得聲明以下uses-feature標(biāo)簽。其它常用權(quán)限這里就不贅述。
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> <uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.CAMERA" />
感謝各位的閱讀!關(guān)于“如何使用Android實(shí)現(xiàn)后臺服務(wù)拍照功能”這篇文章就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,讓大家可以學(xué)到更多知識,如果覺得文章不錯,可以把它分享出去讓更多的人看到吧!
新聞名稱:如何使用Android實(shí)現(xiàn)后臺服務(wù)拍照功能
文章鏈接:http://chinadenli.net/article32/ggpdpc.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供域名注冊、建站公司、企業(yè)網(wǎng)站制作、靜態(tài)網(wǎng)站、品牌網(wǎng)站設(shè)計、網(wǎng)站導(dǎo)航
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)