张墨轩的技术宅

不忘初心,方得始终

ADB与普通APP权限简单对比

最近在做一个车联网的项目,其中一个基本需求是将一台android手机通过USB线缆接入到车机上后,手机屏幕远程映射到车机的屏幕上,我们可以通过操作车机的屏幕来远程控制手机。这个技术本质上就是通过类似VNC的方式在车机上远程控制手机。业内有名的车联网方案如apple的carplay还有google的androidauto本质上其实也都是远程映射,另外一个著名的规范MirrorLink就直接定义了利用VNC技术进行映射。
android平台上有很多VNC相关的程序和库,如开源的libvncserver,如有名的droidVncServer项目,它内部其实就是使用了libvncserver库。还有如Remoteroid等等。这些程序作为远程控制中的《被控端》运行到android上都必须要求root权限,原因也很简单就是需要权限。作为VNC技术中的《被控端》有两个基本工作要做:
第一就是读取本机的屏幕buffer传送给《控制端》,这样《控制端》就可以看到《被控端》的屏幕内容了。
第二就是《被控端》要接受来自《控制端》的操作请求,比如点击了屏幕上某个位置,进而控制android做出相应的反应。
这里获取屏幕buffer暂且不聊,主要说说输入控制相关的部分,android是基于linux的,所以输入控制的部分主要是操作/dev/input下面的设备文件。我们来看看android系统/dev/input下面的设备文件的权限。如图:
可见root用户有读写权限,input组下面的用户也有读写权限。而作为android下面的普通的APP是不具备这样的权限的。
下面我们来看看一个普通的APP的权限,如图:
可见这个普通的APP所属用户为u0_a57,所在组也不包括input组。所以对/dev/input下面的设备文件无权操作,那么输入控制功能将无法实现。所以必须要求root权限才行。
但是别家的车联网项目确实不要求android手机已经root,在没有root的android手机上也能实现类似VNC的远程操作。那么这又是为什么呢。答案就在adb上。adb在设备端所对应的的守护进程叫做adbd,在android启动过程中它会被init进程所加载,程序刚运行的时候是root权限,然后它会给自己降权,最终以shell用户权限运行。而shell属于input组,是可以读写/dev/input下面的设备文件的。如图:
所以当用户把android手机通过USB接入到车机上时(为了方便理解这里可以把车机就想象成一台PC电脑),车机通过adb命令通知android手机上的adbd运行起《被控端》程序,这个时候《被控端》运行在shell用户权限,可以正常读写/dev/input。
因为adb本身就是作为android调试桥的作用存在的。所以它实际上也是具备一定权限的,虽然没有root权限那么大,但是比一般普通的APP的权限还是要大一些。像/system/bin/下面提供的一些工具,比如getevent/sendevent,input,screencap,screenrecord等都只有用adb去调用才能正常工作,普通APP去调用是无法正常工作的,因为缺乏权限。 而像screenshot这样的工具通过adb去调用也无法正常工作,因为这个工具内部是直接操作/dev/graphics/fb0设备的,而这个设备连shell权限也无权操作。如图:
 
只有system用户和graphics组才有权操作。那为什么adb调用screencap又可以呢,通过读取源代码可以知道,它的源代码在frameworks/base/cmds/screencap/ ,它内部实际上是调用SurfaceFlinger提供的接口ScreenshotClient,本质上是通过binder机制与SurfaceFlinger进程间通讯罢了。android自带的调试工具DDMS有一个截图功能,其实也是通过adbd调用screencap完成截图的。


VR,AR之我见

近两年VR,AR等技术越来越火,像微软的Microsoft HoloLens眼镜这样的产品,依靠手势识别,语音识别等交互技术,加上真三维的视觉冲击,可以说把我们和未来一下子拉近了很多。那站在我们程序员的角度,如何看待这样的变革呢。我想估计以后至少会有两种类型的应用。
第一就是传统的二维应用:我们现在开发的PC程序,手机APP等,不管是阅读为主的新闻APP还是大型3D游戏程序,本质上还是看成一个二维程序,为什么这样说呢,因为它们最终需要一个二维的屏幕给显示出来,它们以后在类似Microsoft HoloLens这样的环境中如何表现出来呢?那就是在真三维环境中虚拟出一块屏幕,然后把APP的内容显示到这个虚拟的屏幕上,就像在真实的屏幕中显示一样。如下图:
这种模式下利用手势识别模拟点击和滑动等操作,这样可以将现在传统的APP,全部照搬到真三维环境中。对我们程序员来说可能不需要做太多工作。
第二种就是真三维应用,下面两张图一个是电影《普罗米修斯》,一个是游戏《星际争霸2》中的截图,表现的就是这种类型的应用,做这种应用估计得懂几何学,图形学,机器视觉,DirectX,OpenGL,向量,矩阵变换,顶点,纹理,多边形。哈哈哈,努力学习,天天向上。


微信公众号及其账号授权模式的一些发散思考

用户在使用app前碰到最大的二大问题:
1. APP需要从市场上下载到手机,需要占用流量。
2. 安装完毕后,一般需要注册,才能使用完整的功能。

下载安装这个就不需要多讲了,那为什么要注册呢?注册的目的主要是有2点:

第一:最基础的一点就是用于唯一的标识一个客户,厂家可以知道是有不同的人在进行操作,虽然不知道具体是谁,不知道他的具体情况,但是确实是有这么一个人。
第二:可以在第一点的基础上,记录这个人的各种详细信息,以便于对他进行服务,比如电商系统,可以记录某个人手机是多少,叫什么,住哪里,这样可以为他送货,也可以记录他的购买历史等等。

现在微信公众号的推出,作为一种轻量级app,其实就是解决了一开始说的2个大问题,下载和注册。用户只需要进行关注,这样比下载成本低很多,也不需要刻意去做登录这一步,因为微信本身就已经是登录状态了,只需要把这个登录的账号做网页授权就可以了,获得的openid对这个公众号是唯一的,可以作为一个用户的唯一凭证。当然仅仅只知道一个openid是不够的,它只解决了用户识别问题,至于其他的用户详细信息部分就在软件的使用过程中逐步引导客户去填写,比如我是电商系统我可以在用户下单的时候,要求用户填入手机和姓名和地址等,因为这是理所当然的,不然怎么送货呢。

在需要登录的时候,一般可以如下方式:

1.微信授权登录 获取openid和一些其他信息 算是登录了。
2.自定义登录,又可以分出以下几种方式:
一:邮箱或者手机号码 发验证码登录 不需要固定密码
二:用邮箱或者手机绑定固定密码,以后登录直接用固定密码登录。

