WeTest 导读

2018年8月7日,Google对外发布最新 Android 9.0 正式版系统,并宣布系统版本Android P 被正式命名为代号“Pie”,最新系统已经正式推送包括谷歌Pixel、Pixel2系列以及Essential Phone。

腾讯WeTest平台第一时间进行了系统升级,实现Android 9.0机型兼容测试和远程调试服务。

为了让用户第一时间了解Android 9.0系统的兼容性情况,我们基于谷歌Pixel、Pixel 2机型的Android 9 Pie系统,对市面TOP86款应用进行“深度兼容测试”,发布了《腾讯WeTest Android 9.0兼容性测试报告》(点击阅读原文前往Android 9 专区下载)。


Android P 行为变更适配

 

一、全面屏检测

在 Android 8.0 时代各个手机厂商就开始发布自己的全面屏手机,但是此时 Android 官方并未支持到该功能,所以各个厂商都各自实现了一套全面屏判断逻辑,对于开发者来说甚是麻烦。终于在 Android P 里官方收归了该功能的判断逻辑,Android P 和之后的版本完全可以使用官方 API 来判断全面屏,当然前提是第三方厂商按照 google 官方接口去实现。Android P 版本判断全面屏代码很简单,但是在适配过程中你可能会在网上发现如下判断代码:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
   decorView.setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() {        
   @RequiresApi(api = 28)
       @Override
       public WindowInsets onApplyWindowInsets(View view, WindowInsets windowInsets) {            
           if (windowInsets != null) {
               DisplayCutout cutout = windowInsets.getDisplayCutout();                
               if (cutout != null) {
                   List<Rect> rects = cutout.getBoundingRects();                    
                   //通过判断是否存在rects来确定是否全面屏手机
                   if (rects != null && rects.size() > 0) {
                       isNotchScreen = true;
                   }
               }
           }            
           return windowInsets;
       }
   });
}

这段代码确实可以判断出全面屏与否,但是会造成一个很严重的后果,就是在某些手机(pixel 和 vivo x21 均出现该情况)上底部导航栏会透明,导致应用内容会透到导航栏从而被遮挡,大大影响内容展示。最后经过仔细排查发现仅仅因为在上面那段代码中调用了 setOnApplyWindowInsetsListener 函数,该函数在 Android 官网有详细介绍,是用来在 Android 21 版本之后代替 fitSystemWindows 函数,目的是让 View 根据 Window 的缩进进行相应处理,调用后会影响系统状态栏和导航栏对应用内容的展示,对此的介绍资料网上有很多,就不赘述了。真正完美判断全面屏的代码如下:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
   WindowInsets windowInsets = decorView.getRootWindowInsets();    
   if (windowInsets != null) {
       DisplayCutout displayCutout = windowInsets.getDisplayCutout();        
       if (displayCutout != null) {            
           List<Rect> rects = displayCutout.getBoundingRects();            
           //通过判断是否存在rects来确定是否刘海屏手机
           if (rects != null && rects.size() > 0) {
               isNotchScreen = true;
           }
       }
   }
}

二、非 SDK API 适配详解

2.1 非 SDK API 名单介绍

Android P 版本最大最严格的特性变更应该非 SDK 接口限制莫属了。对于非 SDK API 里面的部分名单来说,就算在不修改 targetSdkVersion 的前提下,不管是直接、反射还是通过 JNI 调用都会造成调用失败、抛出 NoSuchFieldException或 NoSuchMethodException 等严重后果,该行为影响范围波及所有调用此接口的应用。

非 SDK API 名单总共分为三类:light grey list (浅灰名单)、dark grey list (深灰名单)、dark list(黑名单),详情:

名单类型 影响 名单说明
浅灰名单 targetSDK>=P 时,警告 目前,针对非 SDK 接口没有可替代 SDK 接口的这种情况,谷歌暂时将之放在浅灰名单,并在后续版本考虑增加可替代的接口,然后再将接口转移到深灰名单。名单通过代码维护。
深灰名单 targetSDK<P时,警告;>=P 时,不允许调用 目前,针对非 SDK 接口有可替代 SDK 接口的情况,谷歌将之放在深灰名单中,开发者需要将这些非 SDK 接口进行整改。名单通过代码维护。
黑名单 所有第三方应用不允许调用 灰名单(深灰+浅灰)之外的其他所有非 SDK 接口都将被添加到黑名单中,如果应用使用到黑名单接口,需马上整改或者反馈给谷歌申请加入灰名单。

