字符匹配_Pattern_Matcher
正则表达式
正则表达式是处理字符串的特殊字符串,用途:
- 字符串匹配(字符匹配)
- 字符串查找(是建立在匹配之上的查找)
字符串替换(是建立在查找的结果之后的替换)
相关类:
- java.lang.String
- java.util.regex.Pattern
java.util.regex.Matcher
正则表达式的构造摘要
非捕获组(格式:构造 匹配,如“x 字符 x”,正则构造“x”,匹配“字符 x”)
字符
x 字符 x
// 反斜线字符
/0n 带有八进制值 0 的字符 n (0 <= n <= 7)
/0nn 带有八进制值 0 的字符 nn (0 <= n <= 7)
/0mnn 带有八进制值 0 的字符 mnn(0 <= m <= 3、0 <= n <= 7)
/xhh 带有十六进制值 0x 的字符 hh
/uhhhh 带有十六进制值 0x 的字符 hhhh
/t 制表符 ('/u0009')
/n 新行(换行)符 ('/u000A')
/r 回车符 ('/u000D')
/f 换页符 ('/u000C')
/a 报警 (bell) 符 ('/u0007')
/e 转义符 ('/u001B')
/cx 对应于 x 的控制符
字符类
[abc] a、b 或 c(简单类)
[^abc] 任何字符,除了 a、b 或 c(否定)
[a-zA-Z] a 到 z 或 A 到 Z,两头的字母包括在内(范围)
[a-d[m-p]] a 到 d 或 m 到 p:[a-dm-p](并集)
[a-z&&[def]] d、e 或 f(交集)
[a-z&&[^bc]] a 到 z,除了 b 和 c:[ad-z](减去)
[a-z&&[^m-p]] a 到 z,而非 m 到 p:[a-lq-z](减去)
预定义字符类
. 任何字符(与行结束符可能匹配也可能不匹配)
/d 数字:[0-9]
/D 非数字: [^0-9]
/s 空白字符:[ /t/n/x0B/f/r]
/S 非空白字符:[^/s]
/w 单词字符:[a-zA-Z_0-9]
/W 非单词字符:[^/w]
POSIX 字符类(仅 US-ASCII)
/p{Lower} 小写字母字符:[a-z]
/p{Upper} 大写字母字符:[A-Z]
/p{ASCII} 所有 ASCII:[/x00-/x7F]
/p{Alpha} 字母字符:[/p{Lower}/p{Upper}]
/p{Digit} 十进制数字:[0-9]
/p{Alnum} 字母数字字符:[/p{Alpha}/p{Digit}]
/p{Punct} 标点符号:!"#$%&'()*+,-./:;<=>?@[/]^_`{|}~
/p{Graph} 可见字符:[/p{Alnum}/p{Punct}]
/p{Print} 可打印字符:[/p{Graph}/x20]
/p{Blank} 空格或制表符:[ /t]
/p{Cntrl} 控制字符:[/x00-/x1F/x7F]
/p{XDigit} 十六进制数字:[0-9a-fA-F]
/p{Space} 空白字符:[ /t/n/x0B/f/r]
java.lang.Character 类(简单的 java 字符类型)
/p{javaLowerCase} 等效于 java.lang.Character.isLowerCase()
/p{javaUpperCase} 等效于 java.lang.Character.isUpperCase()
/p{javaWhitespace} 等效于 java.lang.Character.isWhitespace()
/p{javaMirrored} 等效于 java.lang.Character.isMirrored()
Unicode 块和类别的类
/p{InGreek} Greek 块(简单块)中的字符
/p{Lu} 大写字母(简单类别)
/p{Sc} 货币符号
/P{InGreek} 所有字符,Greek 块中的除外(否定)
[/p{L}&&[^/p{Lu}]] 所有字母,大写字母除外(减去)
边界匹配器
$ 行的结尾
/b 单词边界
/B 非单词边界
/A 输入的开头
/G 上一个匹配的结尾
/Z 输入的结尾,仅用于最后的结束符(如果有的话)
/z 输入的结尾
Greedy 数量词
X? X,一次或一次也没有
X* X,零次或多次
X+ X,一次或多次
X{n} X,恰好 n 次
X{n,} X,至少 n 次
X{n,m} X,至少 n 次,但是不超过 m 次
Reluctant 数量词
X?? X,一次或一次也没有
X*? X,零次或多次
X+? X,一次或多次
X{n}? X,恰好 n 次
X{n,}? X,至少 n 次
X{n,m}? X,至少 n 次,但是不超过 m 次
Possessive 数量词
X?+ X,一次或一次也没有
X*+ X,零次或多次
X++ X,一次或多次
X{n}+ X,恰好 n 次
X{n,}+ X,至少 n 次
X{n,m}+ X,至少 n 次,但是不超过 m 次
Logical 运算符
XY X 后跟 Y
X|Y X 或 Y
(X) X,作为捕获组
Back 引用
/n 任何匹配的 nth 捕获组
引用
/ Nothing,但是引用以下字符
/Q Nothing,但是引用所有字符,直到 /E
/E Nothing,但是结束从 /Q 开始的引用
特殊构造(非捕获)
(?:X) X,作为非捕获组
(?idmsux-idmsux) Nothing,但是将匹配标志i d m s u x on - off
(?idmsux-idmsux:X) X,作为带有给定标志 i d m s u x on - off
(?=X) X,通过零宽度的正 lookahead
(?!X) X,通过零宽度的负 lookahead
(?<=X) X,通过零宽度的正 lookbehind
(?<!X) X,通过零宽度的负 lookbehind
(?>X) X,作为独立的非捕获组
反斜线、转义和引用
反斜线字符 (‘/‘) 用于引用转义构造,如上表所定义的,同时还用于引用其他将被解释为非转义构造的字符。因此,表达式 // 与单个反斜线匹配,而 /{与左括号匹配。
在不表示转义构造的任何字母字符前使用反斜线都是错误的;它们是为将来扩展正则表达式语言保留的。可以在非字母字符前使用反斜线,不管该字符是否非转义构造的一部分。
根据 Java Language Specification 的要求,Java 源代码的字符串中的反斜线被解释为 Unicode 转义或其他字符转义。因此必须在字符串字面值中使用两个反斜线,表示正则表达式受到保护,不被 Java 字节码编译器解释。例如,当解释为正则表达式时,字符串字面值 “/b” 与单个退格字符匹配,而 “//b” 与单词边界匹配。字符串字面值 “/(hello/)” 是非法的,将导致编译时错误;要与字符串 (hello) 匹配,必须使用字符串字面值 “//(hello//)”。
字符类
字符类可以出现在其他字符类中,并且可以包含并集运算符(隐式)和交集运算符 (&&)。并集运算符表示至少包含其某个操作数类中所有字符的类。交集运算符表示包含同时位于其两个操作数类中所有字符的类。
字符类运算符的优先级如下所示,按从最高到最低的顺序排列:
1 字面值转义 /x
2 分组 […]
3 范围 a-z
4 并集 [a-e][i-u]
5 交集 [a-z&&[aeiou]]
注意,元字符的不同集合实际上位于字符类的内部,而非字符类的外部。例如,正则表达式 . 在字符类内部就失去了其特殊意义,而表达式 - 变成了形成元字符的范围。
行结束符
行结束符是一个或两个字符的序列,标记输入字符序列的行结尾。以下代码被识别为行结束符:
新行(换行)符 (‘/n’)、
后面紧跟新行符的回车符 (“/r/n”)、
单独的回车符 (‘/r’)、
下一行字符 (‘/u0085’)、
行分隔符 (‘/u2028’) 或
段落分隔符 (‘/u2029)。
如果激活 #UNIX_LINES模式,则新行符是唯一识别的行结束符。
如果未指定 #DOTALL 标志,则正则表达式 .可以与任何字符(行结束符除外)匹配。
默认情况下,正则表达式 ^ 和 $ 忽略行结束符,仅分别与整个输入序列的开头和结尾匹配。如果激活 #MULTILINE 模式,则 ^ 在输入的开头和行结束符之后(输入的结尾)才发生匹配。处于 #MULTILINE 模式中时,$ 仅在行结束符之前或输入序列的结尾处匹配。
组和捕获
捕获组可以通过从左到右计算其开括号来编号。例如,在表达式 ((A)(B(C))) 中,存在四个这样的组:
1 ((A)(B(C)))
2 /A
3 (B(C))
4 (C)
组零始终代表整个表达式。
之所以这样命名捕获组是因为在匹配中,保存了与这些组匹配的输入序列的每个子序列。捕获的子序列稍后可以通过 Back 引用在表达式中使用,也可以在匹配操作完成后从匹配器获取。
与组关联的捕获输入始终是与组最近匹配的子序列。如果由于量化的缘故再次计算了组,则在第二次计算失败时将保留其以前捕获的值(如果有的话)例如,将字符串 “aba” 与表达式 (a(b)?)+ 相匹配,会将第二组设置为 “b”。在每个匹配的开头,所有捕获的输入都会被丢弃。
以 (?) 开头的组是纯的非捕获组,它不捕获文本,也不针对组合计进行计数。
实例体会
String str = "ab1cdeabcff2efg"; //"aaa1a" "aaa123aaa123" "aaaa5aaaa6a" "aaaa5aaaaa6" "aaaabaaaaab6"
// 贪婪的
//首次会取最多的10字符开始匹配,会匹配到满足要求的嘴唇字段
String regex1 = ".{3,10}[0-9]"; //3到10个任意字符,加一个数字结尾
Matcher m1 = Pattern.compile(regex1).matcher(str);
if (m1.find()) {
Log.d("xixitest", m1.start() + "-" + m1.end());
Log.d("xixitest", m1.group());
} else
Log.d("xixitest", "1 not match!");
// 非贪婪的, 一次吃进最少的3个字符+1个,开始匹配,
// 不匹配,就再吞一个
regex1 = ".{3,10}?[0-9]";
m1 = Pattern.compile(regex1).matcher(str);
if (m1.find()) {
Log.d("xixitest", m1.start() + "-" + m1.end());
Log.d("xixitest", m1.group());
} else
Log.d("xixitest", "2 not match!");
// 独占的
// 取 最多的 10个字符,先匹配+号前面的正则,前面的匹配则再看后面字符是否匹配后面正则
regex1 = ".{3,10}+[0-9]";
m1 = Pattern.compile(regex1).matcher(str);
if (m1.find()) {
Log.d("xixitest", m1.start() + "-" + m1.end());
Log.d("xixitest", m1.group());
} else
Log.d("xixitest", "3 not match!");
参考学习
Kotlin初步学习和简单编程
彻底搞懂Android文件存储(内部存储,外部存储以及各种存储路径)
今天有人问我“外部存储是单指sd卡么?”“手机内部存储是看不到的, 对吧?”(其他程序是否可访问获取)。存取文件,开发中大家都会用,功能都能实现。但很多都说不清楚。诸如,设置里面应用清除数据、清除缓存数据,都是清除的什么数据;各个安卓系统版本不同,外部存储路径不同,等等。关于文件存取相关的东西比较多,做为开发是需要梳理清楚的。
内存
内存与PC的内存是一样的,是用来运行程序,不能用来永久存储数据,手机一旦关机,在内存中的所有数据都将会丢失,内存也是现在人类制造的所有电子设备所必需拥有的。
内部存储
内部存储是内存吗?不是。内部存储是用于存储Andoid 设备的操作系统和应用程序的存储介质。也就是说,Android设备中的Android系统和应用程序(APK文件)都是存在内部存储区的。例如手机的/system/目录、/data/目录等。是可以永久保存数据的。
将文件存储于内部存储中,文件默认只能被你的应用访问到,且一个应用所创建的所有文件都在和应用包名相同的目录下。也就是说应用创建于内部存储的文件,与这个应用是关联起来的。当一个应用卸载之后,内部存储中的这些文件也被删除。从技术上来讲如果你在创建内部存储文件的时候将文件属性设置成可读,其他app能够访问自己应用的数据,前提是他知道你这个应用的包名,如果一个文件的属性是私有(private),那么即使知道包名其他应用也无法访问。
内部存储空间十分有限,我们要尽量避免使用。
Shared Preferences和SQLite数据库都是存储在内部存储空间上的。内部存储一般用Context来获取和操作。
访问内部存储的API方法:
1、Environment.getDataDirectory()
2、getFilesDir().getAbsolutePath()
3、getCacheDir().getAbsolutePath()
4、getDir(“myFile”, MODE_PRIVATE).getAbsolutePath()
外部存储
pc机(电脑)自带的硬盘算是内部存储,U盘或者移动硬盘就是外部存储。
安卓4.4(API19)以前的手机,内置存储(机身存储:手机自身带的存储卡)就是内部存储,外部存储就是扩展的SD卡。
但从4.4的系统开始,很多的中高端机器都将自己的机身存储扩展到了8G以上、16G、32G等。4.4系统及以上的手机将机身存储存储(手机自身带的存储叫做机身存储)在概念上分成了”内部存储internal” 和”外部存储external” 两部分。如果4.4系统及以上的手机插SD卡,SD卡也是外部存储。4.4系统及以上的手机的外部存储可能包含两部分,一是机身存储的外部存储部分,一是SD卡部分。在4.4以后的系统中,API提供了这样一个方法来遍历手机的外部存储路径:
File[] files;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
files = getExternalFilesDirs(Environment.MEDIA_MOUNTED);
for(File file:files){
Log.e(“main”,file);
}
}
访问外部存储的API方法:
1、Environment.getExternalStorageDirectory().getAbsolutePath()
2、Environment.getExternalStoragePublicDirectory(“”).getAbsolutePath()
3、getExternalFilesDir(“”).getAbsolutePath()
4、getExternalCacheDir().getAbsolutePath()
存储路径
不同的Android版本,存在一些差异。
基于荣耀7的(系统版本6.0)路径:
1、Environment.getDataDirectory() = /data
这个方法是获取内部存储的根路径
2、getFilesDir().getAbsolutePath() = /data/user/0/packname/files
这个方法是获取某个应用在内部存储中的files路径
3、getCacheDir().getAbsolutePath() = /data/user/0/packname/cache
这个方法是获取某个应用在内部存储中的cache路径
4、getDir(“myFile”, MODE_PRIVATE).getAbsolutePath() = /data/user/0/packname/app_myFile
这个方法是获取某个应用在内部存储中的自定义路径
方法2,3,4的路径中都带有包名,说明他们是属于某个应用
…………………………………………………………………………………………
5、Environment.getExternalStorageDirectory().getAbsolutePath() = /storage/emulated/0
这个方法是获取外部存储的根路径
6、Environment.getExternalStoragePublicDirectory(“”).getAbsolutePath() = /storage/emulated/0
这个方法是获取外部存储的根路径
7、getExternalFilesDir(“”).getAbsolutePath() = /storage/emulated/0/Android/data/packname/files
这个方法是获取某个应用在外部存储中的files路径
8、getExternalCacheDir().getAbsolutePath() = /storage/emulated/0/Android/data/packname/cache
这个方法是获取某个应用在外部存储中的cache路径
注意:其中方法7和方法8如果在4.4以前的系统中getExternalFilesDir(“”)和getExternalCacheDir()将返回null,即4.4以前的系统没插SD卡的话,就没有外部存储;而4.4及以后的系统外部存储包括两部分,getExternalFilesDir(“”)和getExternalCacheDir()获取的是机身存储的外部存储部分,即4.4及以后的系统你不插SD卡,它也有外部存储,既然getExternalFilesDir(“”)和getExternalCacheDir()获取的是机身存储的外部存储部分,那么怎么获取SD卡的存储路径呢,通过上面提到的getExternalFilesDirs(Environment.MEDIA_MOUNTED)方法来获取。
…………………………………………………………………………………………
Environment.getDownloadCacheDirectory() = /cache
Environment.getRootDirectory() = /system
这两个方法,每个版本的android系统都一样
…………………………………………………………………………………………
/data目录下的文件物理上存放在我们通常所说的内部存储里面
/storage目录下的文件物理上存放在我们通常所说的外部存储里面 ,API方法都带了一个External
/system用于存放系统文件,/cache用于存放一些缓存文件,物理上它们也是存放在内部存储里面的
设置里面应用清除数据、清除缓存数据,都是清除的什么数据
1.清除缓存:应用程序在运行过程中需要经过很多过程,如读入程序,计算,输入输出等等,这些过程中会产生很多的数据,它们在内存中,以供程序运行时调用。清除缓存清除的是APP运行过程中所产生的临时数据。
- 清除数据:清除数据是真正的删除了我们保存在文件中的数据(永久性数据),当我们在设置里面清除了某个应用的数据,那么/data/user/0/packname/和/storage/emulated/0/Android/data/packname/下的文件里面的数据会全部删除,包括cache,files,lib,shared_prefs等等。
内部存储本身就比较小,而且已经存储了一些系统的文件,我们因尽量不要使用。一般做法:
public static String getFilePath(Context context,String dir) {
String directoryPath="dirName";
if (MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) ) {//判断外部存储是否可用
directoryPath =context.getExternalFilesDir(dir).getAbsolutePath();
}else{//没外部存储就使用内部存储
directoryPath=context.getFilesDir()+File.separator+dir;
}
File file = new File(directoryPath);
if(!file.exists()){//判断文件目录是否存在
file.mkdirs();
}
return directoryPath;
}
参考文章
smali 语法
smali是什么
Smali是Dalvik的寄存器语言,它与Java的关系,简单理解就是汇编之于C。
smali文件是哪来的,获取方法
Smali代码是安卓APK反编译而来的。Smali文件和Java文件一一对应。获取Smali文件,我们需要下载一个辅助工具:ApkTool 。apktool这个命令行工具,最常用的命令有:
- 反编译decode:
apktool d xxx.apk - 打包build:
apktool b
Smali语法
基本数据类型
V void (只能用于返回值类型)
Z boolean
B byte
S short
C char
I int
J long(64位)
F float
D double(64位)
对象类型
Lpackage/ObjectName; 相当于java中的package.ObjectName;
L 表示这是一个对象类型
package 该对象所在的包
ObjectName 对象名称
; 标识对象名称的结束
例如:Ltestdemo/hpp/cn/test/MainActivity;Ljava/lang/String;
数组类型
[I :表示一个整形的一维数组,相当于java的int[];
对于多维数组,只要增加[ 就行了,[[I = int[][];注:每一维最多255个;
对象数组的表示形式:
[Ljava/lang/String 表示一个String的对象数组;
寄存器与变量
android变量都是存放在寄存器中的,寄存器为32位,可以支持任何类型,其中long和double是64为的,需要使用两个寄存器保存。
寄存器采用v和p来命名,v表示本地寄存器,p表示参数寄存器。
例如:
//===================================================================
private void print(String string) {
Log.d(TAG, string);
}
//===================================================================
.method private print(Ljava/lang/String;)V
.registers 3
.param p1, "string" # Ljava/lang/String;
.prologue
.line 29
const-string v0, "MainActivity"
invoke-static {v0, p1}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I
.line 30
return-void
.end method
//===================================================================
.registers 3 说明该方法有三个寄存器,其中一个本地寄存器v0,两个参数寄存器p0,p1,细心的人可能会注意到没有看到p0,原因是p0存放的是this。如果是静态方法的话就只有2个寄存器了,不需要存this了。
基本指令
smali字节码是类似于汇编,如果有汇编基础,理解起来是非常容易的。
move v0, v3 把v3寄存器的值移动到寄存器v0上
const-string v0, “MainActivity” 把字符串”MainActivity”赋值给v0寄存器
invoke-super 调用父函数
return-void 函数返回void
new-instance 创建实例
iput-object 对象赋值
iget-object 调用对象
invoke-static 调用静态函数
invoke-direct 调用函数
例如:
//===================================================================
@Override
public void onClick(View view) {
String str = "Hello World!";
print(str);
}
//===================================================================
# virtual methods
# 参数类型为Landroid/view/View,返回类型为V
.method public onClick(Landroid/view/View;)V
# 表示有三个寄存器
.registers 3
# 参数View类型的view变量对应的是寄存器p1
.param p1, "view" # Landroid/view/View;
.prologue
.line 24
#将"Hello World!"字符串放到寄存器v0中
const-string v0, "Hello World!"
.line 25
# 定义一个Ljava/lang/String类型的str变量对应本地寄存器v0
.local v0, "str":Ljava/lang/String;
# 调用该类的print方法,该方法的参数类型为Ljava/lang/String,返回值为V
# 调用print方法传入的参数为{p0, v0},及print(p0, v0),p0为this,v0为"Hello World!"字符串
invoke-direct {p0, v0}, Ltestdemo/hpp/cn/annotationtest/MainActivity;->print(Ljava/lang/String;)V
.line 26
return-void
.end method
//===================================================================
if判断语句
if判断一共有12条指令:
if-eq vA, VB, cond_** 如果vA等于vB则跳转到cond_**。相当于if (vA==vB)
if-ne vA, VB, cond_** 如果vA不等于vB则跳转到cond_**。相当于if (vA!=vB)
if-lt vA, VB, cond_** 如果vA小于vB则跳转到cond_**。相当于if (vA<vB)
if-le vA, VB, cond_** 如果vA小于等于vB则跳转到cond_**。相当于if (vA<=vB)
if-gt vA, VB, cond_** 如果vA大于vB则跳转到cond_**。相当于if (vA>vB)
if-ge vA, VB, cond_** 如果vA大于等于vB则跳转到cond_**。相当于if (vA>=vB)
if-eqz vA, :cond_** 如果vA等于0则跳转到:cond_** 相当于if (VA==0)
if-nez vA, :cond_** 如果vA不等于0则跳转到:cond_**相当于if (VA!=0)
if-ltz vA, :cond_** 如果vA小于0则跳转到:cond_**相当于if (VA<0)
if-lez vA, :cond_** 如果vA小于等于0则跳转到:cond_**相当于if (VA<=0)
if-gtz vA, :cond_** 如果vA大于0则跳转到:cond_**相当于if (VA>0)
if-gez vA, :cond_** 如果vA大于等于0则跳转到:cond_**相当于if (VA>=0)
循环语句
常用的循环结构有:迭代器循环,for循环,do while循环。
import java.util.*;
public class demo{
public static void main(String[]args)
{
Scanner s=new Scanner(System.in);
int[] arr=new int[5];
for(int i=0;i<5;i++)
{
arr[i]=s.nextInt();
}
for (int i:arr)
{
System.out.println(i);
}
}
}
.class public Ldemo;
.super Ljava/lang/Object;
.source "demo.java"
# direct methods
.method public constructor <init>()V
.registers 1
.prologue
.line 3
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
return-void
.end method
.method public static main([Ljava/lang/String;)V
.registers 7
.prologue
const/4 v5, 0x5
const/4 v0, 0x0
.line 6
new-instance v2, Ljava/util/Scanner;
sget-object v1, Ljava/lang/System;->in:Ljava/io/InputStream;
invoke-direct {v2, v1}, Ljava/util/Scanner;-><init>(Ljava/io/InputStream;)V
.line 7
new-array v3, v5, [I
move v1, v0
.line 8
:goto_c
if-ge v1, v5, :cond_17
.line 10
invoke-virtual {v2}, Ljava/util/Scanner;->nextInt()I
move-result v4
aput v4, v3, v1
.line 8
add-int/lit8 v1, v1, 0x1
goto :goto_c
.line 12
:cond_17
array-length v1, v3
:goto_18
if-ge v0, v1, :cond_24
aget v2, v3, v0
.line 14
sget-object v4, Ljava/lang/System;->out:Ljava/io/PrintStream;
invoke-virtual {v4, v2}, Ljava/io/PrintStream;->println(I)V
.line 12
add-int/lit8 v0, v0, 0x1
goto :goto_18
.line 17
:cond_24
return-void
.end method
switch分支语句
1、case值递增的有规律 switch
private String packedSwitch(int i) {
String str = null;
switch (i) {
case 0:
str = "she is a baby";
break;
case 1:
str = "she is a girl";
break;
case 2:
str = "she is a woman";
break;
case 3:
str = "she is an obasan";
break;
default:
str = "she is a person";
break;
}
return str;
}
.method private packedSwitch(I)Ljava/lang/String;
.locals 1
.parameter "i"
.prologue
.line 21
const/4 v0, 0x0
.line 22
.local v0, str:Ljava/lang/String; #v0为字符串,0表示null
packed-switch p1, :pswitch_data_0 #packed-switch分支,pswitch_data_0指定case区域
.line 36
const-string v0, "she is a person" #default分支
.line 39
:goto_0 #所有case的出口
return-object v0 #返回字符串v0
.line 24
:pswitch_0 #case 0
const-string v0, "she is a baby"
.line 25
goto :goto_0 #跳转到goto_0标号处
.line 27
:pswitch_1 #case 1
const-string v0, "she is a girl"
.line 28
goto :goto_0 #跳转到goto_0标号处
.line 30
:pswitch_2 #case 2
const-string v0, "she is a woman"
.line 31
goto :goto_0 #跳转到goto_0标号处
.line 33
:pswitch_3 #case 3
const-string v0, "she is an obasan"
.line 34
goto :goto_0 #跳转到goto_0标号处
.line 22
nop
:pswitch_data_0
.packed-switch 0x0 #case 区域,从0开始,依次递增
:pswitch_0 #case 0
:pswitch_1 #case 1
:pswitch_2 #case 2
:pswitch_3 #case 3
.end packed-switch
.end method
2、无规律的switch
private String sparseSwitch(int age) {
String str = null;
switch (age) {
case 5:
str = "he is a baby";
break;
case 15:
str = "he is a student";
break;
case 35:
str = "he is a father";
break;
case 65:
str = "he is a grandpa";
break;
default:
str = "he is a person";
break;
}
return str;
}
.method private sparseSwitch(I)Ljava/lang/String;
.locals 1
.parameter "age"
.prologue
.line 43
const/4 v0, 0x0
.line 44
.local v0, str:Ljava/lang/String;
sparse-switch p1, :sswitch_data_0 # sparse-switch分支,sswitch_data_0指定case区域
.line 58
const-string v0, "he is a person" #case default
.line 61
:goto_0 #case 出口
return-object v0 #返回字符串
.line 46
:sswitch_0 #case 5
const-string v0, "he is a baby"
.line 47
goto :goto_0 #跳转到goto_0标号处
.line 49
:sswitch_1 #case 15
const-string v0, "he is a student"
.line 50
goto :goto_0 #跳转到goto_0标号处
.line 52
:sswitch_2 #case 35
const-string v0, "he is a father"
.line 53
goto :goto_0 #跳转到goto_0标号处
.line 55
:sswitch_3 #case 65
const-string v0, "he is a grandpa"
.line 56
goto :goto_0 #跳转到goto_0标号处
.line 44
nop
:sswitch_data_0
.sparse-switch #case 区域
0x5 -> :sswitch_0 #case 5(0x5)
0xf -> :sswitch_1 #case 15(0xf)
0x23 -> :sswitch_2 #case 35(0x23)
0x41 -> :sswitch_3 #case 65(0x41)
.end sparse-switch
.end method
try/catch语句
private void throw2() {
try {
throw new Exception("test throw runtime exception");
} catch (Exception e) {
e.printStackTrace();
}
}
.method private throw2()V
.locals 3
.prologue
.line 31
:try_start_0
new-instance v1, Ljava/lang/Exception;
const-string v2, "test throw runtime exception"
invoke-direct {v1, v2}, Ljava/lang/Exception;-><init>(Ljava/lang/String;)V
throw v1
:try_end_0
.catch Ljava/lang/Exception; {:try_start_0 .. :try_end_0} :catch_0
.line 32
:catch_0
move-exception v0
.line 33
.local v0, "e":Ljava/lang/Exception;
invoke-virtual {v0}, Ljava/lang/Exception;->printStackTrace()V
.line 35
return-void
.end method
头信息——类的主体信息
在打开smali文件的时候,它的头三行描述了当前类的一些信息。
.class <访问权限> [关键修饰字] <类名>;
.super <父类名>;
.source <源文件名>
例如:
//===================================================================
public class MainActivity extends AppCompatActivity {
// ......
}
//===================================================================
.class public Ltestdemo/hpp/cn/test/MainActivity;
.super Landroid/support/v7/app/AppCompatActivity;
.source "MainActivity.java"
//===================================================================
.class指令表示当前的类名,类的访问权限是public,类名为Ltestdemo/hpp/cn/test/MainActivity,类开头的L是遵循Dalvik字节码的相关约定,表示后面跟随的字符串是一个类。
.super指定了当前类所继承的父类,后面指的就是这个父类的类名,L表示后面跟的字符串是一个类
.source指定了当前类的源文件名
注意:经过混淆的dex文件,反编译出来的smali代码可能没有源文件信息,因此source行的代码可能为空。
这三行就是类的主体部分了,另外一个类是由多个字段或者方法组成。
接口
如果一个类实现了一个接口,那么会在smali文件中使用.implements指令指出。
#interfaces
.implements <接口名>
同样,#interfaces是注释,.implements是接口关键字。
例如:
//===================================================================
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
// ......
}
//===================================================================
# interfaces
.implements Landroid/view/View$OnClickListener;
//===================================================================
字段
smali文件中,字段的声明使用.field指令,字段分为静态字段和实例字段。
1 、静态字段
#static fields
.field <访问权限> static [修饰关键字] <字段名>:<字段类型>
可以看到,baksmali在生成smali文件时,会在静态字段声明的起始处添加注释”static fields”,注释是以#开头。
访问权限包括:private、protected、public(三者之一)
修饰关键字为字段的其他属性,例如,final
字段名和类型就不用解释了
例如:
//===================================================================
private static final String TAG = "MainActivity";
//===================================================================
# static fields
.field private static final TAG:Ljava/lang/String; = "MainActivity"
//===================================================================
2、 实例字段
相比于静态自动就少了一个static的静态声明而已,其他都一样。
#instance fields
.field <访问权限> [修饰关键字] <字段名>:<字段类型>
例如:
//===================================================================
private Button mButton;
//===================================================================
# instance fields
.field private mButton:Landroid/widget/Button;
//===================================================================
方法
smali的方法声明使用的.method指令,方法分为直接方法和虚方法两种。
1、直接方法
直接方法指的是该类中定义的方法。
#direct methods
.method <访问权限> [修饰关键字] <方法原型>
<.registers>
[.param]
[.prologue]
[.line]
<.local>
<代码体>
.end method
#direct methods是注释,是baksmali添加的,访问权限和修饰关键字跟字段是一样的。
方法原型描述了方法的名称、参数与返回值。
.registers 指令指定了方法中寄存器的总数,这个数量是参数和本地变量总和。
.param表明了方法的参数,每个.param指令表示一个参数,方法使用了几个参数就有几个.parameter指令。
.prologue指定了代码的开始处,混淆过的代码可能去掉了该指令。
.line指明了该处代码在源代码中的行号,同样,混淆后的代码可能去掉了行号。
.local 使用这个指定表明方法中非参寄存器
//===================================================================
private void print(String string) {
Log.d(TAG, string);
}
//===================================================================
.method private print(Ljava/lang/String;)V
.registers 3
.param p1, "string" # Ljava/lang/String;
.prologue
.line 29
const-string v0, "MainActivity"
invoke-static {v0, p1}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I
.line 30
return-void
.end method
//===================================================================
2、虚方法
虚方法指的是从父类中继承的方法或者实现的接口的方法,它的声明跟直接方法相同,只是起始的初始为virtual methods
//===================================================================
@Override
public void onClick(View view) {
String str = "Hello World!";
print(str);
}
//===================================================================
# virtual methods
.method public onClick(Landroid/view/View;)V
.registers 3
.param p1, "view" # Landroid/view/View;
.prologue
.line 24
const-string v0, "Hello World!"
.line 25
.local v0, "str":Ljava/lang/String;
invoke-direct {p0, v0}, Ltestdemo/hpp/cn/annotationtest/MainActivity;->print(Ljava/lang/String;)V
.line 26
return-void
.end method
//===================================================================
3、静态方法
//===================================================================
public static void setTag(String str) {
TAG = str;
}
//===================================================================
.method public static setTag(Ljava/lang/String;)V
.registers 1
.param p0, "str" # Ljava/lang/String;
.prologue
.line 64
sput-object p0, Ltestdemo/hpp/cn/annotationtest/MainActivity;->TAG:Ljava/lang/String;
.line 65
return-void
.end method
//===================================================================
注解
如果一个类使用了注解,那么smali中会使用.annotation指令。
#annotations
.annotation [注解属性] <注解类名>
[注解字段 = 值]
.end annotation
注解的作用范围可以是类、方法或者字段。如果注解的作用范围是类,.annotation指令会直接定义在smali文件中,如果是方法或者字段,.annotation指令则会包含在方法或者字段的定义中。
1、 注解类
//===================================================================
@BindInt(100)
public class MainActivity extends AppCompatActivity {
}
//===================================================================
# annotations
.annotation build Ltestdemo/hpp/cn/annotationtest/BindInt;
value = 0x64
.end annotation
//===================================================================
2、 注解字段
//===================================================================
@BindView(R.id.button)
public Button mButton;
//===================================================================
# instance fields
.field public mButton:Landroid/widget/Button;
.annotation build Lbutterknife/BindView;
value = 0x7f0c0050
.end annotation
.end field
//===================================================================
3、 注解方法
//===================================================================
@OnClick(R.id.button)
public void click() {
String str = "Hello World!";
print(str);
}
//===================================================================
# virtual methods
.method public click()V
.registers 2
.annotation build Lbutterknife/OnClick;
value = {
0x7f0c0050
}
.end annotation
.prologue
.line 29
const-string v0, "Hello World!"
.line 30
.local v0, "str":Ljava/lang/String;
invoke-direct {p0, v0}, Ltestdemo/hpp/cn/annotationtest/MainActivity;->print(Ljava/lang/String;)V
.line 31
return-void
.end method
//===================================================================
smali插桩
插桩的原理就是静态的修改apk的samli文件,然后重新打包。
1、使用上面的方法得到一个apk的smali文件
2、在关键部位添加自己的代码,需要遵循smili语法,例如在关键地方打log,输出关键信息
3、重新进行打包签名
代码安全,防解密
完全避免破解是不可能的,尽最大可能提高破解成本。
- 混淆代码。代码混淆后,Smali更加晦涩难懂,逻辑也更难掌握。
- 解读汇编比解读Smali难度大的多得多。重要的逻辑可以放到C/C++层去处理就不要放在Java层上去处理。
- 多用连续调用的方式。这样出来的效果是Java只有一行,Smali可能有好几十行,增加查看难度。在一些关键的点上,比如支付,多绕一下。不要直接在Java内用中文显示标注等
### 参考链接
阿里云ECS CentOS系统搭建LAMP
作为PHP自学新手,买了阿里云ECS低配,摸索着弄个服务器,用来PHP前端的学习实践。各种百度,实践操作总结如下,分享给如我一般的菜鸟同学。
认识CentOS系统
(百度)centos是linux系统的一个发行版。也就是linux系统中的一个。它是基于linux红帽版本制作的。红帽版因为是商业版,所以很多东西是要钱的。但是centos完全免费,主要用作服务器的搭建。
LAMP环境,即Linux、Apache、MySQL、PHP环境。centos是Linux系统,接下来就只要装Apache、MySQL、PHP就好。
安装 Apache
//安装
[root@izm5eicge3x0nikiewxdz8z ~]# yum install httpd httpd-devel
//查看是否已经安装
[root@izm5eicge3x0nikiewxdz8z ~]# yum list installed | grep httpd
//结果显示,说明已经安装成功
httpd.x86_64 2.4.6-67.el7.centos.6 @updates
httpd-devel.x86_64 2.4.6-67.el7.centos.6 @updates
httpd-tools.x86_64 2.4.6-67.el7.centos.6 @updates
//运行
[root@izm5eicge3x0nikiewxdz8z ~]# systemctl start httpd
[root@izm5eicge3x0nikiewxdz8z ~]# systemctl restart httpd
//查看80端口是否被监听.有,说明已启动
[root@izm5eicge3x0nikiewxdz8z ~]# netstat -ant
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN
tcp 0 0 172.31.79.65:59786 140.205.140.205:80 ESTABLISHED
tcp 0 0 172.31.79.65:22 115.236.163.195:56720 ESTABLISHED
tcp 0 52 172.31.79.65:22 115.236.163.195:60099 ESTABLISHED
安装 MySQL
[root@izm5eicge3x0nikiewxdz8z ~]# yum install mysql mysql-server
//查看安装后的MySql的服务是否已经启动
[root@izm5eicge3x0nikiewxdz8z www]# service mysqld status
// 没启动则启动MySQL服务
[root@izm5eicge3x0nikiewxdz8z www]# service mysqld start
安装 PHP
//检查是否已经安装
[root@izm5eicge3x0nikiewxdz8z ~]# yum list installed | grep php
//安装
[root@izm5eicge3x0nikiewxdz8z ~]# yum install php php-devel
//安装插件
[root@izm5eicge3x0nikiewxdz8z ~]# yum install php-mysql php-gd php-xml php-imap php-ldap php-odbc php-pear php-xmlrpc
安装结束
重启Apache服务器
[root@izm5eicge3x0nikiewxdz8z www]# systemctl restart httpd
安装目录介绍
Apache默认将网站的根目录指向/var/www/html 目录
默认的主配置文件是/etc/httpd/conf/httpd.conf
配置存储在的/etc/httpd/conf.d/目录
设置mysql数据库密码
//获取零时密码
[root@izm5eicge3x0nikiewxdz8z ~]# grep 'temporary password' /var/log/mysqld.log
// 用临时密码登入mysql
[root@izm5eicge3x0nikiewxdz8z ~]# mysql -u root -p
// 设置密码
mysql> USE MYSQL
ERROR 1820 (HY000): You must reset your password using ALTER USER statement before executing this statement.
mysql> SET PASSWORD FOR 'root'@'localhost'='PwPw_123123';
mysql> use mysql
mysql> show tables;
mysql> desc user;
测试mysql是否链接成功的php代码
<?php
$con = mysql_connect("localhost","root","PwPw_123123"); //主机名,用户,密码
if (!$con)
{
die('Could not connect: ' . mysql_error());
}
mysql_select_db("mydb", $con);
$result = mysql_query("SELECT * FROM sys_user");
while($row = mysql_fetch_array($result))
{
echo $row['UserName'] . " " . $row['PassWord'] . " " . $row['id'];
echo "<br />";
}
mysql_close($con);
?>
可以把上面的代码传入目录/var/www/html/
就可以看到执行情况
Gradle for Android
对Gradle的基本理解
一个基于Gradle的Android工程,必须有一个build.grade构建脚本,而我们用Android Studio创建工程时,Studio以为我们自动生成了该脚本文件。每个Module也会对应各自的一个构建脚本build.grade文件。
Gradle脚本不是像传统的xml文件那样,而是一种基于Groovy的动态DSL,而Groovy语言是一种基于jvm的动态语言。
在grade中两大概念:project和tasks。
Android studio中的project和Gradle中的project不是一个概念。
每个project有至少一个tasks。每一个build.grade文件代表着一个project。tasks在build.gradle中定义。
当初始化构建进程,gradle会基于build文件,集合所有的project和tasks,一个tasks包含了一系列动作,
然后它们将会按照顺序执行,一个动作就是一段被执行的代码,很像Java中的方法。
Android中的Gradle脚本文件
Android的build.gradle
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.2.3'
}
}
这就是实际构建开始的地方,仓库地址,使用了JCenter,JCenter类似maven库,grade还支持其他几个仓库,不论是远程还是本地仓库。
每个Module构建脚本的第一行都会声明使用什么插件:
apply plugin: 'com.android.application'
每个Android应用都需要一个插件:’com.android.application’;依赖库插件为’com.android.library’。(两插件不能同时使用)
插件用于扩展gradle脚本的能力,在一个项目中使用插件,这样该项目的构建脚本就可以定义该插件定义好的属性和使用它的tasks。
当使用Android 插件的时候,Android标签将可以被使用,如:
android {
compileSdkVersion 22
buildToolsVersion "22.0.1"
//设置代码文件源,一般用于导入旧的eclipse Android工程
sourceSets {
main {
manifest.srcFile 'AndroidManifest.xml'
java.srcDirs = ['src']
resources.srcDirs = ['src']
aidl.srcDirs = ['src']
renderscript.srcDirs = ['src']
res.srcDirs = ['res']
assets.srcDirs = ['assets']
}
androidTest.setRoot('tests')
}
}
Gradle 基本构建命令
tasks:命令行,导航到项目文件夹下,命令,列出所以可运行的tasks
$ gradlew tasks
assemble:开发时,构建项目,你需要运行assemble task通过debug配置
$ gradlew assembleDebug
该任务将会创建一个debug版本的app,同时Android插件会将其保存在MyApp/app/build/ outputs/apk目录下。
check 运行所以的checks,这意味着运行所有的tests在已连的设备或模拟器上
build 是check和assemble的集合体
clean 清楚项目的output文件
手机如何伪装成电脑上网
手机浏览器上网
打开浏览器,点开浏览器的“设置”项(一般在页面下方图标为三个杠的菜单中),找到“设置”里“UA设置”;UA设置一般有四个选项“Android;iPhone;iPad;电脑”,默认是“Android”,改选为“电脑”即可。
代码网络请求实现。
通过抓包软件比对,设置浏览器UA选项前后发出去的数据请求,只是请求头user-agent字段不同;我们代码一般不特别设置user-agent字段,默认情况下是发出去的请求头是没有该字段的。于是要实现伪装,只要为请求头添加相应的user-agent字段即可。
实例代码:
Map<String, String> headers = new HashMap<>();
public void request(Context context, String url, final BaseCallback callback) {
headers.clear();
//伪装成电脑上网
headers.put("user-agent", "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.20 (KHTML, likeGecko) Chrome/11.0.672.0 Safari/534.20");
RequestQueue queue = Volley.newRequestQueue(context);
StringRequest stringRequest = new StringRequest(Request.Method.GET, url, new Response.Listener<String>() {
@Override
public void onResponse(String response) {
WxLog.i(tag, " getLoginUUID onResponse: " + response);
if (callback != null) {
callback.onSuccess(response);
}
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
WxLog.i(tag, " onErrorResponse");
if (callback != null) {
callback.onFailed(error.getMessage());
}
}
}) {
@Override
public Map<String, String> getHeaders() throws AuthFailureError {
return headers;
}
};
queue.add(stringRequest);
}
抓包工具推荐AnyProxy
使用Runtime代码中安装APP
private void install( final String apkPath) {
// 安装
final Thread thread = new Thread() {
public void run() {
// install tmp.apk
Process rt = null;
try {
rt = Runtime.getRuntime().exec(“su”);
BufferedReader stdInput = new BufferedReader(new InputStreamReader(rt.getInputStream()));
DataOutputStream os = new DataOutputStream(rt.getOutputStream());
String cmd = “pm install -r -d “ + apkPath + “\n”;
os.writeBytes(cmd + “\n”);
os.flush();
os.writeBytes(“exit\n”);
rt.waitFor();
String std = null;
StringBuilder sb = new StringBuilder();
boolean isSuccess = true;
while ((std = stdInput.readLine()) != null) {
Log.i(UpdaterConfig.TAG, std);
sb.append(std);
if (FAILED_OUTPUT.equals(std)) {
isSuccess = false;
}
}
new File(apkPath).delete();
Log.i(UpdaterConfig.TAG, “install success”);
if (isSuccess) {
showToastMsg(“安装完成”);
startAppIfNeed();
} else {
showToastMsg(“安装异常”);
}
} catch (Exception e) {
e.printStackTrace();
Log.i(UpdaterConfig.TAG, “install failed”);
showToastMsg(“安装异常”);
}
}
};
thread.start();
}
private void startAppIfNeed() {
if (APP_PACKAGE_NAME.equals(mCurrentData.getPackageName())) {
try {
showToastMsg(“启动主程序”);
Intent intent = new Intent();
intent.setComponent(new ComponentName(APP_PACKAGE_NAME, APP_PACKAGE_NAME + “.MainActivity”));
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra(UpdaterConfig.EXTRA_RESULT, UpdaterConfig.RESULT_SUCCESS);
UpdaterApp.getInstance().startActivity(intent);
} catch (Exception e) {
Log.e(UpdaterConfig.TAG, e.getMessage(), e);
}
}S
}