lindroid

lindroid 查看完整档案

珠海编辑深圳大学  |  应用化学 编辑  |  填写所在公司/组织填写个人主网站
编辑

Android初级魔法师

个人动态

lindroid 收藏了文章 · 2020-12-23

学了七年Android,连【架构师筑基必备技能】都不知道有什么,你就废了!

前言

近几年,Android 开发的套路日趋成熟,越来越多的 Android 工程师获得了「高级」的称号,也有不少人在参与公司的 App 从无到有再到火爆的整个开发过程中,顺理成章地拿到了 Leader 职位。

但对于自己的水平,多数人却并不满意,甚至有不少人觉得自己的水平被同事和老板「高估」了。

市场真正需要的所谓「高级架构师」到底需要具备什么条件?在此和大家探讨一下 Android 工程师在当下这个时代该如何真正的成为高级架构师

一、什么是架构师

笼统的说,比高级工程师技术面更广,学习主动性更强,更能紧跟时代发展的就是Android架构师。Android架构师技术深度和广度都要兼顾,需要时间的积累和经验的沉淀,这里给大家看一张大厂的薪资与级别成长路线图。

二、成为架构师必备技能

思维脑图

基础知识

Java语言进阶
  • 泛型与注解在Retrofit中的应用
  • 多线程与Java File IO操作应用实战
  • Rxjava原理分析
  • JVM与内存泄漏原理解析
  • ClassLioader与反射在Hook中应用
  • 动态代理机制在源码中的应用

为什么要学习JAVA ? 因为Android应用是由Java语言进行开发的,SDK也是由Java语言编写,所以我们要学习java语言。另外,虽说kotlin语言得到了Android官方的热推,但是kotlin也是编译成了java语言再运行的。对于Android来说,只要SDK没有用kotlin重写,那么Java语言是都需要学习的。而且Android apk的后台服务器程序大概率是java语言构建,所以学习java也是一种必然。

高级UI与FrameWork

  • UI绘制原理
  • 动画原理
  • 事件响应机制
  • 屏幕适配
  • FrameWork源码解析
  • 相机适配

我们需要从新的角度去分析这些知识点,深入研究他们,要学习源码,模仿源码,然后再hook源码,这样才能说自己懂这块的知识。这些都是做Android开发,做高级工程师的基础。

360° Android app全方位性能调优

  • 从事件
  • 从内存
  • 卡顿调优
  • APP保活
  • 内存优化
  • 高性能编程实战
  • OOM原理解析

一个app的性能好不好我们需要从两个层面努力。

第一个层面:从写代码的时候就需要注意,让自己的代码是高性能高可用的代码,这个过程是书写高性能代码;

第二个层面:对已经成型的代码通过工具检查代码的问题,通过检查到的问题来指导我们进行代码的删改,这个过程被称为调优。

Android前沿技术

  • 热修复/热更新
  • 组件化/插件化
  • RxJava深入研究
  • 图片与网络架构
  • Google I/O大会技术
  • Kotlin项目实操

NDK 模块开发

  • C/C++基础
  • JINI编程基础
  • 图像处理与热修复应用
  • 音视频开发
  • OpenCV人工智能
  • OpenCL 图像绘制

微信小程序

  • 小程序架构介绍
  • UI界面开发
  • 高级API实操
  • 微信对接实战
  • 任务清单项目
  • 电影榜单项目

混合开发

  • Dart语法
  • Flutter线程运行模型
  • Flutter与Native通信架构
  • Flutter内存调优
  • Flutter项目实战

【架构师筑基必备技能】思维脑图

【架构师筑基必备技能】学习笔记

Android应用是由Java语言进行开发的,SDK也是由Java语言编写,所以我们要学习java语言。另外,虽说kotlin语言得到了Android官方的热推,但是kotlin也是编译成了java语言再运行的。对于Android来说,只要SDK没有用kotlin重写,那么Java语言是都需要学习的。而且Androidapk的后台服务器程序大概率是java语言构建,所以学习java也是一种必然。

那么Java中哪些东西是我们Android程序员需要学习的呢?由于Android程序员习惯了CV代码块,所以与Android中比较相关的稍微比较难的Java基础几乎都是一个门槛,像泛型,多线程,反射,JVM,JavaIO,注解,序列化等,都是被CV的对象,而程序员是不懂原理的,具体内容如脑图所示。
目录

第一章深入Java泛型
一、泛型的作用与定义
1.1泛型的作用

二、通配符与嵌套

  • 2.1通配符
  • 2.2泛型嵌套

三、泛型的上下边界

  • 3.1 < extends E >
  • 3.2< super E >

四、RxJava中深入理解泛型

  • 4.1 响应式编程:
  • 4.2观察者模式:
  • 4.3RxJava是对观察者模式的一种高级运用


第二章注解深入浅出

  • 一、注解(ANNOTATIONS)
  • 二、元注解
  • 三、自定义注解
  • 四、 默认参数值(DEFAULT PARAMETER VALUES)
  • 五、APT
  • 六、插桩
  • 七、反射
  • 八、Retrofit中的注解


第三章并发编程

  • 一、基础概念
  • 二、 线程之间的共享
  • 三、 线程间的协作
  • 四、线程池的使用


第四章数据传输与序列化

  • 一、Serializable原理
  • 二、Parcelable的原理和使用方法
  • 三 json

第五章 Java虚拟机原理

  • 一、垃圾回收
  • 二、内存分配策略
  • 三、Dalvik虚拟机


第六章反射与类加载

  • 一、反射
  • 二、类加载


第七章高效IO

  • 01 基于字节的IO操作
  • 02 基于字符的IO操作
  • 03 IO常用类
  • 04 File类

尾声

最后,我再重复一次,如果你想成为一个优秀的 Android 开发人员,请集中精力,对基础和重要的事情做深度研究。

对于很多初中级Android工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。 整理的这些架构技术希望对Android开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。

最后想要拿高薪实现技术提升薪水得到质的飞跃。最快捷的方式,就是有人可以带着你一起分析,这样学习起来最为高效,所以为了大家能够顺利进阶中高级、架构师,我特地为大家准备了一套高手学习的源码和框架视频等精品Android架构师教程,保证你学了以后保证薪资上升一个台阶。

当你有了学习线路,学习哪些内容,也知道以后的路怎么走了,理论看多了总要实践的。
以上进阶BATJ大厂学习资料可以免费分享给大家,需要完整版的朋友,点这里可以看到全部内容

如果需要PDF版本可以在群文件夹里,自行领取!

进阶学习视频

附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题 (含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)

查看原文

lindroid 发布了文章 · 2019-11-10

前端入门一之网页与浏览器

1、网页

1.1 网页的概念

网页是构成网站的最基本元素,通常有图片、视频、文字等元素。网页一般都是以.html或.htm后缀结尾的文件,即HTML文件。HTML文件可以通过浏览器阅读。

1.2 HTML概念

HTML全称HyperText Mark-up Language,即超文本标记语言。它是一种标记语言而非编程语言,通过标记符号来标记要显示的网页中的各个部分。
译名: 超文本标记语言

2、浏览器

常用浏览器

常用的浏览器有IE、火狐(Firefox)、谷歌(Chrome)、Safari和Opera等。我们平时称为五大浏览器。目前谷歌浏览器的份额最大。

2.1 浏览器内核

浏览器内核又可以分成两部分:渲染引擎(layout engineer 或者 Rendering Engine)和JS引擎。

渲染引擎:它负责取得网页的内容(HTML、XML、图像等等)、整理讯息(例如加入 CSS 等),以及计算网页的显示方式,然后会输出至显示器或打印机。浏览器的内核的不同对于网页的语法解释会有不同,所以渲染的效果也不相同。

JS引擎:则是解析Javascript语言,执行javascript语言来实现网页的动态效果。

最开始渲染引擎和 JS 引擎并没有区分的很明确,后来 JS 引擎越来越独立,内核就倾向于只指渲染引擎。有一个网页标准计划小组制作了一个 ACID 来测试引擎的兼容性和性能。内核的种类很多,如加上没什么人使用的非商业的免费内核,可能会有10多种,但是常见的浏览器内核可以分这四种:Trident、Gecko、Blink、Webkit:

  • Trident:IE浏览器、360极速浏览器
  • Gecko:火狐浏览器
  • Webkit:Safari(苹果浏览器)
  • Blink:Chrome和Opera。Blink其实是WebKit 的分支

Android手机使用Webkit内核较多,大部分国产浏览器也是基于webkit二次开发。

3、Web标准

3.1 为何需要Web标准?

浏览器的内核不同,工作原理和解析也就不同,导致显示出来的网页会有差别。

3.2 Web标准的构成

Web标准由W3C和其他标准化组织制定的一系列标准的集合。主要包括结构(Structure)、表现(Presentation)和行为(Behavior)三个方面。

标准说明位置
结构用于对网页元素进行整理和分类Html文件
表现用于设置网页元素的版式、颜色、大小等外观样式CSS文件
行为指网页模型的定义及交互的编写Javascript文件
查看原文

赞 0 收藏 0 评论 0

lindroid 发布了文章 · 2019-11-10

Kotlin之在Gradle中无参(no-arg)编译器插件的使用

1、前言

