作者归档:Shelwee

如何用 Java + OpenCV 实现自动玩跳一跳?

前言

自从微信更新到 6.6.1 版本之后,微信小游戏也随之发布。跳一跳成了第一批小游戏的代表作,到处刷屏。而在程序员的朋友圈里,则是各种刷分,各种版本的算法实现自动玩刷分。

秉着学习 OpenCV 的心态,我也用 Java + OpenCV 实现了一个版本。

目标分解

跳一跳这个小游戏规则很简单,就是根据触摸屏幕的时间长短决定跳跃的距离。
所以整个程序可以分解为以下 6 步,循环执行:

  1. 获取跳一跳游戏画面
  2. 找到起点,即跳一跳中的紫色小人
  3. 找到终点,即小人要跳跃的下一个目标的中点
  4. 根据欧式距离公式计算起点与终点的距离
  5. 根据得出的距离计算触摸时间
  6. 用 adb 工具模拟触摸屏幕

准备工作

1.熟悉 ADB 命令

adb shell screencap -p xxx //截屏
adb pull xxx //从设备复制资源到电脑上
adb shell input swipe x1 y1 x2 y2 time //模拟从(x1,y1)滑动到(x2,y2),滑动时间为time(ms)

2.开发环境

本文基于 Java 1.8.0 与 OpenCV 3.4.0 开发。

3.欧式距离公式

编程实现

1.找起点

//载入跳一跳小人图片
Mat template = Imgcodecs.imread("i.jpg");
//载入屏幕截图
Mat source = Imgcodecs.imread("1.png");
//新建 result 用于保存模板匹配结果
Mat result = Mat.zeros(source.rows(),source.cols(),CvType.CV_32FC1);
//在模板图像和输入图像之间寻找匹配,获得匹配结果图像 result
Imgproc.matchTemplate(source, template, result, Imgproc.TM_CCOEFF_NORMED);
//在给定的矩阵中寻找最大和最小值(包括它们的位置)
MinMaxLocResult minMaxLocResult = Core.minMaxLoc(result);
//minMaxLocResult 返回的 maxLoc 为匹配的模板在截图中的左上位置,所以可以得到起点的位置
Point startPoint = new Point(minMaxLocResult.maxLoc.x + template.width() / 2, minMaxLocResult.maxLoc.y + template.height() - 20);

如图,minMaxLoc 方法返回的 maxLoc 点为匹配到目标的左上点。

2.找终点

先判断下一个目标是否有小白点,有的话按照找起点的方法模板匹配白点的坐标,即可得到终点。

如果找不到白点,则先利用高斯模糊消除噪声,再运行边缘检测算法,然后从上到下遍历得到上边缘的顶点与下边缘的顶点,从而可以得出终点。

//载入小白点图片
Mat template_white_dot = Imgcodecs.imread("white_dot.jpg");
//dot_result 用于保存白点模板匹配结果
Mat dot_result = Mat.zeros(source.rows(),source.cols(),CvType.CV_32FC1);
Imgproc.matchTemplate(source, template_white_dot, dot_result, Imgproc.TM_CCOEFF_NORMED);
MinMaxLocResult minMaxLocDotResult = Core.minMaxLoc(dot_result);
//判断模板匹配值,如果大于 0.7 就相信存在白点,即可直接获取目标点
if (minMaxLocDotResult.maxVal > 0.7) {
    System.out.println("found white dot.");
	targetPoint.x = minMaxLocDotResult.maxLoc.x + template_white_dot.width() / 2;
	targetPoint.y = minMaxLocDotResult.maxLoc.y + template_white_dot.height() / 2;
} else {
	System.out.println("white dot not found.");
	//result2 用于存放高斯模糊结果
	Mat result2 = Mat.zeros(source.rows(),source.cols(),CvType.CV_32FC1);
	//result2 用于存放边缘检测结果
	Mat result3 = Mat.zeros(source.rows(),source.cols(),CvType.CV_32FC1);
	//使用高斯平滑滤波器降噪
	Imgproc.GaussianBlur(source, result2, new Size(5, 5), 0);
	//运行 Canny 算子
	Imgproc.Canny(result2, result3, 1, 10);
	//把小人在图中抹掉,防止高度过高对结果产生影响
	clearTemplateRect(result3, new Rect(minMaxLocResult.maxLoc, p));
	//遍历边缘像素,计算终点
	targetPoint = findTargetPoint(result3);
}

