欢迎光临
专注android技术,聚焦行业精粹,传扬中国传统文化,我们一直在努力

微学习

Lua实用工具-depgraph

小刚阅读(143)

1、前言

使用过Lua的朋友都知道它的强大,今天我们介绍一个非常有用的Lua工具depgraph,这个工具可以打印lua里面“直接加载”和“懒加载”的lua文件。

问题

随着项目工程的规模越来越大,lua文件数会越来越多,也许你会划分一些核心的模块,并且希望这些模块简单纯粹,不需要把一些无关的lua require进来。我们可以借助这个工具帮助我们分析自己的依赖项。

名词解释

  • 直接加载:在文件头部显示申明的require 语句
  • 懒加载:在方法中申明的require,这些只会在运行时被出触发

 

2、depgraph使用

depgraph的源码仓库地址:链接

2.1、depgraph命令概要

以上列举了一个常用的命令的示例,下面解释一个这个示例:

  • -m : 指定分析的lua文件目录,圈定了depgraph要分析的范围
  • –root: 指定从哪个文件开始分析。这里需要注意的是如果前面用的是 -m 方式指定目录,那么这里需要时模块名,模块名的格式是以点符号分隔的,例如:“xx.xx.xx”。如果前面是用 -e 方式指定的目录,那么这里应该是lua的相对路径格式,例如:“xx/xx/xx.lua” (注意:这里文档readme没有讲,我也是翻代码才了解的,下面也会介绍)
  • –dot:会用dot语言,以树状图的方式来描述依赖关系

 

2.2、简单依赖示例

我们建3个文件:test_main.lua, lib1.lua, lib2.lua,目录结构如下:

我们让test_main.lua 依赖 lib1.lua, lib1.lua依赖lib2.lua。他们的代码如下:

test_main.lua:

lib1.lua:

lib2.lua

我们运行luadepgraph命令:

结果:

我们得到了一个dot格式的文本内容,我们将这个文本内容放在dot图片在线生成工具,得到依赖图如下:

 

2.3、循环依赖示例

我们将lib2 依赖 test_main,并修改命令添加参数检测循环依赖,看看会发生什么。

test_main.lua改为如下:

我们的命令行添加一个选项 –cycles,如下:

输出结果如下:

从结果可以看出已经检测出了这个循环依赖的问题,而且详细记载了这个循环的信息。

我们再看下dot图是什么样的,命令结果如下:

dot图如下,这个图也很直观的反应了这个循环依赖问题:

 

 

好了,到这一步你们基本上已经掌握luadepgraph的使用技巧了。接下来想要了解详细更多参数使用,可以参考github中的描述

 

3、构建depgraph工程

depgraph工程里面是用luarocks构建的,所以我们可以把源码下载下来后也用同样的方式构建。构建命令如下:

我们用的是luarocks的make命令,并使用lua-version指定lua版本为5.3。而 “depgraph-scm-1.rockspec” 是depgraph工程根目录的rockspec格式文件,用于构建使用。该命令的运行输出如下:

到这一步我们已经将lua源码构建成可执行文件并安装系统环境了。

 

4、depgraph 源码初分析

我们尝试打开上面输出中安装目录(/Users/ali_1/.luarocks)看看有什么:

我们在bin目录下面找到luadepgraph shell脚本,里面记录了luadepgraph库的位置,我们同样的方式打开那里对应的文件,看到内容如下:

这行代码的意思是使用lua的环境,执行了require命令,require了depgraph.cli模块,并用返回的function类型的匿名对象直接调用执行,传入arg做为参数。

好了,我们已经找到了depgraph的入口了,即depgraph.cli模块,我们可以分析源码了。

看cli这个名字就能很快联想到这是一个解析命令行参数的入口文件。打开这个文件我们可以看到核心的下面几个部分:

  1. depgraph.make_graph:这个方法将设置进来的lua目录进行解析得到完整的依赖关系图
  2. args 解析,通过命令行参数做对应的操作。例如:cycles就是上面提到获取循环依赖信息
  3. 最后print打印这些信息

