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

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


  • 首页

  • 归档

  • 搜索

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();
}
}

本帖附件

点击下载

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

Android 图片实现阴影效果的若干种方法

发表于 2020-04-10

第一种 使用 layer-list

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">

<!--底层的左边距离上层左边3dp, 底层的顶部,距离上层的顶部6dp,如果不做这个控制,底层和上层的左侧和上侧会重合在一起-->
<item android:left="3dp"
android:top="6dp">
<shape>
<solid android:color="#b4b5b6"/>
</shape>
</item>

<!--上层的右边距离底层的右边3dp, 上层的底部距离底层的底部6dp-->
<item android:bottom="6dp"
android:right="3dp">
<shape>
<solid android:color="#fff"/>
</shape>
</item>

</layer-list>

第二种 使用 shadow属性

shadowDX、shadowDy、shadowRadius,分别指的是阴影的横、纵坐标偏移,以及阴影的半径,

如果是TextView可以直接在布局中设置:

1
2
3
4
5
6
7
8
9
10
11
12
13
<TextView 
android:id="@+id/test_shadow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="60sp"
android:textColor="#cc000000"
android:text="Test Shadow"
android:layout_gravity="center"
android:shadowColor="#aa22ff22"
android:shadowRadius="10"
android:shadowDx="0"
android:shadowDy="0"
/>

第三种 使用android:elevation属性

<TextView
android:id="@+id/btn_test_performance"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:elevation="5dp"
android:text="@string/hello"
android:background="@drawable/shape_round_white"
android:padding="20dp"
android:layout_marginTop="10dp"
android:layout_gravity="center"/>

这种方式有个局限性, 那就是api25以上才能显示出来

第四种 使用第三方控件

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
/**
* ShadowLayout.java
* <p>
* Created by lijiankun on 17/8/11.
*/

public class ShadowLayout extends RelativeLayout {

public static final int ALL = 0x1111;

public static final int LEFT = 0x0001;

public static final int TOP = 0x0010;

public static final int RIGHT = 0x0100;

public static final int BOTTOM = 0x1000;

private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

private RectF mRectF = new RectF();

/**
* 阴影的颜色
*/
private int mShadowColor = Color.TRANSPARENT;

/**
* 阴影的大小范围
*/
private float mShadowRadius = 0;

/**
* 阴影 x 轴的偏移量
*/
private float mShadowDx = 0;

/**
* 阴影 y 轴的偏移量
*/
private float mShadowDy = 0;

/**
* 阴影显示的边界
*/
private int mShadowSide = ALL;

public ShadowLayout(Context context) {
this(context, null);
}

public ShadowLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}

public ShadowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(attrs);
}

/**
* 获取绘制阴影的位置,并为 ShadowLayout 设置 Padding 以为显示阴影留出空间
*/
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);

float effect = mShadowRadius + dip2px(5);
float rectLeft = 0;
float rectTop = 0;
float rectRight = this.getWidth();
float rectBottom = this.getHeight();
int paddingLeft = 0;
int paddingTop = 0;
int paddingRight = 0;
int paddingBottom = 0;

if (((mShadowSide & LEFT) == LEFT)) {
rectLeft = effect;
paddingLeft = (int) effect;
}
if (((mShadowSide & TOP) == TOP)) {
rectTop = effect;
paddingTop = (int) effect;
}
if (((mShadowSide & RIGHT) == RIGHT)) {
rectRight = this.getWidth() - effect;
paddingRight = (int) effect;
}
if (((mShadowSide & BOTTOM) == BOTTOM)) {
rectBottom = this.getHeight() - effect;
paddingBottom = (int) effect;
}
if (mShadowDy != 0.0f) {
rectBottom = rectBottom - mShadowDy;
paddingBottom = paddingBottom + (int) mShadowDy;
}
if (mShadowDx != 0.0f) {
rectRight = rectRight - mShadowDx;
paddingRight = paddingRight + (int) mShadowDx;
}
mRectF.left = rectLeft;
mRectF.top = rectTop;
mRectF.right = rectRight;
mRectF.bottom = rectBottom;
this.setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom);
}

/**
* 真正绘制阴影的方法
*/
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawRect(mRectF, mPaint);
}

