乱码三千 – 分享实用IT技术

乱码三千 – 码出一个新世界


  • 首页

  • 归档

  • 搜索

一篇文章让你从此不再畏惧正则表达式

发表于 2020-05-15

1.什么是正则表达式

正则表达式,又称正规表示式、正规表示法、正规表达式、规则表达式、常规表示法(英语:Regular Expression,在代码中常简写为regex、regexp或RE),计算机科学的一个概念。正则表达式使用单个字符串来描述、匹配一系列匹配某个句法规则的字符串。在很多文本编辑器里,正则表达式通常被用来检索、替换那些匹配某个模式的文本。

通俗的讲就是按照某种规则去匹配符合条件的字符串

2.利用图形化工具理解正则表达式

辅助理解正则表达的在线工具:

  • https://regexper.com/

  • http://rubular.com/

  • https://tool.lu/regex/

  • 正则匹配软件

    ​ McTracer

    我们利用这个工具辅助理解,正则表达式。语法没懂表着急,后面会有,这里只是学会用工具帮助我们学习。

手机号正则:

1
/^1[34578][0-9]{9}$/

注释:以1开头,第二位为3 4 5 7 9 其中一个,以9位(本身1次加重复8次)0-9数字结尾

单词边界

1
/\bis\b/

注释: is前后都是单词的边界,比较晦涩难懂?感受下两者的区别,\b 会方道语法部分讲解

URL分组替换

1
/http:(\/\/.+\.jpg)/

看不懂的不要慌语法部分后面会有介绍,这里只是展示利用可视化的图形帮助我们理解正则表达式,可以回来再看木有关系

正则表达式中括号用来分组,这个时候我们可以通过用$1来获取 group#1的内容

说下这个正则的意义,如果网站用了https,网站引用静态资源也必须是https,否则报错。如果写成 // 会自动识别 http 或者 https

日期匹配与分组替换

1
/^\d{4}[/-]\d{2}[/-]\d{2}$/

这个正则比较复杂,画符念咒的地方太多了,一一分析:

Start of line 是由^生效的表示以此开头

对应结尾End of line 由$生效表示以此结尾

接着看digit 由 \d 生效表示数字

3times 由{4} 生效表示重复4次,开始的时候有疑问,为什么不是 4times 。后来明白作者的用意,正则表达式是一个规则,用这个规则去从字符串开始匹配到结束(注意计算机读字符串可是不会分行的,都是一个串,我们看到的多行,人家会认为是个 \t )这里设计好像小火车的轨道一直开到末尾。digit 传过一次,3times表示再来三次循环,共4次,后面的once同理。 自己被自己啰嗦到了。

接下来,是 one of 在手机正则里面已经出现了。表示什么都行。只要符合这两个都让通过。

好了这个正则解释完了,接下来用它做什么呢?

我们可以验证日期的合法性

结合URL分组替换所用到的分组特性,我们可以轻松写出日期格式化的方法

改造下这个正则

1
/^(\d{4})[/-](\d{2})[/-](\d{2})$/

轻松的可以拿到 group#1 #2 #3 的内容,对应 $1$2$3

到现在已经能结合图形化工具看懂正则表达式表达式了,如果想自己写,还要在正则语法上下点功夫

3. 正则表达式语法

1 修饰符 (三个 g i m)

修饰符与其他语法特殊,字面量方法声名的时候放到//后,构造函数声明的时候,作为第二个参数传入。整个正则表达式可以理解为正则表达式规则字符串+修饰符

  • g:global 执行一个全局匹配

  • i:ignore case执行一个不区分大小写的匹配

  • m: multiple lines多行匹配

修饰符可以一起用 const reg =/\bis\b/gim

来说说他们有什么作用

有g和没有g的区别

没有g只替换了第一个,有g 所有的都换了

有i和没有i的区别

有i忽略大小写,没有i严格区分大小写

2 元字符

正则表达式由两种基本字符组成:

  • 原义字符

  • 非打印字符

  • 元字符 (* + ? $ ^ . | \ ( ) { } [ ])

原义字符

这个没什么好解释的,我们一直在举例的 /is/ 匹配字符串’is’