那么这个requiredepgraph是啥呢,我们找到配置文件(.rockspec结尾的文件),我们看到depgraph对应的是src/depgraph/init.lua

我们看一下我们示例中用到的dot命令对应的是render方法。也就是这里的init.luarender方法。

4.1、render方法

总结一下这里的逻辑是遍历graph对象里面的modules(.分隔形式表示)和ext_files(普通目录形式)的deps(依赖信息),然后添加节点(node)和边(edge)得到一个符合dot规范的依赖关系文件。这个文件可以用于生成dot依赖关系图。也就是说这里是把graph信息整理并表达成dot图。

那么真正的信息还是在graph里面,想要了解里面的信息如何生成的可以再继续看make_graph方法。

里面相关的分3步:

  1. 遍历所有目录的lua文件收集依赖信息
  2.  如果设置了root,则先从所有模块张找到该root节点的信息
  3. 从root开始filter_reachable,找到该节点的依赖树信息

 

好了,到此我们已经了解了depgraph的基本命令行的使用,你也了解了如何看源码,修改源码,重新构建depgraph工程了。相信这个工具在日常的开发中能给与你一些有价值的帮助~

 

Android 新一代编译工具 Jack&Jill

小刚阅读(24)

1、为什么要抛弃 Javac/dx,开发 Jack 和 Jill

推测主要有三个目的

  • 提高编译速度
  • 应对 Oracle 的法律诉讼
  • 将编译器掌控权拿在自己手中,不再受制于 Oracle,可以做一些 Android only 的优化

下面比较一下旧的 javac/dx/ProGuard/jarjar toolchain 和新的 Jack 编译器的工作流程

 

2、旧的编译流程

简单的说,将 Java 代码和依赖库编译为 dex 有两个大的阶段

javac (.java –> .class) –> dx (.class –> .dex)

下面是用流程图表示的旧编译过程:

  1. javac 将 java 代码编译为 java bytecode, 以 .class 的形式存在; 以 jar 和 aar 形式存在的依赖库,代码在里面以一堆.class 的形式存在
  2. Proguard 工具读取 Proguard 配置,对 .class 做 shrinking, obfuscation,输出 Proguard mapping
  3. dx 将多个 .class 转化为单一的 classes.dex ; 如果 dex 方法数超过 65k, 就生成 classes.dex, classes1.dex…classesN.dex

 

3、新的编译流程

新的编译过程只有一个阶段了,它完全抛弃了 javac, ProGuard, jarjar 等工具,一个工具搞定一切

Jack (.java –> .jack –> .dex)

  1. 各种依赖库仍然以 jar/aar 的形式存在
  2. 辅助工具 Jill 将根据依赖库中的 .class 生成 Jayce 格式的 IL,并调用 Jack 做 pre-dex 并生成 .jack,此过程只在编译 app 时发生一次
  3. Jack 将 java 源代码也编译为 .jack,然后将多个 .jack 转化为单一的 .dex; 如果 dex 方法数超过 65k, 就生成 classes.dex, classes1.dex…classesN.dex

Jill的工作:Jill工具具体的工作就是将jar/aar中的.class文件转化成.jayce格式的文件,并且再使用Jack做preDex操作。至于preDex的作用,其实就是一个预编译的作用,起到缓存的作用

Jack的工作:Jack使用了Jill工具将依赖库中的.class文件转换成了.jack文件,然后再使用Jack工具将这些文件和Android过程的源代码一起编译为.dex文件,当然.dex文件的数量取决于是否开启multiDex。

 

3.1、.Jack中间文件

里面包含了 Jayce 格式的 IL ,pre-dex,原始 aar 中的资源文件,以及 Jack 会用到的一些 meta 信息

下图简单比较了 java 代码转化的 .class, Jayce IL 和 dex 的内容异同:

简单比较下三种 IL 的区别:

  • Sun/Oracle Hotspot VM 是基于栈式的,所以 .class 文件的内容就是不断地压操作数到栈顶,从栈顶读取操作数,比较或做运算,将结果再压回栈顶
  • Dalvik VM 是基于寄存器的,所以 .dex 的内容就是不断地 move 操作数到寄存器,比较或做运算,将结果写回寄存器或内存地址
  • Jayce 则是 Jack&Jill 专有的 IL, 目前没有查阅到更多的官方资料。只能参阅 Jill 源代码中 com.android.jill.backend.jayce 包的代码了,比如其中的 Token 类就定义了 Jayce 的 Token 定义。