最近在用Kotlin+Spring Boot写一个后端项目,实体类习惯性地用了Kotlin中的data class,但是Spring要求要有一个无参的构造函数,否则可能会抛出java.sql.SQLDataException。要使data class能够生成一个无参的构造函数,有两种方法可以做到:

1.给data class的构造函数中的每一个参数都赋上默认值。比如:

data class User(
    @TableId(value = "id", type = IdType.AUTO)
    var id: Int?=-1,
    var userName: String?=null,  //用户名
    var age: Int?=null,  //年龄
    var password: String?=null,  //密码
    var name: String?=null,  //姓名
    var email: String?=null   //邮箱
) : Serializable

2.使用无参编译器插件no-arg。下面我们就来看看这种方法。

2、具体步骤

2.1 添加插件依赖

首先需要在工程的build.gradle中添加no-arg插件的依赖,版本跟Kotlin的版本一致:

plugins {
       ……
    id "org.jetbrains.kotlin.plugin.noarg" version "1.3.41"
}

我用的是plugins的形式,如果你使用的是buildscript 块的话就可以这样添加:

buildscript {
    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-noarg:1.3.41"
    }
}

apply plugin: "kotlin-noarg"

2.2 指定无参注解列表

说实话,在看官方文档时这一步并不好理解,这里只描述我经过自己的实践之后得出的经验。首先创建一个注解类,类名我们可以定为NoArg

annotation class NoArg

然后最关键一步来了,回到工程的build.gradle文件,添加无参注解列表:

noArg {
    annotation("com.lindroid.projectname.annotation.NoArg")
}

annotation中的路径就是我们创建的NoArg注解类所在的包目录。路径一定要写好,不要出错!此时无参插件就已经配置好了。我们可以在data class前面添加@NoArg注解,这样编译器就能为其生成一个无参构造函数。使用示例如下:

@NoArg
data class User(
    @TableId(value = "id", type = IdType.AUTO)
    var id: Int?,
    var userName: String?,  //用户名
    var age: Int?,  //年龄
    var password: String?,  //密码
    var name: String?,  //姓名
    var email: String?   //邮箱
) : Serializable

2.3 kotlin-jpa中的无参注解

如果你的项目中已经添加了kotlin-jpa插件,那么基本上就不必单独添加无参插件了。kotlin-jpa对无参插件做了包装,当你使用 @Entity @Embeddable @MappedSuperclass这几个注解时,都会默认支持无参注解的。

3、参考文章

Kotlin官方文档

查看原文

赞 2 收藏 2 评论 0

lindroid 赞了文章 · 2019-10-30

一篇文章搞定Github API 调用 (v3)

对于常用Github的用户来说,经常有一些自动化的需求。比如我的需求是定时备份Github的issues和comments到本地。以下为Github的API的使用参考。

v3版API的文档链接
v3版API的官方教程

基本访问路径 (Root Endpoints)

一开始读文档的时候,照着它的事例直接在命令行里curl,或者在InSomnia或Postman软件里访问,都完美显示200状态。可是一旦把链接里改写成自己的用户名就各种显示404无页面。还以为是授权问题,然后在页头HEADER中按照各种方式试了username和token密钥,都没用还是404。结果发现,原来不是方法的问题,纯粹是链接地址没写对!
实际上只是读取的话,完全不用任何授权,可以在命令行、Insomnia、网页等各种情况下直接输入链接访问任何人的所有公开信息。
然后对照官方路径列表Root Endpoints得到的链接,好像怎么访问都不对。反而在Stackoverflow中看到的一个链接,顺藤摸瓜自己发现了各种正确的访问路径,总结如下:

  • 首先!访问的链接最后不能有/。如https://api.github.com/users/solomonxie是可以访问到我个人信息的,但是https://api.github.com/users/solomonxie/就不行了,唯一不同是多了一个/.
  • 其次!不同于一般URL访问,GIthub的API访问链接是区分大小写的!
  • 个人主要信息。 https://api.github.com/users/用户名,得到数据如下图:

image

  • 个人所有repo。https://api.github.com/users/用户名/repos。会得到一个repo的JSON格式列表。
  • repo详细信息。https://api.github.com/repos/用户名/仓库名。repo的路径就开始和个人信息不同了。
  • 获取某个repo的内容列表。https://api.github.com/repos/solomonxie/gists/contents,注意这只会返回根目录的内容。
  • 获取repo中子目录的内容列表。https://api.github.com/repos/solomonxie/gists/contents/目录名。一定要注意这里一定要完全遵循原文件名的大小写,否则无法获得信息。如果是更深层的内容,则在链接列按照顺序逐级写上目录名称。
  • 获取repo中某文件信息(不包括内容)。https://api.github.com/repos/solomonxie/gists/contents/文件路径。文件路径是文件的完整路径,区分大小写。只会返回文件基本信息。
  • 获取某文件的原始内容(Raw)。1. 通过上面的文件信息中提取download_url这条链接,就能获取它的原始内容了。2. 或者直接访问:https://raw.githubusercontent.com/用户名/仓库名/分支名/文件路径
  • repo中所有的commits列表。https://api.github.com/repos/用户名/仓库名/commits
  • 某一条commit详情。https://api.github.com/repos/用户名/仓库名/commits/某一条commit的SHA
  • issues列表。https://api.github.com/repos/用户名/仓库名/issues
  • 某条issue详情。https://api.github.com/repos/用户名/仓库名/issues/序号。issues都是以1,2,3这样的序列排号的。
  • 某issue中的comments列表。https://api.github.com/repos/用户名/仓库名/issues/序号/comments
  • 某comment详情。https://api.github.com/repos/用户名/仓库名/issues/comments/评论详情的ID。其中评论ID是从issues列表中获得的。

查询参数 (Parameters)

如果在上面基本链接中加入查询条件,那么返回的数据就是filtered,过滤了的。比如要求只返回正在开放的issues,或者让列表数据分页显示。常用如下:

  • 分页功能。格式是?page=页数&per_page=每页包含数量

https://api.github.com/users/solomonxie/repos?page=2&per_page=3

  • issues状态。格式是?state=状态

https://api.github.com/repos/solomonxie/solomonxie.github.io/issues?state=closed

权限认证 Authentication

首先需要知道都是,到此为止之前所有都查询都是不需要任何权限的,给个地址就返回数据,全公开。
但是创建文件、更新、删除等就是必须用自己的账号"登录"才能实现的。所以为了下面的增删改做准备,需要先看一下权限问题。
官网虽然写的很简答,不过如果不熟悉API的话还是不能马上就理解。

常用的认证方法有三种,Basic authentication, OAuth2 token, OAuth2 key/secret
三种方法效果一样,但是各有其特点和方便之处。选哪种就要看自己哪种方便了。

认证方法一:Basic authentication

这种最简单,如果是用curl的话,就:

curl -u "用户名:密码" https://api.github.com

如果是用Insomnia等api调试工具的话,直接在Auth选项栏里选Basic Auth,然后填上用户名密码即可。

认证方法二:OAuth2 token

关于token

这种token方式,说实话如果不是操作过API或深度了解REST的话,是很难理解的东西。
说白了就是第二个密码,你既不用到处泄露自己的用户名密码,又可以专门给这个"第二密码"设置不同需要的权限,如有的只可读有的还可以写等。而且这个“第二密码”是既包括用户名又包括密码功能的,全站只此一个绝对不会和别人重复。初次之外,你还可以设置很多个token,也就是第三、第四、第五...密码。很方便。

设置token方法

就位于github个人账号设置->开发者设置->个人token里。创建一个新token时,可以选择具体的权限,创建成功时一定要复制到本地哪里保存,只会让你看见一次,如果忘记的话就需要重新生成(其实丢了也不算麻烦)。
image

另外!注意:

token字符串不能存储在github的repo中,经过测试,一旦提交的文件中包含这个token字符串,那么github就会自动删除这个token -_-! 我用了很久才明白过来,创建的Personal Access Token总是自动消失,还以为是有时限的。

用token通过权限认证

有两种传送方法,哪种都可以:

  1. 作为url中的参数明文传输:
curl https://api.github.com/?access_token=OAUTH-TOKEN
  1. 作为header中的参数传输:
curl -H "Authorization: token OAUTH-TOKEN" https://api.github.com

如果不是用curl而是Insomnia测试的话,和上面basic auth是大同小异的,很容易操作就不复述了。
到此为止,权限认证就算搞清了,而且也实际验证过有效了。强烈建议用insomnia工具操作,有GUI界面方便理解,成功后再转为curl或python等程序语言。

认证方法三:OAuth2 key/secret

这个是除了Personal Access Token之外的另一种好用的方法,即创建自己的OAuth app,然后得到一对client_idclient_secret。如下:
image
image
得到这两个值之后,直接在访问任何api的url连接后面显性加上这两个参数即可完成认证,如:
https://api.github.com/users/yourusername?client_id=YOUR-CLIENT-ID&client_secret=YOUR-CLIENT-SECRET
但是:

目前这种认证方式不支持查询以外的操作,也就是只能GET获取某些api信息,不能执行request里的任何PUT/PATCH/DELETE操作。

创建新文件 Create content

