tags: AppLinks


以下内容分两部分。
第一部分译自官网文档,主要为App Links的接入指南;第二部分为笔者的接入实践,总结了接入过程遇到的问题,以及对App Links的使用上的思考。
以上内容,仅供个人学习、记录、参考使用,如有纰漏,还请留言指正。

[TOC]


验证 Android App Links

官网文档:Verify Android App Links

Android App Links是一种特殊的Deep Links,它使Android系统能够直接通过网站地址打开应用程序对应的内容页面,而不需要用户选择使用哪个应用来处理网站地址。

要添加Android App Links到应用中,需要在应用里定义通过Http(s)地址打开应用的intent filter,并验证你确实拥有该应用和该网站。如果系统成功验证到你拥有该网站,那么系统会直接把URL对应的intent路由到你的应用。

为了验证你对应用和网站的所有权,以下两个步骤是必须的:

  1. 在AndroidManifest里要求系统自动进行App Links的所有权验证。这个配置会告诉Android系统去验证你的应用是否属于在intent filter内指定的URL域名。
  2. 在以下链接地址里,放置一个数字资产链接的Json文件,声明你的网址和应用之间的关系:
    https://domain.name/.well-known/assetlinks.json复制代码

Android App Links 和 Deep Links 的区别

Deep Links 是一种允许用户进入应用某个特定Activity的intent filter。点击这类链接时,系统可能会弹出一个选择列表,让用户在一堆能够处理这类链接的应用里(包括你的)选择一个来处理该链接。图一展示了这样一种情况:用户点击了一个地图相关的链接,系统弹出一个选择列表,让用户选择是要使用地图应用来处理,还是使用Chrome浏览器来处理。

App Links 是一种基于你的网站地址且验证通过的Deep Links。因此,点击一个这样的链接会直接打开你的应用(如果已经安装),系统将不会弹出选择列表。当然,后续用户可以更改配好设置,来指定由哪个应用程序处理这类链接。

下面这个列表描述更多差异:

- Deep Links App Links
Intent URL Scheme https, http,或者自定义 需为http或https
Intent Action 任意Action 需为android.intent.action.VIEW
Intent Category 任意Category 需为android.intent.category.BROWSABLEandroid.intent.category.DEFAULT
链接验证 不需要 需要在网站上放置一个数字资产链接,并能够通过HTTPS访问
用户体验 可能会弹出一个选择列表给用户选择用哪个应用处理连接 没有弹框,系统直接打开你的应用处理网站连接
兼容性 所有Android版本 Android 6.0及以上

要求 App Links 验证

为了让系统为你的应用进行链接验证,需要在AndroidManifest中,在任意一个网站链接intent filter里(包含了android.intent.action.VIEW的Intent Action和android.intent.category.BROWSABLE的Intent Category),设置android:autoVerify="true"。如下面的片段所示:

<activity ...><intent-filter android:autoVerify="true"><action android:name="android.intent.action.VIEW" /><category android:name="android.intent.category.DEFAULT" /><category android:name="android.intent.category.BROWSABLE" /><data android:scheme="http" android:host="www.example.com" /><data android:scheme="https" /></intent-filter></activity>复制代码

android:autoVerify="true"出现在你任意一个intent filter里,在Android 6.0及以上的系统上安装应用的时候,会触发系统对APP里和URL有关的每一个域名的验证。验证过程设计以下步骤:

  1. 系统会检查所有包含以下特征的intent filter:Action为 android.intent.action.VIEW、Category为android.intent.category.BROWSABLEandroid.intent.category.DEFAULT、Data scheme为httphttps
  2. 对于在上述intent filter里找到的每一个唯一的域名,Android系统会到对应的域名下查找数字资产文件,地址是:https://域名/.well-known/assetlinks.json

只有当系统为AndroidManifest里找到的每一个域名找到对应的数字资产文件,系统才会把你的应用设置为特定链接的默认处理器。

让App Link 支持多个域名

在intent filter中的data元素中定义的每一个域名,系统都需要能够验证它的数字资产声明文件,即每一个域名都需要放置一个对应的数字资产声明文件。如果验证过程中有任何一个域名验证失败了,那么系统不会把应用设为应用内声明的任何一种链接模式的默认处理器。系统会用原来处理Deep Links的方式处理这些链接。