个人推测 Jayce 存在的意义是:

  • 为了在整合多个 jack 文件,生成单一的 dex 时,方便 Jack 做一些全局性的后端编译优化。
  • 从 Android 生态圈中完全去除 Oracle 的 Java Bytecode 格式

XML 查询语言 XPath

小刚阅读(19)

1、XPath 介绍

结构化查询语言(SQL)是一种针对查询特定类型的关系库而设计和优化的语言。和SQL相同,XPath也是一种查询语言,它一种为查询 XML 文档而设计的查询语言。下面这个简单的 XPath 查询可以在文档中找到作者为 conio 的所有图书的标题:

作为对照,我们以一个java的纯DOM搜索代码来实现上面的功能:

xpath语法结构如下:

2、Java 代码编程示例

Java 5 推出了 javax.xml.xpath 包,提供一个引擎和对象模型独立的 XPath 库。这个包也可用于 Java 1.3 及以后的版本,但需要单独安装 Java API for XML Processing (JAXP) 1.3。

xml 文档:

XPath查询语法:

  • 查找所有图书的 XPath 查询://book[author="conio"]
  • 找出这些图书的标题,只要增加一步: //book[author="conio"]/title
  • 找出标题的内容: //book[author="conio"]/title/text()

用XPath查询的代码完整示例:

3、Android里面从String.xml里面查找某个字符串key对应的value

4、Android 读取manifest里面的属性值

例如获取application的name值:

这里需要注意如果/manifest/application/@android:name 是找不到的,因为没有设置命名空间。换成命名空间的写法为:

 

微学习-dexopt 与 dex2oat 的区别

小刚阅读(5)

这道题目如果想深入理解就需要去看源码了,不过对于应用层开发来说有个原理上的大致理解也是必须掌握的,具体区别可用如下图概述(图片来自网络)。

通过上图可以很明显的看出 dexoptdex2oat 的区别,前者针对 Dalvik 虚拟机,后者针对 Art 虚拟机。

dexopt 是对 dex 文件 进行 verification 和 optimization 的操作,其对 dex 文件的优化结果变成了 odex 文件,这个文件和 dex 文件很像,只是使用了一些优化操作码(譬如优化调用虚拟指令等)。

dex2oat 是对 dex 文件的 AOT 提前编译操作,其需要一个 dex 文件,然后对其进行编译,结果是一个本地可执行的 ELF 文件,可以直接被本地处理器执行。

除此之外在上图还可以看到 Dalvik 虚拟机中有使用 JIT 编译器,也就是说其也能将程序运行的热点 java 字节码编译成本地 code 执行。所以其与 Art 虚拟机还是有区别的,Art 虚拟机的 dex2oat 是提前编译所有 dex 字节码,而 Dalvik 虚拟机只编译使用启发式检测中最频繁执行的热点字节码

 

android数据安全—加密

小刚阅读(41)

这篇文章是“Android数据安全”系列的一部分:

  1. Encryption
  2. Encryption in Android (Part 1)
  3. Encryption in Android (Part 2)
  4. Encrypting Large Data
  5. Initialization Vector
  6. Key Invalidation
  7. Fingerprint
  8. Confirm Credentials

那些关于”android数据安全“工作室的话题。具有完整代码片段的示例应用程序可在GitHub上获得。

目录

  • 加密
  • 算法类型
  • 模式和填充
  • 密钥类型
  • 下一步是什么
  • 安全提示

加密

这是实现数据安全的最有效方法。在本系列文章中,我们将主要关注它。

要读取加密数据,必须访问一个允许您解密它的密钥或密码。未加密的数据被称为普通数据(明文),加密的数据称为密码数据(密码文本)。

总体加密工作如下:

你有明文的数据,这可能是一些敏感信息(如个人生活信息、身体或心理健康细节、犯罪或民事犯罪、私人照片、私人用户文件等)。财务信息(如账户、交易、报告、信用卡信息等),当然还有凭证(用户名、密码、触摸PIN码、指纹数据和所有其他可以提供对上述数据的访问的东西)。

 然后,根据一些算法,您将创建一个特殊的密钥,并使用它来创建密码数据

例如,一个简单的算法–用一些方式来改变单词中的每个符号。一个秘钥-某物等于字母表中的下一个符号:



当然,所有的工作都是可逆的,如果你有一个密码数据,你知道算法并有一个密钥,你将轻松地得到原始的原始数据。

算法类型

上面,我们看到了加密的一个非常基本的例子。现在算法更复杂,在对称和非对称上分离(也有散列函数,不需要密钥,本文不对该算法分析)。

对称——是最古老和最著名的技术。加密密钥和解密密钥是相同的。它通常被归类为流密码或块密码。
最常用的对称AES——高级加密标准(AES)是美国政府和众多组织信任的标准算法。

非对称性——现代密码学的一个分支。也称为公钥密码学,其中算法使用一对密钥(公钥和私钥),这一对秘钥在算法的不同步骤中有不同的使用方式。
最常用的非对称算法是RSA-公钥加密算法,并且是互联网发送加密数据的标准。

流密码是一种对称加密算法,用一个密钥一次处理一个比特或一个字节的数据,从而产生一个随机化的密码数据或明文数据。

流密码加密流程

 

分组密码算法——分组密码也称块密码,当加密一条长消息(明文)时,首先,将明文编码表示为二进制序列;然后将其分为若干个固定长度的组(最后一组长度不够时还得进行填充,下面会介绍填充的方法),最后,再对每个分组依次进行加密操作。分组长短决定着密码的强度。从算法的安全性考虑,分组长度不能太短,应该保证加密算法能够应对密码分析。从实用性考虑,分组长度又不能太长,要便于操作和运算。近年来随着计算机计算能力不断的提升,分组长度为64位的分组密码的安全性越来越不能满足实际需要,为了提高加密的安全性,很多分组密码开始选择128位作为算法的分组长度。

分组

 

模式与填充

分组密码具有不同的模式和增加其保护等级的填充。

模式—一种操作模式描述如何重复应用密码的单块操作,以安全地变换大于一个块的数据量。

填充— 分组密码在固定大小的单位(称为块大小)上工作,但消息以不同的长度出现。因此,一些模式(即ECB和CBC)要求在加密前填充最终块。

分组密码的填充

 

最常用的模式有 :

ECB — 电子密码本, 最简单的加密模式。信息是分为块的,每一块分别加密。

风险

  • 相同明文加密出来的密文是相同的。因为参与加密的只有明文和密钥,所以只能得出相同的密文。这个可以给攻击者通过推测猜测出明文的内容。
  • 存在被篡改的风险,可以实现无需破解密文,即可以操纵明文。
    这个风险的根本原因是因为,在加密的过程中,每个分组是互相独立的。
    例子:
    假设有明文被分成三个分组,恰好代表三个意思:
    分组1:付款人
    分组2:收款人
    分组3:金额
    怎样通过密文操纵明文呢?
    我们可以截获密文,然后将密文的分组2和分组1调整一下先后顺序,先发送分组2,再发送分组1。这样付款人和收款人就互相调换了位置。
  • 这种模式很容易被黑客使用“重放攻击”的方式攻击。“重放攻击”是一种攻击类型,这种攻击会不断恶意或欺诈性地重复一个有效的数据传输,重放攻击可以由发起者,也可以由拦截并重发该数据的敌方进行。攻击者利用网络监听或者其他方式盗取认证凭据,之后再把它重新发给认证服务器。从这个解释上理解,加密可以有效防止会话劫持,但是却防止不了重放攻击。重放攻击任何网络通讯过程中都可能发生。重放攻击是计算机世界黑客常用的攻击方式之一,它的书面定义对不了解密码学的人来说比较抽象。

CBC — 密码块链, 该模式下每个明文块会先与前一个密文块进行异或后,再进行加密。在这种方法中,每个密文块都依赖于它前面的所有明文块。同时,为了保证每条消息的唯一性,在第一个块中需要使用初始化向量IV。