Contents操作 官方文档
  • 传输方法:PUT
  • 访问路径:https://api.github.com/repos/用户名/仓库名/contents/文件路径
  • JSON格式:
{
  "message": "commit from INSOMNIA",
  "content": "bXkgbmV3IGZpbGUgY29udGVudHM="
}

JSON填写如下图:
image

  • 注意:1.必须添加权限验证(上面有写) 2. 数据传送格式选择JSON 3. 文件内容必须是把文件整体转为Base64字符串再存到JSON变量中 4. 文件路径中如果有不存在的文件夹,则会自动创建

起初不管怎么尝试都一直报同样都错误,400 Invalid JSON,如下图:
[图片上传失败...(image-884e71-1527903120996)]

最后发现原来是犯了很小很小都错误才导致如此:
image
原来,我的token看似是正常的,唯独错误的是,多了一个空行!也就是说,标明都是invalid JSON,结果没注意竟然是invalid Token!

增加文件成功后返回的消息:
image

更新文件 Update content

主要这几点: 1. 传送方式用PUT 和创建文件一样 2. 需要权限验证,3. 传输内容数据用JSON 4. 需要指定该文件的SHA码 4. 路径和访问content时一样 5. 文件内容必须是把文件整体转为Base64字符串再存到JSON变量中
  • 传输方法:PUT
  • 访问路径:https://api.github.com/repos/用户名/仓库名/contents/文件路径
  • JSON格式:
{
  "message": "update from INSOMNIA",
  "content": "Y3JlYXRlIGZpbGUgZnJvbSBJTlNPTU5JQQoKSXQncyB1cGRhdGVkISEhCgpJdCdzIHVwZGF0ZWQgYWdhaW4hIQ==",
  "sha": "57642f5283c98f6ffa75d65e2bf49d05042b4a6d"
}
  • 注意:必须指定该文件的SHA码,相当于文件的ID。

SHA虽然是对文件的唯一识别码,相当于ID,但是它是会随着文件内容变化而变化的!所以必须每次都重新获取才行。

至于获取方式,验证后发现,目前最靠谱的是用前面的get content获取到该文件的信息,然后里面找到sha

对上传时的JSON格式另有要求,如果没有按照要求把必填项输入,则会出现422错误信息:
image

或者如果用错了SHA,会出现409错误消息:
image

如果正确传送,就会显示200完成更新:
image

删除文件 Delete content

  • 传输方法:DELETE
  • 访问路径:https://api.github.com/repos/用户名/仓库名/contents/文件路径
  • JSON格式:
{
  "message": "delete a file",
  "sha": "46d2b1f2ef54669a974165d0b37979e9adba1ab2"
}

删除成功后,会返回200消息:
image

增删改issues

如果做过了上面文件的增删改,这里大同小异,不同的访问路径和JSON的格式而已。唯一不同的是,issues是不用把内容转为Base64码的。

参考链接:github官方文档

增加一条issue

  • 传输方法:POST
  • 访问路径:https://api.github.com/repos/用户名/仓库名/issues
  • JSON格式:
{
  "title": "Creating issue from API",
  "body": "Posting a issue from Insomnia"
}
  • 注意:issue的数据里面是可以加label,milestone和assignees的。但是必须注意milestone和assignees必须是已有的名次完全对应才行,否则无法完成创建。

更改某条issue

  • 传输方法:PATCH
  • 访问路径:https://api.github.com/repos/用户名/仓库名/issues/序号
  • JSON格式:
{
  "title": "Creating issue from API ---updated",
  "body": "Posting a issue from Insomnia \n\n Updated from insomnia.",
  "state": "open"
}
  • 注意:如果JSON中加入空白的labels或assignees,如"labels": [],作用就是清空所有的标签和相关人。

锁住某条issue

不允许别人评论(自己可以)
image

  • 传输方法:PUT
  • 访问路径:https://api.github.com/repos/用户名/仓库名/issues/序号/lock
  • JSON格式:
{
  "locked": true,
  "active_lock_reason": "too heated"
}
  • 注意:active_lock_reason只能有4种值可选:off-topic, too heated, resolved, spam,否则报错。

另外,成功锁住,会返回204 No Content信息。

解锁某条issue

  • 传输方法:DELETE
  • 访问路径:https://api.github.com/repos/用户名/仓库名/issues/序号/lock
  • 无JSON传输

增删改comments

参考官方文档

增加comment

  • 传输方法:POST
  • 访问路径:https://api.github.com/repos/用户名/仓库名/issues/序号/comments
  • JSON格式:
{
  "body": "Create a comment from API"
}

更改comment

  • 传输方法:PATCH
  • 访问路径:https://api.github.com/repos/用户名/仓库名/issues/comments/评论ID
  • JSON格式:
{
  "body": "Create a comment from API \n\n----Updated"
}
  • 注意:地址中,issues后不用序号了,因为可以通过唯一的评论ID追查到。查看评论ID的方法,直接在上面查询链接中找。

删除comment

  • 传输方法:DELETE
  • 访问路径:https://api.github.com/repos/用户名/仓库名/issues/comments/评论ID
  • 无传输数据
查看原文

赞 76 收藏 57 评论 23

lindroid 赞了文章 · 2019-10-29

RESTful API风格

任何对外提供服务的应用都绕不开API的发布,你的API可能是这个样子:

www.baidu.com/api/v1/getUsername?id=1
或者
www.baidu.com/app/index.php?r=info/user/getUsername&id=1

如果答案是YES,那么用句很潮的话,我们可以说你的API很不RESTful

API风格

首先要说,不管你的API属于哪种风格,只要能够满足工作需要,就足够了。API格式并不存在绝对的标准,只存在不同的设计风格。

API设计包含两部分:请求和响应。
请求包含:请求URL、请求方法、请求头部信息等。
响应包含:响应体和响应头部信息。

一个请求URL的组成:

https://www.baidu.com:443/app/index.php?r=info/user/getUsername&id=1#tag
# 请求方法:GET
# 请求协议:protocal: https
# 请求端口:port: 443
# 请求域名:host: www.baidu.com
# 请求路径:pathname: /app/index.php
# 查询字符串:search: r=info/user/getUsername&id=1
# 页面书签:hash: #tag

根据URL组成部分:请求方法、请求路径和查询字符串,我们有几种常见的API风格。比如当删除id=1的作者编写的类别为2的所有文章时:

# 纯请求路径
GET www.baidu.com/articles/delete/authors/1/categories/2
# 一级使用路径,二级以后使用查询字符串
GET www.baidu.com/articles/delete/author/1?category=2
# 纯查询字符串
GET www.baidu.com/deleteArticles?author=1&category=2
# RESTful风格
DELETE www.baidu.com/articles?author=1&category=2

前面三种都是GET请求,主要的区别在于多个查询条件时怎么传递查询字符串,有的通过使用解析路径,有的通过解析传参,有的两者混用。同时在描述API功能时,可以使用article/delete,也可以使用deleteArticles

而第四种RESTful API最大的区别在于行为动词DELETE的位置,不在url里,而在请求方法中。

RESTful设计风格

REST(Representational State Transfer 表现层状态转移)是一种软件架构风格、设计风格,而不是标准。主要用于客户端和服务端的API交互,它的优势在于更简洁、清晰、可读性强。

REST的主要特点是:

  1. 客户端和服务端是无状态的,任意请求都包含了处理需要的所有信息,同时服务端的变更对客户端是透明的。
  2. 资源用URI(Universal Resource Identifier 通用资源标识)来表示,通过请求方法来指明操作类型:GET/POST/PUT/DELETE

无状态是最重要的特性,大部分的Client-Server,Brower-Server也都能很好的支持。第2点则是我们需要去关注的。

REST URL

表现层状态转移(REST)是一个大的概念,也不好理解,我的理解就是在URL的定义上能够清楚的表示其请求含义。它的核心思路就是动词+宾语:

DELETE www.baidu.com/api/v1/articles?author=1&category=2
# 动词 DELETE,表示要进行的操作是删除,即状态转移
# 宾语 articles,表示文章这种资源URI,推荐用复数,即表现层
# 定语 author=1&category=2,表示具体是什么资源
# 版本 api/v1,这个能够帮助我们更好的进行版本的升级,同时不影响原有的请求

我们用articles来指明资源类型,用author=1&category=2来在确定具体的资源,用GET/POST/PUT/DELETE来指明要进行的增删改查操作。

这种风格相比于/api/deleteArticles,声明的方法数量只有1/4,各个接口之间的风格也更加统一,调用起来更加清晰。

同时这种方法能够保证浏览器常规能访问的到的GET请求对资源来说是安全的。比如别人给我们发送了一个链接:/deleteArticle?id=1,我们绝对不会希望随便点击一下就会跳转到浏览器访问,然后误删了这个资源。

响应状态码

除了请求URL有要求,在响应时也有一些要求。我们知道状态码有:

  • 1xx: 请求相关信息
  • 2xx: 操作成功
  • 3xx: 重定向
  • 4xx: 客户端错误
  • 5xx: 服务端错误

所有的状态码 共有100多种,涵盖了绝大多数常见的网络场景,而且是网络通用的定义规范和解释,因此我们应该尽可能使用精确的状态码。

