您现在的位置是:主页 > news > 龙岩找工作网站/免费行情软件网站大全

龙岩找工作网站/免费行情软件网站大全

admin2025/5/1 18:10:09news

简介龙岩找工作网站,免费行情软件网站大全,网站界面设计案例教程,公共资源交易中心招标公告【回复“1024”,送你一个特别推送】 原文作者:GavinLi369 原文地址:http://www.jianshu.com/p/b785a4057388 特别声明:本文为GavinLi369原创并授权发布,未经原作者允许请勿转载,转载请联系原作者 前言 最近…

龙岩找工作网站,免费行情软件网站大全,网站界面设计案例教程,公共资源交易中心招标公告【回复“1024”,送你一个特别推送】 原文作者:GavinLi369 原文地址:http://www.jianshu.com/p/b785a4057388 特别声明:本文为GavinLi369原创并授权发布,未经原作者允许请勿转载,转载请联系原作者 前言 最近…

【回复“1024”,送你一个特别推送】


原文作者:GavinLi369

原文地址:http://www.jianshu.com/p/b785a4057388

特别声明:本文为GavinLi369原创并授权发布,未经原作者允许请勿转载,转载请联系原作者


前言

最近在自己的项目里实现了一个头像选择的功能,就是先从相册里选取一张图片再调用系统的裁剪功能来制作头像,效果就像下面这样:

本以为很小的一个功能,却远远没有我想的那样简单,可以说每一步都暗藏玄机,下面就让我带大家看看这里面究竟有哪些坑。

Android 4.4 之存储访问框架

首先,让我们从图片选择开始,使用隐式 Intent 跳转到图片选择:

private void routeToGallery() {Intent intent = new Intent(Intent.ACTION_GET_CONTENT);intent.addCategory(Intent.CATEGORY_OPENABLE);intent.setType("image/*");startActivityForResult(intent, GALLERY_REQUSET_CODE);
}

在回调中处理返回的图片,继而跳转至图片裁剪:

protected void onActivityResult(int requestCode, int resultCode, Intent data) {if (requestCode == GALLERY_REQUSET_CODE && resultCode == RESULT_OK) {String path = data.getData().getPath();Bitmap image = BitmapFactory.decodeFile(path);File faceFile;try {faceFile = saveBitmap(image);} catch (IOException e) {e.printStackTrace();return;}Uri fileUri = Uri.fromFile(faceFile);routeToCrop(fileUri);      //跳转到图片裁剪}}private void routeToCrop(Uri uri) {Intent intent = new Intent("com.android.camera.action.CROP");intent.setDataAndType(uri, "image/*");intent.putExtra("crop", true);intent.putExtra("aspectX", 1);intent.putExtra("aspectY", 1);intent.putExtra("outputX", 150);intent.putExtra("outputY", 150);intent.putExtra("return-data", true);startActivityForResult(intent, CROP_REQUEST_CODE);}private File saveBitmap(Bitmap bitmap) throws IOException {File file = new File(getExternalCacheDir(), "face-cache");if (!file.exists()) file.createNewFile();try (OutputStream out = new FileOutputStream(file)) {bitmap.compress(Bitmap.CompressFormat.JPEG, 100, out);}return file;
}

这一段代码看似正常,但问题就出在String path = data.getData().getPath();这一句。这一段代码在 Android 4.4 以下是可以正常运行的,不过从 Android 4.4 开始这里获取到的将为一个无效的路径,这是为什么呢?

Android 从 4.4 开始引入了一个概念:存储访问框架(https://developer.android.google.cn/guide/topics/providers/document-provider.html),简单来说就是 Android 提供了一个专门供用户访问资源的软件,将设备上所有可以访问资源的软件接口都整合到了一起,避免了用户只能选择一个特定软件的尴尬,在 Android 4.4 以下,我们发送刚才选取图片的隐式 Intent,效果是这样的,需要用户去选择使用哪个应用:

而从 Android 4.4 开始,就变成了这样:

直接打开一个资源选取的软件(这个软件平时是隐藏的,不会显示在软件列表中),其中包含了访问设备上所有可访问资源软件的接口,这个改变极大的提高的用户操作的便捷性。

不过这也带来了一个问题,从 Android 4.4 开始,在onActivityResult()方法的Intent中所包含的uri不再是file://类型,而是变成了content://类型,这也是为什么在 Android 4.4 以后调用data.getData.getPath()获取到的结果是无效的。因此,我们必须对 Android 4.4 以上的版本进行特殊的处理:

private void routeToGallery() {Intent intent = new Intent(Intent.ACTION_GET_CONTENT);intent.addCategory(Intent.CATEGORY_OPENABLE);intent.setType("image/*");if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {startActivityForResult(intent, GALLERY_REQUSET_CODE_KITKAT);} else {startActivityForResult(intent, GALLERY_REQUSET_CODE);}
}

在回调中对不同版本分别进行处理:

protected void onActivityResult(int requestCode, int resultCode, Intent data) {switch (requestCode) {case GALLERY_REQUSET_CODE:handleGalleryResult(resultCode, data);break;case GALLERY_REQUSET_CODE_KITKAT:handleGalleryKitKatResult(resultCode, data);break;}}private void handleGalleryResult(int resultCode, Intent data) {// 跟之前一样}// Result uri is "content://" after Android 4.4private void handleGalleryKitKatResult(int resultCode, Intent data) {File faceFile;try {ParcelFileDescriptor parcelFileDescriptor =getContentResolver().openFileDescriptor(contentUri, "r");FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor();Bitmap image = BitmapFactory.decodeFileDescriptor(fileDescriptor);faceFile = saveBitmap(image);} catch (IOException e) {e.printStackTrace();return;}Uri fileUri = Uri.fromFile(faceFile);routeToCrop(fileUri);
}

Android 7.0 之 FileProvider

完成了图片的选择功能,转眼又碰到了一个问题:

Android 为了提高私有文件的安全性,从 7.0 开始对外传递file://类型的uri会触发FileUriExposedException。因此,在分享私有文件时必须使用FileProvider。

对 Android 的这一改变还不太了解的同学可以看一下这两篇文章: Android 7.0 行为变更(https://developer.android.google.cn/about/versions/nougat/android-7.0-changes.html?hl=zh-cn) Setting Up File Sharing(https://developer.android.google.cn/training/secure-file-sharing/setup-sharing.html)

第一步

在manifest文件中加入FileProvider:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"package="gavinli.translator"><application...><providerandroid:name="android.support.v4.content.FileProvider"android:authorities="gavinli.translator"android:grantUriPermissions="true"android:exported="false"><meta-dataandroid:name="android.support.FILE_PROVIDER_PATHS"android:resource="@xml/filepaths" /></provider>...    </application>
</manifest>

第二步

在xml文件夹中创建filepaths.xml文件,并声明所要分享的文件目录:

<resources><paths><external-cache-path name="mycache" path="./" /></paths>
</resources>

这里的path就代表你想要分享的文件目录,而name就是具体显示在uri中的信息,最终生成的uri就像下面这样:

这种经过处理的uri可以很好的隐藏掉实际的文件路径。

第三步

在代码中对 Android 7.0 以上的版本进行特殊处理:

private void handleGalleryKitKatResult(int resultCode, Intent data) {...Uri fileUri;if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {// Android 7.0 "file://" uri权限适配fileUri = FileProvider.getUriForFile(this,"gavinli.translator", faceFile);} else {fileUri = Uri.fromFile(faceFile);}routeToCrop(fileUri);
}

这里传入的"gavinli.translator",需要与之前在manifest文件中声明的android:authorities一致。

第四步

在裁剪图片的Intent中加入对该图片的访问权限:

private void routeToCrop(Uri uri) {Intent intent = new Intent("com.android.camera.action.CROP");intent.setDataAndType(uri, "image/*");// 加入访问权限intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION            | Intent.FLAG_GRANT_READ_URI_PERMISSION);...
}

最后一步

在回调中获取裁剪后的图片:

protected void onActivityResult(int requestCode, int resultCode, Intent data) {switch (requestCode) {...case CROP_REQUEST_CODE:Bundle bundle = data.getExtras();Bitmap face = bundle.getParcelable("data");break;}
}

Intent 的限制

你以为到这里就结束了吗?其实还远远没有。我们这里裁剪的图片是用作头像的,所以大小一般都比较小。可以当图片的大小变大后就会发现,每次裁剪后在Intent中获取到的图片其实都是缩略图。

这是因为 Android 对Intent中所包含数据的大小是有限制的,一般不能超过 1M,否则应用就会崩溃,这就是Intent中的图片数据只能是缩略图的原因。而解决的办法也很简单,我们需要给图片裁剪应用指定一个输出文件,用来存放裁剪后的图片:

private void routeToCrop(Uri uri) {...intent.putExtra("return-data", false);intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(new File(getExternalCacheDir(), "face-cropped")));startActivityForResult(intent, CROP_REQUEST_CODE);
}

现在,在回调中的图片就不能再直接从Intent中获取了,而是需要先拿到Intent中的uri,再使用uri进行获取,具体的过程和之前处理uri的方式一样,这里就不再赘述了。当然,直接从之前指定的文件中读取数据也是可以的。

Android 6.0 之运行时权限

不知道大家发现了没有,之前保存图片的目录都是使用的Context.getExternalCacheDir(),这个方法获取到的目录为/sdcard/Android/data/gavinli.translator/cache,是应用专属的外部存储空间,不需要声明权限。而要想使用公共的存储空间,就势必要面对一个问题:Android 6.0 的运行时权限。

首先,在manifest文件中声明读取外置存储的权限:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"package="gavinli.translator"><uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>...
</manifest>

之后,在代码中加入运行时的权限申请:

private void request() {String[] permissions = {Manifest.permission.WRITE_EXTERNAL_STORAGE};if(ContextCompat.checkSelfPermission(this, permisson)!= PackageManager.PERMISSION_GRANTED) {requestPermissions(permissions, REQUEST_CODE);} else {// 存储图片}}public void onRequestPermissionsResult(int requestCode, String[] permissions,int[] grantResults) {if(requestCode == REQUEST_CODE) {if(grantResults[i] == PackageManager.PERMISSION_GRANTED) {// 存储图片}}
}

后记

到这里,这一次的踩坑之旅就全部结束了,我们也看到了 Android 这几个版本以来一步步对权限的限制,虽然这对我们的开发产生一定的影响,但只要能提高用户的使用体验,这点困难又算的了什么呢?