举例来说,一个定义了下面的intent filter的应用,在验证过程中,由于assetlinks.json文件在https://www.example.com/.well-known/assetlinks.jsonhttps://www.example.net/.well-known/assetlinks.json下都没找到。

<application><activity android:name=”MainActivity”><intent-filter android:autoVerify="true"><action android:name="android.intent.action.VIEW" /><category android:name="android.intent.category.DEFAULT" /><category android:name="android.intent.category.BROWSABLE" /><data android:scheme="http" android:host="www.example.com" /><data android:scheme="https" /></intent-filter></activity><activity android:name=”SecondActivity”><intent-filter><action android:name="android.intent.action.VIEW" /><category android:name="android.intent.category.DEFAULT" /><category android:name="android.intent.category.BROWSABLE" /><data android:scheme="https" android:host="www.example.net" /></intent-filter></activity></application>复制代码

注意,同一个intent filter内所有的<data>元素会合并在一起,以解释他们所有组合属性的变量。例如:上述第一个intent filter仅仅定义了一个HTTPS Scheme。但这个Scheme和其他的<data>元素结合在一起,因此这个intent filter既支持http://www.example.com,也支持https://www.example.com。同样的,为了定义不同的URI的Scheme和域名的组合,你需要创建不同的intent filter。

让App Links 支持不同的子域名

数字资产链接协议把你intent filter里的子域名看做唯一的、独立的域名。因此,如果你的intent filter列出一个域名及其多个子域名,你需要在每个子域名上发布一个有效的assetlinks.json文件。举例来说,下面的intent filter包含了www.example.commobile.example.com作为期望处理的intent的URL的主机名。因此,有效的assetlinks.json必须同时发布在https://www.example.com/.well-known/assetlinks.jsonhttps://mobile.example.com/.well-known/assetlinks.json

<application><activity android:name=”MainActivity”><intent-filter android:autoVerify="true"><action android:name="android.intent.action.VIEW" /><category android:name="android.intent.category.DEFAULT" /><category android:name="android.intent.category.BROWSABLE" /><data android:scheme="https" android:host="www.example.com" /><data android:scheme="https" android:host="mobile.example.com" /></intent-filter></activity>
</application>复制代码

或者,如果你用通配符定义了你要处理的主机名(如*.example.com),那么你需要把你的assetlinks.json文件发布在根主机名(根域名)下(example.com)。比如,一个以定义了如下intent filter的应用,如果assetlinks.json发布在https://example.com/.well- known/assetlinks.json,它的任一子域名(如foo.example.com)将会通过验证。

<application><activity android:name=”MainActivity”><intent-filter android:autoVerify="true"><action android:name="android.intent.action.VIEW" /><category android:name="android.intent.category.DEFAULT" /><category android:name="android.intent.category.BROWSABLE" /><data android:scheme="https" android:host="*.example.com" /></intent-filter></activity>
</application>复制代码

声明网站和应用之间的关系

数字资产证明文件必须发布在你的网站上,以证明你的应用是和网站关联在一起的,并且用于验证应用内的URL链接模式。这个JSON文件用下面几个字段来标识关联的应用:

  • package_name:在build.gradle里定义的application ID
  • sha256_cert_fingerprints:应用签名的SHA256指纹信息。你可以用下面的命令,通过Java keytool来生成指纹信息:
    $ keytool -list -v -keystore my-release-key.keystore
    这个字段支持多个指纹信息,可以用来支持不同的应用版本,如开发版本和发布版本。

下面这个示例assetlinks.json授予链接打开权限给com.example应用:

[{"relation": ["delegate_permission/common.handle_all_urls"],"target": {"namespace": "android_app","package_name": "com.example","sha256_cert_fingerprints":["14:6D:E9:83:C5:73:06:50:D8:EE:B9:95:2F:34:FC:64:16:A0:83:42:E6:1D:BE:A8:8A:04:96:B2:3F:CF:44:E5"]}
}]复制代码

让网站和不同应用关联起来

网站可以在同一个assetlinks.json文件里声明和不同的app的关系。下面这个文件列出了两个声明,这两个声明声明了网站和两个应用之间的关联,这个文件位于https://www.example.com/.well-known/assetlinks.json

