安卓7.0踩坑之截图与分享 – FileProvider


 接近尾期了,自然就要用到截图和分享了,在网上查阅了不少时间,最后把得到的一些东西拿出来溜溜,

private void shot(){
 View view = getWindow().getDecorView();
 // 允许当前窗口保存缓存信息
 view.setDrawingCacheEnabled(true);
 view.buildDrawingCache();
 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
 new MediaActionSound().play(MediaActionSound.SHUTTER_CLICK);
 }
 Rect rect = new Rect();
 view.getWindowVisibleDisplayFrame(rect);
 int statusBarHeights = rect.top;
 Display display = activity.getWindowManager().getDefaultDisplay();

 //display.getHeight()与display.getWidth()已过时,使用Point替代
 Point size = new Point();
 display.getSize(size);
 int widths = size.x;
 int heights = size.y;
 Bitmap bitmap = Bitmap.createBitmap(view.getDrawingCache(), 0, statusBarHeights,
 widths, heights - statusBarHeights);
 view.destroyDrawingCache();//释放缓存占用的资源
 File file = null;
 if (bitmap != null) {
 try {
 file =ScreenShot.getInstance()
 .saveScreenshotToPicturesFolder(activity, bitmap, "weekly");
 } catch (Exception e) {
 e.printStackTrace();
 }
 }
 if (file != null)
 showToast(activity, "截图已保存至Picture目录");
}
/**
 * Save screenshot to pictures folder.
 *
 * @param context the context
 * @param image the image
 * @param filename the filename
 * @return the bitmap file object
 * @throws Exception the exception
 */public File saveScreenshotToPicturesFolder(Context context, Bitmap image, String filename)
 throws Exception {
 File bitmapFile = getOutputMediaFile(filename);
 if (bitmapFile == null) {
 throw new NullPointerException("Error creating media file, check storage permissions!");
 }
 FileOutputStream fos = new FileOutputStream(bitmapFile);
 //90是压缩级别,如果图片有问题,请用原图100,文件生成后再去压缩
 image.compress(Bitmap.CompressFormat.PNG, 90, fos);
 fos.close();

 // Initiate media scanning to make the image available in gallery apps
 MediaScannerConnection.scanFile(context, new String[]{bitmapFile.getPath()},
 new String[]{"image/jpeg"}, null);
 return bitmapFile;
}

private File getOutputMediaFile(String filename) {
 // To be safe, you should check that the SDCard is mounted
 // using Environment.getExternalStorageState() before doing this.
 File mediaStorageDirectory = new File(
 Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)
 + File.separator);
 // Create the storage directory if it does not exist
 if (!mediaStorageDirectory.exists()) {
 if (!mediaStorageDirectory.mkdirs()) {
 return null;
 }
 }
 // Create a media file name
 String timeStamp = new SimpleDateFormat("yyyy_MM_dd_HHmmss").format(new Date());
 File mediaFile;
 String mImageName = filename + timeStamp + ".jpg";
 mediaFile = new File(mediaStorageDirectory.getPath() + File.separator + mImageName);
 return mediaFile;
}

decorView是window中的最顶层view,我们把他拿下来,然后去掉状态栏就好,用下面的代码

 Rect rect = new Rect();
 view.getWindowVisibleDisplayFrame(rect);
 int statusBarHeights = rect.top;

然后在生成bitmap的时候把状态栏的高度去掉,剩下的就是activity的内容截图了。但是在某些界面截的图会有些问题,暂时未考虑接近,或许和我的过度绘制以及图表的onDraw有关,毕竟是一直刷新的。

这里再提供2种其他的办法:

(1)通过系统尺寸资源获取

       状态栏高度定义在Android系统尺寸资源中status_bar_height,但这并不是公开可直接使用的,例如像通常使用系统资源那样android.R.dimen.status_bar_height。但是系统给我们提供了一个Resource类,通过这个类可以获取资源文件,借此可以获取到status_bar_height:

 int statusBarHeight = 0;