\将下一个字符标记为一个特殊字符、或一个原义字符、或一个向后引用、或一个八进制转义符。例如,n”匹配字符“n”。“\n”匹配一个换行符。序列“\”匹配“\”而“(”则匹配“(”。

非打印字符

字符

描述

1
\cx

匹配由x指明的控制字符。例如, \cM 匹配一个 Control-M 或回车符。x 的值必须为 A-Z 或 a-z 之一。否则,将 c 视为一个原义的 ‘c’ 字符。

1
\f

匹配一个换页符。等价于 \x0c 和 \cL。

1
\n

匹配一个换行符。等价于 \x0a 和 \cJ。

1
\r

匹配一个回车符。等价于 \x0d 和 \cM。

1
\s

匹配任何空白字符,包括空格、制表符、换页符等等。等价于 [ \f\n\r\t\v]。

1
\S

匹配任何非空白字符。等价于 [^ \f\n\r\t\v]。

1
\t

匹配一个制表符。等价于 \x09 和 \cI。

1
\v

匹配一个垂直制表符。等价于 \x0b 和 \cK。

非打印字符,以\n为例

其他的在前端引用比较少,应该在后端处理文本文件的时候会用到

字符类 []

在前面的手机号正则例子中,我们已经使用过[] /^1[34578][0-9]{9}$/ : [34578]表示34578任意一个数字即可。在日期匹配与分组替换例子中 /^\d{4}[/-]\d{2}[/-]\d{2}$/ 表示符合 /- 都可以

字符类取反 [^]

表示不属于此类

空格也不属于,好多狗

范围类[-]

正则表达式支持一定范围规则比如 [a-z][A-Z][0-9] 可以连写[a-z0-9] 如果你只是想匹配-在 范围类最后加-即可。请看实例。

预定义类

常用为了方便书写

字符

等价类

含义

1
2
3
.

[^\n\r]

除了回车符和换行符之外的所有字符

1
2
3
\d

[0-9]

数字字符

1
2
3
\D

[^0-9]

非数字字符

1
2
3
\s

[\t\n\x0B\f\r]

空白符

1
2
3
\S

[^\t\n\x0B\f\r]

非空白符

1
2
3
\w

[a-zA-Z_0-9]

单词字符(字母、数字、下划线)

1
2
3
\W

[^a-zA-Z_0-9]

非单词字符

有了这些预定义类,写一些正则就很方便了,比如我们希望匹配一个 ab+数字+任意字符 的字符串,就可以这样写了 /ab\d./

边界

字符

含义

1
^

以xx开头

1
$

以xx结尾

1
\b

单词边界,指[a-zA-Z_0-9]之外的字符

1
\B

非单词边界

量词

字符

含义

1
?

出现零次或一次

1
*

出现零次或多次(任意次)

1
+

出现一次或多次(至少一次)

1
{n}

对应零次或者n次

1
{n,m}

至少出现n次但不超过m次

1
{n,}

至少出现n次(+的升级版)

1
{0,n}

至多出现n次(其实就是{n,m} 方便记忆而已)

如果没有量词,要匹配4位数字这样写就可以/\d\d\d\d/, 如果匹配50位100位呢?那不是要疯掉了?

有了量词,就可以这样写/\d{100}/, 量词的使用我们在手机号中使用过,表现在可视化中就是循环多少次。

凑一个上面都包含的实例/\d?@\d*@\d+@\d{10}@\d{10,20}@\d{10,}@\d{0,10}/

贪婪与懒惰(非贪婪)

正则表达式默认会匹配贪婪模式,什么是贪婪模式呢?如其名尽可能多的匹配。我们看个例子。

1
/\d{3,6}/

贪婪模式下,匹配的了最多的情况。

与贪婪对应就是懒惰模式,懒惰对应的就是匹配的尽可能少的情况。如何开启懒惰模式? 在量词后面加?。继续上面的例子

1
/\d{3,6}?/

如果想知道,正则表达式是如何匹配量词的,请看 进阶正则表达式 文中有介绍,正则是如何回溯的。

分组与反向引用

分组,又称为子表达式。把正则表达式拆分成小表达式。概念枯燥,说个例子为嘛用分组:

不分组 /abc{2}/

量词仅作用到最后的c

分组 /(abc){2}/

注意这里 group #1

分组虽然和运算符() 很像,但是分组在正则表达式中,注意理解组的含义。经常有人滥用分组

/^(http|https)/ 真的需要这样么?其实 /^https?/就可以了,你正则写的特别长的时候,会出现一堆没用的结果,看着都头疼吧。

分组往往和反向引用一起使用,别被概念吓到:当一个正则表达式被分组后,每个分组自动被赋予一个组号,一左到右分别是 $1$2…

再把之前的例子拿出来

1
/^(\d{4})[/-](\d{2})[/-](\d{2})$/

轻松的可以拿到 group#1 #2 #3 的内容,对应 $1$2$3

如果在反向引用中不想捕获年该如何操作? 加上 ?:即可

1
/^(?:\d{4})[/-](\d{2})[/-](\d{2})$/

12.前瞻

这部分为进阶部分—选看

正则表达式中有前瞻(Lookahead)和后顾(Lookbehind)的概念,这两个术语非常形象的描述了正则引擎的匹配行为。需要注意一点,正则表达式中的前和后和我们一般理解的前后有点不同。一段文本,我们一般习惯把文本开头的方向称作“前面”,文本末尾方向称为“后面”。但是对于正则表达式引擎来说,因为它是从文本头部向尾部开始解析的(可以通过正则选项控制解析方向),因此对于文本尾部方向,称为“前”,因为这个时候,正则引擎还没走到那块,而对文本头部方向,则称为“后”,因为正则引擎已经走过了那一块地方。

注意:后顾性能损耗比较大,js只支持前瞻(知乎上看到的,具体原因不详)

上面的比较概念话,尝试用大白话讲讲,就说皇上选妃吧,先行条件得是美女吧,长得“如花”那样皇上可不要,漂亮这关过了,皇上想要这个美女也不行,皇室有规矩,必须是贵族血统。

那么“漂亮”就是正常的匹配,匹配到了,还得看看家室是不是贵族。”贵族”相当于前瞻条件

前瞻分两种一种是正向前瞻(?=xxx), 另一种是负向前瞻(?!xxx)

需要区分的地方

1.正则表达式中^的用法

用法一: 限定开头

​ 文档上给出了解释是匹配输入的开始,如果多行标示被设置成了true,同时会匹配后面紧跟的字符。 比如 /^A/会匹配”An e”中的A,但是不会匹配”ab A”中的A

用法二:(否)取反

​ 当这个字符出现在一个字符集合模式的第一个字符时,他将会有不同的含义。

​ 比如:/[^a-z\s]/会匹配"my 3 sisters"中的"3"这里的”^”的意思是字符类的否定,上面的正则表达式的意思是匹配不是(a到z和空白字符)的字符。

总结:

什么时候”^”是否定字符,什么时候是匹配开始行

表示限定开头的示例:

1
`/``[(^\s``+``)(\s``+``$)]``/``g``(^cat)$``(^cat$)``^(cat)$``^(cat$)`

表示否定的示例:

1
`[^a]表示“匹配除了a的任意字符”。``[^a``-``zA``-``Z0``-``9``]表示“找到一个非字母也非数字的字符”。``[\^abc]表示“找到一个插入符或者a或者b或者c”。`

经过对比,只要是”^”这个字符是在中括号”[]”中被使用的话就是表示字符类的否定,如果不是的话就是表示限定开头。我这里说的是直接在”[]”中使用,不包括嵌套使用。
其实也就是说”[]”代表的是一个字符集,”^”只有在字符集中才是反向字符集的意思。

特殊案例

1.正则匹配中括号

在正则表达式中,前中括号 [ 属于特殊字符,要匹配它,需要转义,即加上 \;而后中括号 ] 不属于特殊字符,不必加转义字符

举例说明:

需要匹配以下字符串中括号里面的内容

1
["www.baidu.com","www.wenta.com","www.insoan.com"]

有两种规则可以使用,任选一:

  • 第一种: "|]|\[ 过滤不需要的
  • 第二种: \[(.+)] 取出需要的

整理出常用的正则供学习参考

  • 手机号码匹配
1
/^1[34578][0-9]{9}$/
  • 网址校验
1
/^https?/
  • 日期抽取
1
/^(\d{4})[/-](\d{2})[/-](\d{2})$/
  • IP校验
1
\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}
  • IP单段提取
1
(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})
  • 提取一段内容中的所有IP地址
1
((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})(\.((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})){3}
  • 判断字符串是否是IP地址
1
^((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})(\.((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})){3}$
  • 邮箱校验
1
\w[-\w.+]*@([A-Za-z0-9][-A-Za-z0-9]+\.)+([A-Za-z]{2,14})

附上常用正则英文版

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站

Android studio解决依赖冲突的三种有效方法

发表于 2020-05-15

第一种:

排除依赖中的指定包

1
2
3
compile ('com.mcxiaoke.viewpagerindicator:library:2.4.1') {
exclude group: 'com.android.support'
}

第二种:

force强制设置某个模块的版本

