news 2026/2/11 2:22:29

Android将应用添加到默认打开方式

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Android将应用添加到默认打开方式

文章目录

  • 一、首先你需要先看到效果
  • 二、实现原理
    • 一、发送数据
    • 二、两种方式
    • 三、接收数据
  • 三、工具类

一、首先你需要先看到效果

就是将你的 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}}
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/2/10 22:07:00

用户行为返利业务流程实现

目录 1、引言 2、实现 3、总结 1、引言 在面向用户的平台中&#xff0c;可以在用户各种行为&#xff08;如支付&#xff0c;签到&#xff09;实现后进行用户返利&#xff08;优惠券发放、积分发放等等&#xff09;&#xff0c;可以使用户习惯操作提升系统用户留存与使用。…

作者头像 李华
网站建设 2026/1/31 16:40:29

Python 爬虫实战:Scrapy 框架快速搭建分布式爬虫

前言 在大数据时代&#xff0c;单节点爬虫面对海量数据采集需求时&#xff0c;往往受限于单机的网络带宽、CPU 算力和 IP 资源&#xff0c;采集效率难以满足业务要求。Scrapy 作为一款成熟的 Python 爬虫框架&#xff0c;本身具备轻量级、高扩展性的特点&#xff0c;结合分布式…

作者头像 李华
网站建设 2026/2/7 16:40:48

Python 爬虫实战:爬虫代理 IP 池搭建与自动切换

摘要 本文聚焦爬虫代理 IP 池的核心搭建与自动切换技术&#xff0c;针对反爬机制中 IP 封禁的核心痛点&#xff0c;系统讲解代理 IP 池的架构设计、数据源对接、有效性检测、自动切换及动态维护全流程。实战验证基于IP 检测测试页&#xff08;可直接点击验证 IP 有效性&#x…

作者头像 李华
网站建设 2026/2/8 16:23:12

JAVA面相对象编程—抽象类、接口

#JAVA笔记#抽象类定义抽象类与普通类基本类似&#xff0c;唯一的区别在于使用abstract关键字修饰&#xff0c;且类中有未实现&#xff08;没有方法体&#xff09;的抽象方法&#xff08;abstract修饰&#xff09;。抽象方法必须位于抽象类中&#xff0c;抽象方法只能访问抽象成…

作者头像 李华
网站建设 2026/2/1 10:35:50

2026最新网络安全小白自学之路,别到处拜师了!!

较为完整的学习路线&#xff1a; 这个路线是我和一些已入职大佬来规划整理&#xff0c;也加上了小提示&#xff0c;我也希望你们能看看上面我的心得&#xff0c;都会有所帮助。 第一阶段&#xff0c;初入门学网络基础tip&#xff1a;这部分没有什么逻辑可以说的&#xff0c;半个…

作者头像 李华
网站建设 2026/2/5 23:28:21

加入2025护网,日薪最低1500,能力越强薪资越高!

加入2025护网&#xff0c;日薪最低1500&#xff0c;能力越强薪资越高&#xff01; 什么是护网行动 ** ** 1.护网行动 护网&#xff0c;也称网络保护&#xff0c;是指网络安全人员对企业或组织的网络进行检查、维护和保护&#xff0c;以防止网络受到黑客攻击、病毒、木马或其…

作者头像 李华