所以对于我们应用开发者来说,当前首要任务是适配深灰名单和黑名单。目前 google 官方提供了一个可以实时查询三个名单里面 API 列表的网站:https://android.googlesource.com/platform/frameworks/base/+/master/config/。在之前 DP 版本时开发者如果遇到了不得不使用的黑名单或者深灰名单 API,需要向 google 官方及时提出反馈,申请将其移动到浅灰名单中,但是目前正式版本已经发布,未得知该申请通道是否仍有效。

2.2 非 SDK API 名单扫描

详细了解了非 SDK API 之后,下一步当然是将应用代码里面的深灰名单和黑名单 API 调用找出来一一修改。目前官方提供了一个非常实用的扫描工具,该工具可以把应用里面三个类型名单的 API 调用都扫描出来(但是可能会有遗漏),使用方法也很简单:

打包一个应用 APK,建议使用 release 包,排除一些未使用到的单元测试类或者其他因素的影响,将 APK 放到工具指定目录下;

执行命令 ./appcompat.sh --dex-file=test.apk,在终端上会输出三个名单每个 API 的详细调用处:

 #1: Linking dark greylist Landroid/os/SystemProperties;->get(Ljava/lang/String;)Ljava/lang/String; use(s):
       Ltmsdkobf/gv;->a(Ljava/lang/String;)Ljava/lang/String;
 
#2: Linking dark greylist Landroid/os/SystemProperties;->get(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; use(s):
       Ltmsdkobf/gp;->b(Landroid/content/Context;)Ljava/lang/String; ....

2.3 非 SDK API 适配

经过上一步扫描出应用内非 SDK API 调用之后,接下来就可以直接开始适配。适配的原则是优先黑名单和深灰名单,浅灰名单在官方未有替代 API 之前可以暂时不适配,在 Android P 上运行也不会有任何问题。扫描完成之后,不出意外大家应该会有三类需要适配的 API 调用:

应用代码本身调用到了非 SDK API 接口;

针对应用代码本身调用到了非 SDK API 接口,用的比较频繁的例如 SystemProperties.get,就需要去寻找另外一个可以替代的合法 API,如果找不到就只能认为该 API 调用失败从而走失败逻辑,如果实在必须要用到该 API 就尽早去向 google 申请移动到浅灰名单中。

第三方库调用到了非 SDK API 接口;

针对第三方库调用到了非 SDK API 接口,解决办法当然是直接查询相关资料或者联系库提供方,确认是否有适配 Android P 新版本的 SDK。还有需要提到的一点,就算更换适配完成的第三方 SDK 后,仍然可能会在同一地方扫描出非 SDK API 的调用,这是因为适配工程师只是在调用处加了一个 try-catch 保护逻辑,虽然这样也勉强叫做适配完成,但是还是强烈建议大家使用如下的适配方式:

 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { 
// Android P or above
} else {
// below Android P
}

严格按照上面的适配方案,扫描工具就不会再扫描出此处的非 SDK API 调用,我们也无需每次都去确认所有非 SDK API 调用处都加了保护逻辑。
当然如果第三方库没有适配也没有近期适配的意向,目前有两种方法:第一种是屏蔽入口;第二种是反编译 SDK,在关键地方加上适配代码;

  1. Android 官方库调用到了非 SDK API 接口;

    没错!Android 官方库也会被扫描出非 SDK API 调用,针对这种情况,需要分情况讨论:

该 API 调用查看 v7 support 包源码可以发现已经被 try-catch 住了,测试了相关类也可以正常运行,而且在适配过程中升级 rc 版本的 support-v7 包会导致应用编译不过,所以目前 QQ 音乐暂时认定无需升级到最新版本的 support-v7。除上面介绍的特殊情况之外还是建议更换最新版本的官方 SDK。

三、电源管理改进

3.1 应用待机群组

Android P 上对电源管理又做了一系列的改进措施,不管应用 targetApi 版本是否已经升级到 P,系统都会依据应用最近的使用时间和频率来给应用进行待机分组,然后根据应用所属群组限制应用可以访问的资源,目前总共有五类分组:

    1. 活跃:
      一般为正在使用或者在前台运行的应用,例如:

      • 应用启动一个 Activity;

      • 应用正在运行前台 Service;

      • 应用的同步适配器关联上了一个前台应用;

      • 用户点击了应用的一个通知;