1
2
3
4
configurations.all {
resolutionStrategy {
force 'com.android.support:support-v4:24.1.0'
}

第二种:

force强制设置整个工程的的版本 在根目录的gradle.build文件中设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
allprojects {

configurations.all {
resolutionStrategy.eachDependency { DependencyResolveDetails details ->
def requested = details.requested
if (requested.group == 'com.android.support') {
if (requested.name.startsWith("support-fragment")) {
details.useVersion '28.0.0'
}
}
}
}

}

本文为作者原创 转载时请注明出处 谢谢

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站

Google Admob移动广告快速集成步骤

发表于 2020-05-15

第一步:引入依赖包
1
2
//admob广告
implementation 'com.google.android.gms:play-services-ads:17.2.0'
第二步:在清单文件中设置appID
1
2
3
4
5
6
7
<application
<!-- admob配置 -->
<meta-data
android:name="com.google.android.gms.ads.APPLICATION_ID"
<!-- 注意 这里设置应用id 而不是广告单元id 每个广告都有各自独立的id -->
android:value="ca-app-pub-xxxxxxxxxxxxxxxxxxxx"/>
</application>
第三步:在布局文件中设置广告显示的具体位置
1
2
3
4
5
6
7
8
9
<!-- 布局中可以设置广告单元id  这里考虑到防止反编译 改成在代码中设置-->
<com.google.android.gms.ads.AdView xmlns:ads="http://schemas.android.com/apk/res-auto"
android:id="@+id/adView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
ads:adSize="SMART_BANNER"
></com.google.android.gms.ads.AdView>
第四步:初始化Admob
1
2
// 初始化Admob  这个地方填appid 注意
MobileAds.initialize(this, "ca-app-pub-xxxxxxxxxxxxxxxxxxxx");
第五步: 在对应的Activity或Fragment中设置广告显示
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
private static final String AD_UNIT_ID = "ca-app-pub-xxxxxxxxxxxxxxxxxxxx";

private void initAdmob() {
mAdView = findViewById(R.id.adView);
mAdView.setAdUnitId(AD_UNIT_ID);
AdRequest adRequest = new AdRequest.Builder().build();
mAdView.loadAd(adRequest);

mAdView.setAdListener(new AdListener() {
@Override
public void onAdLoaded() {
// Code to be executed when an ad finishes loading.
//广告加载完成后,系统会执行 onAdLoaded() 方法。
// 如果您想延迟向 Activity 或 Fragment 中添加AdView的操作(例如,延迟到您确定广告会加载时),可以在此处进行。
}

@Override
public void onAdFailedToLoad(int errorCode) {
// Code to be executed when an ad request fails.
//onAdFailedToLoad() 是唯一包含参数的方法。errorCode 参数会指明发生了何种类型的失败。系统将这些可能的类型值定义为AdRequest类中的如下常量:
//ERROR_CODE_INTERNAL_ERROR - 内部出现问题;例如,收到广告服务器的无效响应。
//ERROR_CODE_INVALID_REQUEST - 广告请求无效;例如,广告单元 ID 不正确。
//ERROR_CODE_NETWORK_ERROR - 由于网络连接问题,广告请求失败。
//ERROR_CODE_NO_FILL - 广告请求成功,但由于缺少广告资源,未返回广告。
}

@Override
public void onAdOpened() {
// Code to be executed when an ad opens an overlay that
// covers the screen.
//此方法会在用户点按广告时调用。
}

@Override
public void onAdClicked() {
// Code to be executed when the user clicks on an ad.
}

@Override
public void onAdLeftApplication() {
// Code to be executed when the user has left the app.
//此方法会于 onAdOpened() 之后在用户点击打开其他应用(例如,Google Play)时调用,从而在后台运行当前应用。
}

@Override
public void onAdClosed() {
// Code to be executed when the user is about to return
// to the app after tapping on an ad.
//在用户查看广告的目标网址后返回应用时,会调用此方法。应用可以使用此方法恢复暂停的活动,或执行任何其他必要的操作,以做好互动准备。
// 有关 Android API Demo 应用中广告监听器方法的实现方式,请参阅 AdMob AdListener 示例。
}
});
}

本文为作者原创 转载时请注明出处 谢谢

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站

Android Studio下JNI开发的基本步骤

发表于 2020-05-14
  • 第一步 设置ndk路径 配置相应的开发环境

  • 第二步 配置快捷键 Settings -> External tools中配置javah,javap,ndk-build快捷方式,(这一步主要是为了简化命令行输入,使用原生命令行也是可以的)

javah参数配置(直接拷贝):
Program: $JDKPath$\bin\javah.exe
Parameters: -classpath . -jni -o $ModuleFileDir$/src/main/jni/$Prompt$ $FileClass$
Working directory: $ModuleFileDir$\src\main\Java
Parameters的另外一种写法: -classpath . -jni -d $ModuleFileDir$/src/main/jni $FileClass$

javap参数配置(直接拷贝):*
Program: $JDKPath$\bin\javap
Parameters: -s -p $FileClass$
Working directory: $ModuleFileDir$\build\intermediates\classes\debug

ndk-build参数配置(直接拷贝):

Program: D:\Android_NDK\android-ndk-r11b\ndk-build.cmd

Working directory: $ModuleFileDir$\src\main

  • 第三步 创建java类 引用即将创建的链接库,以及创建所需要的本地方法
1
2
3
4
5
static {
System.loadLibrary("MyJni");//导入指定的so库文件名称
}
public native String getStringFromNative();//本地方法
public native String getString_From_c();
  • 第四步 使用之前配置的javah快捷键快速生成.h头文件

此时 会自动创建jni目录并生成头文件

  • 第五步 参考头文件 在jni目录下开始编写C/C++代码

注:

项目结构切换成 Android状态时,jni文件夹显示成 cpp名字!

当切换成project时就显示成jni文件夹!!

如下图:

添加如下代码:

  • 第六步 配置gradle
1
2
3
4
5
 ndk{
moduleName "MyJni"//编译后so库的名字
ldLibs "log"//连接的库,可以有多个
abiFilters "armeabi","armeabi-v7a","x86"//指定so库运行的cpu架构,有armeabi armeabi-v7a arm64-v8a x86 x86_64 mips mips64这些,常用的是armeabi和armeabi-v7a
}

点击Androidstudio 菜单栏 Build ->ReBuildProject 后自动生成Android.mk文件

把Android.mk文件拷贝到 main/jni文件夹下 然后右键—>External Tools –>ndk-build 生成 .so文件!!

其次 在项目的gradle.properties 文件中添加

1
android.useDeprecatedNdk=true
  • 第七步 运行java代码 调取c库

注意事项

  • 加载生成的动态库指定的文件名( System.loadLibrary(“MyJni”);)和生成.so时指定的名字(build.gradle中的ndk{moduleName “MyJni” }),还有Android.mk中LOCAL_MODULE := MyJni三者名称需要保持一致;

  • 异常提示不支持c++

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    Error: Flag android.useDeprecatedNdk is no longer supported and will be removed in the next version of Android Studio.  Please switch to a supported build system.
    Consider using CMake or ndk-build integration. For more information, go to:
    https://d.android.com/r/studio-ui/add-native-code.html#ndkCompile
    To get started, you can use the sample ndk-build script the Android
    plugin generated for you at:
    E:\IT\youban\module_c++\build\intermediates\ndk\release\Android.mk
    Alternatively, you can use the experimental plugin:
    https://developer.android.com/r/tools/experimental-plugin.html
    To continue using the deprecated NDK compile for another 60 days, set
    android.deprecatedNdkCompileLease=1570504380180 in gradle.properties

    解决方案:在build.gradle中添加如下配置即可:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    android{

    //增加之后如下信息之后,右键项目的时候Link C++ Project with Gradle选项不再显示;
    // externalNativeBuild {
    // ndkBuild {
    // path file('src/main/jni/Android.mk')
    // }
    // }

    }

附:C调java代码

Java中代码如下:
1
2
3
4
5
6
7
8
static {

System.loadLibrary("MyJni");//导入指定的so库文件
}

public void show(){
System.out.println("hahaha C++调了我");
}
C中代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//在c代码里面调用java代码里面的方法

//1 . 找到java代码的 class文件
jclass dpclazz = (*env)->FindClass(env, "com/insworks/module_ccc/CCCTestActivity");
if (dpclazz == 0) {
return (*(*env)).NewStringUTF(env, "NDK 没有找到指定的类");
}

//2 寻找class里面的方法
jmethodID method1 = (*env)->GetMethodID(env, dpclazz, "show", "()V");
if (method1 == 0) {
return (*(*env)).NewStringUTF(env, "NDK 没有找到方法");
}

//3.实例化类
jobject jobject1 = (*env)->AllocObject(env, dpclazz);

//4 .调用这个方法
(*env)->CallVoidMethod(env, jobject1, method1);

注意事项:

  1. 原生方法在C和C++的调用方式不同,例如:
1
2
3
4
/* C */
return (*env)->NewStringUTF(env, "Hello World");
/* C++ */
return env->NewStringUTF("Hello World");

​ 在C语言中,JNIEnv是指向JNINativeInterface结构的指针,使用它必须要解引用。而第一个参数还是env,学过C和C++语言都知道,C语言是面向过程语言,NewStringUTF只是一个函数指针,调用该方法还不清楚调用者,所以要传递env,而C++就不用,因为C++是面向对象语言,这个就不解释咯

2.关于C++调用C函数或者变量

1
2
3
4
5
//在C++中引用C语言中的函数和变量,在包含C语言头文件时(假设为cExample.h),需进行以下处理:
//  extern "C"
//  {
//    #include "cExample.h";
//  }

3.关于so文件的名称问题

Android.mk文件中的LOCAL_MODULE 决定了so文件的名称,LOCAL_MODULE 的名称可以手动修改也可以在build.gradle中配置:

1
2
3
4
       ndk {
// moduleName "native_datahelp"//编译后so库的名称

}

4.关于System.loadLibrary();

为什么loadLibrary中填入的名称不一致,却能依然运行成功不报错?

目前自测发现 build文件中出现很多编译后的so库 有可能是旧so库未及时清除的原因,可以过研究研究build文件夹,里面藏着非常多的秘密

5.ndk-build不执行照样运行成功

C/C++源码文件改动后自动调用ndk-build编译生成新的so库存放在build文件夹中,可以拷贝直接使用,ndk-build生成正式的so库,这跟apk打包是同样的道理 Android.mk文件是必须存在的,否则无法编译生成so库,也无法编译识别源文件

本文为作者原创 转载时请注明出处 谢谢

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站

Android Studio 自定义Gradle插件

发表于 2020-05-14

本文内容包括:

  • 利用AndroidStudio,编写自定义Gradle plugin
  • MavenDeployer 发布plugin
  • 使用Gradle plugin

简介

之前写了一个Android中的AOP框架Cooker.
这里总结一下里面用到的两块小知识:

1)自定义 Gradle plugin
2)发布自己的jar到 maven仓库