CBC是最为常用的工作模式。它的主要缺点在于加密过程是串行的,无法被并行化,而且消息必须被填充到块大小的整数倍。在加密时,明文中的微小改变会导致其后的全部密文块发生改变,而在解密时,从两个邻接的密文块中即可得到一个明文块。因此,解密过程可以被并行化,而解密时,密文中一位的改变只会导致其对应的明文块完全改变和下一个明文块中对应位发生改变,不会影响到其它明文的内容。

秘钥类型

有三种秘钥类型: 秘密秘钥, 私钥和公钥

秘密秘钥— 在传统对称加密中用于加密和解密消息的单个秘密密钥。

私钥 — 非对称密码学中用于解密的一对密钥的秘密组成部分。

公钥— 非对称密码学中用于加密的一对秘钥的公开组成部分。

公共密钥和私有密钥共同构成公钥私钥对。

 

下一篇文章内容

在“Android数据安全”系列的下一篇“Android中的加密”文章中我们会学习:

Android基于Java加密体系结构(JCA)上,它提供了用于数字签名、证书、加密、密钥生成和管理的API…

 

安全提示

通常,我们建议尽量减少用户证书的使用频率。 — 想要使钓鱼攻击更加明显,成功的可能性小。可以使用授权令牌并刷新它的方式来替代。

在可能的情况下,用户名和密码不应存储在设备上。相反,使用用户提供的用户名和密码执行初始身份验证,然后使用过期时间短的、特定于服务的授权令牌。

“尽量避免尽可能多地存储私有用户数据。”

微学习-android里面判断App处于前台还是后台

小刚阅读(8)

需求场景:如果你在为很多的app提供SDK服务,那么你可能没有自己的Activity,但你需要检测当前的App是否处于前台,方便做一些特殊的逻辑。

需求分析:

在android平台你有很多的方式来解决这个问题,例如你可以使用ActivityManager.getRunningTasks获取当前运行的task列表,并从最上层的task的topActivity属性获得当前前台的activity名称(ComponentName),从而获取包名。然后对比当前应用的包名,从而判断当前应用是否处于前台。但该方法在android 5.0及以上版本被移除了,官方解释如下:

This method was deprecated in API level 21.
As of LOLLIPOP, this method is no longer available to third party applications: the introduction of document-centric recents means it can leak person information to the caller. For backwards compatibility, it will still return a small subset of its data: at least the caller’s own tasks, and possibly some other tasks such as home that are known to not be sensitive.

翻译:

该方法在 API level 21 已经被废弃
在 LOLLIPOP, 该方法不再对第三方应用开放。引用文档中心的最新信息意思是该方法会将app的隐私信息泄露给调用方。为了向前兼容,它仍然会返回数据中的其中一小部分: 至少包含调用方自己的task任务,并且也会有其它已知的不敏感的task列表,例如home桌面任务

那么也就是说我们需要另外找方法来解决了。

可选方案对比:

六种方法的区别

方法 判断原理 需要权限 可以判断其他应用位于前台 特点
方法一 RunningTask Android4.0系列可以,5.0以上机器不行 5.0此方法被废弃
方法二 RunningProcess 当App存在后台常驻的Service时失效
方法三 ActivityLifecycleCallbacks 简单有效,代码最少
方法四 UsageStatsManager 需要用户手动授权
方法五 通过Android无障碍功能实现 需要用户手动授权
方法六 读取/proc目录下的信息 当proc目录下文件夹过多时,过多的IO操作会引起耗时

 

方法一:通过RunningTask

原理
当一个App处于前台的时候,会处于RunningTask的这个栈的栈顶,所以我们可以取出RunningTask的栈顶的任务进程,看他与我们的想要判断的App的包名是否相同,来达到效果

缺点
getRunningTask方法在Android5.0以上已经被废弃,只会返回自己和系统的一些不敏感的task,不再返回其他应用的task,用此方法来判断自身App是否处于后台,仍然是有效的,但是无法判断其他应用是否位于前台,因为不再能获取信息

代码实现

 

