Android H5交互实现拍照显示
今天来整理一下android和h5交互实现拍照,相册选图片进行显示,适配了7.0。6.0的网络权限顺便也简单说下。
PackageManager pkgManager = getPackageManager();
// 读写 sd card 权限非常重要, android6.0默认禁止的, 建议初始化之前就弹窗让用户赋予该权限
boolean sdCardWritePermission =
pkgManager.checkPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, getPackageName()) == PackageManager.PERMISSION_GRANTED;
// read phone state用于获取 imei 设备信息
boolean phoneSatePermission =
pkgManager.checkPermission(Manifest.permission.READ_PHONE_STATE, getPackageName()) == PackageManager.PERMISSION_GRANTED;
boolean cameraPermission =
pkgManager.checkPermission(Manifest.permission.CAMERA, getPackageName()) == PackageManager.PERMISSION_GRANTED;
if (Build.VERSION.SDK_INT >= 23 && !sdCardWritePermission || !phoneSatePermission || !cameraPermission) {
//没有权限,去申请
requestPermission();
} else {
//有权限了
}
通过上面的代码,我们可以看到,我写的简单的申请权限,写的不太好,你们可以去找些比较好的来使用,我们这里主要讲的还是与H5交互拍照,申请权限的话就是下面的代码:
private void requestPermission() {
ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE,Manifest.permission.READ_PHONE_STATE,Manifest.permission.CAMERA},
REQUEST_PERMISSION);
}
权限申请好了就是申请权限的回调了:
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
if (requestCode == REQUEST_PERMISSION) {
if ((grantResults.length == 2 && grantResults[0] == PackageManager.PERMISSION_GRANTED
&& grantResults[1] == PackageManager.PERMISSION_GRANTED)) {
//这是我推送的代码
PushManager.getInstance().initialize(this.getApplicationContext(), userPushService);
} else {
Logs.e("tag2", "We highly recommend that you need to grant the special permissions before initializing the SDK, otherwise some "
+ "functions will not work");
PushManager.getInstance().initialize(this.getApplicationContext(), userPushService);
}
} else {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
好了,简单的权限就申请好了,下面就是主要的交互了,(一定要在有读写权限拍照权限的基础上来写):
首先我们和H5商量一个方法来调用:
因为我的项目和H5交互比较多,所以我就将所有的交互写在了一个类中调用。
private Activity act = null;
//调用 照相机
@JavascriptInterface
public void imagePickerWithID(String json) {
((MainActivity) act).imagePickerWithID(json);
}
然后在我们需要调用的类中调用一下
private String photoCallback =null;
//调用照相机传照片
public void imagePickerWithID(final String json) {
mHandlerHelper.sendMessage(json, new MPCallBack() {
@Override
public void callBack(int what, Object obj) {
showActionSheet();
photoCallback = json;
}
});
}
然后我们将拍照后或者去相册选了图片后通过callBack来回调给H5去显示一下:
这里需要注意的是,不要按照我的样式写,因为,我封装了代码,你们写的话,就正常和H5进行交互,回传给H5数据就行。核心代码在下面
上面的方法中的showActionSheet就是去调用客户端的相机或者相册了,接下来就是实际代码:
//拍照START
public static final int PHOTOZOOM = 0; // 相册/拍照
public static final int PHOTOTAKE = 1; // 相册/拍照
private String photoSavePath =Environment.getExternalStorageDirectory().getAbsolutePath()
+ "/ClipHeadPhoto/cache/";// 保存路径
private String photoSaveName;// 图pian名
private String path;// 图片全路径
private File file;
//拍照END
//点击更多弹出 ActionSheet
private void showActionSheet() {
this.setTheme(R.style.ActionSheetStyleIOS7); //右边baritem 点击事件
ActionSheet menuView = new ActionSheet(this);
menuView.setCancelButtonTitle("取消"); // before add items
List<String> list = new ArrayList<>();
list.add("从相册中选择");
list.add("现在拍照");
menuView.addItems(list);
menuView.setItemClickListener(new ActionSheet.MenuItemClickListener() {
@Override
public void onItemClick(int itemPosition) {
if (itemPosition == 0) {
/**
* 相册
*/
Intent openAlbumIntent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);//Intent.ACTION_PICK
openAlbumIntent.setType("image/*");
startActivityForResult(openAlbumIntent, PHOTOZOOM);
} else {
/**
* 相机适配7.0
*/
photoSaveName = String.valueOf(System.currentTimeMillis())
+ ".png";
Uri imageUri = null;
Intent openCameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
// 这里用时间命名是发现用固定名命名后第二次裁剪图片任然是第一次的图,没有覆盖上一次图片资源;7.0之前固定名会替换
file = new File(photoSavePath, photoSaveName);
if (!file.getParentFile().exists()) file.getParentFile().mkdirs();
// 通过FileProvider创建一个content类型的Uri
imageUri = FileProvider.getUriForFile(MainActivity.this, "com.aegiscard.airport.fileprovider", file);
openCameraIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
// 将拍取的照片保存到指定URI
openCameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
// 开启一个带有返回值的Activity,请求码为PHOTO_REQUEST_GALLERY
startActivityForResult(openCameraIntent,PHOTOTAKE);
}
//7.0以下的拍照方式
else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
file = new File(photoSavePath, photoSaveName);
if (!file.getParentFile().exists()) file.getParentFile().mkdirs();
// 加载拍好的照片
imageUri = Uri.fromFile(file);
openCameraIntent.putExtra(MediaStore.Images.Media.ORIENTATION,
0);
openCameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
startActivityForResult(openCameraIntent, PHOTOTAKE);
}
}
}
});
menuView.setCancelableOnTouchMenuOutside(true);
menuView.showMenu();
}
这个注释还是写的比较清楚的,我就不啰嗦了,
photoSavePath图片的路径的话,这个可以自己去设置的,不一定非得来用我的路径。
还有需要注意的就是7.0的创建文件夹的权限问题,上面已经写了怎么使用了,下面就看一些配置文件:
首先,我们需要在res下创建一个文件夹xml然后创建一个文件file_paths.xml
这个文件里面写
<?xml version="1.0" encoding="utf-8"?>
<resources>
<paths>
<external-path
name="camera_photos"
path="" />
</paths>
</resources>
这些就行,下面就说下他们是什么意思:
标签说明
<paths>为顶层标签,它下面可添加任意个(0,1或多个)上面出现的标签
<paths>子标签解释
可以看到上面每个标签都含有一个name和path属性
name:这个可以随意写(用于隐藏真实的目录,后面再演示)
path:要共享的目录,只能是目录,不能是某个具体的文件
<files-path> 代表Context.getFilesDir()所指向的目录
<cache-path>代表Context.getCacheDir()所指向的目录
<external-path>代表Environment.getExternalStorageDirectory()所指向的目录
<external-files-path>代表Context.getExternalFilesDir()所指向的目录
<external-cache-path>代表Context.getExternalCacheDir()所指向的目录
<external-media-path>代表Context.getExternalMediaDirs()所指向的目录(API21+设备才能使用,所以一般不用)
这个创建好了需要在mainfest里面写:
<!-- <!–7.0相机权限–> -->
<provider
android:name="android.support.v4.content.FileProvider"
//com.aegiscard.airport.fileprovider这个名字随便写,需要注意的是需要和我们上面的使用保持一致!
android:authorities="com.aegiscard.airport.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
这样的话,我们的7.0适配就ok了。
我们就可以正常的创建文件夹,保存图片了,然后我们还需要回调:
@Override
protected void onActivityResult(int requestCode, int resultCode, final Intent data) {
// TODO Auto-generated method stub
Uri uri = null;
switch (requestCode) {
case PHOTOZOOM:// 相册
{
if (data == null) {
return;
}
uri = data.getData();
String[] proj = {MediaStore.Images.Media.DATA};
Cursor cursor = managedQuery(uri, proj, null, null, null);
if (cursor != null) {
int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
cursor.moveToFirst();
path = cursor.getString(column_index);// 图片在的路径
} else {
String uRl = uri.toString();
path = uRl.substring(7);
}
Handler handler = new Handler();
handler.post(new Runnable() {
@Override
public void run() {
try {
Bimp bimp = new Bimp();
bimp.clean();
bimp.addSelectBitmap(path, 300, 300);
String imag = "{\"imgUrl\": \"" + ImageUtil.bitmapToBase64(bimp.bmp.get(0)) + "\"}";
String a = "javascript:newTools.native.set(\'" + photoCallback + "\',\'" + imag + "\');";
Logs.v("javascript:newTools=", a);
mHandlerHelper.loadUrl(a);
// bimp.cleanbitmp();
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
break;
case PHOTOTAKE:// 拍照
{
path = photoSavePath + photoSaveName;
Logs.v("javascript:newTools=111", path);
uri = Uri.fromFile(new File(path));
Handler handler = new Handler();
handler.post(new Runnable() {
@Override
public void run() {
try {
Bimp bimp = new Bimp();
bimp.clean();
bimp.addSelectBitmap(path, 300, 300);
String imag = "{\"imgUrl\": \"" + ImageUtil.bitmapToBase64(bimp.bmp.get(0)) + "\"}";
String a = "javascript:newTools.native.set(\'" + photoCallback + "\',\'" + imag + "\');";
Logs.v("javascript:newTools=111", a);
Logs.v("javascript:newTools=111", imag);
mHandlerHelper.loadUrl(a);
//bimp.cleanbitmp();
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
break;
}
super.onActivityResult(requestCode, resultCode, data);
}
这个里面主要的就是loadUrl,mHandlerHelper是我的webview帮助类,你们不用管,就webview.loadurl就行。
中间涉及到了转换:
// bitmap转车base64
public static String bitmapToBase64(Bitmap bitmap) {
String result = null;
ByteArrayOutputStream baos = null;
try {
if (bitmap != null) {
baos = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG,80, baos);
baos.flush();
baos.close();
byte[] bitmapBytes = baos.toByteArray();
result = Base64.encodeToString(bitmapBytes, Base64.DEFAULT);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (baos != null) {
baos.flush();
baos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return result;
}
下面是Bimp的类,这个是用来处理图片的,你们也可以自己写,用自己的就行,不一定要用我的。主要就是上面的转换。
public class Bimp {
public static int max = 0;
public static boolean act_bool = true;
public List<Bitmap> bmp = new ArrayList<Bitmap>();
// 图片sd地址 上传服务器时把图片调用下面方法压缩后 保存到临时文件夹 图片压缩后小于100KB,失真度不明显
public List<String> drr = new ArrayList<String>();
public void clean() {
max = 0;
act_bool = true;
bmp.clear();
drr.clear();
}
/**
* 将图片压缩后存入
*
* @param bitmap
*/
public void addSelectBitmap(Bitmap bitmap) {
//图片允许最大空间 单位:KB
double maxSize =40.00;
//将bitmap放至数组中,意在bitmap的大小(与实际读取的原文件要大)
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, 90, baos);
byte[] b = baos.toByteArray();
//将字节换成KB
double mid = b.length/1024;
//判断bitmap占用空间是否大于允许最大空间 如果大于则压缩 小于则不压缩
if (mid > maxSize) {
//获取bitmap大小 是允许最大大小的多少倍
double i = mid / maxSize;
//开始压缩 此处用到平方根 将宽带和高度压缩掉对应的平方根倍 (1.保持刻度和高度和原bitmap比率一致,压缩后也达到了最大大小占用空间的大小)
bitmap = zoomImage(bitmap, bitmap.getWidth() / Math.sqrt(i),bitmap.getHeight() / Math.sqrt(i));
}
bmp.add(bitmap);
}
public void addSelectBitmap(String path,int w,int h) {
BitmapFactory.Options opts = new BitmapFactory.Options();
// FileInputStream is = new FileInputStream(path);
opts.inJustDecodeBounds=true;
BitmapFactory.decodeFile(path,opts);
opts.inPreferredConfig= Bitmap.Config.RGB_565;
int width = opts.outWidth;
int height = opts.outHeight;
float scaleWidth = 0.f, scaleHeight = 0.f;
if (width > w || height > h) {
scaleWidth = ((float) width) / w;
scaleHeight = ((float) height) / h;
}
opts.inJustDecodeBounds = false;
float scale = Math.max(scaleWidth, scaleHeight);
opts.inSampleSize = (int) scale;
Bitmap mbitmap = BitmapFactory.decodeFile(path, opts);
// mbitmap = BitmapFactory.decodeFile(path,opts);
addSelectBitmap(mbitmap);
}
public int calculateInSampleSize(BitmapFactory.Options op, int reqWidth,
int reqheight) {
int originalWidth = op.outWidth;
int originalHeight = op.outHeight;
int inSampleSize = 1;
if (originalWidth > reqWidth || originalHeight > reqheight) {
int halfWidth = originalWidth / 2;
int halfHeight = originalHeight / 2;
while ((halfWidth / inSampleSize > reqWidth)
&&(halfHeight / inSampleSize > reqheight)) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
public void cleanbitmp(){
/*mbitmap.recycle();
mbitmap=null;*/
bmp=null;
System.gc();
}
public Bitmap zoomImage(Bitmap bgimage, double newWidth, double newHeight) {
// 获取这个图片的宽和高
float width = bgimage.getWidth();
float height = bgimage.getHeight();
// 创建操作图片用的matrix对象
Matrix matrix = new Matrix();
// 计算宽高缩放率
float scaleWidth = ((float) newWidth) / width;
float scaleHeight = ((float) newHeight) / height;
// 缩放图片动作
matrix.postScale(scaleWidth, scaleHeight);
Bitmap bitmap = Bitmap.createBitmap(bgimage, 0, 0, (int) width, (int) height, matrix, true);
return bitmap;
}
// public static Bitmap revitionImageSize(String path) throws IOException {
// BufferedInputStream in = new BufferedInputStream(new FileInputStream(new File(path)));
// BitmapFactory.Options options = new BitmapFactory.Options();
// options.inJustDecodeBounds = true;
// BitmapFactory.decodeStream(in, null, options);
// in.close();
// int i = 0;
// Bitmap bitmap = null;
// while (true) {
// if ((options.outWidth >> i <= 1000) && (options.outHeight >> i <= 1000)) {
// in = new BufferedInputStream(new FileInputStream(new File(path)));
// options.inSampleSize = (int) Math.pow(2.0D, i);
// options.inJustDecodeBounds = false;
// bitmap = BitmapFactory.decodeStream(in, null, options);
// break;
// }
// i += 1;
// }
// return bitmap;
// }
public static void saveBitmap(Bitmap bm, String path) {
File f = new File(path);
if (f.exists()) {
f.delete();
}
try {
FileOutputStream out = new FileOutputStream(f);
bm.compress(Bitmap.CompressFormat.JPEG, 90, out);
out.flush();
out.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
好了,上面就是所有的交互了,客户端就不需要做其它操作了,我们把图片传给前端,前端展示就行了。谢谢观看!