系统不会对该类应用有任何的限制;

  1. 工作集:
    应用经常运行,但是当前未属于活跃状态就会被归属于工作集,该群组的应用在运行作业和触发闹钟方面会被施加轻度的限制;

  2. 常用:
    应用如果被定期使用,但不是每天的话就会被归到该工作群组。该群组的应用在运行作业和触发闹钟方面会被施加较强的限制,FCM 消息数量也会有相关限制;

  3. 极少使用:
    应用如果不经常使用就会被归到该工作群组,系统会对该群组应用运行作业、触发闹钟和接收高优先级别 FCM 的消息能力方面有严格的限制;

  4. 从未使用:
    安装但从未被使用过的应用会被归到该工作群组,该工作群组的应用会被施加极其严格的限制;

更加详细的表述可以参考官网:App Standby Buckets,不同群组的限制的详细表现见:Power management restrictions。系统会动态的将手机里面的应用分配到这五类群组里面,也会根据需要变化应用群组,同时借助了机器学习来将一个应用放到更合适的群组里。目前应用可以通过 UsageStatsManager.getAppStandbyBucket() 函数来获取当前所属的应用群组,借助这个结果来更好的提升自己的打开频率,同时可以借助此来模拟处于不同群组能否正常工作。另外,位于低电耗模式白名单中的应用不适用基于应用待机群组的限制。

3.2 省电模式改进

Android 9 对省电模式又做了很多改进,开启省电模式之后会有如下限制:

  1. 系统会更加积极的将应用置于待机模式,不管应用是否空闲;

  2. 后台执行限制将适用于所有应用,无论他们的 targetApi 是多少;

  3. 屏幕关闭时,位置服务可能被停用;

  4. 后台应用没有网络访问权限;

这里需要重点介绍一下后台执行限制,该限制于 Android O 版本引入,主要是为了优化 Android 在多应用多服务运行时,系统负载过大会杀死后台音乐播放等服务导致用户体验下降的问题,它默认只对 targetApi 大于等于 26 的应用生效。目前用户可以通过设置页面对任意应用施加后台执行限制,后台执行限制会对应用有两方面的影响:

  1. 后台服务限制:
    处于前台(可见、具有前台服务或者关联到前台应用)或临时白名单(处理高优先级 FCM、接收短信等广播或者执行通知的 PendingIntent)时,应用可以自由创建和运行前台与后台服务。 进入后台时,在一个持续数分钟的时间窗内,应用仍可以创建和使用服务,但是超过该时间之后再通过 startService 去启动一个服务就会抛出 java.lang.IllegalStateException: Not allowed to start service Intent 的错误,解决办法是使用 startForegroundService 或者 JobIntentService

  2. 广播限制:
    针对 Android O 和之上的应用无法继续在其清单中为隐式广播注册广播接收器。

四、Apache HTTP client 相关类找不到

将 compileSdkVersion 升级到 28 之后,如果在项目中用到了 Apache HTTP client 的相关类,就会抛出找不到这些类的错误。这是因为官方已经在 Android P 的启动类加载器中将其移除,如果仍然需要使用 Apache HTTP client,可以在 Manifest 文件中加入:

<uses-library android:name="org.apache.http.legacy" android:required="false"/>

或者也可以直接将 Apache HTTP client 的相关类打包进 APK 中。

除上面两种适配方式外,QQ 音乐目前采用了另外一种方式。在音乐项目中,我们已经将使用 Apache HTTP client 的模块单独抽离到了一个 module 中,所以暂时只需要保持 module 中的 compileSdkVersion 在 28 以下即可正常编译运行。

五、其余适配

4.1 前台 Service

在 Android P 中,如果 targeSdkVersion 升级到 28,使用前台 Service 必须要申请 FOREGROUND_SERVICE 权限,如果没有申请该权限,系统会抛出 SecurityException,该权限为普通权限,申请自动授予应用。

4.2 隐私安全保护

  1. Build.SERIAL 标识修改:在 Android P 中,对隐私保护又做了更加严格的要求。在某些应用中为了识别手机的唯一性可能会用到 Build.SERIAL 这个标识,但这个标识在 Android P 中已经被设置成了 UNKNOWN,所以会直接导致该功能出现异常。多进程 webview 信息访问限制:在 Android P 中为了提升系统的安全性,用户无法在多进程的 webview 中共享数据目录,该目录下存储的是一些 cookies、Http 缓存和其他一些永久、临时的缓存。当下不少应用会把 webview 放在另一个进程中打开以避免内存泄漏,但是他们 cookies 的设置往往还是在主进程中,所以开发者需要仔细排查自己的应用是否有这么使用,webview 相关运行是否正常等。

