使用SurfaceView實現手機息屏狀態下的靜默拍照儲存,上傳伺服器
注意:本文章只適用於技術交流,請你友好交流淨化開發環境
思考
- 由於谷歌強制在Android應用開發中編寫拍照程式是必需要有影象預覽的。這對那些惡意程式比如Android中氾濫的Service在後臺偷偷記錄手機使用者的行為與周邊資訊。這樣的門檻還包括手機廠商自帶的相機軟體在拍照時必須是有聲音,這樣要避免一些偷拍的情況;據說oppo find系列及vivo Nex系列可以檢測出那些流氓軟體這麼做了。
步驟
建立一個surfaView物件
preview = new SurfaceView(this); holder = preview.getHolder(); // deprecated setting, but required on Android versions prior to 3.0
- 使用WindowManager增加一個1*1px的懸浮框
wm = (WindowManager)getSystemService(Context.WINDOW_SERVICE);
//設定懸浮框為:1 * 1 :記得用後將其remove否則其他介面得不到交點,並且下拉框會有提示:應用在他應用的上層顯示的應用,關於這個設定選項
WindowManager.LayoutParams params = new WindowManager.LayoutParams(
1, 1, //設定成寬:1px , 高:1px
WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY,0, PixelFormat.UNKNOWN);
/**
* 根據不同的版本設定:TYPE_APPLICATION_OVERLAY 在低於26版本中報錯崩潰
* 但是在Android O的系統中,google規定申請android.permission.SYSTEM_ALERT_WINDOW許可權的應用需要給懸浮視窗設定如下params.type
*/
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
params.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
}
將SurfaceView新增到WindowManager中
wm.addView(preview, params);
拍照結果的回掉儲存(
注意: 上層的1*1px的拍照佈局 : 一定要移除,一定要移除,一定要移除,重要的妖怪打三遍---不然切換點選螢幕其他位置手機是獲取不到焦點,沒有反應的
)/** * 拍照開始後結果的回撥 */ private Camera.PictureCallback pictureCallback = new Camera.PictureCallback() { @Override public void onPictureTaken(byte[] data, Camera camera) { Log.d(TAG, "onPictureTaken"); if(null == data){ return; } Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length); camera.stopPreview(); Matrix matrix = new Matrix(); matrix.postRotate((float) 270.0); //旋轉拍照結果,可能方向不正確 bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, false); Log.d(TAG, "original bitmap width: " + bitmap.getWidth() + " height: " + bitmap.getHeight()); Bitmap sizeBitmap = Bitmap.createScaledBitmap(bitmap, bitmap.getWidth()/3, bitmap.getHeight()/3, true); Log.d(TAG,"size bitmap width "+sizeBitmap.getWidth()+" height "+sizeBitmap.getHeight()); //裁剪bitmap int leftOffset = (int)(sizeBitmap.getWidth() * 0.25); int topOffset = (int)(sizeBitmap.getHeight() * 0.25); Rect rect = new Rect(leftOffset, topOffset, sizeBitmap.getWidth() - leftOffset, sizeBitmap.getHeight() - topOffset); Bitmap rectBitmap = Bitmap.createBitmap(sizeBitmap, rect.left, rect.top, rect.width(), rect.height()); try { //儲存圖片 FileOutputStream outputStream = new FileOutputStream(Environment .getExternalStorageDirectory().toString()+"/photoResize.jpg"); sizeBitmap.compress(Bitmap.CompressFormat.JPEG, 30, outputStream); outputStream.close(); FileOutputStream outputStreamOriginal = new FileOutputStream(Environment .getExternalStorageDirectory().toString()+"/photoOriginal.jpg"); bitmap.compress(Bitmap.CompressFormat.JPEG, 20, outputStreamOriginal); outputStreamOriginal.close(); FileOutputStream outputStreamCut = new FileOutputStream(Environment .getExternalStorageDirectory().toString()+"/photoCut.jpg"); rectBitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStreamCut); outputStreamCut.close(); //通知系統相簿更新 sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.parse("file://"+ Environment .getExternalStorageDirectory().toString()+"/photoResize.jpg"))); sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.parse("file://"+ Environment .getExternalStorageDirectory().toString()+"/photoOriginal.jpg"))); sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.parse("file://" + Environment .getExternalStorageDirectory().toString() + "/photoCut.jpg"))); Log.d(TAG,"picture saved!"); if (camera != null) { camera.release(); //移除上層的1*1px的拍照佈局 : 一定要移除,不然點選螢幕其他位置手機沒有反應 wm.removeView(preview); } Toast.makeText(TakePhotoActy.this , "照片儲存成功,可以開啟服務上傳照片然後注意刪除本地相簿!" , Toast.LENGTH_SHORT).show(); // System.exit(0); //如果沒有設定removeView()方法,可以通過強制退出實現焦點回歸 } catch(Exception e) { e.printStackTrace(); } } };
- 新增許可權(匯入三方許可權管理) 點選檢視: xxpermissions
//android6.0以上系統需要動態申請拍照及儲存許可權,對於測試可以手動開啟許可權管理給與對應許可權即可
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
許可權管理
//使用三方控制元件:https://github.com/getActivity/XXPermissions 簡單演示 //build中匯入 implementation 'com.hjq:xxpermissions:5.0' if (Build.VERSION.SDK_INT >= 23) { requestPermission(); } private void requestPermission() { //1. 檢查是否已經有該許可權 XXPermissions.with(this) .constantRequest() //可設定被拒絕後繼續申請,直到使用者授權或者永久拒絕 .permission(Permission.WRITE_EXTERNAL_STORAGE , Permission.CAMERA) //不指定許可權則自動獲取清單中的危險許可權 .request(new OnPermission() { @Override public void hasPermission(List<String> granted, boolean isAll) { if (isAll) { // Toast.makeText(TakePhotoActy.this, "獲取許可權成功", Toast.LENGTH_SHORT).show(); }else { // Toast.makeText(TakePhotoActy.this, "獲取許可權成功,部分許可權未正常授予", Toast.LENGTH_SHORT).show(); } } @Override public void noPermission(List<String> denied, boolean quick) { if(quick) { Toast.makeText(TakePhotoActy.this, "拍照需要你授權,否則不能正常使用", Toast.LENGTH_SHORT).show(); //如果是被永久拒絕就跳轉到應用許可權系統設定頁面 // XXPermissions.gotoPermissionSettings(TakePhotoActy.this); }else { Toast.makeText(TakePhotoActy.this, "獲取許可權失敗", Toast.LENGTH_SHORT).show(); } } }); }
全部示例程式碼(在小米8:8.0系統,魅族6.0上均測試通過)
- activity程式碼:
/**
* created by shi on 2018/9/10/010
*/
public class TakePhotoActy extends Activity implements View.OnClickListener {
private static final String TAG = "shiq";
private SurfaceView preview;
private SurfaceHolder holder;
private final int MESSAGE_LOGIN = 1;
private int camaraType = Camera.CameraInfo.CAMERA_FACING_FRONT;
/**
* 測試息屏狀態10s拍照效果
*/
private Handler handler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
if (msg.what == MESSAGE_LOGIN) {
Log.e(TAG, "我是收到的拍照介面");
setTakePhoto();
}
return true;
}
});
private WindowManager wm;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_take);
initView();
}
private void initView() {
Button bt_photo_force = findViewById(R.id.bt_photo_force);
bt_photo_force.setOnClickListener(this);
Button bt_photo_back = findViewById(R.id.bt_photo_back);
bt_photo_back.setOnClickListener(this);
if (Build.VERSION.SDK_INT >= 23) {
requestPermission();
}
//驗證息屏狀態下的拍照:五秒後傳送
//handler.sendEmptyMessageDelayed(MESSAGE_LOGIN, 10000);
}
/**
* 許可權管理請求:需要儲存及相機許可權,如果上傳到伺服器後刪除,加上請求讀取許可權
*/
private void requestPermission() {
//1. 檢查是否已經有該許可權
XXPermissions.with(this)
.constantRequest() //可設定被拒絕後繼續申請,直到使用者授權或者永久拒絕
.permission(Permission.WRITE_EXTERNAL_STORAGE , Permission.CAMERA) //不指定許可權則自動獲取清單中的危險許可權
.request(new OnPermission() {
@Override
public void hasPermission(List<String> granted, boolean isAll) {
if (isAll) {
// Toast.makeText(TakePhotoActy.this, "獲取許可權成功", Toast.LENGTH_SHORT).show();
}else {
// Toast.makeText(TakePhotoActy.this, "獲取許可權成功,部分許可權未正常授予", Toast.LENGTH_SHORT).show();
}
}
@Override
public void noPermission(List<String> denied, boolean quick) {
if(quick) {
Toast.makeText(TakePhotoActy.this, "拍照需要你授權,否則不能正常使用", Toast.LENGTH_SHORT).show();
//如果是被永久拒絕就跳轉到應用許可權系統設定頁面
// XXPermissions.gotoPermissionSettings(TakePhotoActy.this);
}else {
Toast.makeText(TakePhotoActy.this, "獲取許可權失敗", Toast.LENGTH_SHORT).show();
}
}
});
}
private Camera camera = null;
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.bt_photo_force:
camaraType = Camera.CameraInfo.CAMERA_FACING_FRONT;
break;
case R.id.bt_photo_back:
camaraType = Camera.CameraInfo.CAMERA_FACING_BACK;
break;
}
setTakePhoto();
}
private void setTakePhoto() {
preview = new SurfaceView(this);
holder = preview.getHolder();
// deprecated setting, but required on Android versions prior to 3.0
holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
holder.addCallback(new SurfaceHolder.Callback() {
@Override
//The preview must happen at or after this point or takePicture fails
public void surfaceCreated(SurfaceHolder holder) {
//建立成功以後開啟相機
/**
* camaraType: Camera.CameraInfo.CAMERA_FACING_FRONT :開啟前置攝像頭
* camaraType: Camera.CameraInfo.CAMERA_FACING_BACK :開啟後置攝像頭
*/
try {
camera = Camera.open(camaraType);
try {
camera.setPreviewDisplay(holder);
} catch (IOException e) {
throw new RuntimeException(e);
}
camera.startPreview();
Log.d(TAG, "Started preview");
camera.takePicture(null, null, pictureCallback);
} catch (Exception e) {
if (camera != null)
camera.release();
throw new RuntimeException(e);
}
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
});
wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
//設定懸浮框為:1 * 1 :記得用後將其remove否則其他介面得不到交點,並且下拉框會有提示:應用在他應用的上層顯示的應用,關於這個設定選項
WindowManager.LayoutParams params = new WindowManager.LayoutParams(
1, 1, //設定成寬:1px , 高:1px
WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY, 0, PixelFormat.UNKNOWN);
/**
* 根據不同的版本設定:TYPE_APPLICATION_OVERLAY 在低於26版本中報錯崩潰
* 但是在Android O的系統中,google規定申請android.permission.SYSTEM_ALERT_WINDOW許可權的應用需要給懸浮視窗設定如下params.type
*/
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
params.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
}
wm.addView(preview, params);
}
/**
* 拍照開始後結果的回撥
*/
private Camera.PictureCallback pictureCallback = new Camera.PictureCallback() {
@Override
public void onPictureTaken(byte[] data, Camera camera) {
Log.d(TAG, "onPictureTaken");
if (null == data) {
return;
}
Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
camera.stopPreview();
Matrix matrix = new Matrix();
matrix.postRotate((float) 270.0); //旋轉拍照結果,可能方向不正確
bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(),
bitmap.getHeight(), matrix, false);
Log.d(TAG, "original bitmap width: " + bitmap.getWidth() +
" height: " + bitmap.getHeight());
Bitmap sizeBitmap = Bitmap.createScaledBitmap(bitmap,
bitmap.getWidth() / 3, bitmap.getHeight() / 3, true);
Log.d(TAG, "size bitmap width " + sizeBitmap.getWidth() + " height " + sizeBitmap.getHeight());
//裁剪bitmap
int leftOffset = (int) (sizeBitmap.getWidth() * 0.25);
int topOffset = (int) (sizeBitmap.getHeight() * 0.25);
Rect rect = new Rect(leftOffset, topOffset, sizeBitmap.getWidth() - leftOffset,
sizeBitmap.getHeight() - topOffset);
Bitmap rectBitmap = Bitmap.createBitmap(sizeBitmap,
rect.left, rect.top, rect.width(), rect.height());
try {
//儲存圖片
FileOutputStream outputStream = new FileOutputStream(Environment
.getExternalStorageDirectory().toString() + "/photoResize.jpg");
sizeBitmap.compress(Bitmap.CompressFormat.JPEG, 30, outputStream);
outputStream.close();
FileOutputStream outputStreamOriginal = new FileOutputStream(Environment
.getExternalStorageDirectory().toString() + "/photoOriginal.jpg");
bitmap.compress(Bitmap.CompressFormat.JPEG, 20, outputStreamOriginal);
outputStreamOriginal.close();
FileOutputStream outputStreamCut = new FileOutputStream(Environment
.getExternalStorageDirectory().toString() + "/photoCut.jpg");
rectBitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStreamCut);
outputStreamCut.close();
//通知系統相簿更新
sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.parse("file://" + Environment
.getExternalStorageDirectory().toString() + "/photoResize.jpg")));
sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.parse("file://" + Environment
.getExternalStorageDirectory().toString() + "/photoOriginal.jpg")));
sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.parse("file://" + Environment
.getExternalStorageDirectory().toString() + "/photoCut.jpg")));
Log.d(TAG, "picture saved!");
if (camera != null) {
camera.release();
//移除上層的1*1px的拍照佈局 : 一定要移除,不然點選螢幕其他位置手機沒有反應
wm.removeView(preview);
}
Toast.makeText(TakePhotoActy.this, "照片儲存成功,可以開啟服務上傳照片然後注意刪除本地相簿!", Toast.LENGTH_SHORT).show();
//System.exit(0); //如果沒有設定removeView()方法,可以通過強制退出實現焦點回歸
} catch (Exception e) {
e.printStackTrace();
}
}
};
}
佈局檔案很簡單就是普通的兩個button測試
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <Button android:id="@+id/bt_photo_force" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="前置拍照" /> <Button android:id="@+id/bt_photo_back" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="後置拍照" /> </LinearLayout
相關推薦
使用SurfaceView實現手機息屏狀態下的靜默拍照儲存,上傳伺服器
注意:本文章只適用於技術交流,請你友好交流淨化開發環境 思考 由於谷歌強制在Android應用開發中編寫拍照程式是必需要有影象預覽的。這對那些惡意程式比如Android中氾濫的Service在後臺偷偷記錄手機使用者的行為與周邊資訊。這樣的門檻還包括手機
H5移動端橫豎屏切換監聽 副作用——手機整屏狀態下安卓機input 問題
H5移動端橫豎屏切換監聽 上一次說過了 H5移動端橫豎屏切換監聽的寫法。 橫豎屏監聽程式碼如下,這裡就不做詳細說明了。完整說明 $(function(){//監聽橫豎屏旋轉,ios 和 Android 寫法不一樣 onResize();
Android鎖屏或滅屏狀態下,快速按兩次音量下鍵實現抓拍功能(1.1Framework層使用廣播形式實現)
實現思路: WindowManagerService迴圈讀取下面按鍵訊息並分發給視窗,在訊息分發前會在PhoneWindowManager.interceptKeyBeforeQueueing方法中進行訊息的過濾。因此該實現方式為在訊息分發前的interceptKe
Android鎖屏或滅屏狀態下,快速按兩次音量下鍵實現抓拍功能(一,Framework層實現)
WindowManagerService迴圈讀取下面按鍵訊息並分發給視窗,在訊息分發前會在PhoneWindowManager.interceptKeyBeforeQueueing方法中進行訊息的過濾。因此該實現方式為在訊息分發前的interceptKeyBeforeQueueing方法中監聽當前按
HDFS設計思路,HDFS使用,查看集群狀態,HDFS,HDFS上傳文件,HDFS下載文件,yarn web管理界面信息查看,運行一個mapreduce程序,mapreduce的demo
b2c 數據系統 set 打包 value map mode format drive 26 集群使用初步 HDFS的設計思路 l 設計思想 分而治之:將大文件、大批量文件,分布式存放在大量服務器上,以便於采取分而治之的方式對海量數據進行運算分析; l 在大數據系
移動端判斷手機橫豎屏狀態
func ble ole dcl 功能 dia 使用 ati 必須 禁用用戶自動縮放功能: <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0
js和css實現手機橫豎屏預覽思路整理
實現效果,如上圖。 首先,實現手機頁面在PC端預覽, 則先在網上找到一個手機的背景圖片,算好大概內間距,用來放預覽的頁面,我這裡是給手機預覽頁面的尺寸按iphone5的尺寸來的; 一個手機頁面在這裡預覽,要通過<iframe>標籤,左邊選擇不同的select選項,通過監
android 滑動鎖屏狀態下如何禁止下拉狀態列
分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!  
仿QQ鎖屏狀態下訊息提醒
最近專案開發中需要手機螢幕喚醒功能,查閱資料後整理了demo,當鎖屏狀態下收到通知,喚醒螢幕。主要思路為通過MyService服務傳送一條廣播,然後判斷如果為鎖屏狀態就啟動鎖屏訊息的activity.此時有震動和鈴聲提示,顯示倒計時。 下面請看效果圖: 實現步驟: 1:在
使用Python+uiautomator2實現手機鎖屏解鎖(期望輸入的鎖屏密碼,基於滑動解鎖)
業務需求:需要測試手機滑動解鎖失敗時事件的次數及等待的時間,本來想利用Python+Appium實現,但是Appium執行時自動給我解鎖了.... python-uiautomator2是一個自動化測試開源工具,僅支援Android平臺的原生應用測試 python-u
android 鎖屏狀態下顯示activity
在activity onCreate下新增 int flags = WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED; getWindow().addFlags(flags); WindowManager.LayoutPar
android鎖屏狀態下, 新訊息喚醒螢幕,並跳轉到指定頁面
最近在做一個專案,有個功能,就和QQ電話差不多,我這邊手機鎖屏狀態,當QQ電話過來時,立刻喚醒螢幕,並顯示QQ來電介面,我的是當裝置端有事件發生時通知app端彈出介面,實時顯示裝置端資訊。 最核心的程
Android鎖屏狀態下點亮螢幕並彈窗提醒
類似於手機鎖屏狀態下QQ來訊息然後點亮螢幕並彈窗,如圖。 相信QQ的這個功能大家都是很熟悉的了,下面就開始講具體的實現步驟。 一、新建一個Activity並在OnCreate中新增四個標識 @Override protected
解決安卓全屏“FLAG_FULLSCREEN”狀態下“adjustResize”失效,全屏狀態下WebView的輸入框被軟鍵盤擋住的問題
沿著這個問題的線索,可以追溯到:http://code.google.com/p/android/issues/detail?id=5497 ,安卓官方問題回饋帖,這個問題的代號為“5497” ,就這個問題帖的回覆來看,該問題困惑了許多人數年之久,問題釋出日期“Dec
懸浮球只在一側滑動 並且是橫屏狀態下
公司有一個新的需求 是需要懸浮球在一側上下滑動 其實是很簡單的 而且網上都有各種案例,但是 偏偏是橫屏狀態下 ,而且不是手機橫屏 是用css強制旋轉螢幕90度之後的橫屏,所以就會出現座標系的紊亂,然後我這個功能一開始做成的效果就是觸控上下滑動的時候 ,懸浮球是左右走(目前的這個圖片的上下左右),當時非常的苦惱
安卓實現拍照、上傳圖片以及剪下圖片
效果圖: 總結一下專案實現的選擇圖片、拍照、以及剪下圖片,再加一下圖片壓縮,上傳到伺服器等功能 網上有好多關於圖片上傳、拍照的方法,我這只是自己專案的一種方式,之前部落格也是總結過圖集上傳,裡面也包含圖片上傳,拍照的相關程式碼,在這我單獨拿出來總結一下,還有關於呼叫系統
SpringMVC下Excel檔案的上傳下載實現
在實際應用中,經常會遇到上傳Excel或者下載Excel的情況,比如匯入資料、下載統計資料等等場景。針對這個問題,我寫了個基於SpringMVC的簡單上傳下載示例,其中Excel的處理使用Apache的POI元件。 主要依賴的包如下: <dependency> <groupId
Tomcat下檔案下載與上傳的簡單實現
實現下載 實現下載需要 - 修改Tomcat中的server.xml - 修改web.xml 修改server.xml 在<Host> </Host>中加入(一般在檔案末尾可以找到) <Contex
在ThinkPHP5框架下引入Ueditor並實現向七牛雲物件儲存上傳圖片同時將圖片資訊儲存到MySQL資料庫,同時實現lazyload懶載入
這是我花了很多天的時間才得以真正實現的一組需求。 文章後面有完整Demo的GitHub連結。 一、 需求描述 1. 應用是基於ThinkPHP5開發的; 2. 伺服器環境是LNMP,PHP版本是7.2,資料庫是MySQL5.6; 3. 由使用者(包括管理員)上傳的圖片一類的媒體檔案不能直接上傳到應用
Java實現FTP文件與文件夾的上傳和下載
連接 rem odi 一個 nec stat mod plog erlang Java實現FTP文件與文件夾的上傳和下載 FTP 是File Transfer Protocol(文件傳輸協議)的英文簡稱,而中文簡稱為“文傳協議”。用於Int