[{"relation": ["delegate_permission/common.handle_all_urls"],"target": {"namespace": "android_app","package_name": "com.example.puppies.app","sha256_cert_fingerprints":["14:6D:E9:83:C5:73:06:50:D8:EE:B9:95:2F:34:FC:64:16:A0:83:42:E6:1D:BE:A8:8A:04:96:B2:3F:CF:44:E5"]}},{"relation": ["delegate_permission/common.handle_all_urls"],"target": {"namespace": "android_app","package_name": "com.example.monkeys.app","sha256_cert_fingerprints":["14:6D:E9:83:C5:73:06:50:D8:EE:B9:95:2F:34:FC:64:16:A0:83:42:E6:1D:BE:A8:8A:04:96:B2:3F:CF:44:E5"]}
}]复制代码

不同应用或许会处理同一个域名下的不同资源链接。比如:app1声明一个intent filter用于处理https://example.com/articles,app2声明一个intent filter用于处理https://example.com/videos

注意:关联同一个域名的不同应用可能会使用相同或不同的签名文件。

关联一个应用到不同网站

不同的网站可以在它们的assetlinks.json文件里声明关联同一个应用。下述文件展示了如何关联example.com和example.net到app1。第一个文件展示了关联app1到example.com:

www.example.com/.well-known…

[{"relation": ["delegate_permission/common.handle_all_urls"],"target": {"namespace": "android_app","package_name": "com.mycompany.app1","sha256_cert_fingerprints":["14:6D:E9:83:C5:73:06:50:D8:EE:B9:95:2F:34:FC:64:16:A0:83:42:E6:1D:BE:A8:8A:04:96:B2:3F:CF:44:E5"]}
}]复制代码

下一个展示了关联app1到example.net。两者的差异只有放置的地方不同。

www.example.net/.well-known…

[{"relation": ["delegate_permission/common.handle_all_urls"],"target": {"namespace": "android_app","package_name": "com.mycompany.app1","sha256_cert_fingerprints":["14:6D:E9:83:C5:73:06:50:D8:EE:B9:95:2F:34:FC:64:16:A0:83:42:E6:1D:BE:A8:8A:04:96:B2:3F:CF:44:E5"]}
}]复制代码

发布JSON验证文件

你必须把你的JSON验证文件发布在下面的位置:

https://你的域名/.well-known/assetlinks.json复制代码

需要确保以下几点:

  • assetlinks.json文件的content-type必须为application/json
  • 不管你的应用的intent filter是否定义https作为data的scheme,assetlinks.json必须能通过HTTPS链接访问
  • assetlinks.json必须能不经过任何重定向被访问到(即没有301、302跳转),同时可以被爬虫访问到(你的robot.txt必须允许抓取/.well-known/assetlinks.json)
  • 如果你的应用支持多种域名,你需要把assetlinks.json发布在这几个域名的服务器上。
  • 不要在你的AndroidManifest文件里发布无法公开访问的开发/测试URL(比如那些只能通过VPN进行访问的URL)。一种可行的做法是配置构建类型,来为开发构建生成不同的清单。

测试App Links

当实现APP Links特性时,你需要测试链接功能是否可用,以保证系统能够将你的APP和网站关联起来,并如预期地处理URL请求。

要测试一个已存在的声明文件,你可以使用声明列表生成器和测试器工具

确认需要进行验证的域名列表

进行测试时,你需要确认你的应用里关联的需要系统进行验证的域名列表。确保列表里的域名对应的intent filter包含以下属性和元素:

  • android:scheme属性值为httphttps
  • android:host属性为一个域名URL模式
  • 包含android.intent.action.VIEWCategory
  • 包含android.intent.category.BROWSABLECategory

同样的,确保列表里的每一个域名及相应的子域名下有数字资产证明链接的JSON文件存在。

确认数字资产证明文件

对每个域名,使用数字资产链接API来确认数字资产证明文件被正确地定义和发布在互联网上:

https://digitalassetlinks.googleapis.com/v1/statements:list?source.web.site=https://你的域名:可选的端口&relation=delegate_permission/common.handle_all_urls复制代码

测试URL Intent

当你确认了你声明的网站列表和你的应用之间的关联,同时也确认了发布的JSON文件是有效的,将应用安装到设备上。等待约20s,以便系统发起的异步验证任务执行完毕。用下面这个命令来检查系统是否验证了你的应用并设置了正确的链接处理策略:

adb shell am start -a android.intent.action.VIEW \-c android.intent.category.BROWSABLE \-d "http://你的域名:可选的端口"复制代码

检查链接策略