4.3 com.android.internal 包下某些类找不到

升级到 28 之后,应用编译后抛出 com.android.internal 包下面有些类找不到的异常,经过查找发现这些类已经从 SDK 中移除。针对这种情况目前有两种处理办法:

  1. 移除该类的调用逻辑;

  2. 在应用中新建一个同名类,将被移除类的所有代码逻辑复制到新建类中(必要时可能需要将被移除类相关类同时拷贝一份到应用中),然后将应用中所有相关 import 引用直接修改成新建类的包名引用即可;

Android P 实用新特性

一、HEIF 图片格式支持

HEIF(High Efficiency Image Format),高帧率图片格式,采用的是 HEVC 编码格式。苹果于 iOS11 版本开始支持该图片格式,而 Android 则是在 Android O MR1 版本开始支持 HEIF 静态图的软解码,在 P 版本上完全支持该格式的软编解码。HEIF 格式的压缩率是 JPEG 的 2.39 倍,同等大小质量的图片可节省 50% 的空间和网络传输流量,而且支持动图。HEIF 格式比起 GIF 格式来说有着更好的图片展示效果,所以 HEIF 格式图片的目标是用来代替 JPEG 成为主流的图片压缩格式。HEIF 格式图片的扩展名为 .heif 或者 .heic:

  HEIF WebP JPEG
最大尺寸 无上限 16383x16383 65535x65535
编码 HEVC VP8 JPEG
是否支持其他编码 YES NO NO
支持音频/文字 YES NO NO
支持多图片 YES YES NO
支持裁剪 YES NO NO
支持透明 YES YES NO
支持缩略图 YES NO YES
分块加载 YES NO NO

看上去很美好,但是目前还不是所有的 Android P 机型都会支持 HEIF 格式硬编解码,因为这需要特殊的硬件支持同时还需要缴纳一定的专利费,所以在编解码效率上就会有机型差异,同时 Android P 软编解码也只能支持静态 HEIF 格式图片。目前开发者可以通过版本来判断是否支持 HEIF 编解码,判断逻辑如下:

fun supportHEIF() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P

解码代码也很简单,支持将 HEIF 格式图片解码成 Bitmap 和 Drawable:

@TargetApi(28)
fun decodeHEIFDrawable(filePath: String): Drawable? {    
   if (!supportHEIF()) {        
       return null
   }    
   var source: ImageDecoder.Source = ImageDecoder.createSource(File(filePath))    
   return ImageDecoder.decodeDrawable(source)
} @RequiresApi(28)
fun decodeHEIFBitmap(filePath: String): Bitmap? {    
   if (!supportHEIF()) {        
      return null
   }    
   var source: ImageDecoder.Source = ImageDecoder.createSource(File(filePath))    
   return ImageDecoder.decodeBitmap(source)
}

另外扫描本地图片则继续使用 ContentResolver 即可,如果设备支持 HEIF 格式,系统会自动扫描上 HEIF 格式的图片:

var cursor : Cursor = getContext().getContentResolver().query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, null, null, null, null)

但是这样还远远没有适配完成,第三方应用适配 HEIF 格式图片有一个很困难的地方是本地虽然可以识别解码 HEIF 格式的图片,但是如果某个用户将其设置为头像上传到后台,后台将其下发给其他不支持 HEIF 图片格式解码的手机,这些手机就肯定有展示问题。解决这个问题目前有两种思路:

  1. 终端在上传之前将其转码成 JPEG 格式的图片,但是这样就根本没有充分利用到 HEIF 图片的高压缩率的优势;

  2. 在到达后端之后,后端将其转码成 JPEG 图片,同时保存一份 HEIF 和 JEPG,到时候根据用户是否可以解码 HEIF 下发不同格式图片。该方案可以充分利用 HEIF 的优点,但是大大增加了后端存储空间和开发工作量。

二、ImageDecoder