项目中引入自定义Gradle plugin一般有三种方法:

  1. 直接写在 build.gradle中.
  2. plugin源码放到rootProjectDir/buildSrc/src/main/groovy目录下
  3. plugin打包成jar, 发布到maven仓库, 然后项目通过Build Script依赖jar的形式引入

下面介绍的是第3种方式.

一. 用AndroidStudio中写plugin

1.新建一个Android工程

2.在这个工程里面,新建一个Android Library

先起名叫cooker-plugin吧, 我们将会用这个library写Gradle plugin

3.建立plugin的目录结构

把这个cooker-plugin中默认产生的文件都删除, 然后按照下面结构新建文件

解释

1.因为我们用Groovy写的插件, 插件代码放在 src/main/groovy下

2.在src/main/resources/META-INF/gradle-plugins 里声明plugin信息
比如:新建cooker-plugin.properties文件,内容如下

1
2
> 
>

implementation-class=com.helen.plugin.CookerPlugin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
> 这里:  
> "cooker-plugin" 是插件名称;
> "com.helen.plugin.CookerPlugin" 是对应的插件实现类

3.build.gradle 声明用groovy开发

```java
apply plugin: 'groovy'

dependencies {
compile gradleApi()
compile localGroovy()
}

repositories {
mavenCentral()
}

4.实现插件

实现plugin,其实就是需要继承实现Plugin 的接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.helen.plugin

import org.gradle.api.Plugin
import org.gradle.api.Project

public class CookerPlugin implements Plugin<Project> {

void apply(Project project) {
//这里实现plugin的逻辑
//巴拉巴拉巴拉
println "hello, this is cooker plugin!"

//cooker-plugin
//比如这里加一个简单的task
project.task('cooker-test-task') << {
println "hello, this is cooker test task!"
}
}
}

5.一个简单的plugin就写好了

在cooker-plugin项目中, build一下.
就能在build/libs下生成对应的plugin插件了

这个插件就能使用了. 可以发布在本地仓库或者Maven仓库.

二. mavenDeployer发布插件

下面介绍一下利用mavenDeployer发布在本地仓库.

1. 引入 mavenDeplayer插件

修改cooker-plugin的build.gradle, 修改后如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
apply plugin: 'groovy'
//添加maven plugin, 用于发布我们的jar
apply plugin: 'maven'

dependencies {
compile gradleApi()
compile localGroovy()
}

repositories {
mavenCentral()
}

//设置maven deployer
uploadArchives {
repositories {
mavenDeployer {
//设置插件的GAV参数
pom.groupId = 'com.helen.plugin'
pom.artifactId = 'cooker-plugin'
pom.version = 1.0
//文件发布到下面目录
repository(url: uri('../release'))
}
}
}

2.用uploadArchices发布

运行uploadArchives. 就能在设置的仓库路径中生成 cooker-plugin了

三. 使用gradle plugin

1.在build.gradle引入 cooker-plugin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
apply plugin: 'com.android.application'
//使用cooker-plugin
apply plugin: 'cooker-plugin'

buildscript {
repositories {
maven {
//cooker-plugin 所在的仓库
//这里是发布在本地文件夹了
url uri('../release')
}
}
dependencies {
//引入cooker-plugin
classpath 'com.helen.plugin:cooker-plugin:1.0'
}
}

2. 我们编译App的时候,cooker-plugin就会介入了

每次clean/build时, 在Gradle Console可以看到我们的log
hello, this is cooker plugin!

1
2
3
4
Configuration on demand is an incubating feature.
hello, this is cooker plugin!
Incremental java compilation is an incubating feature.
:app:preBuild UP-TO-DATE

3.使用cooker-plugin中定义的task

前面demo中, 我们新建了一个task: cooker-test-task, 他简单输出一句log.下面测试运行一下这个task.
在控制台输入 gradle cooker-test-task 运行结果如下

四. 总结

Demo地址-Github
到此为止, 自定义Gradle plugin就介绍完了.
结合AndroidStudio, 自定义Gradle plugin可以完成很多功能.

比如cooker的plugin完成了:
1)添加编译依赖
2)进行Aspecj编译
3)自动生成混淆配置

五. 开发只针对当前项目的Gradle插件(附)

前面我们讲了如何自定义gradle插件并且打包出去,可能步骤比较多。有时候,你可能并不需要打包出去,只是在这一个项目中使用而已,那么你无需打包这个过程。

只是针对当前项目开发的Gradle插件相对较简单。步骤之前所提到的很类似,只是有几点需要注意:

1
2
1.新建的Module名称必须为BuildSrc
2.无需resources目录

目录结构如下所示:

其中,build.gradle内容为:

1
2
3
4
5
6
7
8
9
10
apply plugin: 'groovy'

dependencies {
compile gradleApi()//gradle sdk
compile localGroovy()//groovy sdk
}

repositories {
jcenter()
}

SecondPlugin.groovy内容为:

SecondPlugin.groovy内容为:

1
2
3
4
5
6
7
8
9
10
11
12
package  com.hc.second

import org.gradle.api.Plugin
import org.gradle.api.Project

public class SecondPlugin implements Plugin<Project> {

void apply(Project project) {
System.out.println("========================");
System.out.println("这是第二个插件!");
System.out.println("========================");
}

在app这个Module中如何使用呢?直接在app的build.gradle下加入

1
apply plugin: com.hc.second.SecondPlugin

clean一下,再make project,messages窗口信息如下

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站

Android 中使用WebViewJavaScriptBridge进行H5和原生的交互

发表于 2020-05-14

1. 概述

当我们采用H5与Native原生结合开发,使用H5去开发一些功能的时候,肯定会涉及到Android与Js互相调用的问题,通常有两种实现方式,

第一种 使用原生的addJavascriptInterface()来解决

第二种 使用第三方框架WebViewJavascriptBridge 这也是我今天要分享的部分

2.为什么要使用WsebViewJavascriptBridge

对于安卓开发有一段时间的人来说,知道安卓4.4以前谷歌的webview存在安全漏洞,网站可以通过js注入就可以随便拿到客户端的重要信息,甚至轻而易举的调用本地代码进行流氓行为,谷歌后来发现有此漏洞后,增加了防御措施,如果要是js调用本地代码,开发者必须在代码中申明JavascriptInterface,

列如在4.0之前我们要使得webView加载js只需如下代码:

1
mWebView.addJavascriptInterface(new JsToJava(), "myjsfunction");

4.4之后调用需要在调用方法加入加入@JavascriptInterface注解,如果代码无此申明,那么也就无法使得js生效,也就是说这样就可以避免恶意网页利用js对安卓客户端的窃取和攻击。

但是即使这样,我们很多时候需要在js记载本地代码的时候,要做一些判断和限制,或者有可能也会做些过滤和对用户友好提示,因此JavascriptInterface也就无法满足我们的需求了,特此有大神就写出了WebViewJavascriptBridge框架。

3.开始使用

第一步.Android Studio 导包
1
2
3
4
5
6
7
8
repositories {
// ...
maven { url "https://jitpack.io" }
}

dependencies {
compile 'com.github.lzyzsd:jsbridge:1.0.4'
}
第二步.在布局文件中添加
1
2
3
4
<com.github.lzyzsd.jsbridge.BridgeWebView
android:id="@+id/wv_web_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
第三步.代码中添加交互方法

H5调android方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//android端代码
mWebView.registerHandler("test", new BridgeHandler() {
@Override
public void handler(String data, CallBackFunction function) {
function.onCallBack("指定Handler收到Web发来的数据,回传数据给你");
}

//H5端代码
function test() {
//调用本地java方法
//第一个参数是 调用java的函数名字 第二个参数是要传递的数据 第三个参数js在被回调后具体执行方法,responseData为java层回传数据
var data='发送消息给java代码指定接收';
window.WebViewJavascriptBridge.callHandler(
'test'
,data
, function(responseData) {
bridgeLog('来自Java的回传数据: ' + responseData);
}
);
}

或者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//android端代码 
mWebView.setDefaultHandler(new BridgeHandler() {
@Override
public void handler(String data, CallBackFunction function) {
function.onCallBack("指定Handler收到Web发来的数据,回传数据给你");
}
});

//H5端代码
function test() {
//发送消息给java代码
var data = '发送消息给java代码全局接收';

window.WebViewJavascriptBridge.send(
data
, function(responseData) {
bridgeLog('来自Java的回传数据: ' +responseData);
}
);
}

以上两种方式 一个是指定调具体协定好的方法,一个是全局调用

android调H5

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//android端代码 
mWebView.send("发送数据给web默认接收",new CallBackFunction(){
@Override
public void onCallBack(String data) {
Log.e(TAG, "来自web的回传数据:" + data);
}
});

//H5端代码
//注册回调函数,第一次连接时调用 初始化函数
connectWebViewJavascriptBridge(function(bridge) {
bridge.init(function(message, responseCallback) {
bridgeLog('默认接收收到来自Java数据: ' + message);
var responseData = '默认接收收到来自Java的数据,回传数据给你';
responseCallback(responseData);
});


})

或者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//android端代码 
mWebView.callHandler("test","发送数据给web指定接收",new CallBackFunction(){
@Override
public void onCallBack(String data) {
Log.e(TAG, "来自web的回传数据:" + data);
}
});
//H5端代码
connectWebViewJavascriptBridge(function(bridge) {
bridge.registerHandler("test", function(data, responseCallback) {
bridgeLog('指定接收收到来自Java数据: ' + data);
var responseData = '指定接收收到来自Java的数据,回传数据给你';
responseCallback(responseData);
});
})

同样 两种方式一个是不指定方法,另一个是指定具体方法

到此为止还无法交互,还需要配置setWebViewClient

1
mWebView.setWebViewClient(new BridgeWebViewClient(mWebView));

这步非常关键,如果不配置 测试点击压根就不响应,如果你需要自定义WebViewClient,必须实现对应构造方法,而且重写的方法必须调用父类方法,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
private class MyWebViewClient extends BridgeWebViewClient {
//必须
public MyWebViewClient(BridgeWebView webView) {
super(webView);
}


@Override
public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
super.onReceivedError(view, request, error);//这个不能省略
// 避免出现默认的错误界面
view.loadUrl("about:blank");
}

到此为止,配置完毕,H5和Android就可以互相调用了

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站

如何在markdown中插入js和css

发表于 2020-05-14

有时候我们可能需要改动markdown 的样式。由于markdown的呈现形式是html,可以直接把以下部分添加到markdown中,即可更改默认的样式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150

<style>
h1,
h2,
h3,
h4,
h5,
h6,
p,
blockquote {
margin: 0;
padding: 0;
}
body {
font-family: "Helvetica Neue", Helvetica, "Hiragino Sans GB", Arial, sans-serif;
font-size: 13px;
line-height: 18px;
color: #737373;
background-color: white;
margin: 10px 13px 10px 13px;
}
table {
margin: 10px 0 15px 0;
border-collapse: collapse;
}
td,th {
border: 1px solid #ddd;
padding: 3px 10px;
}
th {
padding: 5px 10px;
}

a {
color: #0069d6;
}
a:hover {
color: #0050a3;
text-decoration: none;
}
a img {
border: none;
}
p {
margin-bottom: 9px;
}
h1,
h2,
h3,
h4,
h5,
h6 {
color: #404040;
line-height: 36px;
}
h1 {
margin-bottom: 18px;
font-size: 30px;
}
h2 {
font-size: 24px;
}
h3 {
font-size: 18px;
}
h4 {
font-size: 16px;
}
h5 {
font-size: 14px;
}
h6 {
font-size: 13px;
}
hr {
margin: 0 0 19px;
border: 0;
border-bottom: 1px solid #ccc;
}
blockquote {
padding: 13px 13px 21px 15px;
margin-bottom: 18px;
font-family:georgia,serif;
font-style: italic;
}
blockquote:before {
content:"\201C";
font-size:40px;
margin-left:-10px;
font-family:georgia,serif;
color:#eee;
}
blockquote p {
font-size: 14px;
font-weight: 300;
line-height: 18px;
margin-bottom: 0;
font-style: italic;
}
code, pre {
font-family: Monaco, Andale Mono, Courier New, monospace;
}
code {
background-color: #fee9cc;
color: rgba(0, 0, 0, 0.75);
padding: 1px 3px;
font-size: 12px;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
border-radius: 3px;
}
pre {
display: block;
padding: 14px;
margin: 0 0 18px;
line-height: 16px;
font-size: 11px;
border: 1px solid #d9d9d9;
white-space: pre-wrap;
word-wrap: break-word;
}
pre code {
background-color: #fff;
color:#737373;
font-size: 11px;
padding: 0;
}
sup {
font-size: 0.83em;
vertical-align: super;
line-height: 0;
}
*{
-webkit-print-color-adjust: exact;
}
@media screen and (min-width: 914px) {
body {
width: 854px;
margin:10px auto;
}
}
@media print {
body,code,pre code,h1,h2,h3,h4,h5,h6 {
color: black;
}
table, pre {
page-break-inside: avoid;
}
}
</style>

直接将以上代码插入文本的上方, 如下:

image-20210831104548134

生成html的结果为:

image-20210831104446641

如何在Hexo发布博客的Md文件中引入JS代码

跟引入css样式一样 直接插入js代码即可 这里有两种方式:

第一种 引用第三方js文件

1
<script type="text/javascript" src="/js/src/echarts.min.js"></script>

第二种 常规写法

1
2
3
4
5
<script>

var bmapChart=echarts.init(document.getElementById("map-wrap"));

</script>

附加

如果使用hexo编译html 如果要将html代码当做普通文本处理 可以添加以下raw标签, 如下:

1
2
3
4
5
{% raw %}

这里是html代码区域, 表示失去html的功能

{% endraw %}

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站

如何使用命令行下载更新Android SDK

发表于 2020-05-13

最近需要在服务器上用Jenkins自动打包Android app,从google官网上下载的Linux版本sdk结果发现里面就只有一个tools目录有文件,其他的都没有。。。
无奈,服务器是没有界面的,之前都习惯用IDE去安装更新,现在尝试用命令行下载更新了。

下载Android SDK for Linux

从google的官网下载最新Linux版本SDK,由于dl.google.com域名一直没有被墙,所以才可以直接从官网下了。这点不错~

1
2
3
4
5
6
下载
$ wget https://dl.google.com/android/android-sdk_r24.4.1-linux.tgz
或者
$curl -o https://dl.google.com/android/android-sdk_r24.4.1-linux.tgz
或者
$wget -O https://dl.google.com/android/android-sdk_r24.4.1-linux.tgz
1
2
解压
$ tar zxvf android-sdk_r24.4.1-linux.tgz

更新Android SDK

前面说到了,我们下载的这个包其实只有tools目录下才有东西。既然google给了我们这个,表示这里面肯定有可以更新SDK的工具啦。
其实就是tools/android这个文件

1
$ cd android-sdk-linux/tools

进入后

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
查看当前可安装的SDK版本
$ ./android list sdk -a

Refresh Sources:
Fetching https://dl.google.com/android/repository/addons_list-2.xml
Validate XML
Parse XML
Fetched Add-ons List successfully
Refresh Sources
Fetching URL: https://dl.google.com/android/repository/repository-11.xml
Validate XML: https://dl.google.com/android/repository/repository-11.xml
Parse XML: https://dl.google.com/android/repository/repository-11.xml
Fetching URL: https://dl.google.com/android/repository/addon.xml
Validate XML: https://dl.google.com/android/repository/addon.xml
Parse XML: https://dl.google.com/android/repository/addon.xml
Fetching URL: https://dl.google.com/android/repository/glass/addon.xml
Validate XML: https://dl.google.com/android/repository/glass/addon.xml
Parse XML: https://dl.google.com/android/repository/glass/addon.xml
Fetching URL: https://dl.google.com/android/repository/extras/intel/addon.xml
Validate XML: https://dl.google.com/android/repository/extras/intel/addon.xml
Parse XML: https://dl.google.com/android/repository/extras/intel/addon.xml
Fetching URL: https://dl.google.com/android/repository/sys-img/android/sys-img.xml
Validate XML: https://dl.google.com/android/repository/sys-img/android/sys-img.xml
Parse XML: https://dl.google.com/android/repository/sys-img/android/sys-img.xml
Fetching URL: https://dl.google.com/android/repository/sys-img/android-wear/sys-img.xml
Validate XML: https://dl.google.com/android/repository/sys-img/android-wear/sys-img.xml
Parse XML: https://dl.google.com/android/repository/sys-img/android-wear/sys-img.xml
Fetching URL: https://dl.google.com/android/repository/sys-img/android-tv/sys-img.xml
Validate XML: https://dl.google.com/android/repository/sys-img/android-tv/sys-img.xml
Parse XML: https://dl.google.com/android/repository/sys-img/android-tv/sys-img.xml
Fetching URL: https://dl.google.com/android/repository/sys-img/google_apis/sys-img.xml
Validate XML: https://dl.google.com/android/repository/sys-img/google_apis/sys-img.xml
Parse XML: https://dl.google.com/android/repository/sys-img/google_apis/sys-img.xml

Packages available for installation or update: 41
1- Android SDK Tools, revision 25.2.2
2- Android SDK Platform-tools, revision 24.0.4
3- Android SDK Build-tools, revision 24.0.3
4- Documentation for Android SDK, API 24, revision 1
5- SDK Platform Android 7.0, API 24, revision 2
6- SDK Platform Android 6.0, API 23, revision 3

因为是首次安装,所有有非常多的版本可下载。
我们可以有2个选择:

  1. 安装所有版本的SDK

    1
    $ ./android update sdk -u
  1. 只安装我们需要SDK版本

    1
    2
    3
    4
    5
    6
    只安装指定序号的版本
    $ ./android update sdk -u -t 序号
    如:安装Build-tools, revision 24.0.3

    $ ./android update sdk -u -t 3
    需要同意license,输入 y 回车即可

因为是在服务器上,建议直接安装所有版本的吧,不然后续可能有些app需要这个版本,又有的需要那个版本。还不如一开始就全部安装好。

安装后可跳转到上一级目录查看是否已经有了。

1
2
3
4
5
6
$ cd ..
$ ls
add-ons build-tools platforms SDK Readme.txt temp tools
$ cd build-tools
$ ls
24.0.3

可以看到安装成功了。

总结

通过这些我们也可以推测出其实那些IDE图形界面底层调用的也是这些命令吧

附加可能会使用到的linux指令

1
2
查看当前目录的实际路径
$pwd
1
2
全局搜索指定文件 包括文件夹和文件
$find . -name "文件名"
1
2
不区分大小写查找
$find -iname "*SaleContractFromDc*"
1
2
从/开始查找以.log结尾的文件
$find / -name "*.log"
1
2
下载文件到指定路径 这将只有在路径存在时下载。下载将保留远程文件名。下载后,将返回原始位置
$cd target/path && { curl -O URL ; cd -; }
1
2
查看系统整体空间剩余情况
$df -h
1
2
查看每个文件夹的占用情况
$du -sh
1
2
删除非空目录
$rm -rf 目录
1
2
3
4
查看 或 编辑 环境变量的配置
$vim /etc/profile
或者直接使用
$export
1
2
3
4
配置环境变量
export ANDROID_SDK_HOME=/root/Android/sdk/android-sdk-linux
export PATH=$PATH:${ANDROID_SDK_HOME}/tools
export PATH=$PATH:${ANDROID_SDK_HOME}/platform-tools
1
2
移动文件夹下所有东西到zone中
$mv /usr/lib/* /zone

本帖附件

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站

Android Android 获取应用签名证书的SHA1值和MD5值几种方法

发表于 2020-04-13

第一种 只有APK文件的情况下

  1. 首先将你的项目打包,一定要使用签名文件打包成release版本的apk文件。

  2. 将你的apk文件后缀修改成rar文件,解压。

  3. 在解压后的文件中找到META-INF文件,该目录下会存在CERT.RSA文件。

  4. 在META-INF目录下打开cmd(按住Shift,点击鼠标右键),输入命令 :keytool -printcert -file CERT.RSA,就可以在CMD命令窗口中看到签名文件的信息了,其中包括了SHA1值和MD5值。

第二种 你已经有了签名文件 并且知道密码

在jks签名文件目录下打开cmd(按住Shift,点击鼠标右键),输入命令 :keytool -list -v -keystore xxx.jks,就可以在CMD命令窗口中看到签名文件的信息了,其中包括了SHA1值和MD5值。

第三种 通过代码获取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167

/**
* 获取签名工具类
*/
public class AppSigning {
public final static String MD5 = "MD5";
public final static String SHA1 = "SHA1";
public final static String SHA256 = "SHA256";
private static HashMap<String, ArrayList<String>> mSignMap = new HashMap<>();

/**
* 返回一个签名的对应类型的字符串
*
* @param context
* @param type
* @return 因为一个安装包可以被多个签名文件签名,所以返回一个签名信息的list
*/
public static ArrayList<String> getSignInfo(Context context, String type) {
if (context == null || type == null) {
return null;
}
String packageName = context.getPackageName();
if (packageName == null) {
return null;
}
if (mSignMap.get(type) != null) {
return mSignMap.get(type);
}
ArrayList<String> mList = new ArrayList<String>();
try {
Signature[] signs = getSignatures(context, packageName);
for (Signature sig : signs) {
String tmp = "error!";
if (MD5.equals(type)) {
tmp = getSignatureByteString(sig, MD5);
} else if (SHA1.equals(type)) {
tmp = getSignatureByteString(sig, SHA1);
} else if (SHA256.equals(type)) {
tmp = getSignatureByteString(sig, SHA256);
}
mList.add(tmp);
}
} catch (Exception e) {
LogUtil.e(e.toString());
}
mSignMap.put(type, mList);
return mList;
}

/**
* 获取签名sha1值
*
* @param context
* @return
*/
public static String getSha1(Context context) {
String res = "";
ArrayList<String> mlist = getSignInfo(context, SHA1);
if (mlist != null && mlist.size() != 0) {
res = mlist.get(0);
}
return res;
}

/**
* 获取签名MD5值
*
* @param context
* @return
*/
public static String getMD5(Context context) {
String res = "";
ArrayList<String> mlist = getSignInfo(context, MD5);
if (mlist != null && mlist.size() != 0) {
res = mlist.get(0);
}
return res;
}

/**
* 获取签名SHA256值
*
* @param context
* @return
*/
public static String getSHA256(Context context) {
String res = "";
ArrayList<String> mlist = getSignInfo(context, SHA256);
if (mlist != null && mlist.size() != 0) {
res = mlist.get(0);
}
return res;
}

/**
* 返回对应包的签名信息
*
* @param context
* @param packageName
* @return
*/
private static Signature[] getSignatures(Context context, String packageName) {
PackageInfo packageInfo = null;
try {
packageInfo = context.getPackageManager().getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
return packageInfo.signatures;
} catch (Exception e) {
LogUtil.e(e.toString());
}
return null;
}

/**
* 获取相应的类型的字符串(把签名的byte[]信息转换成16进制)
*
* @param sig
* @param type
* @return
*/
private static String getSignatureString(Signature sig, String type) {
byte[] hexBytes = sig.toByteArray();
String fingerprint = "error!";
try {
MessageDigest digest = MessageDigest.getInstance(type);
if (digest != null) {
byte[] digestBytes = digest.digest(hexBytes);
StringBuilder sb = new StringBuilder();
for (byte digestByte : digestBytes) {
sb.append((Integer.toHexString((digestByte & 0xFF) | 0x100)).substring(1, 3));
}
fingerprint = sb.toString();
}
} catch (Exception e) {
LogUtil.e(e.toString());
}

return fingerprint;
}

/**
* 获取相应的类型的字符串(把签名的byte[]信息转换成 95:F4:D4:FG 这样的字符串形式)
*
* @param sig
* @param type
* @return
*/
private static String getSignatureByteString(Signature sig, String type) {
byte[] hexBytes = sig.toByteArray();
String fingerprint = "error!";
try {
MessageDigest digest = MessageDigest.getInstance(type);
if (digest != null) {
byte[] digestBytes = digest.digest(hexBytes);
StringBuilder sb = new StringBuilder();
for (byte digestByte : digestBytes) {
sb.append(((Integer.toHexString((digestByte & 0xFF) | 0x100)).substring(1, 3)).toUpperCase());
sb.append(":");
}
fingerprint = sb.substring(0, sb.length() - 1).toString();
}
} catch (Exception e) {
LogUtil.e(e.toString());
}

return fingerprint;
}
}

第四种 使用Gradle Tasks

这种方法适合有源码的情况, 操作非常的简单

本帖附件

点击下载

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站

Android 设置TextView Drawable大小的几种方法

发表于 2020-04-10

第一种 使用 layer-list

1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<!--设置图片的大小 -->
<item
android:width="45dp"
android:height="45dp">
<bitmap android:src="@drawable/icon_profit" />
</item>


</layer-list>

第二种 使用自定义控件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128

/**
* 可自定义设置drawable宽高的TextView
*/
public class DrawableTextView extends AppCompatTextView {
private Drawable drawableLeft;
private Drawable drawableRight;
private Drawable drawableTop;
private int leftWidth;
private int rightWidth;
private int topWidth;
private int leftHeight;
private int rightHeight;
private int topHeight;
private Context mContext;

public DrawableTextView(Context context) {
super(context);
this.mContext = context;
init(context, null);
}

public DrawableTextView(Context context, AttributeSet attrs) {
super(context, attrs);
this.mContext = context;
init(context, attrs);
}

public DrawableTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.mContext = context;
init(context, attrs);
}

private void init(Context context, AttributeSet attrs) {
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.DrawableTextView);
drawableLeft = typedArray.getDrawable(R.styleable.DrawableTextView_leftDrawable);
drawableRight = typedArray.getDrawable(R.styleable.DrawableTextView_rightDrawable);
drawableTop = typedArray.getDrawable(R.styleable.DrawableTextView_topDrawable);
if (drawableLeft != null) {
leftWidth = typedArray.getDimensionPixelOffset(R.styleable.DrawableTextView_leftDrawableWidth, dip2px(context, 20));
leftHeight = typedArray.getDimensionPixelOffset(R.styleable.DrawableTextView_leftDrawableHeight, dip2px(context, 20));
}
if (drawableRight != null) {
rightWidth = typedArray.getDimensionPixelOffset(R.styleable.DrawableTextView_rightDrawableWidth, dip2px(context, 20));
rightHeight = typedArray.getDimensionPixelOffset(R.styleable.DrawableTextView_rightDrawableHeight, dip2px(context, 20));
}
if (drawableTop != null) {
topWidth = typedArray.getDimensionPixelOffset(R.styleable.DrawableTextView_topDrawableWidth, dip2px(context, 20));
topHeight = typedArray.getDimensionPixelOffset(R.styleable.DrawableTextView_topDrawableHeight, dip2px(context, 20));
}
}


public int dip2px(Context context, float dpValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (drawableLeft != null) {
drawableLeft.setBounds(0, 0, leftWidth, leftHeight);
}
if (drawableRight != null) {
drawableRight.setBounds(0, 0, rightWidth, rightHeight);
}
if (drawableTop != null) {
drawableTop.setBounds(0, 0, topWidth, topHeight);
}
}

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
this.setCompoundDrawables(drawableLeft, drawableTop, drawableRight, null);

}

/**
* 设置左侧图片并重绘
*/
public void setDrawableLeft(Drawable drawableLeft) {
this.drawableLeft = drawableLeft;
invalidate();
}

/**
* 设置左侧图片并重绘
*/
public void setDrawableLeft(int drawableLeftRes) {
this.drawableLeft = mContext.getResources().getDrawable(drawableLeftRes);
invalidate();
}

/**
* 设置右侧图片并重绘
*/
public void setDrawableRight(Drawable drawableRight) {
this.drawableRight = drawableLeft;
invalidate();
}

/**
* 设置右侧图片并重绘
*/
public void setDrawableRight(int drawableRightRes) {
this.drawableRight = mContext.getResources().getDrawable(drawableRightRes);
invalidate();
}

/**
* 设置上部图片并重绘
*/
public void setDrawable(Drawable drawableTop) {
this.drawableTop = drawableTop;
invalidate();
}

/**
* 设置右侧图片并重绘
*/
public void setDrawableTop(int drawableTopRes) {
this.drawableTop = mContext.getResources().getDrawable(drawableTopRes);
invalidate();
}
}

本帖附件

点击下载

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站

1…404142…48

乱码三千

android程序员一枚,擅长java,kotlin,python,金融投资,欢迎交流~

479 日志
139 标签
RSS
© 2024 乱码三千
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%