当获得手机或者邮箱或者一个openid的时候 就都算登录了。 因为这三者每一个都能唯一表示一个用户,用户的其他的信息,可以在随后的使用中逐步引导客户去输入。

比如京东PC站点有一种不需要登录一次性购买的行为,就是你可以不用登录,看完商品 然后在最后下单的时候 京东会要求你输入手机和送货地址 并且验证你的手机,然后就下单了。这样看上去好像是不需要登录直接购买,实际上是变相的方式引导客户进行了注册而已。所以方式很重要。

账号体系是如此之重要,所以BAT等都在争抢。所以我们也可以看到各种授权登录模式,比如微信授权登录,QQ授权登录,支付宝授权登录等等。

微信开放平台的核心思想就是账号授权给其他app免去注册这个坎。


小蚁直递

小蚁直递是我2015年做的一个社区电商领域的项目,虽然一直没什么起色,不过还是记录下来,自勉之。


Xdebug调试PHP原理浅析

用很多工具写过PHP,最后还是发现PhpStorm最好用,同时用PhpStorm搭配Xdebug调试PHP也很舒服。至于如何配置Xdebug网上已经有了太多文章可以参考,这里不再说明,本文只是简单的记录一下Xdebug调试PHP的原理和过程。

要进行调试有三个必要组件:

组件一:实现了DBGP调试协议的调试插件,这个PhpStorm默认就已经包含了,设置里面也可以看到相关设置,如图:
通过点击导航栏上电话一样的按钮(如下图),PhpStorm会开启9000端口进行监听。
通过进程信息可以清楚的看到PhpStorm已经在监听9000端口。如下图:
DBGP协议介绍地址:

组件二:PHP的Xdebug扩展。下载地址为:
Xdebug作为PHP扩展模块将会被PHP所加载,加载成功后可以通过 php -m 命令 或者 phpinfo() 函数查看。如下图:
php.ini文件中的配置示例:

[Xdebug]
zend_extension=E:\php-5.4.8-Win32-VC9-x86\ext\php_xdebug-2.4.0rc3-5.4-vc9.dll
xdebug.profiler_enable=on
xdebug.trace_output_dir="../xdebug"
xdebug.profiler_output_dir="../xdebug"
xdebug.idekey=PhpStorm
xdebug.remote_handler=dbgp
xdebug.remote_enable=on

组件三:Chrome浏览器的xdebug helper扩展。安装地址(记得翻墙):

当把上面三大必要组件都配置好后(具体配置网上很多文章都有详细介绍),就可以开始调试了。具体的调试原理如下:
1. 运行WEB服务器(如Apache),Apache会加载PHP,PHP会加载Xdebug扩展模块。
2. WEB浏览器(如Chrome)会通过80端口访问 WEB服务器中的某个PHP文件
3. WEB服务器会调用PHP解释器解析PHP文件,如果开启调试的话,PHP会通过Xdebug(根据php.ini中的Xdebug配置)主动连接调试客户端,默认是localhost:9000,如图:
也就是说 PHP会通过Xdebug扩展模块主动连接上PhpStorm正在监听的9000端口,从而PHP会与PhpStorm建立起调试会话通讯,这样程序员就可以通过PhpStorm对PHP程序进行各种调试交互动作了,比如查看变量值,比如下断点等等。
4. 当PHP的代码执行完毕以后,Xdebug会断开与PhpStorm的连接,从而结束本次调试会话过程。

交互流程如下:

browser <==> Port 80 <==> Apache+PHP+Xdebug <==> Port 9000 <==> PhpStorm(DBGP Plugin)

文章写到这里,可能细心的朋友就发现了。Chrome浏览器的xdebug helper扩展 这个部分一直没有提起。不是说它是三大必要组件之一吗!确实,从操作体验上来看用Chrome浏览器配合xdebug helper扩展确实会体验好很多,但是从纯技术角度来讲,它确实不是必须的,因为你想啊,PHP是一门服务端语言,调试也是服务端调试,怎么需要某个特定的浏览器呢?难道只能用Chrome浏览器,其他的浏览器不行吗,这个从技术角度来讲说不过去啊。事实上确实与特定浏览器没有关系,Chrome浏览器的xdebug helper扩展实际作用其实只是设置了一个标志在cookie中,当浏览器访问服务端时,这个cookie值会被传到服务端,这样Xdebug读取到这个值后就知道本次请求用户是想要开启调试功能,它才会连接调试客户端建立调试会话。
上图就是浏览器传给服务端的cookie值,意味着要开启调试。

所以简单来讲,不管用的是什么浏览器,其实只要设置对了相应的cookie值,就可以开启调试。在我这个示例当中还可以直接利用下面的代码来开启调试:
<div id="xdebug_bookmarklets" class="hide" style=" displayblock" >
    <h3>Bookmark these links for future use </h3>
    <h4>Debug: </h4>
    <ul class= "starlist" >
        <li>< a id="xdebugStartSession" href="javascript:(/** @version 0.5.2 */function() {document .cookie='XDEBUG_SESSION='+'PHPSTORM'+';path=/;';})()"> Start debugger</a></li>
        <li>< a id="xdebugStopSession" href="javascript:(/** @version 0.5.2 */function() {document .cookie='XDEBUG_SESSION='+''+';expires=Mon, 05 Jul 2000 00:00:00 GMT;path=/;';})()"> Stop debugger</a></ li>
        <li>< a id="xdebugThisPage" href="javascript:(/** @version 0.5.2 */function() {document .cookie='XDEBUG_SESSION='+'PHPSTORM'+';path=/;';document. location.reload(); document.cookie='XDEBUG_SESSION='+''+';expires=Mon, 05 Jul 2000 00:00:00 GMT;path=/;';})()" >Debug this page </a></li>
    </ul>
    <h4>Profile: </h4>
    <ul class= "starlist" >
        <li>< a id="xdebugStartProfiler" href="javascript:(/** @version 0.5.2 */function() {document .cookie='XDEBUG_PROFILE='+'1'+';path=/;';})()"> Start profiler</a></li>
        <li>< a id="xdebugStopProfiler" href="javascript:(/** @version 0.5.2 */function() {document .cookie='XDEBUG_PROFILE='+''+';expires=Mon, 05 Jul 2000 00:00:00 GMT;path=/;';})()"> Stop profiler</a></ li>
    </ul>
    <h4>Function Trace: </h4>
    <ul class= "starlist" >
        <li>< a id="xdebugStartTracer" href="javascript:(/** @version 0.5.2 */function() {document .cookie='XDEBUG_TRACE='+'1'+';path=/;';})()"> Start tracer</a></li>
        <li>< a id="xdebugStopTracer" href="javascript:(/** @version 0.5.2 */function() {document .cookie='XDEBUG_TRACE='+''+';expires=Mon, 05 Jul 2000 00:00:00 GMT;path=/;';})()"> Stop tracer</a></ li>
    </ul>