作为你的测试的一部分,你可以当前系统的链接处理设置。用下面的命令来获取设备上所有应用的链接处理策略:

adb shell dumpsys package domain-preferred-apps复制代码

下面这个命令也是同样的作用:

adb shell dumpsys package d复制代码

注意:为了让系统能完成验证过程,确保你在安装应用之后等待了至少20秒

命令返回了设备上每一个用户配置的列表,以如下格式的头部信息开头:

App linkages for user 0:复制代码

在头部信息后面的输出以如下的格式输出链接处理配置信息:

Package: com.android.vending
Domains: play.google.com market.android.com
Status: always : 200000002复制代码

这个列表表示了哪个应用和哪个网站之间的关联:

  • Package:标识应用的包名
  • Domains:列出应用所处理的所有网站域名列表,以空格作为分隔符
  • Status:表示应用当前的链接处理策略。通过了验证的应用,且它的Manifest文件里包含android:autoVerify="true",它的状态为always。跟在状态之后的十六进制数字和系统的应用链接配置记录有关。这个数值和验证是否成功无关。

注意:If a user changes the app link settings for an app before verification is complete, you may see a false positive for a successful verification, even though verification has failed.This verification failure, however, does not matter if the user explicitly enabled the app to open supported links without asking. This is because user preferences take precedence over programmatic verification (or lack of it). As a result, the link goes directly to your app, without showing a dialog, just as if verification had succeeded.

测试示例

为了App Links能验证成功,系统必须能够验证你定义在intent filter里的所有网站,当然这些定义需要符合App Links的标准。下面这是示例演示了一个定义了几个App Links的Manifest配置。

<application><activity android:name=”MainActivity”><intent-filter android:autoVerify="true"><action android:name="android.intent.action.VIEW" /><category android:name="android.intent.category.DEFAULT" /><category android:name="android.intent.category.BROWSABLE" /><data android:scheme="https" android:host="www.example.com" /><data android:scheme="https" android:host="mobile.example.com" /></intent-filter><intent-filter><action android:name="android.intent.action.VIEW" /><category android:name="android.intent.category.BROWSABLE" /><data android:scheme="https" android:host="www.example2.com" /></intent-filter></activity><activity android:name=”SecondActivity”><intent-filter><action android:name="android.intent.action.VIEW" /><category android:name="android.intent.category.DEFAULT" /><category android:name="android.intent.category.BROWSABLE" /><data android:scheme="https" android:host="account.example.com" /></intent-filter></activity><activity android:name=”ThirdActivity”><intent-filter><action android:name="android.intent.action.VIEW" /><category android:name="android.intent.category.DEFAULT" /><data android:scheme="https" android:host="map.example.com" /></intent-filter><intent-filter><action android:name="android.intent.action.VIEW" /><category android:name="android.intent.category.BROWSABLE" /><data android:scheme="market" android:host="example.com" /></intent-filter></activity></application>复制代码

根据上述配置,系统会尝试进行验证的域名有如下几个:

www.example.com
mobile.example.com
www.example2.com
account.example.com复制代码

系统不会尝试进行验证的域名有:

map.example.com (没有设置 android.intent.category.BROWSABLE)
market://example.com (不是“http”或“https” scheme)复制代码

官网文档正文完

以上便是官网上有关AppLinks的接入指导。但在笔者接入过程中,仍然碰到了一些问题。下文将记录笔者遇到的问题和解决的方法。


AppLink 接入实践总结

验证

AppLinks的接入验证,依照上文,可分为两步进行:

  1. 验证网站上是否将assetlink.json
  2. 第一步验证通过后,将应用安装到设备上,进行验证

第一步的验证可使用上文提及的声明列表生成器和测试器工具。笔者在验证过程中发现,此工具确实能有效帮助我们生成,但对于验证环节,错误信息的提示并不怎么友好。

为了获得具体的出错信息,需要我们右键打开Chrome浏览的检查功能,选中Network标签页,然后再触发一次验证请求。此时,可以在Network标签页上看到刚刚发出去的验证请求。查看这个请求的返回结果,在errorMessage里可以看到完整的错误信息。

进行第二步验证,依照上文,需要在应用安装后,等待至少20秒的时间,以便系统验证完毕。但是,上文只告诉我们如何获得应用是否是特定域名的默认处理器的配置信息,系统的这个验证过程是否发起,是否执行完毕,验证结果如何,我们是无从得知的。