上面已经介绍到了 ImageDecoder 在解码 HEIF 图片中的应用,但是实际它的功能完全不仅于此,在 Android P 中它可以完全替代 BitmapFactory 和 BitmapFactory.Options 相关类。ImageDocoder 类可以通过字节数据、文件和 URI 来解码一张图片。用法和之前一样,首先通过 createSource 方法创建一个图片文件的 ImageDecoder.Source 对象,然后调用 decodeDrawable 或者 decodeBitmap 方法传入之前的 ImageDecoder.Source 对象就能生成图片的 Drawable 或者 Bitmap 对象引用。ImageDecoder 支持 PNG、JPEG、WEBP、GIF 和 HEIF 多种格式图片的解码,另外解码 GIF 或者 WEBP 格式图片得到的是一个 AnimatedImageDrawable 对象,AnimatedImageDrawable 类的工作原理和 AnimatedVectorDrawable类似,都是使用一个工作线程来解码,所以解码线程和显示线程互不干扰。AnimatedImageDrawable 用法也很简单:

var drawable: Drawable = ImageDecoder.decodeDrawable(source);
if (drawable is AnimatedImageDrawable){
   image.setImageDrawable(drawable)
   drawable.start()
}

ImageDecoder 除了基础的解码功能之外,还有很多非常实用的方法,比如通过设置 OnHeaderDecodedListener 就可以在解析图片之前获取到图片的宽高等信息,同时还可以根据需要设置采样率:

val listener = object : OnHeaderDecodedListener {    
   fun onHeaderDecoded(decoder: ImageDecoder, info: ImageInfo, source: Source) {
       decoder.setTargetSampleSize(2)
   }
}
val drawable = ImageDecoder.decodeDrawable(source, listener)

另外还可以通过 setPostProcessor 方法来添加一些自定义的效果,比如最常用的切圆角:

var drawable = ImageDecoder.decodeDrawable(source) { decoder, info, src ->
   decoder.setPostProcessor { canvas ->
       val path = Path()        
       path.setFillType(Path.FillType.INVERSE_EVEN_ODD)
       val width = canvas.getWidth()
       val height = canvas.getHeight()        
       path.addRoundRect(0, 0, width, height, 20, 20, Path.Direction.CW)
       val paint = Paint()
       paint.setAntiAlias(true)
       paint.setColor(Color.TRANSPARENT)
       paint.setXfermode(PorterDuffXfermode(PorterDuff.Mode.SRC))
       canvas.drawPath(path, paint)
       PixelFormat.TRANSLUCENT
   }
}

非常便捷。用法远不仅于此,有了 Canvas 对象,开发者完全可以发挥想象去实现自己想要的炫酷效果。另外如果解码的图片不完整或者包含错误,一般情况下会抛出 DecodeException,但是如果这个时候通过 setOnPartialImageListener 函数传递一个 OnPartialImageListener 对象,并且在 onPartialImage 函数中返回 true,则图片就会只展示解析成功的一部分而不会抛出 DecodeException

var drawable = ImageDecoder.decodeDrawable(source) { decoder, info, src ->
   decoder.setOnPartialImageListener { e: ImageDecoder.DecodeException ->
       true
   }
}

最新Android 9.0 系统机型已经上线,有测试需求的同学可前往WeTest官网免费体验一把。现在完成个人或企业实名认证,我们将免费赠送您额外测试福利!

 上图为WeTest云真机选择界面,可以自由选择所需机型及操作系统

WeTest提供深度兼容测试及远程调试服务。提供上千台真实手机,支持市面主流机型,覆盖真正目标用户。独家支持微信/QQ账号自动登录,一键提测,测试完成微信邮件即时通知。

点击:https://wetest.qq.com/cloud/help/AndroidP 抢先领略 Android P测试专区。

如果使用当中有任何疑问,欢迎联系腾讯WeTest企业QQ:2852350015