/**
* 读取设置的阴影的属性
*
* @param attrs 从其中获取设置的值
*/
private void init(AttributeSet attrs) {
setLayerType(View.LAYER_TYPE_SOFTWARE, null); // 关闭硬件加速
this.setWillNotDraw(false); // 调用此方法后,才会执行 onDraw(Canvas) 方法

TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.ShadowLayout);
if (typedArray != null) {
mShadowColor = typedArray.getColor(R.styleable.ShadowLayout_shadowColor,
ContextCompat.getColor(getContext(), android.R.color.black));
mShadowRadius = typedArray.getDimension(R.styleable.ShadowLayout_shadowRadius, dip2px(0));
mShadowDx = typedArray.getDimension(R.styleable.ShadowLayout_shadowDx, dip2px(0));
mShadowDy = typedArray.getDimension(R.styleable.ShadowLayout_shadowDy, dip2px(0));
mShadowSide = typedArray.getInt(R.styleable.ShadowLayout_shadowSide, ALL);
typedArray.recycle();
}
mPaint.setAntiAlias(true);
mPaint.setColor(Color.TRANSPARENT);
mPaint.setShadowLayer(mShadowRadius, mShadowDx, mShadowDy, mShadowColor);
}

/**
* dip2px dp 值转 px 值
*
* @param dpValue dp 值
* @return px 值
*/
private float dip2px(float dpValue) {
DisplayMetrics dm = getContext().getResources().getDisplayMetrics();
float scale = dm.density;
return (dpValue * scale + 0.5F);
}
}

属性文件:

1
2
3
4
5
6
7
<declare-styleable name="ShadowLayout">
<attr name="shadowColor" format="color"/>
<attr name="shadowRadius" format="dimension"/>
<attr name="shadowDx" format="dimension"/>
<attr name="shadowDy" format="dimension"/>
<attr name="shadowSide" format="integer"/>
</declare-styleable>

第五种 使用9patch图片(强烈推荐)

该种方式定制性强, 兼容性好, 需要注意的是避免将9patch宽高设置过大 小图可以拉大 大图不方便缩小

链接地址:http://inloop.github.io/shadow4android/

本帖附件

点击下载

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

同一个局域网内远程控制电脑桌面的若干种方法

发表于 2020-02-11

本文持续更新中

  • 第三方软件
  • 局域网ip+3389端口

本帖附件

点击下载

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

外网电脑远程控制内网电脑桌面的若干种方法

发表于 2020-02-10

本文持续更新中

  • 第三方软件(如:QQ、TeamViewer、向日葵、anydesk、Splashtop….)
  • 路由器端口映射
  • frp

咱们接下来挨个介绍, 首先

第三方软件

如果你是小白, 追求实用性, 并且想快速上手远程控制, 那么第三方软件无疑是最好的选择, 如果你想用, 那么TeamViewer绝对是首选,个人版免费, 而且可设置无人值守, 只需一个ID和密码就能立马控制远程主机, 速度非常可观

官网地址: https://www.teamviewer.cn/cn/

路由器端口映射

如果你不情愿在电脑上装一堆软件, 或者想设置备用控制方案, 那么我建议你使用端口映射的方案, 将公网ip转发到内网需要控制的电脑ip上

frp

如果你有IT技术功底, 喜欢折腾, 并且有一颗耐操的脑袋, 同时不差钱, 那么我会建议你采用frp方案

详见《使用内网云进行内网穿透实现外网访问局域网中的服务器》

VNC桌面共享

linux可参考:https://www.jianshu.com/p/3488968c81cb

本帖附件

点击下载

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

Linux环境下docker常用指令集合

发表于 2020-01-21

使用前提

需要先安装docker, 可参考《ubuntu安装docker详细步骤》

Docker容器基础指令

  1. 启动docker

    1
    sudo systemctl start docker
  2. 停止docker

    1
    sudo systemctl stop docker
  3. 查看docker状态

    1
    sudo systemctl status docker
  4. 重启docker

    1
    sudo systemctl restart docker
  5. 设置开启启动docker

    1
    sudo systemctl enable docker