笔者在接入过程中,观察系统(Android 7.1.2)日志输出,发现SingleHostAsyncVerifier这个tag会输出验证相关的log,如下:

09-05 14:10:08.122 1386-26740/? I/SingleHostAsyncVerifier: Verification result: checking for a statement with source a <a: "https://kg.qq.com">, relation delegate_permission/common.handle_all_urls, and target b <a: "com.tencent.karaoke"b <a: "02:38:85:6C:4B:EF:4A:5D:E1:81:E1:CB:07:B5:EF:02:B2:EC:4E:69:AC:C3:7F:CD:FC:08:8D:40:C7:6B:9C:68">>--> true.复制代码

上述日志输出表示成功验证了这个AppLinks。可以参考本方法,观察系统log来确定是否验证通过。另一种方法是以应用包名作为过滤器来过滤日志,也能发现一些蛛丝马迹。

AppLinks生效条件

AppLinks是Android M之后系统支持的特性,因此也只有使用Android M及以上的系统的用户能体验到这个特性。

但要使这个特性生效,是否需要我们应用的目标SDK版本(targetSdkVersion)也提到Android M+呢?答案是否定的。因此,即时应用还未适配Android M以上的动态权限申请,也可以接入这个特性。

另外,AppLinks本质上是一种特殊的DeepLinks,也就是说,在不支持的系统上,它会跟DeepLinks一样,作为一个可处理某一类请求的处理器,显示在系统的弹出列表里。

AppLinks的场景验证

要验证AppLinks,可以使用上文提到的adb命令:

adb shell am start -a android.intent.action.VIEW \-c android.intent.category.BROWSABLE \-d "http://你的域名:可选的端口"复制代码

来发送一个构造好的intent给系统。这种方式适合开发者进行自测。

如果要给他人体验这个效果,那么可以尝试,在记事本里输入可处理的链接,然后在预览模式下,点击该链接,触发AppLinks生效的场景。

另外,笔者尝试在Android手机上的Chrome浏览器、猎豹浏览器、UC浏览器里直接输入网址来验证,均无法触发系统的AppLinks特性。推测浏览器内发起的跳转,会被浏览器直接拦截处理了,因此无法进入系统的分发处理Intent流程,故没触发AppLinks特性。

系统的默认处理配置消息

另外,笔者在调试过程中发现,即便AppLinks特性已经生效了,但通过:

adb shell dumpsys package domain-preferred-apps复制代码

adb shell dumpsys package d复制代码

这两个命令得到的列表里,笔者的应用显示的仍然不是always的状态。这在笔者调试的过程中,给笔者造成了很大的困扰,导致笔者以为AppLinks的验证过程未通过,导致这个情况。

实际验证过程推荐使用查看系统日志、手动构造发送intent的方式进行验证。

快速集成方法

Android Studio 2.3开始,在Tools里集成了App Links Assistant,这个图形化工具能帮助我们完成上述提及的所有步骤。了解了上述细节后,可以使用这个工具,快速集成。

小结

总的来说,App Links赋予了应用直接处理其相关网站连接的能力,使得应用能更方便地触达用户,提供优于Web的体验。同时,通过assetlink这种方式,来确保网站所有者及其对应应用的相关性,避免外部应用恶意拦截处理网站连接的情况。

但在实际使用中发现,这种方式只能用于系统处理URL的intent的情况,若URL本身没有抛给系统处理,而是直接在应用内打开了,那就无法直接拉起应用。

这也是App Links和iOS的Universal Link不一样的地方。App Links是在URL被处理前,转发给应用处理,而Universal Link则是先打开对应的网页,然后再直接跳转到应用。

由于大部分应用,如微博、微信、第三方浏览器(包括Chrome),都不会将URL抛给系统处理,因此App Links生效的情况就很有限了,比如只能从记事本应用、短信应用这些进行跳转。总体来说,实属鸡肋。