当前有一些不太恰当的做法是即便请求出错,依然使用200状态码,转而将错误信息包含在响应体中,或者响应使用纯文本导致无法和正常数据一样标准化解析,如:

httpCode: 200
response: {
    status: 1,
    msg: '请求无权限'
}
# 或:
httpCode: 200
response: '请求无权限'

但我们其实可以用更精确的状态码403代表权限不足,所以合适的响应结果应该是:

# 异常时
httpCode: 403,
httpErrorMsg: '请求无权限'
# 正常时:
httpCode: 200,
response: {
    status: 0,
    msg: '',
    data: []
}

总结

RESTful是一种API设计风格,并不是一种强制规范和标准,它的特点在于请求和响应都简洁清晰,可读性强。

我们主要关注几点:

  1. 无状态
  2. 请求URL = 动作(GET/POST/PUT/DELETE)+ 资源
  3. 响应使用精确的状态码和JSON格式数据

参考资料

  1. RESTful API 最佳实践: http://www.ruanyifeng.com/blo...
  2. RESTful: https://baike.baidu.com/item/...
  3. 怎样用通俗的语言解释REST,以及RESTful?: https://www.zhihu.com/questio...
  4. Status Code Definitions: https://www.w3.org/Protocols/...
查看原文

赞 4 收藏 2 评论 0

lindroid 赞了文章 · 2019-10-11

Spring Boot学习笔记(二)简单CRUD实现

开发配置

依赖包准备

这里选用mysql作为数据库,需要用到的包有三个,需要将相关依赖写入pom.xml,如下:

  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
  </dependency>
    
  <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
  </dependency>

  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
  </dependency>

数据库连接设定

在src/main/resources/application.properties文件中,加入以下配置信息:

spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.username=test
spring.datasource.password=test
spring.datasource.tomcat.max-idle=10
spring.datasource.tomcat.max-wait=10000
spring.datasource.tomcat.min-idle=5
spring.datasource.tomcat.initial-size=5

其中test是数据库名称,username和password可自行设置和修改

实体定义

我们将要操作的实体很简单,只有一个id属性和一个name属性。定义如下:

@Entity
@Table(name="T_USER")
public class User {

    /**
     * 用户ID
     */
    @Id
    @GeneratedValue
    private int id;
    
    /**
     * 用户名
     */
    private String name;

    // GETTER和SETTER此处省略...
}

<说明>

  1. @Entity注解,表明了这是一个Entity,可以使用默认的ORM规则,即class名即为要更新的数据表名
  2. @Table注解,可以指定这个Entity要更新的数据表名,也不使用默认的ORM规则。
  3. @Id注解,表明了这个数据表的主键ID,这个主键可以是AUTO_INCREMENT类型的数据。

备忘
在测试中,此处的id类型使用Integer会报错,原因尚不清楚,使用int则无问题。

路由设置

以简单的User对象为例,我们来实现一个简(chou)单(lou)的的CRUD处理

Restful风格

对于简单的增删改查功能,一般的restful接口定义如下:

GET    /user        # 取得user列表
POST   /user        # 提交user
PUT    /user        # 更新user
PATCH  /user        # 更新user(个别属性)
DELETE /user/:id    # 删除user

路由设置

对于纯数据的Java后端架构来讲,MVC中少了View,但M和C仍是必不可少的。Model用于管理数据实体及持久化相关的处理,Controller则是整个业务的核心。

首先,对于user的每个接口,我们在controller中定义对应请求接收方法:

@RestController
@RequestMapping("/user")
public class UserController {
    
    @Autowired
    private UserService userService;

    @GetMapping("")
    public ResponseEntity<?> getList() {
        return new ResponseEntity<Object>(userService.getUserList(), HttpStatus.OK);
    }
    
    @PostMapping("")
    public ResponseEntity<?> create(@RequestBody User user) {
        return new ResponseEntity<User>(userService.save(user), HttpStatus.CREATED);
    }

    @PatchMapping("")
    public ResponseEntity<?> update(@RequestBody User user) {
        return new ResponseEntity<User>(userService.save(user), HttpStatus.ACCEPTED);
    }
    
    @DeleteMapping("/{id}")
    public ResponseEntity<?> remove(@PathVariable("id") int id) {
        userService.remove(id);
        return new ResponseEntity<User>(HttpStatus.ACCEPTED);
    }
}