3.计算距离

//传入起点和终点,根据欧式距离公式计算距离
public static double getDistance(Point s, Point e){
	return Math.sqrt(Math.pow(s.x-e.x, 2) + Math.pow(s.y-e.y, 2));
}

4.计算时间

//1.35 这个参数适合1080*1920分辨率,其他分辨率需要调整。
int time = getDistance(startPoint, targetPoint) * 1.35;

5.模拟触屏

int x1 = source.width() / 3 * 2; //取图片宽度的 2/3
int y1 = source.height() / 3 * 2; //取图片高度的 2/3
int x2 = x1 + random.nextInt(10); //随机模拟移动10像素内
int y2 = y1 + random.nextInt(10); //随机模拟移动10像素内
cmd("adb shell input swipe "+ x1 + " " + y1 + " "+ x2 +" " + y2 + " " + time);

最后

在与微信防作弊的不断博弈中,算法实现也需要不断更新,需要更加真实地模拟人为操作,否则刷到很高的分也会被微信拒绝。截止目前,这个版本已经可以跑得很高分了。

当然,这篇文章的目的并不是为了宣传刷分作弊,只是作为我这次学习实践的一次记录。而通过这次的实践,对 OpenCV 有了初步了解,相信以后有机会用在更有趣的场景。

如需完整项目及代码,关注公众号「现代晓说」回复【跳一跳】获取。

THE END

2018你好,2017再见

1.前言

也许是随着心智的成熟,也许是将近而立之年而产生的一种错觉,总觉得时间过得很快。转眼又到了该给自己一个交代的时候了,也该梳理一下自己在过去一年的「得与失」。

2.再见2017

2016 年的这个时候立下要在 2017 实现的小目标,如今多多少少肯定有几个没能完成的,比如写作。2016 年的计划是在 2017 年一周写一篇,而实际上却只写了八篇,平均下来一个月一篇都没能达到。这是 2017 年度我做得相对比较不好的一件事。

而 2017 年度对我来说比较满意的事情就是:

  • 与 2016 年一样,又坚持骑行了一年。(一个月大约 200 公里)
  • 健身从 2017 下半年开始,坚持了 12 周,后面由于天气问题停止,计划等到春末夏初再开始。
  • 一天背 20 个单词从 2017 年 11 月中旬开始,至今坚持了 49 天。
  • 2017 年已经坚持看书 24 周了,阅读量相比去年有所增加。

因此坚持就成为了我 2017 的年度关键词,以坚持来告别 2017,用学习来迎接 2018。

3.你好2018

最近几年「终身学习」这个词像智能推荐似的常出现在我的视野里,这个时代变化得太快了,我希望自己能在风口中抓住机会,迎难而上,而不是因为所谓的「中年危机」而迷茫。

对于「终身学习」这个词我深有感触。想学的东西很多,想做的事情更多,所以努力让自己更加自律。

2018 我的计划是:

  • 至少写 12 篇文章
  • 学习 OpenCV
  • 入门机器学习

计划学习 OpenCV 与机器学习倒不是因为某一门技术的火热而跟风学习,而是之前在接触到相关的技术时就感觉很有意思,所以计划在业余时间扩展相关技术深度。

我觉得人最难能可贵的是在人生的每个阶段都知道自己的目标是什么,并为之奋斗。 我希望能成为这样的人。

你可能还想看:

THE END

引用视频网站资源的正确姿势

1.前言

目前大部分网站如果需要嵌入视频的话,应该都是先把视频上传到视频网站平台上(如腾讯视频、爱奇艺、优酷等),然后再引用视频网站平台的资源,而引用的方式则是插入视频网站提供的分享代码。

腾讯视频、爱奇艺、优酷等视频网站都提供了多种引用方式,分别有 flash 地址、html、通用代码等。

