ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

安卓应用列表:系统分享与打开方式 Intent 匹配指南

安卓应用列表:系统分享与打开方式 Intent 匹配指南 1. 被列出来这件事本质就是 Intent 匹配很多人第一次做这个需求是想让自家安卓应用出现在系统分享面板、文件打开方式、或者别的应用的应用选择器里。折腾半天发现代码写了、应用装了、重启了列表里就是不见踪影。问题的根子往往不在代码本身而在于没搞明白系统是怎么找到你的应用的——它从来不主动扫描你它只做一件事拿一个 Intent去问所有已安装应用里的 IntentFilter谁能接谁就上列表。所以这篇东西不讲虚的就讲清楚安卓应用列表、系统分享面板、打开方式列表这三类列表各自靠什么机制把应用拉进去以及为什么你的应用没被拉进去。核心关键词就两个安卓、应用列表。适合已经能写 Activity、但没系统啃过 Manifest 匹配规则的开发也适合做 SDK 集成、需要让宿主应用发现自己能力的同学。我默认你已经会基本的 Android Studio 操作懂 Kotlin 或 Java 其中一门Manifest 也能看懂。全文的配置我都在compileSdk 34、targetSdk 34、Android 10 到 14 的真机上实测过部分行为在国产 ROM 上有差异我会单独标出来。1.1 三种典型的应用列表场景先把场景分清因为不同场景用的 action 和匹配规则差别很大混在一起写必然出问题。第一种是系统分享列表Share Sheet。用户在相册点分享、在浏览器点分享、在文件管理器长按分享弹出来的那个横向或纵向应用条就是它。背后是ACTION_SEND和ACTION_SEND_MULTIPLE两个隐式 Intent发送方构造 Intent系统查询谁能处理把结果渲染成列表。第二种是打开方式列表。用户点开一个 PDF、一个自定义后缀文件系统问用什么打开或者点某个链接想跳到你的应用。背后是ACTION_VIEW靠 MIME 类型和 URI 结构匹配。第三种是别的应用主动查询出来的列表。比如某个编辑器要做导入来源它自己调queryIntentActivities拿一个列表展示。这种场景下你的应用要能被查到除了 IntentFilter 写对还得考虑 Android 11 之后的包可见性规则——但注意这条规则约束的是查询方不是你。你只要把 filter 声明好被别人查到这件事本身不受限制。1.2 Intent 与 IntentFilter 的匹配规则Intent 有三个可匹配维度action、category、data。IntentFilter 也是这三样。匹配逻辑是与关系三条全过才算命中。actionIntent 的 action 必须在 filter 声明的 action 集合里。filter 不声明 action 就永远匹配不上任何带 action 的 Intent。categoryIntent 里的每一个 category都必须在 filter 的 category 集合里。注意方向是 Intent 的 category 被 filter 包含不是反过来。而且系统在解析隐式 Intent 时会自动给 Intent 加一个android.intent.category.DEFAULT所以你的 filter 里如果不写CATEGORY_DEFAULT从外面来的隐式 Intent 基本都匹配不上。这是新手最容易漏的一行。data这块最绕。data元素里的属性会被拆成两部分看——URI 部分scheme、host、port、path、pathPrefix、pathPattern和类型部分mimeType。同一个 intent-filter 里写多个data同维度的属性取并集。也就是说Intent 的 data URI 只要匹配上 filter 的 URI 集合中任意一条Intent 的 type 只要匹配上 filter 的 mimeType 集合中任意一条data 这一项就算过。正因为是分维度取并集会出现一个反直觉的结果你写了data android:schemecontent /和data android:mimeTypeimage/* /很多人以为意思是content 的图片实际上匹配的是content 的任意类型或者任意 scheme 的图片。这个坑我在给一个图片编辑 SDK 做集成时踩过结果应用莫名其妙出现在了一堆文档类文件的打开方式里。注意如果你只想匹配特定 scheme 下的特定类型最好在每个data里同时写 scheme 和 mimeType可读性更好也方便后期维护时一眼看懂意图。1.3 一个被忽略的前提exported 与包可见性android:exported这个属性targetSdk 31 之后只要组件带了 intent-filter就必须显式声明不声明直接装不上报错信息是INSTALL_PARSE_FAILED_MANIFEST_MALFORMED。这是硬性要求不是建议。要让外部应用能唤起你的组件exported必须是true。设成false的话就算 filter 写得再漂亮系统也不会把你的组件对外暴露列表里自然没有。另一个容易搞混的是 Android 11 引入的包可见性。它的作用是限制应用能看到哪些别的应用影响的是getPackageInfo、queryIntentActivities这类查询 API 的返回结果。你的应用要出现在别人的列表里不受这条限制但你的应用如果自己要查这个文件能用哪些应用打开那就得声明queries否则查出来只有你自己和少数系统应用。这两件事方向相反别弄反了。2. 出现在系统分享列表ACTION_SEND 完整实现分享列表是最常见的需求也是细节最多的一个。下面从头到尾捋一遍包括最小配置、多类型、文件传输和 Direct Share 这种进阶玩法。2.1 最小可用配置与 CATEGORY_DEFAULT 的坑先从能跑起来的最小配置开始。假设你有一个ShareReceiverActivity用来接分享进来的内容activity android:name.share.ShareReceiverActivity android:exportedtrue android:labelstring/share_target_label android:excludeFromRecentstrue android:themestyle/Theme.Transparent intent-filter action android:nameandroid.intent.action.SEND / category android:nameandroid.intent.category.DEFAULT / data android:mimeTypetext/plain / /intent-filter /activity这几行里有几个点必须说清楚。android:exportedtrue是前提前面讲过。android:label决定的是分享面板里显示的名字。如果你不写系统会往上找 Application 的 label再找不到就显示包名。我见过有团队为了多语言把 label 设成空字符串结果分享面板里显示成一串包名用户根本不知道那是什么。android:excludeFromRecentstrue配合透明主题是为了让这个中转页不进入最近任务列表。分享是个用完即走的动作如果用户从分享面板点了你的应用处理完回到桌面一按多任务发现多了个空白卡片体验很差。透明主题的写法style nameTheme.Transparent parentandroid:Theme.Translucent.NoTitleBar item nameandroid:windowBackgroundandroid:color/transparent/item item nameandroid:windowIsTranslucenttrue/item item nameandroid:windowNoTitletrue/item /styleCATEGORY_DEFAULT那行是必须的。系统在调用startActivity处理隐式 Intent 时会先给 Intent 加CATEGORY_DEFAULTfilter 里不声明就匹配不上。这个规则在PackageManager的匹配逻辑里是写死的我试过用命令行手动指定 category 绕过没必要。关于mimeTypetext/plain只覆盖纯文本。微信、系统浏览器分享文字走的就是这个类型。但有些应用分享文本时会带一个 URLtype 还是text/plain内容在EXTRA_TEXT里。2.2 多类型、多文件SEND_MULTIPLE 与 data 的合并规则要让你的应用同时支持文本、图片、视频、任意文件就要写多个data或者多个 intent-filter。intent-filter action android:nameandroid.intent.action.SEND / category android:nameandroid.intent.category.DEFAULT / data android:mimeTypetext/plain / data android:mimeTypeimage/* / data android:mimeTypevideo/* / data android:mimeTypeapplication/pdf / /intent-filter这是单文件分享。多文件分享是另一个 actionintent-filter action android:nameandroid.intent.action.SEND_MULTIPLE / category android:nameandroid.intent.category.DEFAULT / data android:mimeTypeimage/* / data android:mimeTypevideo/* / /intent-filter注意SEND和SEND_MULTIPLE是两个独立 action不能互相覆盖。有些应用只发SEND_MULTIPLE比如相册多选分享你只注册了SEND用户在相册多选几张图点分享你的应用就不见了。这个现象特别容易被当成偶现 bug其实是 action 没注册全。接收端处理多文件时if (intent.action Intent.ACTION_SEND_MULTIPLE) { val uris: ListUri if (Build.VERSION.SDK_INT 33) { intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM, Uri::class.java) ?: emptyList() } else { Suppress(DEPRECATION) intent.getParcelableArrayListExtraUri(Intent.EXTRA_STREAM) ?: emptyList() } }关于application/*这种通配我建议慎用。*/*更是要命写上去你的应用几乎会出现在所有分享面板里包括那些跟你业务八竿子打不着的场景。而且部分 ROM 会把过于宽泛的匹配排在列表很靠后的位置甚至直接折叠进更多里。真要接任意文件我一般会列具体的类型清单再加一个application/octet-stream兜底。2.3 分享大文件FileProvider 与 URI 授权如果你不只是接收还要主动分享文件出去Android 7.0 之后直接传file://会抛FileUriExposedException必须用FileProvider转成content://。先声明 Providerprovider android:nameandroidx.core.content.FileProvider android:authorities${applicationId}.fileprovider android:exportedfalse android:grantUriPermissionstrue meta-data android:nameandroid.support.FILE_PROVIDER_PATHS android:resourcexml/file_paths / /provider${applicationId}这个占位符建议保留避免多渠道打包时 authorities 冲突。android:exportedfalse是对的Provider 不需要对外暴露靠临时授权就够了。res/xml/file_paths.xmlpaths files-path nameinternal_files path. / cache-path nameinternal_cache path. / external-files-path nameext_files path. / external-cache-path nameext_cache path. / /paths这几个标签对应的是不同目录files-path指向Context.getFilesDir()cache-path指向getCacheDir()external-files-path指向外部存储的应用私有目录。传文件的时候一定要确认文件真的在这些目录下路径没对上会抛IllegalArgumentException: Failed to find configured root that contains ...这个报错信息还算友好直接看路径就能定位。发送端val file File(cacheDir, export_${System.currentTimeMillis()}.jpg) val uri FileProvider.getUriForFile( this, ${packageName}.fileprovider, file ) val send Intent(Intent.ACTION_SEND).apply { type image/jpeg putExtra(Intent.EXTRA_STREAM, uri) addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) } startActivity(Intent.createChooser(send, 分享到))FLAG_GRANT_READ_URI_PERMISSION这一行不能省。它给接收方应用授予一次性的读权限没有它接收方拿到 URI 也读不出内容会抛SecurityException。这是我在对接某个第三方阅读器时反复遇到的问题——对方反馈你们分享的文件打不开最后发现是我们漏了这个 flag。顺带说一句createChooser的一个小技巧可以用EXTRA_EXCLUDE_COMPONENTS把自己从列表里排除掉避免用户分享到自己应用又绕回来的尴尬val chooser Intent.createChooser(send, 分享到).apply { putExtra( Intent.EXTRA_EXCLUDE_COMPONENTS, arrayOf(ComponentName(thisMainActivity, ShareReceiverActivity::class.java)) ) }2.4 把入口做进分享面板头部的 Direct ShareAndroid 10 之后系统分享面板顶部会有一排头像式的快捷入口这是 Direct Share。它比普通的应用图标更醒目适合做分享到某个具体联系人/项目/会话这种场景。实现方式是用ShortcutManager配一个 share target XML。先在 Manifest 里给接收 Activity 加 meta-dataactivity android:name.share.ShareReceiverActivity android:exportedtrue intent-filter action android:nameandroid.intent.action.SEND / category android:nameandroid.intent.category.DEFAULT / data android:mimeTypetext/plain / /intent-filter meta-data android:nameandroid.service.chooser.chooser_target_service android:valueandroidx.sharetarget.ChooserTargetServiceCompat / /activity再定义res/xml/shortcuts.xmlshortcuts xmlns:androidhttp://schemas.android.com/apk/res/android share-target android:targetClasscom.example.app.share.ShareReceiverActivity data android:mimeTypetext/plain / category android:namecom.example.app.category.TEXT_SHARE_TARGET / /share-target /shortcuts然后在代码里动态发布 Shortcutval shortcut ShortcutInfoCompat.Builder(context, target_$id) .setShortLabel(name) .setIcon(IconCompat.createWithBitmap(avatarBitmap)) .setIntent( Intent(context, ShareReceiverActivity::class.java).apply { action Intent.ACTION_DEFAULT putExtra(target_id, id) } ) .setCategories(setOf(com.example.app.category.TEXT_SHARE_TARGET)) .build() ShortcutManagerCompat.pushDynamicShortcut(context, shortcut)这套东西有个前提Direct Share 的条目是系统根据用户使用频率动态排序的发布之后不保证立刻出现也不保证排在前面。而且每个应用最多只能在分享面板里出现有限个 Direct Share 条目一般是 4 到 8 个看 ROM。我实测下来同一个 Shortcut 用几次之后排名会明显上升属于用得越多越靠前的机制。3. 接管打开方式文件关联的精确控制分享是我把内容送出去打开方式是用户点文件找到我。这一块对匹配精度的要求更高写宽了会抢占别人的入口写窄了自己进不去。3.1 ACTION_VIEW 与 MIME、扩展名的匹配组合最基础的配置intent-filter action android:nameandroid.intent.action.VIEW / category android:nameandroid.intent.category.DEFAULT / data android:schemecontent / data android:schemefile / data android:mimeTypeapplication/pdf / /intent-filter这里我特意分开写scheme和mimeType是因为想强调前面说的分维度规则。content和file是 Android 里文件 URI 最常见的两种 schemecontent://是 ContentProvider 提供的file://是直接文件路径从第三方文件管理器点开文件时两种都可能出现所以一般两个都要写。android:mimeType有大小写敏感的问题。MIME 类型规范上是不区分大小写的但 Android 的匹配实现里做的是字符串比较部分 ROM 上Application/PDF和application/pdf匹配不上。稳妥做法是全部小写接收端再做一次规范化。如果你的文件是自定义后缀系统识别不出 MIME就会走*/*或者application/octet-stream。这时候要么用pathPattern精确匹配后缀要么在接收端二次校验。3.2 pathPattern 正则的转义与分段匹配pathPattern是唯一支持正则的路径匹配方式但它的正则语法是不完整的只支持.、*、.*这几种不支持字符类、分组、量词。而且有个大坑pathPattern是按路径逐段匹配的/会被当作段分隔符处理.*不跨段。匹配.mydoc后缀的写法data android:schemefile android:host* android:pathPattern.*\\.mydoc /注意 XML 里的\\.这是两层转义XML 层面\\表示一个反斜杠传给匹配引擎后变成\.也就是正则里的字面点号。如果只写\.XML 解析器会报错或者解析成别的字符。我第一次写的时候就漏了一层匹配一直不生效查了半天才反应过来。还有个更隐蔽的问题Android 官方文档里提到从 Android 6.0 开始pathPattern匹配时.*不跨/。所以.*\\.mydoc匹配/sdcard/a/b.mydoc是没问题的.*匹配b但如果路径里带层级比如想匹配/sdcard/dir.mydoc/file.txt就匹配不上了。实际使用中这个限制影响不大因为后缀一般在最后一段。如果你要匹配多种后缀pathPattern得写多条或者干脆用pathPrefix加接收端判断后者代码更简单但会多匹配一些不该匹配的路径。我的取舍是后缀不超过三个就用多条pathPattern超过三个统一用*/*接收然后在 onCreate 里根据后缀分发。3.3 避免抢占系统默认优先级与 autoVerify 的取舍android:priority这个属性只对同一次解析里有多个候选时起作用影响的是resolveActivity返回哪个、以及某些系统组件的排序。它不会让系统把用户已经设过的默认应用换掉也不会在打开方式列表里把别人挤走。很多人误以为调高 priority 就能抢到默认实际上用户一旦在打开方式里选了始终默认就固定了只能靠用户自己去设置里改。真正要谨慎的是data android:mimeType*/* /加schemecontent这种组合。它会让你的应用出现在几乎任何文件的打开方式里。如果你的应用不是文件管理器或者通用查看器这种声明会显著降低用户信任度也会被应用市场在审核时质疑。关于autoVerify它是给 App Links 用的配合intent-filter android:autoVerifytrue和android:host声明让系统自动验证域名归属验证通过后点击对应链接直接进你的应用不再弹选择框。这个只对http/https的 scheme 有意义对file、content无效。如果你的应用有官网而且需要做点链接直接进 App这个值得配单纯做本地文件关联不需要。4. Android 11 之后的包可见性这一节的核心是想清楚你的应用是被查方还是查询方。前面提过被查方不受限制但很多需求其实是双向的——你既要被别人找到自己也要列出一份能处理这个 Intent 的应用列表给用户选择。4.1 queries 声明怎么写Android 11 开始不声明queries的话queryIntentActivities、getPackageInfo、resolveActivity这些 API 只能看到自己、系统框架和少数几个系统应用。这个限制确实让一批老代码直接失效表现是原来能拿到一堆应用升级后只剩两三个。按 Intent 查询的写法queries intent action android:nameandroid.intent.action.SEND / data android:mimeTypeimage/* / /intent intent action android:nameandroid.intent.action.VIEW / data android:schemehttps / /intent /queries注意queries里intent的写法跟intent-filter不一样它不支持category声明也不支持pathPattern这类路径匹配。所以能表达的查询范围比 filter 窄。如果你需要按包名精确查询用package android:namecom.example.target /。还有一个场景需要额外注意如果你的应用要跟某个特定应用比如合作的厂商应用互相唤起而那个应用不在queries的 Intent 覆盖范围内最好直接加package声明。这个比靠 Intent 匹配更稳也不依赖对方的 filter 是否写对。4.2 QUERY_ALL_PACKAGES 的适用边界QUERY_ALL_PACKAGES是一个普通权限声明了就能拿到所有已安装应用列表uses-permission android:nameandroid.permission.QUERY_ALL_PACKAGES /但它的适用范围被严格限制在特定类别比如安全软件、文件管理器、启动器、设备管理类应用。普通应用在应用市场上架时声明这个权限大概率会被打回要求你说明用途并提供更精确的queries方案。我在一个工具类应用上试过审核反馈明确要求改成queries按需查询。所以在动手之前先想清楚你是真的需要全量应用列表还是只是需要能处理某个操作的应用列表。后者用queries加intent完全够用而且用户体验上更合理——用户看到的列表里只有真正相关的应用不是一屏没用的图标。5. 拿到数据之后解析 Intent 与常见崩溃点配置写对只是第一步真正出问题的地方往往在接收端。这一节说几个我踩得最多的坑。5.1 Intent 解析的边界处理先看一段看起来很正常的代码val uri intent.getParcelableExtraUri(Intent.EXTRA_STREAM) val inputStream contentResolver.openInputStream(uri!!)这段代码在生产环境下有三种崩法。第一种intent.action可能是SEND也可能是SEND_MULTIPLE取EXTRA_STREAM的方式完全不同。如果是SEND_MULTIPLEgetParcelableExtra返回 null后面uri!!直接 NPE。第二种某些应用的实现不规范会把EXTRA_STREAM塞成 String 而不是 Uri。这种情况getParcelableExtra返回 null或者在某些低版本 ROM 上抛 ClassCastException。稳妥做法是兼容处理private fun extractSingleUri(intent: Intent): Uri? { val raw intent.extras?.get(Intent.EXTRA_STREAM) ?: return null return when (raw) { is Uri - raw is String - runCatching { Uri.parse(raw) }.getOrNull() else - null } }第三种contentResolver.openInputStream抛SecurityException。原因是发送方没加FLAG_GRANT_READ_URI_PERMISSION或者你的 Activity 不是通过startActivity直接拉起的比如被某个中转页转了一手权限没传递下来。这种只能 try-catch 并给用户提示没法从接收端补救。Android 13 之后getParcelableExtra(String)被标记了 deprecation推荐用带 class 参数的重载val uri if (Build.VERSION.SDK_INT 33) { intent.getParcelableExtra(Intent.EXTRA_STREAM, Uri::class.java) } else { Suppress(DEPRECATION) intent.getParcelableExtra(Intent.EXTRA_STREAM) }写起来啰嗦但能避免一堆类型相关的警告。5.2 权限授予与 URI 持久化FLAG_GRANT_READ_URI_PERMISSION授予的是临时权限作用范围是这一次 startActivity 拉起来的组件。如果你的接收 Activity 处理完内容后要启动另一个 Activity 继续处理那个新 Activity 拿同一个 URI 还能读因为授权是跟着 URI 走的在整个任务栈生命周期内有效。但如果你的 Activity 处理完就 finish 了把 URI 存在数据库里下次启动再读就会抛SecurityException。要长期持有得让发送方加FLAG_GRANT_PERSISTABLE_URI_PERMISSION接收方再调contentResolver.takePersistableUriPermission( uri, Intent.FLAG_GRANT_READ_URI_PERMISSION )但分享场景里发送方基本不会加这个 flag所以实际上长期持有很难做到。我的做法是分享进来之后立刻把内容复制到应用私有目录之后操作副本。多了点 IO但省掉一堆权限问题。特别是图片、视频这类大文件复制耗时明显最好放在子线程并且给个进度提示不然用户会以为卡死了。6. 调试与排错实录配置写完怎么快速验证有没有生效比反复安装重启高效得多。6.1 adb 命令验证 Intent 匹配最直接的验证方式是让系统帮你跑一次匹配查询adb shell cmd package query-activities \ -a android.intent.action.SEND \ -t image/jpeg输出里会列出所有能处理这个 Intent 的组件。如果你的应用不在里面问题就一定出在 Manifest 配置上跟代码无关。这个命令比装一圈应用来测试快太多了。还可以直接构造一次分享调用adb shell am start \ -a android.intent.action.SEND \ -t text/plain \ --es android.intent.extra.TEXT hello from adb看系统弹出来的分享面板里有没有你的应用以及点进去之后能不能正常拿到内容。查自己应用注册了哪些 filteradb shell dumpsys package com.example.app | grep -A 30 IntentFilter这个输出会比较长但能清楚看到每个组件实际注册的 action、category、scheme 和 type是排查我以为我写了但其实没写的利器。还有一个偏门但好用的命令查某个 action 的解析结果adb shell cmd package resolve-activity -a android.intent.action.VIEW -t application/pdf6.2 常见问题速查表现象可能原因排查方式分享面板里完全没有我的应用缺CATEGORY_DEFAULTdumpsys package看 filter 内容只支持分享文字图片不出现只注册了text/*没注册image/*query-activities -t image/jpeg多选图片分享时不出现没注册ACTION_SEND_MULTIPLEquery-activities -a android.intent.action.SEND_MULTIPLE安装时报 manifest 错误targetSdk 31 未声明exported看 Gradle 报错堆栈列表里显示成包名android:label为空或未设置检查 Activity 与 Application 的 label同一应用出现两次多个 Activity 注册了相同 filter检查是否有activity-alias或重复组件能进列表但点进去崩溃接收端空指针或权限异常adb logcat过滤包名和SecurityException打开方式里能选但文件读不出发送方未授权读 URI检查FLAG_GRANT_READ_URI_PERMISSION升级到 targetSdk 33 后查询列表变少未声明queries检查 manifest 是否有 queries 节点6.3 几个踩过的坑坑一透明主题导致黑屏一闪。用Theme.Translucent.NoTitleBar做中转页在部分国产 ROM 上会先黑一帧再显示内容。解决办法是给windowBackground设成透明同时把windowIsTranslucent和windowNoTitle都显式设为 true不要只依赖父主题。我试过只改父主题在某个 Android 12 的机器上还是闪加了三项之后稳定了。坑二android:label用了资源引用但没做本地化。分享面板里显示的名字是跟着系统语言走的。如果你的应用只有中文 label在英文系统上显示中文反之亦然。如果应用有出海需求share_target_label这类字符串一定要放进所有语言的 strings 文件里。坑三接收端在onCreate里做重活。分享进来之后做文件复制、图片解码、上传全都塞在onCreate用户点完应用看起来像卡住了。正确做法是onCreate只解析 Intent 参数重活放onStart之后的协程或者后台线程同时给一个加载状态。如果是透明中转页处理完直接finish()不要让用户看到界面。坑四activity-alias的targetActivity必须存在同名组件。用 alias 让同一个 Activity 出现在多个列表里是很实用的技巧但android:targetActivity必须指向一个已经声明的 Activity而且那个 Activity 本身也要在 manifest 里有定义。我见过只写了 alias 没写原 Activity 的装上去直接报错。坑五不要指望 priority 改变分享面板排序。分享面板的排序基本由系统按使用频率决定android:priority在这里几乎没影响。想让入口靠前靠谱的做法是 Direct Share 加高频使用而不是调 priority。最后分享一个我在实际项目里固定下来的做法把所有跟被外部调用相关的 Activity 集中放在一个external包里Manifest 里也集中写在一起每个组件上面都加一行注释说明它是被哪个 Intent、哪个场景调起的。这个习惯是踩过一次大坑之后养成的——当时有个历史遗留的分享 Activity 混在业务包里改需求时顺手删了结果用户反馈分享到我们应用的入口消失了排查了两个小时才找到原因。集中管理之后这类问题基本不会再发生。
返回列表