<说明>

  1. @RestController注解,表明这个controller专门用于Restful服务,返回值不是传统的WEB数据。
  2. @RequestMapping注解,表明访问的相对路径为“/user"
  3. @GetMapping、@PostMapping、@PatchMapping和@DeleteMapping分别对应了GET、POST、PATCH和DELETE请求,这四个方法将分别接收对应访问路径上对应方法的HTTP请求。它们也可用@RequestMapping(value="", method=RequestMethod.POST(或者GET、PATCH等)来替代。
  4. @PathVariable用于取得访问路径中的参数
  5. ResponseEntity用于返回处理结果与错误码

上面通过UserService的调用,实现了user的CRUD操作。Spring通过@Autowired注解,会自动创建UserService的实例。

CRUD操作

通过UserService的相关操作,我们可以很容易的对user表进行CRUD操作。代码如下:

@Service
public class UserService {

    @Autowired
    private UserRepository userRepo;
    
    public Iterable<User> getUserList() {
        return userRepo.findAll();
    }
    
    public User save(User user) {
        return userRepo.save(user);
    }
    
    public void remove(int id) {
        userRepo.delete(id);
    }
}

<说明>

  1. 通过@Service注解,Spring会自动实例化到UserController中
  2. Service中注解了@Autowired的实例userRepo,实际上是继承了CrudRepository的一个接口。Repository在Spring的概念体系中是类似于传统DAO的实体,一个Repository对应一个数据表的操作。
  3. UserService中使用的findAll、save和delete方法,均是CrudRepository中预定义的方法。

对于UserRepository来讲,仅仅是一个简单的继承了CrudRepository的接口。代码如下:

public interface UserRepository extends CrudRepository<User, Integer> {}

关于Repository的说明,可以看这篇文章pring Boot学习笔记(三)Repository的使用

结果确认

简单接口测试可使用POSTMAN进行,结果如下:

取得用户列表
clipboard.png

插入2条数据
clipboard.png
clipboard.png

取得用户列表
clipboard.png

更新用户
clipboard.png

删除用户
clipboard.png

取得用户列表
clipboard.png

待改进事项

目前只是实现了一个简单的CRUD操作,就这个简单流程来讲,以下几点尚欠缺:

  1. 返回值需要有结构化的设计
  2. 分页和排序功能也有欠缺。
  3. 缺少自动化测试
查看原文

赞 4 收藏 4 评论 1

lindroid 收藏了文章 · 2019-08-13

彻底理解 Android 中的阴影

如果我们想创造更好的 Android App,我相信我们需要遵循 Material Design 的设计规范。一般而言,Material Design 是一个包含光线,材质和投影的三维环境。如果我们想要在 App 的开发过程中,跟随 Material Design 的设计原则,那么理解 光 与 阴影 就显得尤为重要了。

我将尝试解释本文中的以下主题。

  • Android 中的 3D
  • 深度(Depth)
  • Z 轴,Elevation 和 Translation Z。
  • 光源
  • 按钮状态(按下和静止)
  • Outline
  • 自定义的 ViewOutlineProvider

在深入到阴影和光线之前,我想告诉你我们的真实环境什么?

什么是 3D?

真实的物质环境,是一个三维空间,这意味着所有的物体对象都有 X、Y 和 Z 维度。Z轴与显示器的平面垂直对齐,正 Z 轴朝向观察者延伸。在 Material Design 的世界里,每个物体都有 1dp 的厚度。

Android 的 Depth

Material Design 不同于其他的设计指南,因为它增加了 Depth(深度)的概念。而 Depth 在真实的视觉中,有重要的意义。

我们可以认为我们桌子上有一层纸,如果我们再贴一张纸,我们的眼睛会觉得它有一个深度。

让我们通过 Material Design 的应用截图来想象它。

让我们来看看屏幕上的各个元素。

  • 屏幕(表层 - 深度为 0 )
  • CardViews
  • App UI 布局
  • 浮动动作按钮(Floating Action Button)

这里面,每个元素都在另一个元素之上。CardView 可以滚动,所以我们可以说第一层是可滚动的内容,第二层是 AppBar 布局,第三层(顶层)是浮动动作按钮。

那么,我们如何定义层级?我们如何让用户感受到深度?答案是: Z 轴。

Android 中的 Z 值是什么?

View 的 Z 值有两个组成部分:

  • Elevation:高度,一个静态值。
  • Translation Z:Z轴变动值,用于动画的动态值。

我总是在想 Elevation 和 Translation Z 有什么区别。

Elevation 是静态的,所以你最好不要动态的去改变他。如果你想在 Z 轴上做动画的效果(如按下态或者静止态),你需要使用 Translation Z 属性。

Translation Z 是动态的,当你创建一个空白项目,并在其中增加一个按钮的时候,当你按下它你将会看到阴影变大了。实际上 Elevation 并没有变化,而是 Translation Z 属性在变化。这是 Android 使用默认的状态列表动画,更改 Z 属性。

Z Vaue = Elevation + TranslationZ

如果我们改变两个具有 Z 值的 View,让它们相交。Android如何处理屏幕上的层级?让我用一个我设计的图表向你展示。

另外一个问题,我们如何看到物体的影子?我们是需要一个阴影吗?不是的,我们是需要一个光源。

Android 中的光源是什么?

其实问题不在于是什么?而是在哪里。

在现实中,如果我们手持一个手电筒照桌子上的物体(从它的顶部),阴影的长度会缩短,当你降低它的时候,阴影的长度会增加。

那么在 Android 的 Material Design 中,光源在哪里?在顶部?还是有角度的?经过一番研究,我发现这个现象。

Android 中存在两个光源,顶部那个是关键的光源,而另一个是环境光源,我们看到的阴影实际上是这两个光源的组合。

让我们看看显示的效果。

在 Android 中,我们有很多小部件。按钮、CardView、对话框,抽屉等所有这些都是视图。如果有一个光源,我们就有阴影。那么我们如何在Android中决定 Z 值呢?Material Design 是如何规定这些的?

有一个示意图来反映这种情况。

静止或者按下

正如我之前提到的,在 Android Framework 中,一些动画是为小部件而实现的。如果你在布局中放置浮动操作按钮,默认情况下它将具有 6dp 的 Elevation。但是你会注意到当你按下按钮时,FAB 的 Elevation 将会提高到12 dp

让我告诉你,在这个过程中发生了什么。

其实FAB有 6dp 的 Elevation。当您按下按钮时,translationZ 值开始增加。ViewPropertyAnimator 通过将 translationZ 值从 0dp 更改为 6dp 来 让视图动起来。如果你释放按钮,ViewPropertyAnimator 播放动画,将 translationZ 从 6dp 变到 0dp。你可以为你的视图创建自定义状态列表动画,并将其添加到你的视图上。

我们来看一下这个过程的流程图。

阴影的秘密:Outline

Outline 是一个属于 android.graphic 下的类,看看它的文档都说了什么

定义一个简单的形状,用于图形的边界区域。

可以为 View 计算,也可以由 Drawable 计算,以驱动由视图投射的阴影的形状,或剪裁视图的内容。

每个 View 都有默认的轮廓以显示其阴影。如果我们创建一个可绘制的自定义形状,其轮廓将根据其形状在内部进行计算。所以,如果我们画圆,轮廓将会是圆的。如果我们绘制矩形,轮廓将是矩形。

总而言之,有一个 Outlin 可以让你以不可见的方式看到这个效果。但是,如果我想创建一个自定义的视图,并动态地改变它的边界呢?Android 会为我的自定义视图提供了 Outline 吗?

Android 当然为我们提供了自定义 Outline 的办法,那就是 : ViewOutlineProvider

什么是 ViewOutlineProvider

在我最近的开源的 ScalingLayout 库中,我没有对自定义视图实现阴影效果。我以为这是非常漂亮,没有影子。但要记住 Material Design 的基础知识,3D,Depth,Z-Value。

在这个动画中,我们可能无法确定那些地方是可以被点击的,而且缩放布局中并没有阴影。

接下来我们为自定义的视图提供动态的轮廓。

public class ScalingLayoutOutlineProvider extends ViewOutlineProvider {

    @Override
    public void getOutline(View view, Outline outline) {
        outline.setRoundRect(0, 0, width, height, radius);
    }
}
public class ScalingLayout extends FrameLayout {
    
    //...
    viewOutline = new ScalingLayoutOutlineProvider(w, h, currentRadius);
    setOutlineProvider(viewOutline);
    //..
    
}

这样,我们就为自定义的 View 增加了高度的支持。

更多有关于 ViewOutlineProvider 的使用中,被简化的一些基础知识,你可以在 ScalingLayout 中找到细节。

https://github.com/iammert/Sc...

作者 | Mert Şimşek

翻译 | 承香墨影

授权 承香墨影 翻译并发布

原文地址

今天在公众号后台回复成长『成长』,将会得到我整理的一些学习资料,也能回复『加群』,一起学习进步。

推荐阅读:

查看原文

lindroid 收藏了文章 · 2019-07-11

JitPack.io 基本使用法

JitPack.io 是一个 GitHub 开源代码库的便捷发布渠道。它可以让你的 Android/Java 代码库自动完成发布,从而令使用者能够最便利地享受到你的代码库。

本质上说,一切能够通过 Maven Repository 发布的代码库,你都可以借助 JitPack 的自动打包和发布功能,从 GitHub 上发布给大众使用。例如你的 Kotlin/Java 代码库,SpringBoot 工具库,Android 三方库,等等,一旦你发布了源代码到 GitHub,并完成了提交、Release标签动作,那么 JitPack 上将会自动生成一个相应的符合 Maven 包引用规则的 ID:com.github.your-github-username:your-github-reponame:release-tag。在这里,Maven Group Name 即 com.github.your-github-username,Maven Artifact Name 即 your-github-repo-name。这样的 Maven ID,三方库使用者能够通过 POM 或 gradle 引用到它。

那么这和 Maven Central,JCenter 有何不同呢?最大的区别就在于你不必完成 Maven Central 的一系列注册手续,乃至发布一个库之前的登记 Post 和等待管理员批准,也不必在 JCenter 上填写冗长的标签,找图做图做图标写说明,更不必每到发布时做一系列的准备工作,使用专用的工具完成最后一击。你只需要写好你的 GitHub Repo README就行了,其他的事情,JitPack 会全数包办。

所以很明显,发布一个公开的第三方库前所未有地简便。

当然,这一切大体上限定在 Java 及其衍生领域,例如 Android。而诸如 Python,Nodes 等就没法

除了支持 GitHub 上的公开 Repository 的自动发布之外,JitPack 也支持 Bucket,GitLab,Gitee 账户中的公开库的发布。

JitPack is a novel package repository for JVM and Android projects. It builds Git projects on demand and provides you with ready-to-use artifacts (jar, aar).

那么,这里会讲讲利用 JitPack.io 的方法。为了讲述方便,下面会主要使用 Android Library 作为讲解的例子。

你是库开发者

假定你是一个Android开发者,手头有一堆代码行之有效,而且厌倦了每次在各个 Projects 之间拷贝来拷贝去,那么你就建立了一个独立的 Android Library 项目,将这些代码打包在一起。这时,问题来了,别的工程怎么引用这个库呢?

Android使用一个库,有很多种方法。

没有 JitPack

你可以将 Library Module 嵌入到目标项目中,然后通过

dependencies {
    implements ':my-library'
}

来引用它。

这很蠢,但最简单。所以我们会将库独立建立一个工程 my-library,然后通过 gradle 命令构建它,从而得到一个 .aar 文件,然后我们可以复制这个 .aar 文件到目标项目中的 app/libs/ 之中,利用:

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
}

来引用它。

这就方便多了,不过如果修订了 my-library 的话,我们需要复制那个 .aar 到每个引用它的目标项目中,如果我们引用了5次,甚至我的团队伙伴们引用了它时,那么灾难还是会发生。

所以我们可以用过 gradle deploy 指令将 .aar 发布到 $HOME/.m2/repositories/ 之中,然后通过 Maven Artifact ID方式引用它。如果我的团队伙伴们也需要引用它,那么我发布到 Maven 私服上,就解决问题了。这时候通常是这样的:

allprojects {
    repositories {
        jcenter()
        google()
        mavenCentral()
        mavenLocal()
        maven { url "https://maven.our-nexus.com" }
    }
}
dependencies {
    implementation 'com.example.android:my-library:1.0.1'
}

使用 JitPack

那么新问题还是不可避免地来了,我们没有私服呢,又或者我们的异地伙伴呢,而我们也没有公网上的 Maven 私服呢?又或者,如果我觉得这个代码库具有通用性,我希望让任何感兴趣的人都能够使用它呢?这些情况下,我们只能考虑 Maven Central,JCenter 那样的公共 Maven Repository Servers 了,特别是针对 Android 开发的情况,JCenter 是首选的推荐。所以前文我们已经提到过了,当你的技能水平和开发经验推进到一定水平时,你就会面临这样的选择。而且你幸运的是,现在你不必要那么麻烦了,JitPack.io 可以更好地为你的设想进行变现。

这时候,你的 gradle 脚本中可能是像这样子声明引用的:

allprojects {
    repositories {
        jcenter()
        google()
        maven { url "https://jitpack.io" }
    }
}
dependencies {
    implementation 'com.github.Username:my-library:Tag'
}

在这里,Username 是你的 GitHub 账户登录名。而 my-library 是你的 Repository 的名字。换句话说,你的库被放在 https://github.com/Username/m... 处可以被浏览器访问。

当你的开源库变得越来越受欢迎时,你不能使用 JitPack 方式发布它,因为那时候你会发现更多新的需求,你会需要更好地规划版本推进路线图,解决全球各地使用者的依赖、补丁修复等各类型的新问题,也为使用者们释疑和提供可信度,那时候你需要更严谨的登记自己的开源库到 JCenter 并采取更稳定更可信赖的方式来发布代码库。那将会是另一篇文章了。

按照 JitPack 的官方说明,Tag 是这样的:Tag 可以是 Release 标签,commit hash,分支名加上 -SNAPSHOT 后缀等等。