2.Flash 与 Html 代码引用

众所周知,目前 Flash 已经逐渐衰落,各大视频网站也正在逐渐向 HTML5 技术转移。所以 Flash 的引用方式兼容性很差,PC 端目前还能正常使用,而移动端基本上已经惨不忍睹了。如果你用的是 Chrome 浏览器,那么你应该看过下面的提示:

所以就不要用 flash 这种方式了。

现在看看各家视频网站提供的 Html 代码:

<embed type="application/x-shockwave-flash" src="url" allowFullScreen="true" quality="high" width="480" height="400" align="middle" allowScriptAccess="always" ></embed>

看到 application/x-shockwave-flash 没有?这种引用方式实际上也是依赖的 flash,在手机上也是完全不显示,因此这个方式也不推荐。

3.通用代码

之所以叫通用代码是因为它的特点是通用,以腾讯视频为例,看看代码:

<iframe frameborder="0" width="1280" height="720" src="https://v.qq.com/iframe/player.html?vid=t00253jl9mv&tiny=0&auto=0" allowfullscreen></iframe>

它是直接用 iframe 嵌入视频地址,然后网页在加载的时候 iframe 包含的视频部分就会由视频网站负责渲染,并以最佳的方式呈现,所以不管是 PC 端还是移动端都能正常显示播放。

因此通用代码是最佳的引用方式。当然,如果你的网站是纯粹面向 PC 端的,那么用 html 代码 embed 暂时也是没问题的。

4.可能会出现的问题

通用代码虽然是相对最优的选择,但是也有可能出现问题,比如引用了上面的代码,PC 端访问正常,移动端虽然也能正常访问,但是视频上下两端都出现了一大截黑块,高度无法自适应。

解决思路

让 iframe 宽度为 100%,与父容器宽度一致,高度动态计算赋值。把视频比例当成 16:9 来处理,则 iframe 高度为: height = 父容器宽度 * 9 /16

转成代码

<iframe id="video_iframe" frameborder="0" width="100%" height="" src="https://v.qq.com/iframe/player.html?vid=t00253jl9mv&tiny=0&auto=0" allowfullscreen></iframe>

把 js 代码放在 /body 之前

<script type="text/javascript">
    iframe = document.getElementById('video_iframe');
    //parent-id 为 iframe 的父容器 id
    iheight = document.getElementsById("parent-id").offsetWidth *9/16;
    iframe.style.height = iheight + "px";
</script>
THE END

如何优雅地给网站添加赞赏功能?

1.前言

前阵子发布了一个很小的开源项目 reward.js,这是一个高效且通用的、低侵入性的赞赏功能。不论何种类型的网站,仅仅在需要显示赞赏功能的页面引入一行 JS 代码即可实现这个功能。

 2.添加赞赏步骤

  1. 下载 reward.min.js
  2. 把 reward.min.js 文件里的两个二维码图片链接改为自己的并上传到服务器上
  3. 在对应的页面插入下面这行代码即可显示赞赏按钮。(同样把下面的 src 的链接修改为你服务器上的 reward.min.js 的链接。)
<script type="text/javascript">document.write(unescape("%3Cdiv id='reward' %3E%3C/div%3E%3Cscript src='http://www.shelwee.com/wp-content/themes/shelwee/js/reward.min.js' type='text/javascript'%3E%3C/script%3E"));</script>

至此,你就可以把上面这行代码引用到任何你想放的地方。

假如你同时运营多个网站,那只要把上面那行代码分别放到不同网站页面的指定位置就行了。一次配置,任意使用。够优雅吧?

3.后期更新计划

目前这个版本在 PC 端使用没有发现任何问题,但这还远远不够。后期的更新计划如下:

  • 支持识别微信 WebView
  • 增加支持移动端显示

本想支持移动端场景,但考虑到通过微信公众号阅读原文链接(或公众号菜单等)跳转到网站更加高频,而其它移动端场景暂时不考虑,使用场景及频率都不高。所以仅支持第一条即可覆盖绝大部分使用场景。