Android AppLinks 接入相关推荐

  1. android原生接入rn,Android原生项目集成RN页面

    Android原生接入ReactNative 许久不接触RN,重新捡起重复踩坑,折腾三天就此记录 优化后接入步骤 新建文件夹,将原AndroidStudio项目拷贝至此目录 同目录下新建package ...

  2. Unity3d Android SDK接入解析(四)通用的Android SDK接入中间件

    一.前言 接入Android SDK正式告一段落,在这段时间里面,依次接入了华为.应用宝.小米.360等等大大小小十来个SDK,也算对Unity接入渠道SDK有了较为全面的理解,对各个渠道的坑也算深有 ...

  3. 踩坑!穿山甲广告Android SDK接入

    随着流量变现的兴起,越来越多的广告SDK汹涌而来,除了字节的穿山甲,还有腾讯的优量汇.百度的广告联盟,其他的例如AdView和万普世纪已经逐渐退出历史的舞台. 本篇文章将基于com.pangle.cn ...

  4. Android App接入支付功能

    微信支付,请参考我另一篇:Android App接入支付功能--微信篇 因为项目中用到支付功能,而且支付宝文档和微信文档写的很简洁,不仔细研究,真的无法集成成功 老样子,上效果图由于涉及到输入密码,我 ...

  5. android客户端接入新浪、腾讯微博以及人人网

    本文原创http://blog.csdn.net/yanbin1079415046,转载请注明出处. 从事android工作也有段时间了,碍于肚子里料不多,一直也没写过什么东西.最近刚好项目中要接入新 ...

  6. Android支付接入(七):Googlenbsp;In-app-Billing

    http://qing.blog.sina.com.cn/tj/634ac835330048cu.html 原文地址:Android支付接入(七):Google In-app-Billing作者:屌丝 ...

  7. Unity3d Android SDK接入解析(三)接入Android Library的理解(爱贝云支付为例)

    一.前言 写这个主题的原因,出于刚入门u3d,需要接入爱贝云支付的内容,苦于爱贝支付是一个Android的Library库,看到网上漫天遍野都是Android接入的帖子,但却没有我想要的关于Libra ...

  8. Android开发-在Android里接入阿里云推流SDK实现直播推流的功能

    前 言 如今,在国内移动互联网发展了几年的时间,移动开发技术也相对的成熟,在咱们日常使用的手机App中也少不了直播的功能,不管是娱乐类.游戏类.体育类还是教育类等的App都会有直播的功能,可以说直播的 ...

  9. android 360游戏sdk,360ssp sdk接入说明 360移送媒体平台Android SDK 接入说明

    360ssp sdk接入说明 360移送媒体平台Android SDK 接入说明 360ssp sdk接入说明 360移送媒体平台Android SDK 接入说明: Android SDK SDK版本 ...

最新文章

  1. 关于《强化狼群等级制度的灰狼优化算法》的问题邮件回复
  2. pci 中断冲突_Linux 内核PCI 中断
  3. 成功解决CatBoostError: Invalid type for cat_feature cat_features must be integer or string, real number
  4. LOL手游2.3版本终于来临,国服玩家满意新增的皮肤吗?
  5. 家用电器用户行为分析与事件识别代码详解+修改后运行无误的代码
  6. 图册图册图册图册图册图册
  7. mysql字段分隔符拆分_面试题Mysql数据库优化之垂直分表
  8. Python使用最小二乘法求解回归直线案例一则
  9. Cookie和Session有什么区别?
  10. 怎么把图片压缩到30K以下?如何用手机快速压缩图片?
  11. 凯联医疗完成逾亿元C轮融资 加速布局微量药物输注领域
  12. 鼠标精灵对码软件_暗影精灵6游戏主机评测
  13. 如何搭建一个自己的FTP服务器
  14. 什么是WIFI真机同步?
  15. protocol buffers 序列化数据
  16. FCK上传图片问题解决
  17. 微积分中计算椭圆面积的几种方法
  18. 6款工具助力分析JVM问题
  19. 断电后,台式计算机无法启动,练习台式机电脑停电后开不了机怎么办呢?
  20. 青藤 #210733 奶牛碑文

热门文章

  1. APP性能优化之工具使用- 调试GPU过度绘制
  2. i技术会 | 大数据应用与数据中台实践
  3. [iOS微博项目 - 3.2] - 发送微博
  4. 安卓识别exfat_关于在 Android 8 上实现 exFAT 支持
  5. LED体重秤解决方案
  6. 图文并茂(Excel生成有图片的报表)
  7. javaweb输出所有学生信息_遍历工作表中所有形状并输出信息
  8. 【MM小贴士】母子工单物料主数据设置及工单核算
  9. 个人怎么查车辆的保养及维修记录查询,JavaScript let 和 const
  10. .NetCore——日志系统