引用 Snapshots

在开发过程中,Snapshots 版本是非常有用的。你可以这样指定版本号来引用 repo 的 snapshots:

  • commit hash
  • branch-SNAPSHOT (替换 branch 为你的分支名)

例如:

    // dependency on the latest commit in the master branch
    implementation 'com.github.jitpack:gradle-simple:master-SNAPSHOT'
    // dependency on the latest commit in the devel branch
    implementation 'com.github.jitpack:gradle-simple:devel-SNAPSHOT'
    // dependency on the certain a commit 33e0c37ee8
    implementation 'com.github.jitpack:gradle-simple:33e0c37ee8'
刷新缓存

注意 Gradle 会缓存SNAPSHOT内容,所以有时候你可能无法获取某个分支上的最新 build。Gradle 自身也提供的方法来控制这一点。你可以在 build.gradle 中要求 Gradle 总是拉取最新的 build 版本:

configurations.all {
    resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
}

你也可以通过命令行添加 --refresh-dependencies 来要求一次 gradle build 中额外地废除 cache 内容并从远程拉取最新的 build 版本。Gradle documentation 提供了更多的关于控制缓存的相关信息。

使用 Android Studio 时,如果你刷新了 gradle cache,那么你可能也需要在 AS 中通过 File -> Synchronize 菜单来更新和同步 gradle 各种依赖状态。

对于在你的开源库中提供多个 Modules 的情况,JitPack 也提供了相应的支持:https://jitpack.io/docs/BUILD...。不过我们并不建议你这么去做。

引用 Release

通过 JitPack 来发布开源库,而不是使用你的库的 Snapshots 版本,也是超级容易的。当然,你需要具备 GitHub Release 的相关知识。

本质上说,GitHub 的 Release 和 git 的 tag 没有什么不同,只是在 tag 名称上略有要求。你可以在GitHub 上通过 Web 界面建立 pre-release 和 release,但你也可以直接通过本机的命令行或者 IDE 或者 Git Client 来建立 release 标签。无论如何,对于这些标签的命名的这些要求,通常也符合软件团队的发布策略:

git tag 0.17.2
git tag v0.17.3
git tag release-0.1.1
git tag release/v0.13.3

不同的团队可能采取不同的 CI 策略以及 Release 命名策略。

  • 较为简明的方式是 0.17.2,它便于手工管理且视觉上明确无歧义。
  • 采用自动化 CI 的团队可能会使用 v0.17.3release/v0.13.3,前者利用前缀触发 CI builder Rules,而后者则在兼顾触发规则的同时,提供一个可视化层面的更好的组织形式:当你通过多数 Git 客户端审查代码时,所有的 releases tags 会被组织为 release/ 之下的一组节点——类似地,往往同时也会使用诸如 hotfix/v0.13.3-h101rc/v2beta/1 等 tags 来组织和管理其他情形的版本。

当然,tag 的命名是全自由度的。你可以在无论是 git,git client,github,jitpack 等各种不同场合使用像 temporary-rel-1 或者 fxx-123-kk 这样的 tag 名称,毫无障碍地。

可以通过 Gradle 插件来帮助你管理你的 Git/GitHub 版本号:

Gradle release & version management plugin

如果你尚未有任何关于如何进行 Git Tag 命名的概念的话。

一旦通过 git 命令或者任何方式建立了一个 git tag,那就代表着你建立了一个 Release,无论其名称多么狗屎都可以。那么一旦你推送这个 tag 到 GitHub 之后,例如 fxx-123-kk,任何人就可以在项目中这样引用它:

   repositories {
        jcenter()
        maven { url "https://jitpack.io" }
   }
   dependencies {
       implementation 'com.github.yourname:your-repo:fxx-123-kk'
   }

这就是全部了。

发布 JavaDoc

如果你的库的构建脚本能够产出 javadoc jar 包,那么 JitPack 将会自动处理 javadoc 的生成以及发布。你可以直接在这里浏览:

  • https://jitpack.io/com/github/USER/REPO/VERSION/javadoc/ or
  • https://jitpack.io/com/github/USER/REPO/latest/javadoc/ (latest release tag)

对于一个多 Module 的项目来说,它的 Maven 发布点使用 com.github.USER.REPO:MODULE:VERSION 这样的 Artifact。因此,相应的 JavaDoc 位置在:

  • https://jitpack.io/com/github/USER/REPO/MODULE/VERSION/javadoc/
Source codes jar 包会被 JitPack 自动处理,你无需额外提供处理依据或编排。

一个简短的 Module-level build.gradle 样本如下:

apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'com.github.dcendents.android-maven'

repositories {
    mavenCentral()
    google()
    jcenter()
    maven { url "https://jitpack.io" }
}

group = 'com.github.jitpack'
version = '1.0'

android {
    compileSdkVersion 28
    buildToolsVersion "28.0.2"

    defaultConfig {
        minSdkVersion 16
        targetSdkVersion 28
        versionCode 1
        versionName version
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    lintOptions {
        abortOnError false
    }
    sourceSets {
        main.java.srcDirs += 'src/main/kotlin'
        androidTest.java.srcDirs += 'src/androidTest/kotlin'
        androidTest.resources.srcDirs += 'src/androidTest/res'
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])

    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'

    implementation 'com.android.support:appcompat-v7:28.0.0-rc01'
    implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
}

task sourcesJar(type: Jar) {
    classifier = 'sources'
    from android.sourceSets.main.java.sourceFiles
}

task javadoc(type: Javadoc) {
    source = android.sourceSets.main.java.sourceFiles
    classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
}

task javadocJar(type: Jar, dependsOn: javadoc) {
    classifier = 'javadoc'
    from javadoc.destinationDir
}

task classesJar(type: Jar) {
    from "$buildDir/intermediates/classes/release"
}

artifacts {
    archives classesJar
    archives javadocJar
    archives sourcesJar
}

其他特性

  • 支持私有 Repositories。

    你只需要提供一个授权给予 jitpack.io 并微调一下开发机环境就可以了,其他的部分和前文述及的用法没有区别。具体的步骤可以参考:https://jitpack.io/private。不过,你想要通过 JitPack 发布私有repo的话,是收费的,按月订阅制。

  • 支持动态版本号。

    所以你可以使用 '1.+' 这样的版本号。

  • 可以使用自己的域名作为groupId。

    这个需要更高价位的订阅级别。

    不过对于 free 的 GitHub repo 来说,你也可以免费获得这个特性。稍后章节我们会提到这一点。

  • 一般情况下 jitpack 按照构建顺序存储你的若干个库版本,但并不确保这些构建后的版本总是存在。就是说,直到用户向服务器发起了拉取一个库的请求的时候,jitpack按需就地构建请求的版本而不是提前构建好之后等待用户发起请求。这意味着首位用户的首次请求通常是失败的,他需要等待构建完成后再次发布请求时才能完成pom和库包的下载。

    通常,一个 jitpack 构建完成的版本至少会维持 7 天不变。

    多数时候,我们并不真的介意有时候会拉取失败的问题,尤其是有一次成功后本地.m2也会有一份缓存的情况。你可以登录 JitPack.io 之后在 对应库的构建列表中点击 “Get It” 按钮来明确地发出构建请求而不是由使用者发起。

自定义域名

默认时,你的库的 groupId 为 com.github.USER 或者 com.github.yourORGANIZATION。但你也可以使用自己的域名前缀,例如 com.hedzr 或者 com.hedzr.lib。为了使用自己的域名前缀作为 groupId,应该遵循以下的步骤:

  1. 添加一条 DNS TXT 记录,以完成从 git.yourcompany.com 到 https://github.com/yourcomany。另外一种情形是:从 lib.yourdomain.com 映射到 https://github.com/USER。你需要在自己的域名托管商提供的DNS记录修改工具中完成这样的操作,可以参考 How to add a TXT record
  2. 然后请前往 https://jitpack.io/#com.yourc... 并点击 Look up 按钮,确定映射已经生效。

为了检测TXT记录已经生效,可以执行命令行:

dig TXT git.yourcompany.com

例如:

~$ dig txt git.jitpack.io
...
;; ANSWER SECTION:
git.jitpack.io.        600    IN    TXT    "https://github.com/jitpack"

支持的代码库网站

GitHub

GitLab

BitBucket

Gitee

角标 Badges

在你的项目的 README.md 中添加相应的角标的方法是:

[![Release](https://jitpack.io/v/User/Repo.svg)](https://jitpack.io/#User/Repo)

Release

也可以使用非圆角的样式:

[![Release](https://jitpack.io/v/jitpack/maven-simple.svg?style=flat-square)](https://jitpack.io/#jitpack/maven-simple)

Release

当你使用自定义域名或者 BitBucket 时,可以这样:

[![Release](https://jitpack.io/v/com.example/Repo.svg)](https://jitpack.io/#com.example/Repo)

[![Release](https://jitpack.io/v/org.bitbucket.User/Repo.svg)](https://jitpack.io/#org.bitbucket.User/Repo)

你是库使用者

你需要的引用方式是在 Module-level build.gradle 中添加:

dependencies {
    implementation 'com.github.Username:my-library:Tag'
}

你应该在 Top-level build.gradle 中添加 jitopack 的 Maven 仓库:

allprojects {
    repositories {
        jcenter()
        google()
        maven { url "https://jitpack.io" }
    }
}

详细的解释,请阅读库开发者相关章节内容。

后记

关于 JitPack 的 API

这给予我们 ChatOps 回调能力或者 CI 控制能力,或者其他——取决于你的想象力。

由于 API 设计的非常简单,因此不必另文专述。有需要者不妨直达:

https://jitpack.io/docs/API/

查看原文

lindroid 发布了文章 · 2019-03-17

Android之调节屏幕亮度

1、需求分析

在使用微信或者支付宝的付款码支付时,如果你点击放大付款码,就会跳转到一个新的页面去显示大尺寸的付款码,而且你会发现屏幕变亮了,这样会便于扫码机识别你的付款码。当你付款成功退出付款码放大的界面后,屏幕就会恢复到原先的亮度。我很早就注意到了这点,所以当我自己的项目需要做二维码点击放大功能时,我也在放大的同时把屏幕的界面调亮一点。尽管我当时比较轻松地实现了这个功能,但是当我编写屏幕亮度工具类时,发现里面其实内有乾坤。现在就让我们来系统学习一下。

首先我们要明确“屏幕亮度”是什么。它其实包含了两种情况:

  1. 当前窗口的亮度。如果只改变当前窗口的亮度的话,当你退出该窗口(比如销毁了当前的Activity或者干脆退出了应用),那么屏幕就会恢复原先的亮度。也就是说,此处的改变只对当前的窗口有效。微信或支付宝在点击放大付款码后,改变的就是这个。
  2. 改变系统屏幕亮度。在下拉的手机设置面板中,有一个改变屏幕亮度的进度条(下图中的红框),这里改变的就是系统的屏幕亮度,适用于所有的窗口。

下来设置面板

2、准备工作

创建一个BrightnessActivity,然后在里面放置两个进度条,一个改变系统亮度,一个改变窗口亮度。为了便于以后使用,我们会把用到的方法都封装到一个工具类中。所以再创建一个名称为BrightnessUtil的Kotlin文件,但是不要创建类,因为我们会使用扩展成员的方式来编写工具类。

温馨提示:最近大半年都在使用Kotlin,这实在是一门很棒的语言,推荐大家学习。以后的博客我一般都会使用Kotlin了。

3、改变当前窗口亮度

首先来看看怎么改变窗口亮度。十分简单,只需改变窗口属性中的屏幕亮度(screenBrightness)一项。让我们直接来看代码:

/**
 * 当前窗口亮度
 * 范围为0~1.0,1.0时为最亮,-1为系统默认设置
 */
var Activity.windowBrightness
    get() = window.attributes.screenBrightness
    set(brightness) {
        //小于0或大于1.0默认为系统亮度
        window.attributes = window.attributes.apply {
            screenBrightness = if (brightness > 1.0 || brightness < 0) -1.0F else brightness
        }
    }

改变窗口亮度的上下文必须是Activity,所以我给Activity加了一个扩展属性windowBrightness,它的值就是当前的窗口亮度,改变它的值就可以改变窗口亮度。它的范围是0~1.0,从0到1.0亮度逐渐增大;如果赋值为-1,那就表示跟随系统的亮度。

使用起来也很简单:

        tvWindowBright.text = "当前窗口亮度=$windowBrightness"
        sbWindowBright.progress = if (windowBrightness > 0) (windowBrightness * 100).toInt() else 0
        sbWindowBright.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
            override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
                windowBrightness = progress.toFloat() / 100F
                tvWindowBright.text = "当前窗口亮度=$windowBrightness"
            }

            override fun onStartTrackingTouch(seekBar: SeekBar?) {
            }

            override fun onStopTrackingTouch(seekBar: SeekBar?) {
            }

        })

没有接触过Kotlin的小伙伴们可能会不知道这属性在Java中怎么用?毕竟我们连类名都没有看到。其实Kotlin会默认为Java生成一个“类名+kt”的类,属性则会生成getter和setter静态方法。所以在Java代码中只需要 这么写:

        BrightnessUtilKt.getWindowBrightness(Activity);
        BrightnessUtilKt.setWindowBrightness(Activity,brightness);

4、改变系统亮度

比起改变窗口亮度,改变系统亮度就要麻烦一点了。作为个人,我们改变世界都是不容易的,那么一个应用想要改变系统自然也不会轻而易举。

4.1 清单文件申请权限

第一步,我们需要到AndroidManifest.xml中申请权限:

    <uses-permission android:name="android.permission.WRITE_SETTINGS"
        tools:ignore="ProtectedPermissions" />

之所以加上tools:ignore="ProtectedPermissions"是因为改变系统设置的权限一般只归系统App所有,所以编译器会报一个警告,加上这个可以忽略警告。

4.2 申请动态权限

如果你的手机系统是Android6.0以上的,那么还得动态申请权限。系统设置权限的动态申请有点特别,它需要跳转到系统的“可修改系统设置”界面,让用户决定是否允许当前应用修改系统设置,然后再在onActivityResult中处理回调结果。

我们在进入BrightnessActivity时就动态申请权限,代码如下:

        //修改系统屏幕亮度需要修改系统设置的权限
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            //如果当前平台版本大于23平台
            if (!Settings.System.canWrite(mContext)) {
                val intent = with(Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS)) {
                    data = Uri.parse("package:$packageName")
                    this
                }
                startActivityForResult(intent, RQ_WRITE_SETTINGS)
            } else {
                changeSystemBrightness()
            }
        } else {
            //Android6.0以下的系统则直接修改亮度
            changeSystemBrightness()
        }

首先调用Settings.System.canWrite(Context)判断手机系统,Android6.0以下的直接允许修改亮度的操作;Android6.0以上的则要进一步判断是否已经获得了修改系统设置的权限,没有的话就要打开如下界面去设置。

修改系统设置界面

无论用户是否授权,我们都需要一个回调,这时onActivityResult就可以派上用场了:

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        when (requestCode) {
            RQ_WRITE_SETTINGS -> {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                    if (Settings.System.canWrite(mContext)) {
                        shortToast("已获取权限")
                        changeSystemBrightness()
                    } else {
                        shortToast("你拒绝了权限")
                    }
                }
            }
        }
    }

代码很简单,就不做过多解释了。

4.3 去除自动亮度

前面我们虽然解决了权限问题,但是还要考虑到一个实际情况,那就是用户可能会设置了自动亮度,在这个前提下是无法改变系统屏幕亮度的。所以这里要做两步处理:

  1. 判断用户是否开启了自动亮度;
  2. 如果当前开启了自动亮度,则需要将其关闭。

4.3.1 判断是否自动亮度

我们在工具类中添加isAutoBrightness属性,它只有Getter方法,返回一个布尔值。这里调用Settings.System.getInt()方法,第二个参数传入 Settings.System.SCREEN_BRIGHTNESS_MODE表示我们要获取系统屏幕亮度模式,如果是Settings.System.SCREEN_BRIGHTNESS_MODE,则表示当前自动亮度模式。

val isAutoBrightness:Boolean
    get() = try {
        Settings.System.getInt(
            AndUtil.appContext.contentResolver,
            Settings.System.SCREEN_BRIGHTNESS_MODE
        ) == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC
    } catch (e: Settings.SettingNotFoundException) {
        e.printStackTrace()
        false
    }

4.3.2 设置开启和关闭自动亮度

前面我们获取了系统亮度模式,通过设置它的值,我们就可以控制自动亮度模式的开关了。
在工具类中创建一个setAutoBrightness()函数,如果设置成功就返回true。这里用到的是Settings.System.putInt(),第二个参数即为我们要设置的亮度模式。当参数enable为true时就是自动模式了。

/**
 * 设置是否开启自动亮度
 * @param enable : 为true时开启,false时关闭
 * @return 设置成功返回true
 */
fun setAutoBrightness(enable: Boolean) = Settings.System.putInt(
    AndUtil.appContext.contentResolver, Settings.System.SCREEN_BRIGHTNESS_MODE,
    if (enable) Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC else Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL
)

4.4 封装改变系统屏幕亮度属性

现在我们总算可以编写修改系统亮度的代码了。跟修改窗口亮度一样,我们这里也使用了一个属性,命名为systemBrightness

/**
 * 系统屏幕亮度,需要WRITE_SETTINGS权限,并在代码中申请系统设置权限
 * 范围为0~255
 */
var systemBrightness
    get() = try {
        Settings.System.getInt(AndUtil.appContext.contentResolver, Settings.System.SCREEN_BRIGHTNESS)
    } catch (e: Settings.SettingNotFoundException) {
        e.printStackTrace()
        -1
    }
    @RequiresPermission(Manifest.permission.WRITE_SETTINGS)
    set(@IntRange(from = 0, to = 255) brightness) {
        if (isAutoBrightness) {
            //如果当前是自动亮度,则关闭自动亮度
            setAutoBrightness(false)
        }
        val uri = Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS)
        Settings.System.putInt(AndUtil.appContext.contentResolver, Settings.System.SCREEN_BRIGHTNESS, brightness)
        AndUtil.appContext.contentResolver.notifyChange(uri, null)
    }