所以最后计划要实现的功能就是: reward.js 如果识别到是用微信 WebView 在阅读网站内容,就只显示微信赞赏码。

THE END

WordPress 免费开启全站 HTTPS

1.前言

记不清楚 Chrome 是从哪个版本开始标记网站是否采用 HTTPS 协议,即在网址前面有一把绿色的锁,旁边有「安全」两字。而一般未采用 HTTPS 协议的网站则是一个黑色的i图标,表示 「不安全」。
虽然使用 HTTP 协议并不影响使用,但这事估计早晚都得做。于是今天就干脆把网站从 HTTP 过渡到 HTTPS,顺便记录一下阿里云服务器上 WordPress 开启全站 HTTPS 的过程。

2.申请证书

由于我用的是阿里云服务器,所以直接在阿里云申请的 CA 安全证书。如果服务器用的不是阿里云,可以在 Let’s Encrypt 申请。
登录阿里云后,点击「产品与服务」-> 「安全(云盾)」栏目下的「CA证书服务(数据安全)」,然后点击「购买证书」,选择「免费型DV SSL」,购买。

免费数字证书,最多保护一个明细子域名,不支持通配符,一个阿云帐户最多签发20张免费证书。

购买成功后,按要求补全相关个人信息,然后等待审核通过即可下载证书。

3.Nginx 配置证书

在 Nginx 安装目录创建 cert 文件夹,把下载的证书上传到此目录。上传后的路径类似这样:

  • /etc/nginx/cert/1127.key
  • /etc/nginx/cert/1127.pem

然后配置 nginx.conf ,配置如下,直接将访问 HTTP 的请求 301 跳转到 HTTPS 上。

server {
    listen       80;
    server_name shelwee.com www.shelwee.com;
    return 301    https://$host$request_uri;
}


server {
    listen 443;
    server_name shelwee.com www.shelwee.com;
    ssl on;
    root /home/www.shelwee.com;
    index index.php index.html index.htm;
    ssl_certificate   cert/1127.pem;
    ssl_certificate_key  cert/1127.key;
    ssl_session_timeout 5m;
    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_prefer_server_ciphers on;
    location / {
        try_files $uri $uri/ /index.php?$args;
    }
        location ~ \.php$ {
        fastcgi_pass   127.0.0.1:9000;
        try_files $uri $uri/ /index.php?$args;
        fastcgi_connect_timeout 300;
        fastcgi_read_timeout 300;
        fastcgi_send_timeout 300;
        fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
        include        fastcgi_params;
    }
}

保存 nginx.conf 并重启 Nginx 。

sudo service nginx reload

这时刷新网站,虽然网址可能已经出现 HTTPS ,但前面的图标应该还不是绿色的安全锁,因为网站的资源还是 HTTP 协议下的,所以下一步要把网站的所有资源链接都替换成 HTTPS。

4.WordPress 修改

function.php

打开主题模板下的 function.php 文件,在文件末尾追加下面的函数:

/* 替换资源链接为 https */
function resources2Https($content){
    if( is_ssl() ){
        $content = str_replace('https://www.shelwee.com/wp-content/uploads', 'https://www.shelwee.com/wp-content/uploads', $content);
    }
    return $content;
}
add_filter('the_content', 'resources2Https');

wp-config.php

最后修改 wp-config.php 文件,文件末尾追加下面语句:

/* 强制后台和登录使用 SSL */
define('FORCE_SSL_LOGIN', true);
define('FORCE_SSL_ADMIN', true);