</div>
本质就是设置一个cookie值而已。
当然其实还有个更简单的办法 就是直接在请求的url后面连上一个&XDEBUG_SESSION_START=1,这样也开启了调试。

其实不单单是PhpStorm,其他编辑器如 vim,notepad++等编辑器装上DBGP调试插件后也都能进行PHP调试,原理流程也和上文介绍的一样,只是调试客户端不同而已。
下面是notepad++的DBGP调试插件的下载地址:
下面是vim插件下载地址:


APK【Volume Power】逆向全过程

之前对音量键点亮屏幕比较感兴趣就分析了一个这样功能的APP,现在又分析了另一个名叫Volume Power的APP,感觉比较坑爹,权且当学习吧。我们开始,先看看包结构,如图:
文件很少,非常简单,唯一一个可疑的是empty.mp3文件,这个文件稍后分析中会提到。运行后主界面如下:
就是一个设置界面,有三个设置选项:
一:启用还是禁用音量键点亮屏幕
二:是否启动运行
三:通过通知栏的按钮关闭屏幕
接下来正式开始分析,这里只会讲重点部分,其他可以自己看代码。按惯例先看AndroidManifest.xml文件内容:
这里注册了几个广播接收器,还有一个服务。其中:
android.media.VOLUME_CHANGED_ACTION 会在调节音量的时候被触发。
android.intent.action.SCREEN_OFF 在关闭屏幕的时候触发
android.intent.action.SCREEN_ON   在点亮屏幕的时候触发
android.app.action.DEVICE_ADMIN_ENABLED 将程序设置成Android设备管理器或者取消的时候被触发
android.intent.action.BOOT_COMPLETED 系统启动后自动运行
其他都先不关注,按程序执行流程先看com.example.stayawake.BootReceiver,它主要是创建一个线程并且执行,如图:
那么线程具体做了什么,接着看 BootReceiver$1类,这个是个匿名类,如图:
这里注解的很清楚,这是一个内部类,没有名字,它存在于BootReceiver.onReceive中,线程主要就是运行起了SoundService服务:
然后我们来看看这个服务,这个服务最主要的作用就是做了一件坑爹的事,如图:
就是不停的循环播放empty.mp3文件,而empty.mp3实际上是一个空的没有声音的文件,所以用户并不知道在播放声音,但是后台确实一直在播放,那就意味着运行了这个程序后,电量将飞快的被消耗掉,真是坑爹啊。那为什么要这样做呢,主要目的就是保持这个APP一直存在于内存中不被kill掉,如果被kill了功能就丧失了,因为在Android系统内存低时系统会根据一些策略kill掉一些进程来腾出内存,所以作者这里用了这样一种坑爹的方式来保证APP能一直运行。
接下来当用户点击音量键的时候,android.media.VOLUME_CHANGED_ACTION所对应的广播接收器VolumeReceiver.onReceive会被执行,然后事情就简单了:
直接调用系统的电源管理器点亮屏幕。另外程序为了能有关闭屏幕的权限会在设置界面将自己提升为Android设备管理器:
好了,基本就是这样,这个APP非常简单也没什么太多要说的,具体的可以自行查看源代码。
全部代码已经上传到github上,源代码的github地址:
https://github.com/phonegapX/com.teliapp.powervolume


特殊字符转义的一点体会

在编程的过程中经常会碰到一些特殊字符需要进行字符转义才能使程序正常运行,那为什么要进行字符转义才能使程序正常运行呢,我们举几个例子:

例子一:
比如在C语言中 我们想定义一个字符串如 hello,"moses" 那么如果写成 
char *str = "hello,"moses",are you ok"; 
那么程序铁定出错,编译不过,因为双引号"在C语言中是作为定义字符串开始和结束的一个特殊符号来用的,C编译器遇到上面这样的代码直接晕倒,那么必须像下面这样写:
char *str = " hello,\"moses\",are you ok";  
加上转义符号\才能让C编译器正确进行认知,简单来说就是,对于C编译器来讲,在字符串中【\"】才等于一个双引号【“】。
例子二:
比如在JS语言中我们定义一个JSON 如
{"content" : "hello,"moses",are you ok"}
同样JS解析引擎也会出错,因为双引号"在JS中也是作为定义字符串开始和结束的一个特殊符号来用的,那么必须像下面这样写:
{"content" : "hello,\"moses\",are you ok"}
加上转义符号\才能让JS解析引擎正确进行认知,简单来说就是,对于JS解析引擎来讲,在字符串中【\"】才等于一个双引号【“】。
例子三:
比如在SQL中,这里假设是MYSQL,我们定义一个SQL语句:
select * from mytable where content like 'hello,'moses',are you ok';
SQL解析引擎会出错,因为单引号'在SQL中是做为字符串开始和结束的一个特殊符号来用的,必须像下面这样写:
select * from mytable where content like 'hello,\'moses\',are you ok';
加上转义符号\才能让SQL解析引擎正确进行认知,简单来说就是,对于SQL解析引擎来讲,在字符串中【\'】才等于一个单引号【’】。
例子四:
比如在HTML中,我们定义如下一个标签:
<input type="text" value="hello,"moses",are you ok">
大家可以在浏览器中试一试,输出会被截断,并不能完整的输出 hello,"moses" 这样的字符串,因为双引号在这里是用来定义标签属性的,所以必须像下面这样写:
<input type="text" value="hello,&quot;moses&quot;,are you ok">
这里需要注意的是HTML里面的转义符号是&,而不是符号\。在这个例子上为了在标签属性中表示一个双引号【"】必须要用【&quot;】,当然也可以用【&#34;】来表示,前面那种叫实体名称,后面这种叫实体编号,具体请自行google或者baidu。这里需要强调的是在HTML中是否转义需要根据上下文来,比如:
<html>
<body>
<input type="text" value="hello,&quot;moses&quot;,are you ok">
hello,"moses",are you ok
</body>
</html>
这段代码在浏览器中是能正确显示的,因为第二处字符串并没有和任何特殊字符有冲突。当然如果你一定要转义表示那也没任何问题。如下:
<html>
<body>
<input type="text" value="hello,&quot;moses&quot;,are you ok">
hello,&quot;moses&quot;,are you ok
</body>
</html>
你会发现它在浏览器中也能正确显示,并且显示和前面的一模一样。
这里举了四个例子,总结起来,其实都是一次人机交互的过程,这里的“机器”分别指的是【C编译器】【JS解析引擎】【SQL解析引擎】【HTML解析引擎】,当我们“人”想告诉“机器”,比如告诉【JS解析引擎】我要输入的是一个双引号时,因为双引号对【JS解析引擎】有特殊含义,所以我们必须输入【\"】这样的字符序列给到【JS解析引擎】,这样【JS解析引擎】才会明白我们“人”其实只是想输入一个双引号【"】。
人=>输入序列\"=>JS解析引擎=>理解成=>双引号"
这里需要重点强调的就是关于转义字符问题对于不同的开发语言有着不同的规则,虽然也有很多相同的地方,但是不能预先做假设,需要查阅相关文档。
另外在实际开发过程中经常是各种语言混合开发的,比如做一个网站最常见的形式是 服务端用PHP+MySQL开发,客户端是HTML+JS。服务端PHP代码里面可能会包含SQL语句,而客户端的HTML代码里面可能也会混编入JS代码。那么这个时候我们就必须要有非常清晰的认知,对于一段包含有特殊字符的字符序列来说什么时候由【PHP解析引擎】处理,什么时候由【SQL解析引擎】处理,什么时候由【HTML解析引擎】处理,什么时候由【JS解析引擎】处理,必须搞的明明白白,否则就可能会发生莫名其妙的错误。我们来看一次简单的网页输出过程,很可能是:
【PHP解析引擎】=>sql语句序列=>【SQL解析引擎】=>返回各种结果字符序列=>【PHP解析引擎】=>网络输出给浏览器=>【HTML解析引擎】=>js代码字符序列=>【JS解析引擎】=>执行结果字符序列=>【HTML解析引擎】=>电脑屏幕
简单来说可以理解成一种引擎的输出很可能是作为另一种引擎的输入。其实这也符合计算机系统的基本概念,计算机系统本质上就是输入+处理+输出。
这里我们举几个例子:
我们先来看看例子一,这是一段HTML+JS混编代码,代码如下:
<html>
<body>
<input type="button" value="button" onclick="alert("hello,"moses",are you ok");">
</body>
</html>
这段代码包含了HTML代码也包含了JS代码,这段代码的功能是当点击按钮会弹出一个对话框,对话框的内容是:hello,"moses",are you ok
但是当点击按钮的时候,铁定出错,肯定不会弹出对话框,这么多双引号,估计都晕了,哈哈。既然点击按钮执行的是JS代码,那么首先肯定是想当然的利用JS语言的转义规则进行处理,代码如下:
<html>
<body>
<input type="button" value="button" onclick="alert(\"hello,\"moses\",are you ok\");">
</body>
</html>
结果还是不行,而且错得一塌糊涂,看样子想当然是不行的了,还的仔细捋一捋把来龙去脉搞清楚才能解决。首先我们必须得先明白,上面的代码是首先被【HTML解析引擎】解析后才会交给【JS解析引擎】处理,那么看看上面的代码被【HTML解析引擎】解析后是个什么样子,因为符号\并不是【HTML解析引擎】的转义符,所以最后交给JS执行的代码是alert(\,不出错才怪。既然是先被【HTML解析引擎】解析,那么我们肯定得先用HTML的转义规则来处理,修改后的代码如下:
<html>
<body>
<input type="button" value="button" onclick="alert(&quot;hello,&quot;moses&quot;,are you ok&quot;);">
</body>
</html>
可惜的是这样修改后还是不能正常工作,那是为什么呢,仔细分析一下就可以得知,这段代码被【HTML解析引擎】解析出来的JS代码实际如下:
alert(“hello,”moses“,are you ok”);
这行代码交给【JS解析引擎】后肯定会出错,原因就是中间那两个双引号,必须要按JS语言的规则转义,应该修改成下面这样子:
alert(“hello,\”moses\“,are you ok”);
这样才能正常工作,那么现在我们把HTML和JS两种规则结合起来处理后,得到了最终如下的整合代码:
<html>
<body>
<input type="button" value="button" onclick="alert(&quot;hello,\&quot;moses\&quot;,are you ok&quot;);">
</body>
</html>
至此,程序终于能按照期望运行了。当然实际上我们可以通过双引号和单引号配合使用来解决问题,但是这里我们为了很好的说明问题的来龙去脉,所以不会考虑其他途径。
接下来看看例子二,这是一段PHP+SQL混编代码,为了更好的说明原理和问题,这里全部使用单引号,代码如下:
<?php
$con = mysql_connect('localhost','root','123456');
mysql_select_db('db_test', $con);
mysql_query('INSERT INTO T_Test (F_Content) VALUES ('hello,'moses',are you ok')');
mysql_close($con);
?>
代码的目的是将字符序列 hello,'moses',are you ok 插入数据库中,但是上面这代码肯定是错的,因为对于【PHP解析引擎】来说单引号是作为定义字符串开始和结束的一个特殊字符,所以这里肯定必须进行转义处理,而PHP也是用符号\来作为转义符的,所以我们将代码改成如下样子:
<?php
$con = mysql_connect('localhost','root','123456');
mysql_select_db('db_test', $con);
mysql_query('INSERT INTO T_Test (F_Content) VALUES (\'hello,\'moses\',are you ok\')');
mysql_close($con);
?>
不幸的是,这段代码还是无法正常工作,那是为什么呢,我们来分析一下原因。首先要说的是这段代码是先交给【PHP解析引擎】解析后再把其中的SQL语句交给MySQL的【SQL解析引擎】来处理,有了这个前提后就比较好分析了,我们先来看看通过【PHP解析引擎】解析后出来的代码是什么样子:
解析之前:
INSERT INTO T_Test (F_Content) VALUES (\'hello,\'moses\',are you ok\')
解析之后:
INSERT INTO T_Test (F_Content) VALUES ('hello,'moses',are you ok')
然后解析之后的SQL语句会交给【SQL解析引擎】来处理,答案很明显了,肯定出错,因为在【SQL解析引擎】中单引号也是定义字符串开始和结束的特殊符号。那么这里要重点强调的是:
【PHP解析引擎】解析是没问题的,但是在【SQL解析引擎】解析的时候出错了。
然后接下来我们继续修改代码:
<?php
$con = mysql_connect('localhost','root','123456');
mysql_select_db('db_test', $con);
mysql_query('INSERT INTO T_Test (F_Content) VALUES (\'hello,\\'moses\\',are you ok\')');
mysql_close($con);
?>
一运行,代码还是出错,这又是为什么呢,那是因为中间\\'这样的语法出现了问题,前面那个\将后面的\给转义了从而失去了转义的特性导致单引号没被转义,所以直接在【PHP解析引擎】解析的时候就出错了,那么这里要重点强调的是:
这次是【PHP解析引擎】解析的时候就已经出问题了,压根和【SQL解析引擎】没啥关系。
只能继续修改代码了,修改后的代码如下:
<?php
$con = mysql_connect('localhost','root','123456');
mysql_select_db('db_test', $con);
mysql_query('INSERT INTO T_Test (F_Content) VALUES (\'hello,\\\'moses\\\',are you ok\')');
mysql_close($con);
?>
这次终于成功了,顺利执行完毕,数据也正常写入成功。我们来分析一下这次为什么可以:
【PHP解析引擎】解析前的代码:
INSERT INTO T_Test (F_Content) VALUES (\'hello,\\\'moses\\\',are you ok\')
【PHP解析引擎】解析后的代码:
INSERT INTO T_Test (F_Content) VALUES ('hello,\'moses\',are you ok')
然后再把这句SQL交给【SQL解析引擎】解析,根据转义规则,【SQL解析引擎】也非常清楚的明白要写入的数据里面包含有2个单引号,而不会发生冲突,最终数据终于成功的写入了。
如图:
最后要说的是在实际开发中其实有很多办法可以解决类似的问题,比如单引号和双引号配合使用,比如magic_quotes_gpc=on,addslashes和stripslashes,mysql_real_escape_string等等函数都能比较好的解决问题。
好了,具体的技术细节就说到这里了,那么接下来我们从宏观架构的角度来简单讨论一下这个话题。在项目的实际开发中要考虑的转义字符不单单只有引号,还有诸如<,>,&等等很多很多,这里只讨论几个最常见的。这些转义字符如果不做处理,可能会导致很大的安全隐患,比如古老的SQL注入技术,比如XSS攻击,这里举个最简单的例子,比如在一个文章发布系统里面不做任何特殊处理,那么有人在提交的文章里面加入一个<script src='x.js'>这样的标签那就麻烦大了,所以要做处理。那么到底什么时候做处理呢,我们先看一幅图:
这是现在最为常见的系统架构,一个软件可能会有不同平台的客户端,大家都统一通过RESTfulAPI进行通讯,一个典型的例子就是微信,它有PC端,Android端,IOS端,WEB端等等。
我们在设计这样的系统的时候 RESTfulAPI可能会用PHP,Java,NodeJS等语言开发,Android端开发有Java,IOS上有Objective-C,PC上用C/C++,.Net,Delphi等等,WEB端有HTML+JS。
你会发现这么多语言各有各的规则,所以最好的办法就是在保证安全的前提下输入的数据不做转换直接进入到数据库,输出的数据也不做转换直接给到客户端,然后各种客户端都根据自己的实际情况自行处理,这里打个简单的比方: 
比如用户输入一段字符序列 【hello<hr>"<br>】,那么最终进入数据库的也要是同样的序列,而不应该是【hello&lt;hr&gt;&quot;&lt;br&gt;】,因为这样的转换HTML认得,但是其他客户端比如C/C++就不认得,就会造成各种困扰,所以最终的处理过程应该留给各个客户端自行处理。


五种MySQL管理工具简单介绍

我在弄MySQL的过程中接触到了一些管理工具,这里把它们作为笔记记录下来,因为自己搞的东西实在太多太杂,要是不做笔记搞不好几年后又都忘记啦,哈哈哈。这里不对这些工具做任何评论。什么工具好用,大家仁者见仁,智者见智。
一:Navicat for MySQL
Navicat for MySQL 是一套管理和开发 MySQL 或 MariaDB 的理想解决方案。它使你以单一程序同时连接到 MySQL 和 MariaDB。这个功能齐备的前端软件为数据库管理、开发和维护提供了直观而强大的图形界面。
官方地址为:http://www.navicat.com.cn/products/navicat-for-mysql
下面是它的软件截图:
二:SQLyog
SQLyog是业界著名的Webyog公司出品的一款简洁高效、功能强大的图形化MySQL数据库管理工具。使用SQLyog可以快速直观地让您从世界的任何角落通过网络来维护远端的MySQL数据库。
官方地址为:https://www.webyog.com/
下面是它的软件截图:
三:MySQL Workbench
MySQL Workbench是一款专为MySQL设计的ER/数据库建模工具。它是著名的数据库设计工具DBDesigner4的继任者。你可以用MySQL Workbench设计和创建新的数据库图示,建立数据库文档,以及进行复杂的MySQL 迁移。
官方地址为:http://dev.mysql.com/downloads/workbench/
下面是它的软件截图:
四:phpMyAdmin
phpMyAdmin怕是大家最熟悉的一款管理工具了,它是由php开发的一套开源的web端管理工具,通过web方式控制和操作MySQL数据库。
官方地址为:http://www.phpmyadmin.net/
下面是它的软件截图:
五:MySQL自带的命令行工具
MySQL自带的命令行工具,虽然使用起来没有图形工具那么直观,但是如果熟悉MySQL命令的话,其实也挺不错。
下面是它的软件截图:


云之讯融合通讯开放平台Nodejs版本SDK

最近用到云之讯平台的短信和语音验证码功能,因为没有在他们官网上发现nodejs版本的SDK,所以我按照他们的PHP版本实现了一个nodejs版本的SDK供大家使用。原版PHPSDK也一并上传了,方便大家对比,另外就是XML协议部分并未实现。

在您的nodejs项目中用 npm install ucpaas-sdk --save 命令进行安装。

示例代码如下:
var ucpaasClass = require('ucpaas-sdk/lib/ucpaasClass');
var options = {
    accountsid: 'XXXX193c69eaXXXXXXbe89017fcXXXXX',
    token: 'XXXXXXdfe88a37XXXXXX288ccaXXXXXX'
};
var ucpaas = new ucpaasClass(options);

//开发者账号信息查询
ucpaas.getDevinfo(function (status, responseText) {
    console.log('code: ' + status + ', text: ' + responseText);
});

//语音验证码
var appId = 'XXXXXX2fd25eXXXXb8XXXbaXXXXXXX7a';
var verifyCode = '1256';
var to = '18612345678';
ucpaas.voiceCode(appId, verifyCode, to, function (status, responseText) {
    console.log('code: ' + status + ', text: ' + responseText);
});

//短信验证码
var appId = 'XXXXXX2fd25eXXXXb8XXXbaXXXXXXX7a';
var to = '18612345678';
var templateId = '16021';
var param = '1256,5';
ucpaas.templateSMS(appId, to, templateId, param, function (status, responseText) {
    console.log('code: ' + status + ', text: ' + responseText);
});

//双向回拨
var appId = 'XXXXXX2fd25eXXXXb8XXXbaXXXXXXX7a';
var fromClient = '63314039999129';
var to = '13412345678';
var fromSerNum = '4008800800';
var toSerNum = '18612345678';
ucpaas.callBack(appId, fromClient, to, fromSerNum, toSerNum, function (status, responseText) {
    console.log('code: ' + status + ', text: ' + responseText);
});

//申请client账号
var appId = 'XXXXXX2fd25eXXXXb8XXXbaXXXXXXX7a';
var clientType = '0';
var charge = '0';
var friendlyName = '';
var mobile = '13412345678';
ucpaas.applyClient(appId, clientType, charge, friendlyName, mobile, function (status, responseText) {
    console.log('code: ' + status + ', text: ' + responseText);
});

//释放client账号
var appId = 'XXXXXX2fd25eXXXXb8XXXbaXXXXXXX7a';
var clientNumber = '63314039999129';
ucpaas.releaseClient(clientNumber, appId, function (status, responseText) {
    console.log('code: ' + status + ', text: ' + responseText);
});

//分页获取Client列表
var appId = 'XXXXXX2fd25eXXXXb8XXXbaXXXXXXX7a';
ucpaas.getClientList(appId, '0', '100', function (status, responseText) {
    console.log('code: ' + status + ', text: ' + responseText);
});

//以Client账号方式查询Client信息
var appId = 'XXXXXX2fd25eXXXXb8XXXbaXXXXXXX7a';
ucpaas.getClientInfo(appId, '63314039999129', function (status, responseText) {
    console.log('code: ' + status + ', text: ' + responseText);
});

//以手机号码方式查询Client信息
var appId = 'XXXXXX2fd25eXXXXb8XXXbaXXXXXXX7a';
ucpaas.getClientInfoByMobile(appId, '18612345678', function (status, responseText) {
    console.log('code: ' + status + ', text: ' + responseText);
});

//应用话单下载
var appId = 'XXXXXX2fd25eXXXXb8XXXbaXXXXXXX7a';
ucpaas.getBillList(appId, 'week', function (status, responseText) {
    console.log('code: ' + status + ', text: ' + responseText);
});

代码已经提交到github上,您可以在如下地址访问到:
https://github.com/phonegapX/ucpaas

同时也已经发布到了npmjs.com上,可以直接在node中用 npm install ucpaas-sdk 命令进行安装。

这次具体改写过程也比较简单,没有太多好说的,唯一一个小知识点就是如何用JS中的闭包来模拟php类中的私有变量和私有函数
比如php中:
<?php
class Ucpaas
{
    private $timestamp;
    public function __construct()
    {
        $this->timestamp = 100;
    }
    private function getResult()
    {
        return $this->timestamp;
    }
    public function getDevinfo()
    {
        return getResult();
    }
}
?>
这个PHP类包含了一个私有变量$timestamp,一个构造函数,一个public函数,一个private函数,那么对应JS怎么写比较好呢,如下:
(function () {
    //模拟PHP中的私有变量
    var timestamp;
    //构造函数
    function Ucpaas() {
        timestamp = 100;
    }
    //模拟private函数
    function getResult() {
        return timestamp;
    }
    //模拟public函数
    Ucpaas.prototype.getDevinfo = function() {
        return getResult();
    };
    //导出类
    module.exports = Ucpaas;
}());

这样就很好的利用了JS中闭包的特性来模拟了一个PHP类。

最后附上云之讯的网址:
http://www.ucpaas.com/


APK【解放电源键 1.6.0】逆向全过程

android手机在双击点亮屏幕功能出来以前,为了点亮屏幕得频繁的使用电源键,时间长了电源键容易损坏,所以聪明的人们就开发出用音量键来点亮屏幕的apk程序,安装这样的apk后,点击音量+键或者音量-键都可以点亮屏幕,这样就大大提高了电源键的使用寿命。最近突然对这类功能有点好奇,所以利用空闲时间研究了其中一个apk,叫做【解放电源键 1.6.0】,看了软件的关于信息,作者叫王龙,向他致敬。另外此程序需要root权限支持。首先来看看包结构,如图:
其中比较可疑的是assets/athena.dat文件,经证实这是一个可执行文件,只是取了一个.dat的后缀名迷惑人而已,利用ps查看进程可以得知,如图:
另外利用cat查看内存布局也确定lib/armeabi/librpkjni.so文件也已经被加载,如图:
那么到此分析的重点就在classes.dex,librpkjni.so,athena.dat三个文件上面。
对于dex文件有三个比较好的工具进行反汇编。
一个是 baksmali 或者直接用apktool(内部集成了baksmali ),它会将dex反汇编成smali文件。其实就是文本文件可以直接查看,我用设置了语法高亮的Notepad++查看。效果如图:
二个是用JEB,这是个收费软件 官方网站以前是http://www.android-decompiler.com/ 后来改成了https://www.pnfsoftware.com/ 效果如图:
三是用IDA,IDA Pro从6.1版本开始支持Android。包括Dalvik指令集的反汇编、原生库(ARM/Thumb代码)的反汇编、原生库(ARM/Thumb代码)的动态调试等,IDA 6.6新添加了对dex文件的动态调试支持,具体可以查阅相关文档。效果如图:

这三种工具对Dalvik指令的语法解析上都有些许不同,至于选择何种工具,可以根据自己的喜好来。 而对于librpkjni.so和athena.dat这2个原生程序的反汇编那毫无疑问肯定是用IDA了。
接下来正式进入流程,这里我们只关心关键点,至于其他的就不做过多说明了。通过分析AndroidManifest.xml文件可以得知入口activity是 com.wujianai.rpk.activity.Main,另外有一个服务
是 com.wujianai.rpk.service.RPKService。先看 com.wujianai.rpk.activity.Main的onCreate函数:

.method public onCreate(Bundle )V
          .registers 8
          .param p1, "savedInstanceState"
          .prologue
////////////////////////////////////////////////////
代码省略。。。。。。
////////////////////////////////////////////////////
000000A0  iget-object              v3, p0, Main->serviceIntent :Intent
000000A4  iget-object              v4, p0, Main->serviceConnection :ServiceConnection
000000A8  const/4                  v5, 0x1
000000AA  invoke-virtual           Main->bindService (Intent, ServiceConnection, I)Z, p0, v3, v4, v5
000000B0  invoke-direct            Main->hasRoot ()Z, p0
000000B6  move-result              v3
000000B8  if-nez                   v3, :C2
:BC
000000BC  invoke-direct            Main->doNoRoot ()V, p0
:C2
000000C2  return-void
.end method
这里会启动RPKService服务,同时会调用hasRoot函数检查是否有root权限。
来看看hasRoot函数是如何检查是否有root权限的:
.method private hasRoot()Z
          .registers 8
00000000  const/4                  v6, 0x0
          .prologue
00000002  const/16                 v4, 0x400
00000006  new-array                v0, v4, [C
:A
          .local v0, buf:[C
0000000A  invoke-static            Runtime->getRuntime ()Runtime
00000010  move-result-object       v4
00000012  const-string             v5, "su -c ls"
00000016  invoke-virtual           Runtime->exec(String )Process, v4, v5
0000001C  move-result-object       v2
          .local v2, exec:Ljava/lang/Process;
0000001E  new-instance             v3, InputStreamReader
00000022  invoke-virtual           Process->getErrorStream ()InputStream, v2
00000028  move-result-object       v4
0000002A  invoke-direct            InputStreamReader-><init> (InputStream)V, v3, v4
          .local v3, r:Ljava/io/InputStreamReader;
00000030  invoke-virtual           InputStreamReader->read([C)I, v3, v0
:36
00000036  move-result              v4
00000038  const/4                  v5, 0xFFFFFFFFFFFFFFFF
0000003A  if-ne                    v4, v5, :42
:3E
0000003E  const/4                  v4, 0x1
:40
00000040  return                   v4
:42
00000042  move                     v4, v6
00000044  goto                     :40
:46
00000046  move-exception           v4
00000048  move-object              v1, v4
          .local v1, e:Ljava/io/IOException;
0000004A  move                     v4, v6
0000004C  goto                     :40
          .catch IOException {:A .. :36} :46
.end method

原来是看"su -c ls"命令能否执行成功来判断的:)
 
接下来看看RPKService服务启动后会干些什么:
.method public run()V
          .registers 6
          .prologue
00000000  new-instance             v1, StringBuilder
00000004  invoke-direct            StringBuilder-><init> ()V, v1
:A
          .local v1, res:Ljava/lang/StringBuilder;
0000000A  invoke-static            Runtime->getRuntime ()Runtime
00000010  move-result-object       v2
00000012  new-instance             v3, StringBuilder
00000016  const-string             v4, "su -c ./"
0000001A  invoke-direct            StringBuilder-><init> (String)V, v3, v4
00000020  iget-object              v4, p0, RPKService$3->val$file :File
00000024  invoke-virtual           File->getAbsolutePath ()String, v4
0000002A  move-result-object       v4
0000002C  invoke-virtual           StringBuilder->append (String) StringBuilder, v3, v4
00000032  move-result-object       v3
00000034  invoke-virtual           StringBuilder->toString ()String, v3
0000003A  move-result-object       v3
0000003C  invoke-virtual           Runtime->exec(String )Process, v2, v3
:42
00000042  return-void
:44
00000044  move-exception           v2
00000046  move-object              v0, v2
          .local v0, ex:Ljava/lang/Exception;
00000048  const-string             v2, "wanghelong"
0000004C  invoke-virtual           StringBuilder->toString ()String, v1
00000052  move-result-object       v3
00000054  invoke-static            Log->e(String , String)I, v2, v3
0000005A  goto                     :42
          .catch Exception {:A .. :42} :44
.end method

.method public runScipt()V
          .registers 5
          .prologue
00000000  new-instance             v0, File
00000004  const-string             v2, "bin"
00000008  const/4                  v3, 0x0
0000000A  invoke-virtual           RPKService->getDir (String, I) File, p0, v2, v3
00000010  move-result-object       v2
00000012  const-string             v3, "athena.dat"
00000016  invoke-direct            File-><init> (File, String)V, v0, v2, v3
          .local v0, file:Ljava/io/File;
0000001C  new-instance             v1, RPKService$3
00000020  invoke-direct            RPKService$3-><init> (RPKService, File)V, v1, p0, v0
          .local v1, thread:Ljava/lang/Thread;
00000026  invoke-virtual           Thread->start()V, v1
0000002C  return-void
.end method
.method private static copyRawFile (Context, InputStream, File , String)V
          .registers 10
          .annotation system Throws
              value = {
                  IOException,
                  InterruptedException
              }
          .end annotation
          .param p0, "ctx"
          .param p1, "inputStream"
          .param p2, "file"
          .param p3, "mode"
          .prologue
00000000  new-instance             v2, FileOutputStream
00000004  invoke-direct            FileOutputStream-><init> (File)V, v2, p2
          .local v2, out:Ljava/io/FileOutputStream;
0000000A  const/16                 v3, 0x400
0000000E  new-array                v0, v3, [B
:12
          .local v0, buf:[B
00000012  invoke-virtual           InputStream->read([B)I, p1, v0
00000018  move-result              v1
          .local v1, len:I
0000001A  if-gtz                   v1, :7C
:1E
0000001E  invoke-virtual           FileOutputStream->close()V, v2
00000024  invoke-virtual           InputStream->close()V, p1
0000002A  invoke-static            Runtime->getRuntime ()Runtime
00000030  move-result-object       v3
00000032  new-instance             v4, StringBuilder
00000036  const-string             v5, "chmod "
0000003A  invoke-direct            StringBuilder-><init> (String)V, v4, v5
00000040  invoke-virtual           StringBuilder->append (String) StringBuilder, v4, p3
00000046  move-result-object       v4
00000048  const-string             v5, " "
0000004C  invoke-virtual           StringBuilder->append (String) StringBuilder, v4, v5
00000052  move-result-object       v4
00000054  invoke-virtual           File->getAbsolutePath ()String, p2
0000005A  move-result-object       v5
0000005C  invoke-virtual           StringBuilder->append (String) StringBuilder, v4, v5
00000062  move-result-object       v4
00000064  invoke-virtual           StringBuilder->toString ()String, v4
0000006A  move-result-object       v4
0000006C  invoke-virtual           Runtime->exec(String )Process, v3, v4
00000072  move-result-object       v3
00000074  invoke-virtual           Process->waitFor ()I, v3
0000007A  return-void
:7C
0000007C  const/4                  v3, 0x0
0000007E  invoke-virtual           FileOutputStream->write([B, I, I)V, v2, v0, v3, v1
00000084  goto                     :12
.end method

.method private initAssets ()V
          .registers 7
00000000  const-string             v5, "athena.dat"
          .prologue
00000004  invoke-virtual           RPKService->getAssets ()AssetManager, p0
0000000A  move-result-object       v0
          .local v0, assetManager:Landroid/content/res/AssetManager;
0000000C  new-instance             v2, File
00000010  const-string             v3, "bin"
00000014  const/4                  v4, 0x0
00000016  invoke-virtual           RPKService->getDir (String, I) File, p0, v3, v4
0000001C  move-result-object       v3
0000001E  const-string             v4, "athena.dat"
00000022  invoke-direct            File-><init> (File, String)V, v2, v3, v5
          .local v2, file:Ljava/io/File;
00000028  invoke-virtual           File->exists ()Z, v2
0000002E  move-result              v3
00000030  if-nez                   v3, :4A
:34
00000034  const-string             v3, "athena.dat"
00000038  invoke-virtual           AssetManager->open(String )InputStream, v0, v3
0000003E  move-result-object       v3
00000040  const-string             v4, "777"
00000044  invoke-static            RPKService->copyRawFile (Context, InputStream, File, String )V, p0, v3, v2, v4
:4A
0000004A  return-void
:4C
0000004C  move-exception           v3
0000004E  move-object              v1, v3
          .local v1, e:Ljava/lang/Exception;
00000050  invoke-virtual           Exception->printStackTrace ()V, v1
00000056  goto                     :4A
          .catch Exception {:34 .. :4A} :4C
.end method

.method private onServiceStart ()V
          .registers 1
          .prologue
00000000  invoke-direct            RPKService->initAssets ()V, p0
00000006  invoke-virtual           RPKService->runScipt ()V, p0
0000000C  return-void
.end method
总结起来就一句话:释放athena.dat这个可执行文件然后修改成可执行属性后再执行它。
RPKService服务还会启动一个线程:
.method public onStartCommand(Intent , I, I)I
          .registers 5
          .param p1, "intent"
          .param p2, "flags"
          .param p3, "startId"
          .prologue
00000000  invoke-super             Service->onStartCommand (Intent, I, I)I, p0, p1, p2, p3
00000006  iget-object              v0, p0, RPKService->thread :Thread
0000000A  invoke-virtual           Thread->isAlive ()Z, v0
00000010  move-result              v0
00000012  if-nez                   v0, :20
:16
00000016  iget-object              v0, p0, RPKService->thread :Thread
0000001A  invoke-virtual           Thread->start()V, v0
:20
00000020  const/4                  v0, 0x3
00000022  return                   v0
.end method
线程是在RPKService的构造函数里面定义的:
.method public run()V
          .registers 5
          .prologue
00000000  const/4                  v1, 0xFFFFFFFFFFFFFFFE
00000002  invoke-static            Process->setThreadPriority (I)V, v1
00000008  const/4                  v0, 0x0
:A
          .local v0, wakeLock:Landroid/os/PowerManager$WakeLock;
0000000A  iget-object              v1, p0, RPKService$2->this$0 :RPKService
0000000E  invoke-static            RPKService->access$3 (RPKService) RPKJNILoad, v1
00000014  move-result-object       v1
00000016  invoke-virtual           RPKJNILoad->fun1()V, v1
0000001C  invoke-static            Thread->interrupted ()Z
00000022  move-result              v1
00000024  if-eqz                   v1, :2A
:28
00000028  return-void
:2A
0000002A  iget-object              v1, p0, RPKService$2->this$0 :RPKService
0000002E  invoke-static            RPKService->access$4 (RPKService) PowerManager, v1
00000034  move-result-object       v1
00000036  const                    v2, 0x1000000A
0000003C  const-string             v3, "RPKService"
00000040  invoke-virtual           PowerManager->newWakeLock (I, String)PowerManager$WakeLock , v1, v2, v3
00000046  move-result-object       v0
00000048  const-wide/16            v1, 0x1388
0000004C  invoke-virtual           PowerManager$WakeLock->acquire (J)V, v0, v1, v2
00000052  goto                     :A
.end method
这个线程里面会调用librpkjni.so中的fun1函数,此时线程会阻塞在这个函数当中,一直等待,直到这个函数返回,线程就会调用PowerManager->newWakeLock 点亮屏幕(终于看到核心点了)。这里的PowerManager是电源管理器,在之前由getSystemService(Context.POWER_SERVICE)赋值。那么问题来了,librpkjni.so中的fun1函数内部到底做了什么呢?为了搞清楚这个问题我们接下来要开始分析librpkjni.so和athena.dat这两个原生程序了。
先看看librpkjni.so中的fun1函数(JNI相关的知识可自行查询资料),如图:
将其还原成伪代码大概就是下面这个样子:
其实本质上就是在做进程间通讯,和一个名叫com.my.MyService的服务通讯,而这个服务就是在athena.dat进程中实现的(android服务是一个复杂的系统,包含java层面的服务,原生层面的服务,服务管理器等等,具体的细节请自行查阅相关资料)。记住athena.dat进程是以root权限运行的,它会监听linux输入子系统,判断用户是否有按音量键,如图:
通过打开/dev/input/event系列设备进行监听,一旦用户按了音量键athena.dat进程就会捕获到,这样librpkjni.so中的fun1函数就会返回,应用中等待的线程就会继续执行调用
PowerManager->newWakeLock 点亮屏幕。完成一次点亮屏幕的过程。
最后总结一下:站在linux进程的角度来讲本程序运行后会产生2个进程,一个APK主进程,一个athena.dat进程(root权限运行),APK主进程中会调用librpkjni.so中的fun1函数与
athena.dat进程通讯并且一直等待通知。而在athena.dat进程中会监听linux输入子系统来判断用户是否按了电源键,如果用户按了电源键后 APK主进程中的librpkjni.so中的fun1函数
就会结束等待,代码就会执行到PowerManager->newWakeLock 从而点亮屏幕。具体详细可参阅源代码。
全部代码已经上传到github上,为了演示和编译的方便,源代码中进程通讯部分没有利用andorid的binder机制,只是简单的用了管道来实现,因为要实现
android服务需在android源代码环境下进行开发和编译,比较麻烦,所以就简单处理之。
补充说明:原版程序在android5.x的系统上是失效的,原因是athena.dat进程运行不起来,手动运行此文件会报错:error: only position independent executables (PIE) are supported.
原因是google给android5.0增加了新的安全特性,不支持PIE(position independent executables)的程序都不能运行。所以编译的时候要加上-pie -fPIE 的选项,这样才能在android5.x系统上正常运行。
源代码的github地址:
https://github.com/phonegapX/com.wujianai.rpk


«1234567»

Powered By Z-Blog 2.2 Prism Build 140101

Copyright phonegap.me Rights Reserved.