Android 9 适配怎么做? “QQ音乐”优化实录的更多相关文章

  1. QQ音乐Android客户端Web页面通用性能优化实践

    QQ音乐 Android 客户端的 Web 页面日均 PV 达到千万量级,然而页面的打开耗时与 Native 页面相距甚远,需要系统性优化.本文将介绍 QQ 音乐 Android 客户端在进行 Web ...

  2. 高仿手机QQ音乐之——Android带进度条的开关

    最新版的手机QQ音乐体验确实不错,发现首页播放按钮能够显示歌曲当前进度条.认为挺有新意.效果例如以下: 自己琢磨了下.能够用自己定义组件来实现,试着做了一下.效果例如以下: 整理了下思路.大概设计流程 ...

  3. Android自定义View,高仿QQ音乐歌词滚动控件!

    最近在以QQ音乐为样板做一个手机音乐播放器,源码下篇博文放出.今天我想聊的是这个QQ音乐播放器中歌词显示控件的问题,和小伙伴们一起来探讨怎么实现这个歌词滚动的效果.OK,废话不多说,先来看看效果图: ...

  4. 个人作业2:QQ音乐APP案例分析

    APP案例分析 QQ音乐 选择理由:毕竟作为QQ音乐九年的资深老用户以及音乐爱好者 第一部分 调研 1.第一次上手的体验    我算是很早期的QQ音乐的用户,用QQ音乐七八年,除了体验各方面还不错之外 ...

  5. QQ音乐的动效歌词是如何实践的?

    本文由云+社区发表 作者:QQ音乐技术团队 一. 背景 1. 现状 歌词浏览已经成为音乐app的标配,展示和动画效果也基本上大同小异,主要是单行的逐字染色的卡拉OK效果和多行的滚动效果.当然,我们也不 ...

  6. QQ音乐:React v16 新特性实践

    欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~ 本文由QQ音乐技术团队发表于云+社区专栏 自从去年9月份 React 团队发布了 v16.0 版本开始,到18年3月刚发布的 v16.3 版 ...

  7. android屏幕适配之精准适配

    (1554068430@qq.com)(android精准适配工具)近期这段时间项目要做适配,在网上方便的方法.后来依据http://blog.csdn.net/jdsjlzx/article/det ...

  8. 【收藏】Android屏幕适配全攻略(最权威的Google官方适配指导)

    来源:http://blog.csdn.net/zhaokaiqiang1992 更多:Android AutoLayout全新的适配方式, 堪称适配终结者 Android的屏幕适配一直以来都在折磨着 ...

  9. Android屏幕适配全攻略(最权威的官方适配指导)(转),共大家分享。

    Android的屏幕适配一直以来都在折磨着我们这些开发者,本篇文章以Google的官方文档为基础,全面而深入的讲解了Android屏幕适配的原因.重要概念.解决方案及最佳实践,我相信如果你能认真的学习 ...

随机推荐

  1. 十六、详述 IntelliJ IDEA 创建 Maven 项目及设置 java 源目录的方法

    Maven 是一个优秀的项目管理工具,它为我们提供了一个构建完整的生命周期框架.现在,就让我们一起看看如何利用 IntelliJ IDEA 快速的创建 Maven 项目吧! 如上图所示,点击Creat ...

  2. nodejs中如何连接mysql

    nodejs中如何连接mysql,下面给出一个小Demo. 第一步安装mysql模块npm install mysql 第二步导入mysql模块var mysql = require('mysql') ...

  3. hdu 1010(迷宫搜索,奇偶剪枝)

    传送门: http://acm.hdu.edu.cn/showproblem.php?pid=1010 Tempter of the Bone Time Limit: 2000/1000 MS (Ja ...

  4. NopCommerce 3.4中商品详情页面单选框、复选框的美化

    先上图给大家看看效果,点这里打开网站(后期可能会找不到这个商品,现在再测试阶段) 现在你能看到的这个页面中,尺寸.文本描述是单选框(属性是我乱写的名字),上门安装是复选框.效果就看到这里,请君跳过图片 ...

  5. Excel批量导入商品,遇到导入失败记录到另一个Excel中供下载查看

    /// <summary> /// EXCEL批量导入 /// </summary> /// <param name="filePath">文件 ...

  6. Gradle Goodness: Set Java Compiler Encoding

    If we want to set an explicit encoding for the Java compiler in Gradle we can use the options.encodi ...

  7. one or more listeners failed to start问题解决思路

    今日搭建一个web应用的时候总是遇到tomcat报错:one or more listeners failed to start. Full detail balabale....而且还没有其他提示, ...

  8. 关于Hibernate基于version的乐观锁

    刚刚接触SSH框架,虽然可能这个框架已经比较过时了,但是个人认为,SSH作为一个成熟的框架,作为框架的入门还是可以的. 马马虎虎学完了Hibernate的基础,总结一点心得之类的. 学习Hiberna ...

  9. JavaScript 中 Property 和 Attribute 的区别详解

    property 和 attribute非常容易混淆,两个单词的中文翻译也都非常相近(property:属性,attribute:特性),但实际上,二者是不同的东西,属于不同的范畴. property ...

  10. vue 图片压缩 基于cli3 配置

    chainWebpack: config => { const imagesRule = config.module.rule('images') imagesRule .use('image- ...