方法二:通过RunningProcess

原理
通过runningProcess获取到一个当前正在运行的进程的List,我们遍历这个List中的每一个进程,判断这个进程的一个importance 属性是否是前台进程,并且包名是否与我们判断的APP的包名一样,如果这两个条件都符合,那么这个App就处于前台

缺点
在聊天类型的App中,常常需要常驻后台来不间断的获取服务器的消息,这就需要我们把Service设置成START_STICKY,kill 后会被重启(等待5秒左右)来保证Service常驻后台。如果Service设置了这个属性,这个App的进程就会被判断是前台,代码上的表现就是appProcess.importance的值永远是 ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND,这样就永远无法判断出到底哪个是前台了。

代码实现

 

方法三:通过ActivityLifecycleCallbacks

原理
AndroidSDK14 (android 4.0) 在Application类里增加了ActivityLifecycleCallbacks,我们可以通过这个Callback拿到App所有Activity的生命周期回调。

知道这些信息,我们就可以用更官方的办法来解决问题,当然还是利用Activity生命周期的特性,我们只需要在Application的onCreate()里去注册上述接口,然后由Activity回调回来运行状态即可。

可能还有人在纠结,我用back键切到后台和用Home键切到后台,一样吗?以上方法适用吗?在Android应用开发中一般认为back键是可以捕获的,而Home键是不能捕获的(除非修改framework),但是上述方法从Activity生命周期着手解决问题,虽然这两种方式的Activity生命周期并不相同,但是二者都会执行onStop();所以并不关心到底是触发了哪个键切入后台的。另外,Application是否被销毁,都不会影响判断的正确性

代码实现

方法四:通过使用UsageStatsManager获取

原理
通过使用UsageStatsManager获取,此方法是Android5.0之后提供的新API,可以获取一个时间段内的应用统计信息,但是必须满足一下要求

使用前提

  1. 此方法只在android5.0以上有效
  2. AndroidManifest中加入此权限
  1. 打开手机设置,点击安全-高级,在有权查看使用情况的应用中,为这个App打上勾

 

代码实现

 

方法五:通过Android自带的无障碍功能

此方法无法直观的通过下拉通知视图来进行前后台的观察,请到LogCat中进行观察即可,以下是LogCat中打印的信息

enter image description here

原理
Android 辅助功能(AccessibilityService) 为我们提供了一系列的事件回调,帮助我们指示一些用户界面的状态变化。 我们可以派生辅助功能类,进而对不同的 AccessibilityEvent 进行处理。 同样的,这个服务就可以用来判断当前的前台应用

优势

  1. AccessibilityService 有非常广泛的 ROM 覆盖,特别是非国产手机,从 Android API Level 8(Android 2.2) 到 Android Api Level 23(Android 6.0)
  2. AccessibilityService 不再需要轮询的判断当前的应用是不是在前台,系统会在窗口状态发生变化的时候主动回调,耗时和资源消耗都极小
  3. 不需要权限请求
  4. 它是一个稳定的方法,与 “方法6”读取 /proc 目录不同,它并非利用 Android 一些设计上的漏洞,可以长期使用的可能很大
  5. 可以用来判断任意应用甚至 Activity, PopupWindow, Dialog 对象是否处于前台

劣势

  1. 需要要用户开启辅助功能
  2. 辅助功能会伴随应用被“强行停止”而剥夺

代码实现

方法六:读取Linux系统内核保存在/proc目录下的process进程信息

此方法并非我原创,原作者是国外的大神,GitHub项目在这里,也一并加入到工程中,供大家做全面的参考选择

原理
无意中看到乌云上有人提的一个漏洞,Linux系统内核会把process进程信息保存在/proc目录下,Shell命令去获取的他,再根据进程的属性判断是否为前台

优点

  1. 不需要任何权限
  2. 可以判断任意一个应用是否在前台,而不局限在自身应用

缺点

  1. 当/proc下文件夹过多时,此方法是耗时操作

代码实现:

 

 

国内精品Android技术社区

专注android技术,聚焦行业精粹,传扬中国传统文化,我们一直在努力

联系我们

登录

找回密码

注册