这里我们重点来看设置系统亮度,也就是set()里面的代码。首先判断当前是否开启了自动亮度模式,如果是则将其关闭。后面的代码类似于setAutoBrightness(),都是在Settings.System.putInt()中赋值,不同的是还要调用Context.contentResolver.notifyChange()方法去通知系统我们已经修改了屏幕亮度,这样设置的值才会起作用。另外,要注意系统屏幕亮度的取值范围是0~255。

最后当然是设置SeekBar的监听了:

    private fun changeSystemBrightness() {
        sbSystemBright.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
            override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
                systemBrightness = progress * 255 / 100
                tvSystemBright.text = "系统亮度=$systemBrightness"
            }


            override fun onStartTrackingTouch(seekBar: SeekBar?) {
            }


            override fun onStopTrackingTouch(seekBar: SeekBar?) {
            }

        })
    }

实现的效果如下:

效果

视频录制看不出亮度的变化,但真机上是没有问题的。

5、后记

本文分析了屏幕亮度的类型,并给出了设置的方法。其中,设置系统屏幕亮度时要格外注意动态权限申请和自动亮度模式的影响。

最后给一下主要的源码:

BrightnessUtil

BrightnessActivity

查看原文

赞 5 收藏 4 评论 0

lindroid 赞了文章 · 2018-11-28

正则表达式手册

原文链接:正则表达式手册

表达式全集

字符描述
\将下一个字符标记为一个特殊字符、或一个原义字符、或一个向后引用、或一个八进制转义符。例如,“n”匹配字符“n”。“\n”匹配一个换行符。串行“\\”匹配“\”而“\(”则匹配“(”。
^匹配输入字符串的开始位置。如果设置了RegExp对象的Multiline属性,^也匹配“\n”或“\r”之后的位置。
$匹配输入字符串的结束位置。如果设置了RegExp对象的Multiline属性,$也匹配“\n”或“\r”之前的位置。
*匹配前面的子表达式零次或多次。例如,zo*能匹配“z”以及“zoo”。*等价于{0,}
+匹配前面的子表达式一次或多次。例如,“zo+”能匹配“zo”以及“zoo”,但不能匹配“z”。+等价于{1,}
?匹配前面的子表达式零次或一次。例如,“do(es)?”可以匹配“does”或“does”中的“do”。?等价于{0,1}
{n}n是一个非负整数。匹配确定的n次。例如,“o{2}”不能匹配“Bob”中的“o”,但是能匹配“food”中的两个o
{n,}n是一个非负整数。至少匹配n次。例如,“o{2,}”不能匹配“Bob”中的“o”,但能匹配“foooood”中的所有o。“o{1,}”等价于“o+”。“o{0,}”则等价于“o*”。
{n,m}mn均为非负整数,其中n<=m。最少匹配n次且最多匹配m次。例如,“o{1,3}”将匹配“fooooood”中的前三个o。“o{0,1}”等价于“o?”。请注意在逗号和两个数之间不能有空格。
?当该字符紧跟在任何一个其他限制符(*,+,?{n}{n,}{n,m})后面时,匹配模式是非贪婪的。非贪婪模式尽可能少的匹配所搜索的字符串,而默认的贪婪模式则尽可能多的匹配所搜索的字符串。例如,对于字符串“oooo”,“o+?”将匹配单个“o”,而“o+”将匹配所有“o”。
.匹配除“\n”之外的任何单个字符。要匹配包括“\n”在内的任何字符,请使用像“(.\n)”的模式。
(pattern)匹配pattern并获取这一匹配。所获取的匹配可以从产生的Matches集合得到,在VBScript中使用SubMatches集合,在JScript中则使用$0…$9属性。要匹配圆括号字符,请使用“\(”或“|</code>”。
(?:pattern)匹配pattern但不获取匹配结果,也就是说这是一个非获取匹配,不进行存储供以后使用。这在使用或字符“(|)”来组合一个模式的各个部分是很有用。例如“industr(?:y|ies)”就是一个比“industry|industries”更简略的表达式。
(?=pattern)正向肯定预查,在任何匹配pattern的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如,“Windows(?=95|98|NT|2000)”能匹配“Windows2000”中的“Windows”,但不能匹配“Windows3.1”中的“Windows”。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始。
(?!pattern)正向否定预查,在任何不匹配pattern的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如“Windows(?!95|98|NT|2000)”能匹配“Windows3.1”中的“Windows”,但不能匹配“Windows2000”中的“Windows”。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始。
(?<=pattern)反向肯定预查,与正向肯定预查类拟,只是方向相反。例如,“(?<=95|98|NT|2000)Windows”能匹配“2000Windows”中的“Windows”,但不能匹配“3.1Windows”中的“Windows”。
(?<!pattern)反向否定预查,与正向否定预查类拟,只是方向相反。例如“(?<!95|98|NT|2000)Windows”能匹配“3.1Windows”中的“Windows”,但不能匹配“2000Windows”中的“Windows”。
x|y匹配xy。例如,“z|food”能匹配“z”或“food”。“(z|f)ood”则匹配“zood”或“food”。
[xyz]字符集合。匹配所包含的任意一个字符。例如,“[abc]”可以匹配“plain”中的“a”。
[^xyz]负值字符集合。匹配未包含的任意字符。例如,“[^abc]”可以匹配“plain”中的“p”。
[a-z]字符范围。匹配指定范围内的任意字符。例如,“[a-z]”可以匹配“a”到“z”范围内的任意小写字母字符。
[^a-z]负值字符范围。匹配任何不在指定范围内的任意字符。例如,“[^a-z]”可以匹配任何不在“a”到“z”范围内的任意字符。
\b匹配一个单词边界,也就是指单词和空格间的位置。例如,“er\b”可以匹配“never”中的“er”,但不能匹配“verb”中的“er”。
\B匹配非单词边界。“er\B”能匹配“verb”中的“er”,但不能匹配“never”中的“er”。
\cx匹配由x指明的控制字符。例如,\cM匹配一个Control-M或回车符。x的值必须为A-Za-z之一。否则,将c视为一个原义的“c”字符。
\d匹配一个数字字符。等价于[0-9]
\D匹配一个非数字字符。等价于[^0-9]
\f匹配一个换页符。等价于\x0c\cL
\n匹配一个换行符。等价于\x0a\cJ
\r匹配一个回车符。等价于\x0d\cM
\s匹配任何空白字符,包括空格、制表符、换页符等等。等价于[\f\n\r\t\v]
\S匹配任何非空白字符。等价于[^\f\n\r\t\v]
\t匹配一个制表符。等价于\x09\cI
\v匹配一个垂直制表符。等价于\x0b\cK
\w匹配包括下划线的任何单词字符。等价于“[A-Za-z0-9_]”。
\W匹配任何非单词字符。等价于“[^A-Za-z0-9_]”。
\xn匹配n,其中n为十六进制转义值。十六进制转义值必须为确定的两个数字长。例如,“\x41”匹配“A”。“\x041”则等价于“\x04&1”。正则表达式中可以使用ASCII编码。
\num匹配num,其中num是一个正整数。对所获取的匹配的引用。例如,“(.)\1”匹配两个连续的相同字符。
\n标识一个八进制转义值或一个向后引用。如果\n之前至少n个获取的子表达式,则n为向后引用。否则,如果n为八进制数字(0-7),则n为一个八进制转义值。
\nm标识一个八进制转义值或一个向后引用。如果\nm之前至少有nm个获得子表达式,则nm为向后引用。如果\nm之前至少有n个获取,则n为一个后跟文字m的向后引用。如果前面的条件都不满足,若nm均为八进制数字(0-7),则\nm将匹配八进制转义值nm
\nml如果n为八进制数字(0-3),且ml均为八进制数字(0-7),则匹配八进制转义值nml
\un匹配n,其中n是一个用四个十六进制数字表示的Unicode字符。例如,\u00A9匹配版权符号(©)。

常用正则表达式

匹配内容正则表达式
用户名/^[a-z0-9_-]{3,16}$/
密码/^[a-z0-9_-]{6,18}$/
十六进制值/^#?([a-f0-9]{6}|[a-f0-9]{3})$/
电子邮箱/^([a-z0-9_\.-]+)@([\da-z\.-]+)\.([a-z\.]{2,6})$/
/^[a-z\d]+(\.[a-z\d]+)*@([\da-z](-[\da-z])?)+(\.{1,2}[a-z]+)+$/
URL/^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/
IP 地址/((2[0-4]d|25[0-5]|[01]?dd?).){3}(2[0-4]d|25[0-5]|[01]?dd?)/
/^(?:(?:25[0-5]|20-4|[01]?0-9?).){3}(?:25[0-5]|20-4|[01]?0-9?)$/
HTML标签/^<([a-z]+)([^<]+)(?:>(.)</1>|s+/>)$/
删除代码\注释(?<!http:|S)//.*$
Unicode编码中的汉字范围/^[\u2E80-\u9FFF]+$/
查看原文

赞 366 收藏 298 评论 0

认证与成就

  • 获得 53 次点赞
  • 获得 6 枚徽章 获得 0 枚金徽章, 获得 1 枚银徽章, 获得 5 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2016-08-24
个人主页被 5.6k 人浏览