现在刷新网站,绿色的安全锁是不是已经出现了?如果没有的话,检查一下网站上是不是存在一些写死的资源路径,找出他们(通常是 http:// 开头的)并修改成 https:// 。

THE END

Processing 播放透明通道视频

1.前言

Processing 是一种基于 Java 且简化了语法的编程语言,拥有丰富的第三方库,开发者也能非常容易的扩展开发出满足自己独特需求的库。因此在一些交互场景项目中,Processing 通常是我首选的工具。

2.需求

实际项目中,往往需要加入一些效果好的动效。

比如在原来的背景上,加入一个酷炫的动画。通常可以使用 gif 图来解决,但有时 gif 图会出现锯齿,效果不理想,又或者需要加入声音,那么就只能采用带透明通道的视频来解决了。

假设现在的需求如下:在原来的视频之上叠加一个类似摄像机在录制的一个动画,如下图:

3.分析

正常情况下,两个相同分辨率的视频叠加处理如下:

import processing.video.*;

Movie v1, v2;

void setup() {
  size(640, 480);
  v1 = new Movie(this, "1.mov");
  v1.loop();
  v2 = new Movie(this, "2.mov");
  v2.loop();
}

void draw() {
  background(255);
  image(v1, 0, 0, width, height);
  image(v2, 0, 0, width, height);
}

void movieEvent(Movie m) {
  m.read();
}

但是这样 v2 会直接把 v1 覆盖掉,透明通道并没有生效。效果如下:

正确处理透明通道代码如下:

import processing.video.*;

Movie v1, v2;

void setup() {
  size(640, 480, P2D);
  //如果需要全屏的话, 把 size(640, 480, P2D) 换成 fullScreen(P2D) 即可
  //fullScreen(P2D);
  v1 = new Movie(this, "1.mov");
  v1.loop();
  v2 = new Movie(this, "2.mov");
  v2.loop();
}

void draw() {
  background(255);
  image(v1, 0, 0, width, height);
  image(v2, 0, 0, width, height);
}

void movieEvent(Movie m) {
  m.read();
}

相信你已经看出来了,关键就在于有没有 P2D,但实际上如果把 P2D 改成 P3D 也是可以的。而缺省值则代表 JAVA2D,这个是不支持透明通道视频的。

附:

size(width, height, renderer)
fullScreen(renderer)

Parameters
renderer	String: the renderer to use, e.g. P2D, P3D, JAVA2D (default)
THE END

Tomcat 多实例部署

1.前言

最近遇到一个 Tomcat 部署的问题:多个项目均部署在一个 Tomcat 实例上,也就是说一台服务器上只运行一个 Tomcat,而这个 Tomcat 上部署着多个项目,这就意味着只要其中一个项目重新部署需要重启 Tomcat,就会导致其他所有项目也会出现暂时无法访问的状态。

2.准备

  • 假设已下载 Apche Tomcat 压缩包,并且已经解压到 D 盘,路径为:D:\apache-tomcat。
  • 假设有两个 Web 项目,分别是 project1、project2。

3.配置

1.在 D:\apache-tomcat 下新建 project1、project2 目录(也可以是其他任意目录)。

2.将 D:\apache-tomcat 下的 conf、logs、temp、webapps、work 目录移动到 project1、project2 这两个目录, D:\apache-tomcat 只保留 bin、lib 这两个目录。

3.在两个项目的根目录下创建启动批处理脚本 startup.bat

@echo off

SET CATALINA_BASE=%CD%
SET CATALINA_HOME=D:\apache-tomcat

%CATALINA_HOME%\bin\catalina.bat start

4.在两个项目的根目录下创建服务批处理脚本 service.bat(非必须。将项目的 tomcat 启动程序注册为 windows 服务,桌面上就不会产生一个命令行窗口)

@echo off
SET CATALINA_BASE=%CD%
SET CATALINA_HOME=D:\apache-tomcat

if "%1%" == "install" goto gotInstall
if "%1%" == "remove" goto gotRemove
:gotInstall
%CATALINA_HOME%\bin\service.bat install "%2%"
:gotRemove
%CATALINA_HOME%\bin\service.bat remove "%2%"

5.修改端口:将 D:\apache-tomcat\project1\conf\server.xml 的 Server port 改为 8001,Connector port 改为 8081

<Server port="8001" shutdown="SHUTDOWN">

<Connector port="8081" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />

6.修改端口:将 D:\apache-tomcat\project2\conf\server.xml 的 Server port 改为 8002,Connector port 改为 8082

<Server port="8002" shutdown="SHUTDOWN">

<Connector port="8082" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />

4.部署

将 project1 的项目代码部署到 D:\apache-tomcat\project1\webapps 下, project2 的项目代码部署到 D:\apache-tomcat\project2\webapps 下,配置完后的目录树如下图:

5.运行

分别启动两个项目的 startup.bat ,然后就可以通过 http://localhost:8081、http://localhost:8082 访问对应的项目。

注:service.bat 使用说明

以 project1 为例说明。打开 cmd,切换到 D:\apache-tomcat\project1,输入

  • 注册服务
service install tomcat-project1
  • 删除服务
service remove tomcat-project1

注册服务完后,即可在 Windows 服务中进行启动、停止等操作。

THE END

Apache 代理转发 Tomcat 请求

1.前言

Web 服务器配置时常常会遇到这么个问题:80 端口已经被 Apache 或者 Tomcat 其中之一占用了,另一个怎么使用80端口? 最常见的解决方案是使用 Nginx 作为代理应用服务器,所以本文主要谈的并不是使用 Nginx 来解决这个问题,而是直接使用 Apache 作为代理应用服务器来解决。

2.背景介绍

  • 服务器上原本已经有多个 PHP 站点部署在 Apache 下,使用 80 端口;
  • 现在新增了一个 Java Web 项目,部署在 Tomcat 下, 使用 8080 端口;
  • 两者各自在不同的端口均能正常访问,现在要求 Java Web 项目也要使用 80 端口。

3.Tomcat 配置

Tomcat 无需额外配置,只要如下配置,保证在 IP + port 能正常访问的情况下即可:

Tomcat Server.xml 部分相关配置

<!-- 默认配置,无需修改,检查一下即可 -->
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" /> 
<Host name="localhost"  appBase="webapps" unpackWARs="true" autoDeploy="true">

    <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" prefix="localhost_access_log." suffix=".txt" pattern="%h %l %u %t &quot;%r&quot; %s %b" />
    
    <!-- docBase 配置项目所在绝对路径 -->
    <Context docBase="D:\project\test" path="" reloadable="true" source="org.eclipse.jst.jee.server:test"/>
    
</Host> 

4.Apache 配置

Apache httpd.conf 模块加载

#LoadModule proxy_module modules/mod_proxy.so
#LoadModule proxy_ajp_module modules/mod_proxy_ajp.so
#LoadModule proxy_balancer_module modules/mod_proxy_balancer.so

# 找到上面三个模块,去掉 # 开启模块
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_ajp_module modules/mod_proxy_ajp.so
LoadModule proxy_balancer_module modules/mod_proxy_balancer.so

Apache httpd-vhosts.conf 配置

<VirtualHost *:80>
    ServerName test.shelwee.com
    ProxyPass / ajp://127.0.0.1:8009/ 
    ProxyPassReverse / ajp://127.0.0.1:8009/
    ErrorLog "logs/test.shelwee.com.log"  
    CustomLog "logs/test.shelwee.com.log" combined  
</VirtualHost>

配置完毕后,重启 Apache 服务器,然后开启 Tomcat 服务器。

5.代理过程说明

用户通过浏览器访问 test.shelwee.com 之后,Apache 会接收到这个请求,然后通过 AJP 协议转发到本地 8009 端口上,请求数据就能被 Tomcat 处理后返回。

THE END

UpdateHelper 1.0版本发布

本来预计14号便可将 UpdateHelper 收尾,结果由于临时有事,推迟到昨晚才将代码推送到 Github,然后今晚发出这篇。

不知不觉 UpdateHelper 这个项目已经在 Github 发布了两年多,至今收获 Star 121Fork 60(码云上收获了Star 84, Fork 35)。令我感到高兴的是,期间有不少开发者朋友提了 Issues 给我,以至于现在还能更新。

感谢的同时也欢迎 Pull request。

1

UpdateHelper 近期做了许多重要的更新,所以目前直接将这个版本定为 1.0。这个版本的改动主要有:

  •  从 ADT 转换成 Android Studio 项目
  • 优化部分代码实现逻辑
  • 网络部分全部改为 HttpURLConnection 实现
  • 修复下载过程与通知栏进度条卡顿情况
  • 提示文字全部提取到 strings.xml,方便国际化操作
  • 新增强制升级功能,通过后台接口 JSON 返回 forceUpgrade:true / false

UpdateHelper 1.0 从 ADT 转换成了 Android Studio 项目,因为目前 Android Studio 是开发 Android 的最好开发工具,且 Google 官方也已经不再支持 ADT。

UpdateHelper 从 1.0 开始已适配 Android 6.0 动态权限申请(只对存储权限做适配),但 UpdateHelper 只是简单的实现,起辅助作用,只有当检查到没有存储权限的时候才会弹出权限调用申请。

强烈建议开发者在 APP 中适配动态权限,因为用户有可能误操作拒绝后,而不知道怎么开启,这时就需要开发者引导如何再次开启权限。

权限应当由 APP 主程序配置引导,而不应该依靠任何第三方 Library。

2

UpdateHelper 会持续改善,目前制定的下一步开发计划主要有:

  • 下载过程可控制
  • 支持灰度测试

UpdateHelper 是我业余时间维护的一个开源项目,所以会不定期更新。项目也许还存在许多不足之处,希望开发者朋友们能加以体谅,同时也能加以鞭策。

也再次欢迎大家提交 Issuses,Pull request。

THE END

2017你好,2016再见

故事的开头往往是先从过去讲起,2016 即将成为过去,这一年对我来说是充实的,是我人生当中一个重要的转折点。

婚姻

2016,正如我开头所说的,是的!我结婚了,一辈子一次的那种。一直以为,婚姻对我来说相当遥远,没想到这一天这么快已经过去了。

2017 及余下的人生就是等待与另一半共同书写,一起慢慢变老。

运动

2016,我尝试过多种不同的运动,包括有氧运动跟无氧运动:

  • 骑行
  • 跑步
  • 哑铃
  • 篮球
  • 羽毛球
  • 俯卧撑 / 仰卧起坐

上述种种运动,却只有骑行是我唯一坚持一年多的运动,除了下雨没骑外,几乎每个工作日上下班都是骑车。

粗略算了一下,一天平均 8 公里,一年单单骑车就可以绕地球 1/20 圈,这样想想还是蛮惊人的。

而坚持骑行给我带来的好处也很明显,有氧运动把我多余的脂肪都燃烧了,现在我的体重、体脂率都很稳定的停留在标准水平,我坚信好处不只如此。

2017,我计划继续骑行,继续健身,继续保持有氧+无氧运动结合的方式运动,等下一次体检的时候希望没有一点问题。

足迹

2016,我走过了中国的 5 个城市,不论因公因私,这都丰富了我的视野。

对我来说印象最为深刻的就是《从你的全世界路过》拍摄地——重庆。重庆的地形很有趣,坡地很多,很多地方都是明显的高低起伏,公路上几乎看不到自行车,不由自主地脑补了一下在这里骑车的场景,想想都觉得好累。还有一个就是吃,基本都是面,基本都是麻辣,简直就是麻辣爱好者的天堂。

2017,我计划去更多没去过的城市。

成长

2016,我阅读了几本书,听了几次直播课程,也订阅了几个「得到」专栏,还有无数次的碎片化阅读。不能说这些行为能改变我什么,但至少有在潜移默化的影响我,对我是有帮助的,学习的过程总是在短期内看不到效果,而长期来说又是有助于个人成长的一种行为

2017,我计划继续大量的阅读并输出。

目前我的阅读方式主要为数字化阅读,数字化阅读意味着我经常盯着屏幕看,而且内容不比纸质来的系统。所以之后至少保证一年读 12 本的纸质图书,除了减少盯屏幕的时间外,还能保证系统性阅读。

输出则是为了证明自己已经理解了知识,并且对于所阅读的东西有自己的见解。我的输出渠道则是公众号(现代晓说)、博客简书

总结中的总结

2017 代表着明天,对于明天我永远保持着憧憬。

我的 2017 小目标:

  1. 至少阅读 12 本纸质书籍
  2. 规律的写作,一周写一篇
  3. 运动,一周至少三次
  4. 至少去一个没去过的城市
  5. 给自己的职业生涯做一份规划
THE END