文章目录
- 一、首先你需要先看到效果
- 二、实现原理
- 一、发送数据
- 二、两种方式
- 三、接收数据
- 三、工具类
一、首先你需要先看到效果
就是将你的 activity 添加到打开方式,比如我这里有两个 activity,PdfViewerActivity 负责打开 pdf 文件,OfficeViewerActivity 负责打开 word,excel,ppt 文件
<activityandroid:name=".activity.PdfViewerActivity"android:exported="true"android:screenOrientation="portrait"><intent-filter><actionandroid:name="android.intent.action.VIEW"/><categoryandroid:name="android.intent.category.DEFAULT"/><categoryandroid:name="android.intent.category.BROWSABLE"/><dataandroid:mimeType="application/pdf"/></intent-filter></activity><activityandroid:name=".activity.OfficeViewerActivity"android:exported="true"android:screenOrientation="portrait"><intent-filter><actionandroid:name="android.intent.action.VIEW"/><categoryandroid:name="android.intent.category.DEFAULT"/><categoryandroid:name="android.intent.category.BROWSABLE"/><!-- Word --><dataandroid:mimeType="application/msword"/><dataandroid:mimeType="application/vnd.openxmlformats-officedocument.wordprocessingml.document"/><!-- Excel --><dataandroid:mimeType="application/vnd.ms-excel"/><dataandroid:mimeType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"/><!-- PowerPoint --><dataandroid:mimeType="application/vnd.ms-powerpoint"/><dataandroid:mimeType="application/vnd.openxmlformats-officedocument.presentationml.presentation"/></intent-filter></activity>二、实现原理
一、发送数据
Manifest 配置完成后,如果调起了系统打开方式,系统会这样发送数据
Intent{action=ACTION_VIEWdata=content://xxx/xxx//代表文件的 uritype=application/pdf//代表文件类型}二、两种方式
自己伪装成系统系统打开方式发送数据
// 把 File 转成 content:// Uri(和系统行为一致)valuri=FileProvider.getUriForFile(activity,"${activity.packageName}.fileprovider",file)// 构造 ACTION_VIEW Intent(系统打开方式标准格式)valintent=Intent(Intent.ACTION_VIEW).apply{setDataAndType(uri,"application/pdf")addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)}// 发起跳转activity.startActivity(intent)有什么区别:
Intent{action=android.intent.action.VIEWdata=content://your.package.fileprovider/...type=application/pdf}Intent{action=android.intent.action.VIEWdata=content://com.android.providers.downloads.documents/document/1234type=application/pdf}可以这样判断:
uri.authority=="${context.packageName}.fileprovider"三、接收数据
在相应的页面接收数据:
valuri:Uri=intent.data?:returnvalinputStream=contentResolver.openInputStream(uri)如果你必须要使用文件真实路径而不用 uri,可通过 uri 复制文件到一个目录得到:
funcopyUriToCache(context:Context,uri:Uri):File{valfileName=getFileName(context,uri)?:"temp_file"valdestFile=File(context.cacheDir,fileName)context.contentResolver.openInputStream(uri)?.use{input->destFile.outputStream().use{output->input.copyTo(output)}}returndestFile}获取文件名:
fungetFileName(context:Context,uri:Uri):String?{valcursor=context.contentResolver.query(uri,arrayOf(OpenableColumns.DISPLAY_NAME),null,null,null)cursor?.use{if(it.moveToFirst()){returnit.getString(0)}}returnnull}三、工具类
// 获取传入的文件路径和文件名// 优先从 extra 获取(应用内调用,就是我们常用的 activity 之间跳转传参)varfilePath=intent.getStringExtra(EXTRA_PDF_FILE_PATH)?:""varfileName=intent.getStringExtra(EXTRA_PDF_FILE_NAME)?:""// 如果 extra 中没有文件路径,尝试从 Intent.data URI 获取(系统打开方式调用)if(filePath.isEmpty()&&intent.data!=null){filePath=UriFileResolver.getFilePathFromUri(this,intent.data!!)if(fileName.isEmpty()){// 从文件路径中提取文件名fileName=File(filePath).name}}/** * Uri 文件路径解析工具 * * 设计原则: * - 不根据系统版本做假设 * - 能直接获取真实路径就直接用 * - 获取不到再复制到 App 私有缓存目录 * * 适用于: * - 系统“打开方式” * - 第三方文件管理器 * - 应用内 FileProvider */objectUriFileResolver{/** * 从 Uri 获取一个可用的文件路径 * * @return 文件路径,失败返回空字符串 */fungetFilePathFromUri(context:Context,uri:Uri):String{returnwhen(uri.scheme){ContentResolver.SCHEME_FILE->{uri.path?:""}ContentResolver.SCHEME_CONTENT->{try{// 1️⃣ 自家 FileProvider,直接还原真实路径(零拷贝)if(isOwnFileProvider(context,uri)){resolveFromFileProvider(context,uri)?.let{returnit}}// 2️⃣ 尝试通过 MediaStore 获取真实路径(不做版本假设)valmediaPath=getFilePathFromMediaStore(context,uri)if(mediaPath.isNotEmpty()){returnmediaPath}// 3️⃣ 拿不到路径,复制到缓存目录兜底copyUriToTempFile(context,uri)}catch(e:Exception){""}}else->""}}// ================= FileProvider =================privatefunisOwnFileProvider(context:Context,uri:Uri):Boolean{returnuri.authority=="${context.packageName}.fileprovider"}/** * 解析自家 FileProvider Uri * * content://authority/path_name/relative_path */privatefunresolveFromFileProvider(context:Context,uri:Uri):String?{valsegments=uri.pathSegmentsif(segments.isEmpty())returnnullvalroot=segments[0]valrelativePath=if(segments.size>1)segments.subList(1,segments.size).joinToString(File.separator)else""valbaseDir=when(root){"files"->context.filesDir"cache"->context.cacheDir"external_files"->context.getExternalFilesDir(null)"external_cache"->context.externalCacheDirelse->null}?:returnnullreturnif(relativePath.isNotEmpty()){File(baseDir,relativePath).absolutePath}else{baseDir.absolutePath}}// ================= MediaStore =================/** * 尝试从 MediaStore 查询真实文件路径 * * 注意: * - 高版本系统上不保证一定成功 * - 能成功就直接用,失败交给兜底方案 */privatefungetFilePathFromMediaStore(context:Context,uri:Uri):String{varcursor:Cursor?=nullreturntry{cursor=context.contentResolver.query(uri,arrayOf(MediaStore.Files.FileColumns.DATA),null,null,null)if(cursor!=null&&cursor.moveToFirst()){valindex=cursor.getColumnIndex(MediaStore.Files.FileColumns.DATA)if(index>=0)cursor.getString(index)?:""else""}else{""}}catch(e:Exception){""}finally{cursor?.close()}}// ================= Copy =================/** * 将 Uri 指向的文件复制到 App 缓存目录 */privatefuncopyUriToTempFile(context:Context,uri:Uri):String{returntry{valtempDir=File(context.cacheDir,"temp_files")if(!tempDir.exists()){tempDir.mkdirs()}varfileName=getFileNameFromUri(context,uri)if(fileName.isEmpty()){fileName="temp_${System.currentTimeMillis()}"}valtempFile=File(tempDir,fileName)if(tempFile.exists()){returntempFile.absolutePath}context.contentResolver.openInputStream(uri)?.use{input->tempFile.outputStream().use{output->input.copyTo(output)}}tempFile.absolutePath}catch(e:Exception){""}}// ================= File name =================privatefungetFileNameFromUri(context:Context,uri:Uri):String{varfileName=""try{context.contentResolver.query(uri,null,null,null,null)?.use{cursor->if(cursor.moveToFirst()){valindex=cursor.getColumnIndex(MediaStore.Files.FileColumns.DISPLAY_NAME)if(index>=0){fileName=cursor.getString(index)?:""}}}if(fileName.isEmpty()){uri.path?.let{fileName=it.substringAfterLast('/')}}}catch(e:Exception){}returnfileName}}