//获取status_bar_height资源的ID 
 int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
 if (resourceId > 0) {
 //根据资源ID获取响应的尺寸值 
 statusBarHeight = getResources().getDimensionPixelSize(resourceId);
 }
 Log.d(“info", statusBarHeight);

另外一种:

(2)通过R类的反射 

大家都知道Android的所有资源都会有惟一标识在R类中作为引用。我们也可以通过反射获取R类的实例域,然后找status_bar_height:

 int statusBarHeight = 0;
 try {
 Class<?> clazz = Class.forName("com.android.internal.R$dimen");
 Object object = clazz.newInstance();
 int height = Integer.parseInt(clazz.getField("status_bar_height")
 .get(object).toString());
 statusBarHeight = getResources().getDimensionPixelSize(height);
 } catch (Exception e) {
 e.printStackTrace();
 }
 Log.e("WangJ", statusBarHeight2);

关于这个这里一篇文章很详细:Android完美获取状态栏高度、标题栏高度、编辑区域高度的获取

再提供一些截图方法:getDrawingCache()方法截取部分屏幕:

有了图片,要分享怎么办?这里的话不考虑三方平台的接入,单纯简单实现,就使用Intent进行内容分享吧,但是在7.0以上的会有一个隐私权限问题,官方是这样称呼的:

Android6.0引入了动态权限控制,7.0使用了私有目录被限制访问Strict Mode API 政策

先来段申请权限的代码,在6.0以上的系统中,需要动态的去判断是否已经授予,大多数情况下,都是默认禁止的

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
 // android 6.0及以上版本
 if (checkSelfPermission(Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED
 || checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
 // 有权限没有授予
 ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.READ_PHONE_STATE,Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_CODE_REQUEST_PERMISSION);
 }
}

但是每个地方这样来一次,不是很麻烦吗?这里有篇比较好的实例: Android 6.0: 动态权限管理的解决方案

最后由于一些什么原因之类的我选择了这个库:AndPermission

很详细,也很好用。不多说了,看README吧。

tips:这个作者是个lib小能手:http://www.yanzhenjie.com

我咋就还没这么厉害呢,唉,还需努力啊。。。

权限有了,但是Intent的操作并没你想得那么简单了,以前可以使用

Uri.fromFile(file)

但是现在需要使用FileProvider,主要记录下使用:

1,先在manifest里的application里注册provider

<provider 
 android:name="android.support.v4.content.FileProvider"
 android:authorities="你的包名.fileprovider"
 android:grantUriPermissions="true"
 android:exported="false">
 <meta-data 
 android:name="android.support.FILE_PROVIDER_PATHS"
 android:resource="@xml/file_paths"/> 
</provider>

exported必须要求为false,为true则会报安全异常。grantUriPermissions为true,表示授予URI临时访问权限。

2,指定共享目录

为了指定共享的目录我们需要在资源目录下(res)创建一个xml目录,然后创建一个名为“file_paths”(名字可以随便起,只要和在manifest里注册的provider所引用的resource保持一致即可)的资源文件

<?xml version="1.0" encoding="utf-8"?>
<resources>
 <paths>
 <external-path path="" name="camera_photos" />
 </paths>
</resources>

* 代表的根目录: Context.getFilesDir()

* 代表的根目录: Environment.getExternalStorageDirectory()

* 代表的根目录: getCacheDir()

path=""是有意义的,它代表根目录,你可以向其他的应用共享根目录及其子目录下的任何一个文件。若设置path="pictures",它代表着根目录下的pictures目录,那么你想向其他应用共享pictures目录范围之外的文件是不可行的。

3.

使用FileProvider

 Intent shareIntent = new Intent();
 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
 shareIntent.setAction(Intent.ACTION_SEND);
 shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); //添加这一句表示对目标应用临时授权该Uri所代表的文件
 Uri uri=FileProvider.getUriForFile(this,"com.lckiss.screenshotmaster.fileprovider",file);
 Log.i("info", "saveScreenshot: "+uri);
 shareIntent.setDataAndType(uri, "image/jpeg");
 } else {
 shareIntent.setDataAndType(Uri.fromFile(file), "image/jpeg");
 shareIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
 }

 startActivity(Intent.createChooser(shareIntent, "image/jpeg"));

之前Uri的scheme类型为file的Uri改成了有FileProvider创建一个content类型的Uri。

添加了intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);来对目标应用临时授权该Uri所代表的文件。

通过FileProvider的getUriForFile(Context context, String authority, File file)静态方法来获取URI,方法中的authority就是manifest里注册provider使用的authority。

其实以上的分享方法是带不过去图片的,只能跳转到那个app,具体为什么,应该和app写的接收方法有关,所以像QQ,微信这些都有他自己的jar包,赶时间的话,可以考虑友盟。

这里有个比较好的例子:Android M、N适配踩坑

然后分享的类型可以有很多:Android实现分享和接收分享内容

好晚了,休息了该,留下一篇明天拓展的文章:Android Notification的使用

声明:TIL|版权所有,违者必究|如未注明,均为原创|本网站采用BY-NC-SA[ZH]协议进行授权

转载:转载请注明原文链接 - 安卓7.0踩坑之截图与分享 – FileProvider


Life is very interesting. In the end, some of your greatest pains become your greatest strengths.