常用Docker镜像指令

  1. 查看本地镜像

    1
    sudo docker images
  2. 拉取最新镜像

    1
    sudo docker pull [镜像名]:latest
  3. 创建并后台运行容器

    1
    sudo docker run -itd --name=[名称] [镜像名]:版本名
  4. 查看当前所有运行的容器信息

    1
    2
    3
    sudo docker ps
    另外
    sudo docker ps -a //查看所有容器
  5. 运行容器 并设置在后台一直运行

    1
    sudo docker run -itd --name [名称]  -d [镜像名]
  6. 查看镜像可用版本

    1
    sudo docker search [镜像名]
  7. 进入指定容器

    1
    sudo docker exec -it [镜像名] /bin/bash
  8. 创建容器 并将本地 8080 端口映射到容器内部的 80 端口

    1
    sudo docker run --name [镜像名] -p 8080:80
  9. 创建容器 并将主机中当前目录下的 test 挂载到容器的指定目录

    1
    sudo docker run --name [镜像名]  -v $PWD/test:[容器目录]
  10. 创建容器 并指定工作目录

    1
    sudo docker run --name [镜像名]  -w [工作目录]
  11. 停止运行容器

    1
    docker stop [容器id]

    或者

    1
    docker stop [容器名]
  12. 创建容器 并设置需要密码才能访问容器服务

    1
    sudo docker run --name [镜像名]  --auth
  13. 查看容器内的标准输出

    1
    2
    3
    sudo docker logs [容器名]
    另外
    sudo docker logs -f [容器名] //停留在尾部
  14. 删除容器

    1
    sudo docker rm -f [容器id] [容器id2] //多个容器以空格隔开-f表示强制删除
  15. 重启容器

    1
    sudo docker restart [容器id]
  16. 导出容器

    1
    sudo docker export [容器id] > [目标文件]
  17. 查看容器内部运行的进程

    1
    sudo docker top [容器名]
  18. 查看容器的配置和状态信息

    1
    sudo docker inspect  [容器名]
  19. 查询最后一次创建的容器

    1
    sudo docker ps -l
  20. 为镜像添加一个新的标签

    1
    sudo docker tag [镜像名]
  21. 查询镜像在什么位置

    1
    which [镜像名]
  22. 查看容器资源占用情况

    1
    sudo docker stats
  23. 删除所有容器

    1
    sudo docker rm $(docker ps -aq)
  24. 暂停指定容器

    1
    sudo docker pause [容器名]
  25. 查看具体指令的使用方法

    1
    sudo docker help [具体指令]
  26. 查看当前已有的网络

    1
    sudo docker network ls
  27. 创建自定义网络

    1
    docker network create [网络名]
  28. 后台运行容器 并指定容器想要连接的网络

    1
    docker run -dit --name [容器名] --network [网络名] alpine
  29. 将指定容器连接到指定网络上

    1
    docker network connect [网络名] [容器名]
  30. 删除指定网络

    1
    docker network rm [网络名]
  31. 查看某时间段日志

    1
    docker logs -t --since="2019-10-24T13:23:37" --until "2019-10-25T12:23:37" [容器名]
  32. 查看某时间之后的日志

    1
    docker logs -t --since="2019-10-24T13:23:37" [容器名]
  33. 查看最近30分钟的日志

    1
    docker logs --since 30m [容器名]
  34. 只打印最后50行日志

    1
    docker logs --tail=50 [容器名]
  35. 查看指定时间后的日志,只显示最后100行

    1
    docker logs -f -t --since="2019-10-24" --tail=100 CONTAINER_ID
  36. 设置开机自动启动docker服务

    1
    sudo systemctl enable docker
  37. 禁止自启

    1
    sudo systemctl disable docker
  38. 查看docker版本

    1
    docker version
  39. 创建数据卷容器

    1
    docker run -it --name=data -v /volume ubuntu:latest /bin/bash
  40. 其他容器挂载数据卷容器

    1
    docker run -it --name=c1 --volumes-from data ubuntu:latest /bin/bash
  41. 启动已经停止的容器

    1
    docker start [容器名]
  42. 强制停止正在运行的容器

    1
    docker kill [容器名]
  43. 查看所有容器id, 包括已经停止的容器

    1
    docker ps -aq
  44. 查看所有已启动容器id

    1
    docker ps -q
  45. 查看容器内部的进程

    1
    docker top 容器名
  46. 容器配置更新

    1
    docker update xxx

    比如 更新是否自启动:

    1
    docker update --restart=always

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

1…434445…51

乱码三千

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

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