kotlin下 “android.os.NetworkOnMainThreadException” 或 “Only the original thread that created a view hierarchy can touch its views” 解决

 开发  kotlin下 “android.os.NetworkOnMainThreadException” 或 “Only the original thread that created a view hierarchy can touch its views” 解决已关闭评论
9月 272020
 

kotlin下使用协程时,出现错误提示android.os.NetworkOnMainThreadException 或 Only the original thread that created a view hierarchy can touch its views, 可以使用下面的方法解决:

 

1. 使用协程需build.gradle先加入

dependencies {

implementation “org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9”
}

 

2.  套用下面的模版:

GlobalScope.launch {
    //TODO("Background processing...")
    withContext(Dispatchers.Main) {
        // TODO("Update UI here!")
    }
    TODO("Continue background processing...")
}

kotlin下泛型使用中in / out 区别

 kotlin  kotlin下泛型使用中in / out 区别已关闭评论
9月 242020
 

在看kotlin泛型资料时,对于使用in或out看得很头大:

  1. 根据需要,泛型参数可以扮演两种角色:生产者(producer)-> out 或消费者(consumer)-> in。生产者角色就意味着泛型参数可读而不可写;消费者角色则相反,可写而不可读。out关键字表明,泛型参数将扮演可读而不可写的生产者角色。也就是说,不能再用var关键字,需要改用val.
  2. 顺便说一下,你可能听到有人用协变(covariance)和逆变(contravariance)来描述outin的用处。

 

最终总结下其实如何使用就是:

  • 父类泛型对象可以赋值给子类泛型对象,用 in;
  • 子类泛型对象可以赋值给父类泛型对象,用 out。

Done!!

PM2 如何优雅的重新加载配置文件(How to gracefully reload JSON/JS file)

 Nodejs  PM2 如何优雅的重新加载配置文件(How to gracefully reload JSON/JS file)已关闭评论
9月 102020
 

PM2使用配置文件启动,但如果修改了配置文件,使用如下命令几乎都无效(invalid)

  1. pm2 restart config.js
  2. pm2 reload config.js

似乎只有下面的粗暴方法才有效(roughly):

  1. pm2 kill
  2. pm2 start config.js

 

其实可以使用下面的命令实现优雅的重载配置文件(gracefully reload config JSON/JS file)

pm2 startOrReload config.js –update-env

 

备注:

pm2 -h 可以看到参数startOrReload , –update-env的解释:

–update-env     

force an update of the environment with restart/reload (-a <=> apply)

 

startOrReload <json>

start or gracefully reload JSON file

9个提升逼格的redis命令(keys,scan,slowlog,rename-command,bigkeys,monitor,info,config,set)

 redis  9个提升逼格的redis命令(keys,scan,slowlog,rename-command,bigkeys,monitor,info,config,set)已关闭评论
9月 082020
 

9个提升逼格的redis命令

非常好的文章,特别是使用bigkeys查找占大内存的key真是方便,推荐https://www.jianshu.com/p/4df5f2356de9

keys

我把这个命令放在第一位,是因为笔者曾经做过的项目,以及一些朋友的项目,都因为使用keys这个命令,导致出现性能毛刺。这个命令的时间复杂度是O(N),而且redis又是单线程执行,在执行keys时即使是时间复杂度只有O(1)例如SET或者GET这种简单命令也会堵塞,从而导致这个时间点性能抖动,甚至可能出现timeout。

强烈建议生产环境屏蔽keys命令(后面会介绍如何屏蔽)。

scan

既然keys命令不允许使用,那么有什么代替方案呢?有!那就是scan命令。如果把keys命令比作类似select * from users where username like '%afei%'这种SQL,那么scan应该是select * from users where id>? limit 10这种命令。

官方文档用法如下:

SCAN cursor [MATCH pattern] [COUNT count]

初始执行scan命令例如scan 0。SCAN命令是一个基于游标的迭代器。这意味着命令每次被调用都需要使用上一次这个调用返回的游标作为该次调用的游标参数,以此来延续之前的迭代过程。当SCAN命令的游标参数被设置为0时,服务器将开始一次新的迭代,而当redis服务器向用户返回值为0的游标时,表示迭代已结束,这是唯一迭代结束的判定方式,而不能通过返回结果集是否为空判断迭代结束。

使用方式:

127.0.0.1:6380> scan 0
1) "22"
2)  1) "23"
    2) "20"
    3) "14"
    4) "2"
    5) "19"
    6) "9"
    7) "3"
    8) "21"
    9) "12"
   10) "25"
   11) "7"

返回结果分为两个部分:第一部分即1)就是下一次迭代游标,第二部分即2)就是本次迭代结果集。

slowlog

上面提到不能使用keys命令,如果就有开发这么做了呢,我们如何得知?与其他任意存储系统例如mysql,mongodb可以查看慢日志一样,redis也可以,即通过命令slowlog。用法如下:

SLOWLOG subcommand [argument]

subcommand主要有:

  • get,用法:slowlog get [argument],获取argument参数指定数量的慢日志。
  • len,用法:slowlog len,总慢日志数量。
  • reset,用法:slowlog reset,清空慢日志。

执行结果如下:

127.0.0.1:6380> slowlog get 5
1) 1) (integer) 2
   2) (integer) 1532656201
   3) (integer) 2033
   4) 1) "flushddbb"
2) 1) (integer) 1  ----  慢日志编码,一般不用care
   2) (integer) 1532646897  ----  导致慢日志的命令执行的时间点,如果api有timeout,可以通过对比这个时间,判断可能是慢日志命令执行导致的
   3) (integer) 26424  ----  导致慢日志执行的redis命令,通过4)可知,执行config rewrite导致慢日志,总耗时26ms+
   4) 1) "config"
      2) "rewrite"

命令耗时超过多少才会保存到slowlog中,可以通过命令config set slowlog-log-slower-than 2000配置并且不需要重启redis。注意:单位是微妙,2000微妙即2毫秒。

rename-command

为了防止把问题带到生产环境,我们可以通过配置文件重命名一些危险命令,例如keys等一些高危命令。操作非常简单,只需要在conf配置文件增加如下所示配置即可:

rename-command flushdb flushddbb
rename-command flushall flushallall
rename-command keys keysys

bigkeys

随着项目越做越大,缓存使用越来越不规范。我们如何检查生产环境上一些有问题的数据。bigkeys就派上用场了,用法如下:

redis-cli -p 6380 --bigkeys

执行结果如下:

... ...
-------- summary -------

Sampled 526 keys in the keyspace!
Total key length in bytes is 1524 (avg len 2.90)

Biggest string found 'test' has 10005 bytes
Biggest   list found 'commentlist' has 13 items

524 strings with 15181 bytes (99.62% of keys, avg size 28.97)
2 lists with 19 items (00.38% of keys, avg size 9.50)
0 sets with 0 members (00.00% of keys, avg size 0.00)
0 hashs with 0 fields (00.00% of keys, avg size 0.00)
0 zsets with 0 members (00.00% of keys, avg size 0.00)

最后5行可知,没有set,hash,zset几种数据结构的数据。string类型有524个,list类型有两个;通过Biggest ... ...可知,最大string结构的key是test,最大list结构的key是commentlist

需要注意的是,这个bigkeys得到的最大,不一定是最大。说明原因前,首先说明bigkeys的原理,非常简单,通过scan命令遍历,各种不同数据结构的key,分别通过不同的命令得到最大的key:

  • 如果是string结构,通过strlen判断;
  • 如果是list结构,通过llen判断;
  • 如果是hash结构,通过hlen判断;
  • 如果是set结构,通过scard判断;
  • 如果是sorted set结构,通过zcard判断。

正因为这样的判断方式,虽然string结构肯定可以正确的筛选出最占用缓存,也可以说最大的key。但是list不一定,例如,现在有两个list类型的key,分别是:numberlist–[0,1,2],stringlist–[“123456789123456789”],由于通过llen判断,所以numberlist要大于stringlist。而事实上stringlist更占用内存。其他三种数据结构hash,set,sorted set都会存在这个问题。使用bigkeys一定要注意这一点。

monitor

假设生产环境没有屏蔽keys等一些高危命令,并且slowlog中还不断有新的keys导致慢日志。那我们如何揪出这些命令是由谁执行的呢?这就是monitor的用处,用法如下:

redis-cli -p 6380 monitor

如果当前redis环境OPS比较高,那么建议结合linux管道命令优化,只输出keys命令的执行情况:

[[email protected] ~]# redis-cli -p 6380 monitor | grep keys 
1532645266.656525 [0 10.0.0.1:43544] "keyss" "*"
1532645287.257657 [0 10.0.0.1:43544] "keyss" "44*"

执行结果中很清楚的看到keys命名执行来源。通过输出的IP和端口信息,就能在目标服务器上找到执行这条命令的进程,揪出元凶,勒令整改。

info

如果说哪个命令能最全面反映当前redis运行情况,那么非info莫属。用法如下:

INFO [section]

section可选值有:

  • Server:运行的redis实例一些信息,包括:redis版本,操作系统信息,端口,GCC版本,配置文件路径等;
  • Clients:redis客户端信息,包括:已连接客户端数量,阻塞客户端数量等;
  • Memory:使用内存,峰值内存,内存碎片率,内存分配方式。这几个参数都非常重要;
  • Persistence:AOF和RDB持久化信息;
  • Stats:一些统计信息,最重要三个参数:OPS(instantaneous_ops_per_sec),keyspace_hitskeyspace_misses两个参数反应缓存命中率;
  • Replication:redis集群信息;
  • CPU:CPU相关信息;
  • Keyspace:redis中各个DB里key的信息;

config

config是一个非常有价值的命令,主要体现在对redis的运维。因为生产环境一般是不允许随意重启的,不能因为需要调优一些参数就修改conf配置文件并重启。redis作者早就想到了这一点,通过config命令能热修改一些配置,不需要重启redis实例,可以通过如下命令查看哪些参数可以热修改:

config get *

热修改就比较容易了,执行如下命令即可:

config set 

例如:config set slowlog-max-len 100config set maxclients 1024

这样修改的话,如果以后由于某些原因redis实例故障需要重启,那通过config热修改的参数就会被配置文件中的参数覆盖,所以我们需要通过一个命令将config热修改的参数刷到redis配置文件中持久化,通过执行如下命令即可:

config rewrite

执行该命令后,我们能在config文件中看到类似这种信息:

# 如果conf中本来就有这个参数,通过执行config set,那么redis直接原地修改配置文件
maxclients 1024
# 如果conf中没有这个参数,通过执行config set,那么redis会追加在Generated by CONFIG REWRITE字样后面
# Generated by CONFIG REWRITE
save 600 60
slowlog-max-len 100

set

set命令也能提升逼格?是的,我本不打算写这个命令,但是我见过太多人没有完全掌握这个命令,官方文档介绍的用法如下:

SET key value [EX seconds] [PX milliseconds] [NX|XX]

你可能用的比较多的就是set key value,或者SETEX key seconds value,所以很多同学用redis实现分布式锁分为两步:首先执行SETNX key value,然后执行EXPIRE key seconds。很明显,这种实现有很严重的问题,因为两步执行不具备原子性,如果执行第一个命令后出现某些未知异常导致无法执行EXPIRE key seconds,那么分布式锁就会一直无法得到释放。

通过SET命令实现分布式锁的正式姿势应该是SET key value EX seconds NX(EX和PX任选,取决于对过期时间精度要求)。另外,value也有要求,最好是一个类似UUID这种具备唯一性的字符串。当然如果问你redis是否还有其他实现分布式锁的方案。你能说出redlock,那对方一定眼前一亮,心里对你竖起大拇指,但嘴上不会说。

Mac 上使用多点触控手势(含鼠标手势)

 mac  Mac 上使用多点触控手势(含鼠标手势)已关闭评论
8月 072020
 

在 Mac 上使用多点触控手势

使用多点触控触控板或妙控鼠标,您可以通过轻点、轻扫、捏合或开合一根或多根手指进行有用的操作。

触控板手势

有关这些手势的更多信息,请选取苹果菜单 () >“系统偏好设置”,然后点按“触控板”。您可以关闭某个手势,更改手势类型,以及了解哪些手势可在您的 Mac 上使用。

触控板手势要求使用妙控板或内建的多点触控触控板。如果您的触控板支持力度触控,您还可以进行“用力点按”操作并获得触感反馈

轻点来点按
用单指轻点来进行点按。

辅助点按(右键点按)
用双指点按或轻点。

智能缩放
用双指轻点两下可放大网页或 PDF,或缩小回原来的大小。

滚动
双指向上或向下滑动可滚动。1

放大或缩小
双指捏合或张开可放大或缩小。

旋转
双指互相以对方为中心移动,可旋转照片或其他项目。

在页面之间轻扫
双指向左或向右轻扫,可显示上一页或下一页。

打开“通知中心”
用双指从右边缘向左轻扫,可显示“通知中心”。

三指拖移
用三根手指拖移屏幕上的项目,然后点按或轻点以放下。可在“辅助功能”偏好设置中开启此功能2

 

查找和数据检测器
用三根手指轻点可查找字词,或者对日期、地址、电话号码和其他数据采取相关操作。

显示桌面
将拇指和另外三根手指同时展开,可显示桌面。

 

“启动台”
将拇指和另外三根手指合拢到一起,可显示“启动台”。

“调度中心”
用四根手指向上轻扫3,可打开“调度中心”。

应用 Exposé
用四根手指向下轻扫3,可查看正在使用的应用的所有窗口。

在全屏应用之间轻扫
用四根手指向左或向右轻扫3,可在桌面与全屏应用之间移动。

 

鼠标手势

有关这些手势的更多信息,请选取苹果菜单 () >“系统偏好设置”,然后点按“鼠标”。您可以从中关闭某个手势,更改手势类型,以及了解哪些手势可在您的 Mac 上使用。鼠标手势要求使用妙控鼠标

辅助点按(右键点按)
点按鼠标的右侧。

滚动
单指向上或向下滑动可滚动。1

智能缩放
用单指轻点两下可放大网页或 PDF,或缩小回原来的大小。

“调度中心”
用双指轻点两下,可打开“调度中心”。

在全屏应用之间轻扫
用双指向左或向右轻扫,可在桌面与全屏应用之间移动。

在页面之间轻扫
用单指向左或向右轻扫,可显示上一页或下一页。

1. 您可以在“辅助功能”偏好设置中关闭触控板滚动功能:选取苹果菜单 >“系统偏好设置”,然后点按“辅助功能”。在“鼠标与触控板”部分中,点按“触控板选项”,然后取消选择“滚动”复选框。

2.“辅助功能”偏好设置还包含单指拖移的选项:选取苹果菜单 >“系统偏好设置”,然后点按“辅助功能”。在“鼠标与触控板”部分中,点按“触控板选项”。选择“启用拖移”,然后从弹出式菜单中选取一个“拖移锁定”选项。点按问号按钮可了解有关每个选项的更多信息。

3. 在某些版本的 macOS中,这个手势使用的是三根手指,而不是四根。

 

转自苹果官网:https://support.apple.com/zh-cn/HT204895

windows/mac os里 microsoft excel下单元格内内容输入时如何回车换行?

 mac  windows/mac os里 microsoft excel下单元格内内容输入时如何回车换行?已关闭评论
8月 042020
 

如何在excel单元格输入时回车换行?

  1. windows时,要在excel单元格内换行,按alt+enter

 

  1. mac下要在excel单元格内换行,按键盘control+option(alt)+enter

DONE!

sublimeREPL下python 2 切换为 python 3

 python, sublime  sublimeREPL下python 2 切换为 python 3已关闭评论
7月 202020
 

转自:https://www.jianshu.com/p/41df9da08f60

1、安装python3

 

$ brew search python
$ brew install python3

这里安装完后不需要单独添加环境变量,程序已经处理好,可以直接运行python3命令。

$ which python
/usr/bin/python

$ which python3
/Library/Frameworks/Python.framework/Versions/3.6/bin/python3

$ echo $PATH
/Library/Frameworks/Python.framework/Versions/3.6/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin

2、安装REPL插件,并设置快捷键绑定。

打开后,搜索 SublimeREPL 安装即可。

设置快捷键:

Preferences -> Key Bindings

[   
    {"keys":["f1"],
    "caption": "SublimeREPL: Python - RUN current file",
    "command": "run_existing_window_command", "args":
    {"id": "repl_python_run",
    "file": "config/Python/Main.sublime-menu"}},

    {"keys":["f2"],
    "caption": "SublimeREPL: Python",
    "command": "run_existing_window_command", "args":
    {"id": "repl_python",
    "file": "config/Python/Main.sublime-menu"}}
]

这里我设置的 F1 运行代码,F2 打开终端。

3、按 F2 运行终端,发现默认集成的是 python2,手动改成 pyhton3,方法如下:

$ sudo find / -iname 'Main.sublime-menu'
$ vim /Users/keithtt/Library/Application\ Support/Sublime\ Text\ 3/Packages/SublimeREPL/config/Python/Main.sublime-menu

"cmd": ["python3", "-i", "-u"],
"cmd": ["python3", "-i", "-u", "-m", "pdb", "$file_basename"],
"cmd": ["python3", "-u", "$file_basename"],
"cmd": {
    "osx": ["python3", "-u", "${packages}/SublimeREPL/config/Python/ipy_repl.py"],
    "linux": ["python3", "-u", "${packages}/SublimeREPL/config/Python/ipy_repl.py"],
    "windows": ["python3", "-u", "${packages}/SublimeREPL/config/Python/ipy_repl.py"]
},

将该文件中调用 python 命令的地方全部改成 python3 即可。

改完之后再次按 F2 运行终端,效果如下:

Android 编译出现 “Cannot fit requested classes in a single dex file (# methods: xxx > 65536)” 问题解析

 android  Android 编译出现 “Cannot fit requested classes in a single dex file (# methods: xxx > 65536)” 问题解析已关闭评论
7月 082020
 

测试使用google play service的location功能时编译出现:“Cannot fit requested classes in a single dex file (# methods: 71234 > 65536)”,  在官网以下地址有关于这个问题详细的说明: https://developer.android.com/studio/build/multidex#mdex-gradle

国内因为某些原因可能打不开这个url, 复制内容如下:

为方法数超过 64K 的应用启用 MultiDex

当您的应用及其引用的库包含的方法数超过 65536 时,您会遇到一个构建错误,指明您的应用已达到 Android 构建架构规定的引用限制:

trouble writing output:
Too many field references: 131000; max is 65536.
You may try using --multi-dex option.

较低版本的构建系统会报告一个不同的错误,但指示的是同一问题:

Conversion to Dalvik format failed:
Unable to execute dex: method ID not in [0, 0xffff]: 65536

这两种错误情况会显示一个共同的数字:65536。此数字是单个 Dalvik Executable (DEX) 字节码文件内的代码可调用的引用总数。本页介绍如何通过启用称为 MultiDex 的应用配置(该配置使您的应用能够构建和读取多个 DEX 文件)越过这一限制。

关于 64K 引用限制

Android 应用 (APK) 文件包含 Dalvik Executable (DEX) 文件形式的可执行字节码文件,这些文件包含用来运行应用的已编译代码。Dalvik Executable 规范将可在单个 DEX 文件内引用的方法总数限制为 65536,其中包括 Android 框架方法、库方法以及您自己的代码中的方法。在计算机科学领域内,术语千(简称 K)表示 1024(即 2^10)。由于 65536 等于 64 X 1024,因此这一限制称为“64K 引用限制”。

Android 5.0 之前版本的 MultiDex 支持

Android 5.0(API 级别 21)之前的平台版本使用 Dalvik 运行时执行应用代码。默认情况下,Dalvik 将应用限制为每个 APK 只能使用一个 classes.dex 字节码文件。为了绕过这一限制,您可以在项目中添加 MultiDex 支持库:

dependencies {
    def multidex_version = "2.0.1"
    implementation 'androidx.multidex:multidex:$multidex_version'
}
   

如需查看此库的当前版本,请参阅版本页面中有关 MultiDex 的信息。

如果您不使用 AndroidX,请改为添加以下支持库依赖项:

dependencies {
  implementation 'com.android.support:multidex:1.0.3'
}

此库会成为应用的主要 DEX 文件的一部分,然后管理对其他 DEX 文件及其所包含代码的访问。如需了解详情,请参阅下面有关如何针对 MultiDex 配置应用的部分。

Android 5.0 及更高版本的 MultiDex 支持

Android 5.0(API 级别 21)及更高版本使用名为 ART 的运行时,它本身支持从 APK 文件加载多个 DEX 文件。ART 在应用安装时执行预编译,扫描 classesN.dex 文件,并将它们编译成单个 .oat 文件,以供 Android 设备执行。因此,如果您的 minSdkVersion 为 21 或更高的值,则默认情况下会启用 MultiDex,并且您不需要 MultiDex 支持库。

如需详细了解 Android 5.0 运行时,请阅读 ART 和 Dalvik

注意:使用 Android Studio 运行应用时,会针对您部署到的目标设备优化 build。这包括在目标设备搭载 Android 5.0 及更高版本时启用 MultiDex。由于此优化仅在使用 Android Studio 部署应用时应用,因此您可能仍需要为 MultiDex 配置发布 build,以规避 64K 限制。

规避 64K 限制

在将您的应用配置为支持使用 64K 或更多方法引用之前,您应该采取措施以减少应用代码调用的引用总数,包括由您的应用代码或包含的库定义的方法。以下策略可帮助您避免达到 DEX 引用限制:

  • 检查应用的直接依赖项和传递依赖项 – 确保您在应用中使用任何庞大依赖库所带来的好处多于为应用添加大量代码所带来的弊端。一种常见的反面模式是,仅仅为了使用几个实用方法就在应用中加入非常庞大的库。减少应用代码依赖项往往能够帮助您规避 DEX 引用限制。
  • 通过 R8 移除未使用的代码 – 启用代码缩减以针对发布 build 运行 R8。启用缩减可确保您交付的 APK 不含有未使用的代码。

使用这些技巧使您不必在应用中启用 MultiDex,同时还会减小 APK 的总体大小。

针对 MultiDex 配置应用

如果您的 minSdkVersion 设为 21 或更高的值,则默认情况下会启用 MultiDex,并且您不需要 MultiDex 支持库。

不过,如果您的 minSdkVersion 设为 20 或更低的值,则必须使用 MultiDex 支持库并对应用项目进行以下修改:

  1. 修改模块级 build.gradle 文件以启用 MultiDex,并将 MultiDex 库添加为依赖项,如下所示:
    android {
        defaultConfig {
            ...
            minSdkVersion 15
            targetSdkVersion 28
            multiDexEnabled true
        }
        ...
    }
    
    dependencies {
      implementation 'com.android.support:multidex:1.0.3'
    }
    
  2. 根据您是否替换 Application 类,执行以下某项操作:(这条我自己测试MultiDexApplication类找不到,可以不需要)
    • 如果您不替换 Application 类,请修改清单文件以设置 <application> 标记中的 android:name,如下所示:
      <?xml version="1.0" encoding="utf-8"?>
      <manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.example.myapp">
          <application
                  android:name="android.support.multidex.MultiDexApplication" >
              ...
          </application>
      </manifest>
      
    • 如果您替换 Application 类,请对其进行更改以扩展 MultiDexApplication(如果可能),如下所示:
      public class MyApplication extends MultiDexApplication { ... }
      
    • 或者,如果您替换 Application 类,但无法更改基类,则可以改为替换 attachBaseContext() 方法并调用 MultiDex.install(this) 以启用 MultiDex:
      public class MyApplication extends SomeOtherApplication {
        @Override
        protected void attachBaseContext(Context base) {
           super.attachBaseContext(base);
           MultiDex.install(this);
        }
      }
      

      注意:在 MultiDex.install() 完成之前,不要通过反射或 JNI 执行 MultiDex.install() 或其他任何代码。MultiDex 跟踪功能不会追踪这些调用,从而导致出现 ClassNotFoundException,或因 DEX 文件之间的类分区错误而导致验证错误。

现在,当您构建应用时,Android 构建工具会根据需要构造主要 DEX 文件 (classes.dex) 和辅助 DEX 文件(classes2.dex 和 classes3.dex 等)。然后,构建系统会将所有 DEX 文件打包到 APK 中。

在运行时,MultiDex API 使用特殊的类加载器搜索适用于您的方法的所有 DEX 文件(而不是只在主 classes.dex 文件中搜索)。

MultiDex 支持库的局限性

MultiDex 支持库具有一些已知的局限性,将其纳入您的应用构建配置时,您应注意这些局限性并进行针对性的测试:

  • 启动期间在设备的数据分区上安装 DEX 文件的过程相当复杂,如果辅助 DEX 文件较大,可能会导致应用无响应 (ANR) 错误。为避免此问题,请启用代码缩减,以尽量减小 DEX 文件的大小,并移除未使用的代码部分。
  • 当搭载的版本低于 Android 5.0(API 级别 21)时,使用 MultiDex 不足以避开 linearalloc 限制(问题 78035)。此上限在 Android 4.0(API 级别 14)中有所提高,但这并未完全解决该问题。在低于 Android 4.0 的版本中,您可能会在达到 DEX 索引限制之前达到 linearalloc 限制。因此,如果您的目标 API 级别低于 14,请在这些版本的平台上进行全面测试,因为您的应用可能会在启动时或加载特定类组时出现问题。代码缩减可以减少甚至有可能消除这些问题。

Android 9 下使用webview加载页面提示“net::ERR_CLEARTEXT_NOT_PERMITTED”的解决方法

 android  Android 9 下使用webview加载页面提示“net::ERR_CLEARTEXT_NOT_PERMITTED”的解决方法已关闭评论
7月 062020
 

Android  9.0 下使用webview加载页面时提示错误:

net::ERR_CLEARTEXT_NOT_PERMITTED

 

原来是因为: 从Android 9.0(API级别28)开始,默认情况下禁用明文支持。因此http的url均无法在webview中加载。

解决方法:

  1. 将原来的http修改为https, 当然服务端需支持https访问.
  2. 修改manifests目录下的AndroidManifest.xml文件,在application节点添加android:usesCleartextTraffic=”true”

如:

<application
    android:usesCleartextTraffic="true"
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:roundIcon="@mipmap/ic_launcher_round"
    android:supportsRtl="true"
    android:theme="@style/AppTheme">
    <activity android:name=".TestActivity">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>

</application>

nginx 403 forbidden, Permission denied 错误处理方法

 nginx  nginx 403 forbidden, Permission denied 错误处理方法已关闭评论
7月 052020
 

nginx配置好后端网站配置文件后,提示 403 forbidden 的问题,这个问题其实是很常见的问题,一般都是由以下4个方面的原因引起的:

 

一、由于启动用户和nginx工作用户不一致所致

1.1查看nginx的启动用户,发现是nobody,而实际用root启动的

ps aux | grep "nginx: worker process" | awk'{print $1}'

1.2将nginx.config的user改为和启动用户一致,

vi conf/nginx.conf

 

二、缺少index.html或者index.php文件,就是配置文件中index index.html index.htm这行中的指定的文件。
    server {
      listen       80;
      server_name  localhost;
      index  index.php index.html;
      root  /data/www/;
    }

如果在/data/www/下面没有index.php,index.html的时候,直接文件,会报403 forbidden。

三、权限问题,如果nginx没有web目录的操作权限,也会出现403错误。

解决办法:修改web目录的读写权限,或者是把nginx的启动用户改成目录的所属用户,重启Nginx即可解决

   chmod -R 777 /data
   chmod -R 777 /data/web/
四、SELINUX设置为开启状态(enabled)的原因。

4.1、查看当前selinux的状态。

    /usr/sbin/sestatus

4.2、将SELINUX=enforcing 修改为 SELINUX=disabled 状态。

    vi /etc/selinux/config

    #SELINUX=enforcing
    SELINUX=disabled

4.3、重启生效。reboot。

    reboot

 

PendingIntent的基本理解和使用

 android  PendingIntent的基本理解和使用已关闭评论
7月 032020
 

简书上看到的一篇PendingIntent的文章,收藏起来,链接:https://www.jianshu.com/p/a37f0ce2da2e

 

PendingIntent可以看作是对Intent的一个封装,但它不是立刻执行某个行为,而是满足某些条件或触发某些事件后才执行指定的行为(启动特定Service,Activity,BrcastReceive)。

我们可以把Pending Intent交给其他程序,其他程序按照PendingIntent进行操作。

Alarm定时器与Notification通知中都使用了PendingIntent

1.获得PendingIntent类内部静态方法获得PendingIntent实例:

//获得一个用于启动特定Activity的PendingIntent

public static PendingIntent getActivity(Context context, int requestCode,Intent intent, int flags)

//获得一个用于启动特定Service的PendingIntent

public static PendingIntent getService(Context context, int requestCode,Intent intent, int flags)

//获得一个用于发送特定Broadcast的PendingIntent

public static PendingIntent getBroadcast(Context context, int requestCode,Intent intent, int flags)

参数说明:

context:上下文对象。

requstCode:请求码,发件人的私人请求代码(当前未使用)。

intent:请求意图。用于要指明要启动的类以及数据的传递;

flags:这是一个关键的标志位:

主要常量

FLAG_CANCEL_CURRENT:如果当前系统中已经存在一个相同的PendingIntent对象,那么就将先将已有的PendingIntent取消,然后重新生成一个PendingIntent对象。

FLAG_NO_CREATE:如果当前系统中不存在相同的PendingIntent对象,系统将不会创建该PendingIntent对象而是直接返回null。

FLAG_ONE_SHOT:该PendingIntent只作用一次。在该PendingIntent对象通过send()方法触发过后,PendingIntent将自动调用cancel()进行销毁,那么如果你再调用send()方法的话,系统将会返回一个SendIntentException。

FLAG_UPDATE_CURRENT:如果系统中有一个和你描述的PendingIntent对等的PendingInent,那么系统将使用该PendingIntent对象,但是会使用新的Intent来更新之前PendingIntent中的Intent对象数据,例如更新Intent中的Extras。

注意:两个PendingIntent对等是指它们的operation一样, 且其它们的Intent的action, data, categories, components和flags都一样。但是它们的Intent的Extra可以不一样。

 

Android 8.0及以上版本,通知消息不显示,不出现原因、问题解决、分析

 android  Android 8.0及以上版本,通知消息不显示,不出现原因、问题解决、分析已关闭评论
7月 022020
 

在学习《android权威编程指南》第3版28章通知消息时,发现使用书上的代码始终无法在测试机上显示状态栏通知,很是郁闷,后来终于在android官网的文档中找到了答案。(见: https://developer.android.com/training/notify-user/build-notification?hl=zh-cn#java

问题其实出在我的测试机的Android版本上 (我的机器是Android 9.0)。

官网对创建通知时, 有以下主要注意点:

  1. Android 8.0以上 NotificationCompat.Builder 构造函数除了要求context参数,还要求您提供channel  id参数。这是兼容 Android 8.0(API 级别 26)及更高版本所必需的,但会被较旧版本忽略。
  2. 也就是说Android 8.0及以上的版本, 必须先通过 createNotificationChannel() 创建NotificationChannel 的实例,  再将 NotificationChannel 的实例的id用于NotificationCompat.Builder的构造,在系统中注册应用的通知渠道,然后才能在 Android 8.0 及更高版本上提供通知。

 

就是说需要把书上的代码做以下改变:

原来的代码:

            Notification notification = new NotificationCompat.Builder(this)
                    .setTicker(resources.getString(R.string.new_pictures_title))
                    .setSmallIcon(android.R.drawable.ic_menu_report_image)
                    .setContentTitle(resources.getString(R.string.new_pictures_title))
                    .setContentText(resources.getString(R.string.new_pictures_text))
                    .setContentIntent(pi)
                    .setAutoCancel(true)
                    .build();

修改为新代码:

            NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);
            String channelId = "default";
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
                String channelName = "默认通知";
                notificationManager.createNotificationChannel(new NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_HIGH));
            }

            Notification notification = new NotificationCompat.Builder(this, channelId)
                    .setTicker(resources.getString(R.string.new_pictures_title))
                    .setSmallIcon(android.R.drawable.ic_menu_report_image)
                    .setContentTitle(resources.getString(R.string.new_pictures_title))
                    .setContentText(resources.getString(R.string.new_pictures_text))
                    .setContentIntent(pi)
                    .setAutoCancel(true)
                    .build();

DONE!!!

Android下使用Picasso图片加载库详解

 android  Android下使用Picasso图片加载库详解已关闭评论
7月 012020
 

网上看到的一篇关于Android下 使用 Picasso 图片加载库的好文章,虽然有点老,但还是值得分享 !来自:https://www.jianshu.com/p/c68a3b9ca07a

写在前面

Android 中有几个比较有名的图片加载框架,Universal ImageLoader、Picasso、Glide和Fresco。它们各有优点,以前一直用的是ImageLoader 做项目中的图片加载,由于作者宣布ImageLoader 不会在更新了,因此新的项目打算换一个图片加载框架-Picasso, Picasso 是Square 公司开源的Android 端的图片加载和缓存框架。Square 真是一家良心公司啊,为我们Android开发者贡献了很多优秀的开源项目有木有!像什么Rerefoit 、OkHttp、LeakCanary、Picasso等等都是非常火的开源项目。扯远了,回到正题,除了使用简单方便,Picasso还能自动帮我们做以下事情:

  • 处理Adapter 中ImageView的回收和取消下载。
  • 使用最小的内存 来做复杂的图片变换。比如高斯模糊,圆角、圆形等处理。
  • 自动帮我们缓存图片。内存和磁盘缓存。

以上只是列出了Picasso 比较核心的几点,其实它的优点远远不止这些,接下来就看一下如何使用Picasso。

Picasso-Android.png

本文目录

0,添加依赖
1, 加载显示图片
2,Placeholder & noPlaceholder & noFade
3, 设置图片尺寸(Resize)、缩放(Scale)和裁剪(Crop)
4,图片旋转Rotation()
5, 转换器Transformation
6,请求优先级
7,Tag管理请求
8,同步/异步加载图片
9,缓存(Disk 和 Memory)
10,Debug 和日志
11,Picasso 扩展

正文

0. 添加依赖

要使用Picasso,首先我们要添加版本依赖,去官网或者Github 看一下当前的最新版本(截止本文最新版本为2.5.2),然后在build.gradle中添加依赖:

   compile 'com.squareup.picasso:picasso:2.5.2'

1. 加载显示图片

将Picasso添加到项目之后,我们就可以用它来加载图片了,使用方法非常简单:

 Picasso.with(this)
        .load("http://ww3.sinaimg.cn/large/610dc034jw1fasakfvqe1j20u00mhgn2.jpg")
        .into(mImageView);

只需要一行代码就完成了加载图片到显示的整个过程,链式调用,非常简洁,其实有三步,一次调用了三个方法:

  • with(Context) 获取一个Picasso单例,参数是一个Context上下文
  • load(String) 调用load 方法加载图片
  • into (ImageView) 将图片显示在对应的View上,可以是ImageView,也可以是实现了Target j接口的自定义View。

上面演示了加载一张网络图片,它还支持其它形式的图片加载,加载文件图片,加载本地资源图片,加载一个Uri 路径给的图片,提供了几个重载的方法:

1, load(Uri uri) 加载一个以Uri路径给的图片

Uri uri = Uri.parse(ANDROID_RESOURCE + context.getPackageName() + FOREWARD_SLASH + resourceId)

Picasso.with(this).load(uri).into(mImageView);

** 2,load(File file) 加载File中的图片**

 Picasso.with(this).load(file).into(mImageView);

3, load(int resourceId) 加载本地资源图片

Picasso.with(this).load(R.mipmap.ic_launcher).into(mImageView);

提醒:上面介绍了load的几个重载方法,加载不同资源的图片,另外提醒注意一下load(String path)接受String 参数的这个方法,参数String 可以是一个网络图片url,也可以是file 路径、content资源 和Android Resource。看一下源码的注释。

/**
   * Start an image request using the specified path. This is a convenience method for calling
   * {@link #load(Uri)}.
   * <p>
   * This path may be a remote URL, file resource (prefixed with {@code file:}), content resource
   * (prefixed with {@code content:}), or android resource (prefixed with {@code
   * android.resource:}.
   * <p>
   * Passing {@code null} as a {@code path} will not trigger any request but will set a
   * placeholder, if one is specified.
   *
   * @see #load(Uri)
   * @see #load(File)
   * @see #load(int)
   * @throws IllegalArgumentException if {@code path} is empty or blank string.
   */
  public RequestCreator load(String path) {
    if (path == null) {
      return new RequestCreator(this, null, 0);
    }
    if (path.trim().length() == 0) {
      throw new IllegalArgumentException("Path must not be empty.");
    }
    return load(Uri.parse(path));
  }

要使用string 参数加载上面的几种资源,除了网络url,其它几种需要加上对应前缀,file文件路径前缀:file: , content 添加前缀:content: ,Android Resource 添加:android.resource:

2. placeholder& error & noPlaceholder & noFade

通过上面的第一步我们就可以通过Picasso 加载图片了,我们的项目中通常最常用的就是加载网络图片,但是由于网络环境的差异,有时侯加载网络图片的过程有点慢,这样界面上就会显示空ImageView什么也看不见,用户体验非常不好。其实以前用过ImageLoader的同学都知道,ImageLoader 是可以设置加载中显示默认图片的,Picasso当然也给我们提供了这个功能,这就是我们要说的placeholder(占位图)。

1,placeholder
placeholder提供一张在网络请求还没有完成时显示的图片,它必须是本地图片,代码如下:

 Picasso.with(this).load(URL)
                .placeholder(R.drawable.default_bg)
                .into(mImageView);

设置placeholder之后,在加载图片的时候,就可以显示设置的默认图了,提升用户体验。
2, error
和placeholder 的用法一样,error 提供一张在加载图片出错的情况下显示的默认图


        Picasso.with(this).load(URL)
                .placeholder(R.drawable.default_bg)
                .error(R.drawable.error_iamge)
                .into(mImageView);

3,noPlaceholder
这个方法的意思就是:在调用into的时候明确告诉你没有占位图设置。根据这个方法签名的解释是阻止View被回收的时候Picasso清空target或者设置一个应用的占位图。需要注意的是placeholder和noPlaceholder 不能同时应用在同一个请求上,会抛异常。


        Picasso.with(this).load(URL)
                .noPlaceholder()
                .error(R.drawable.error_iamge)
                .into(mImageView);

4,noFade
无论你是否设置了占位图,Picasso 从磁盘或者网络加载图片时,into 显示到ImageView 都会有一个简单的渐入过度效果,让你的UI视觉效果更柔顺丝滑一点,如果你不要这个渐入的效果(没有这么坑爹的需求吧!!!),就调用noFade方法。

 Picasso.with(this).load(URL)
                .placeholder(R.drawable.default_bg)
                .error(R.drawable.error_iamge)
                .noFade()
                .into(mImageView);

3. 设置图片尺寸(Resize)、缩放(Scale)和裁剪(Crop)

1, Resize(int w,int h)
在项目中,为了带宽、内存使用和下载速度等考虑,服务端给我们的图片的size 应该和我们View 实际的size一样的,但是实际情况并非如此,服务端可能给我们一些奇怪的尺寸的图片,我们可以使用resize(int w,int hei) 来重新设置尺寸。

 Picasso.with(this).load(URL)
                .placeholder(R.drawable.default_bg)
                .error(R.drawable.error_iamge)
                .resize(400,200)
                .into(mImageView);

resize()方法接受的参数的单位是pixels,还有一个可以设置dp单位的方法,将你的尺寸写在dimens.xml文件中,然后用resizeDimen(int targetWidthResId, int targetHeightResId)方法

 <dimen name="image_width">300dp</dimen>
 <dimen name="image_height">200dp</dimen>

 Picasso.with(this).load(URL)
                .placeholder(R.drawable.default_bg)
                .error(R.drawable.error_iamge)
                .resizeDimen(R.dimen.image_width,R.dimen.image_height)
                .into(mImageView);

2,onlyScaleDown
当调用了resize 方法重新设置图片尺寸的时候,调用onlyScaleDown 方法,只有当原始图片的尺寸大于我们指定的尺寸时,resize才起作用,如:

  Picasso.with(this).load(URL)
                .placeholder(R.drawable.default_bg)
                .error(R.drawable.error_iamge)
                .resize(4000,2000)
                .onlyScaleDown()
                .into(mImageView);

只有当原来的图片尺寸大于4000 x 2000的时候,resize 才起作用。
3,图片裁剪 centerCrop()
这个属性应该不陌生吧!ImageView 的ScaleType 就有这个属性。当我们使用resize 来重新设置图片的尺寸的时候,你会发现有些图片拉伸或者扭曲了(使用ImageView的时候碰到过吧),我要避免这种情况,Picasso 同样给我们提供了一个方法,centerCrop,充满ImageView 的边界,居中裁剪

 Picasso.with(this).load(URL)
                .placeholder(R.drawable.default_bg)
                .error(R.drawable.error_iamge)
                .resize(400,200)
                .centerCrop()
                .into(mImageView);

4,centerInside
上面的centerCrop是可能看不到全部图片的,如果你想让View将图片展示完全,可以用centerInside,但是如果图片尺寸小于View尺寸的话,是不能充满View边界的。

 Picasso.with(this).load(URL)
                .placeholder(R.drawable.default_bg)
                .error(R.drawable.error_iamge)
                .resize(400,200)
                .centerInside()
                .into(mImageView);

5,fit
fit 是干什的呢?上面我们需要用resize()来指定我们需要的图片的尺寸,那就是说在程序中需要我们计算我们需要的尺寸(固定大小的除外),这样很麻烦,fit 方法就帮我们解决了这个问题。fit 它会自动测量我们的View的大小,然后内部调用reszie方法把图片裁剪到View的大小,这就帮我们做了计算size和调用resize 这2步。非常方便。代码如下:

Picasso.with(this).load(URL)
                .placeholder(R.drawable.default_bg)
                .error(R.drawable.error_iamge)
                .fit()
                .into(mImageView);

使用fit 还是会出现拉伸扭曲的情况,因此最好配合前面的centerCrop使用,代码如下:

 Picasso.with(this).load(URL)
                .placeholder(R.drawable.default_bg)
                .error(R.drawable.error_iamge)
                .fit()
                .centerCrop()
                .into(mImageView);

看一下对比图:
fit(会拉伸):

image_fit.png

fit & centerCrop (不会拉伸):

fit_centerCrop.png

注意:特别注意,
1,fit 只对ImageView 有效
2,使用fit时,ImageView 宽和高不能为wrap_content,很好理解,因为它要测量宽高。

4. 图片旋转Rotation()

在图片显示到ImageView 之前,还可以对图片做一些旋转操作,调用rotate(int degree)方法

Picasso.with(this).load(URL)
                .placeholder(R.drawable.default_bg)
                .error(R.drawable.error_iamge)
                .rotate(180)
                .into(mImageView);

这个方法它是以(0,0)点旋转,但是有些时候我们并不想以(0,0)点旋转,还提供了另外一个方法可以指定原点:

  • rotate(float degrees, float pivotX, float pivotY) 以(pivotX, pivotY)为原点旋转

 Picasso.with(this).load(URL)
                .placeholder(R.drawable.default_bg)
                .error(R.drawable.error_iamge)
                .rotate(180,200,100)
                .into(mImageView);

5. 转换器Transformation

Transformation 这就是Picasso的一个非常强大的功能了,它允许你在load图片 -> into ImageView 中间这个过成对图片做一系列的变换。比如你要做图片高斯模糊、添加圆角、做度灰处理、圆形图片等等都可以通过Transformation来完成。

来看一个高斯模糊的例子:

1,首先定义一个转换器继承 Transformation

 public static class BlurTransformation implements Transformation{

        RenderScript rs;

        public BlurTransformation(Context context) {
            super();
            rs = RenderScript.create(context);
        }

        @Override
        public Bitmap transform(Bitmap bitmap) {
            // Create another bitmap that will hold the results of the filter.
            Bitmap blurredBitmap = bitmap.copy(Bitmap.Config.ARGB_8888, true);

            // Allocate memory for Renderscript to work with
            Allocation input = Allocation.createFromBitmap(rs, blurredBitmap, Allocation.MipmapControl.MIPMAP_FULL, Allocation.USAGE_SHARED);
            Allocation output = Allocation.createTyped(rs, input.getType());

            // Load up an instance of the specific script that we want to use.
            ScriptIntrinsicBlur script = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs));
            script.setInput(input);

            // Set the blur radius
            script.setRadius(25);

            // Start the ScriptIntrinisicBlur
            script.forEach(output);

            // Copy the output to the blurred bitmap
            output.copyTo(blurredBitmap);

            bitmap.recycle();

            return blurredBitmap;
        }

        @Override
        public String key() {
            return "blur";
        }
    }

2, 加载图片的时候,在into 方法前面调用 transform方法 应用Transformation

  Picasso.with(this).load(URL)
                .placeholder(R.drawable.default_bg)
                .error(R.drawable.error_iamge)
                .transform(new BlurTransformation(this))
                .into(mBlurImage);

看一下效果图:

transformation.png

上面为原图,下面为高斯模糊图

是不是很强大,任何复杂的变换都可以通过Transformation 来做。

还不止于此,还有更强大的功能。可以在一个请求上应用多个Transformation

比如:我想先做个度灰处理然后在做一个高斯模糊图:

1, 度灰的Transformation

 public static class GrayTransformation implements Transformation{

        @Override
        public Bitmap transform(Bitmap source) {
            int width, height;
            height = source.getHeight();
            width = source.getWidth();

            Bitmap bmpGrayscale = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565);
            Canvas c = new Canvas(bmpGrayscale);
            Paint paint = new Paint();
            ColorMatrix cm = new ColorMatrix();
            cm.setSaturation(0);
            ColorMatrixColorFilter f = new ColorMatrixColorFilter(cm);
            paint.setColorFilter(f);
            c.drawBitmap(source, 0, 0, paint);

            if(source!=null && source!=bmpGrayscale){
                source.recycle();
            }
            return bmpGrayscale;
        }

        @Override
        public String key() {
            return "gray";
        }
    }

2, 如果是多个Transformation操作,有2种方式应用
方式一:直接调用多次transform 方法,不会覆盖的。它只是保存到了一个List 里面

 Picasso.with(this).load(URL)
                .placeholder(R.drawable.default_bg)
                .error(R.drawable.error_iamge)
                .fit()
                .centerCrop()
                .transform(new GrayTransformation())//度灰处理
                .transform(new BlurTransformation(this))//高斯模糊
                .into(mBlurImage);

需要注意调用的顺序
方式二:接受一个List,将Transformation 放大list 里

        List<Transformation> transformations = new ArrayList<>();
        transformations.add(new GrayTransformation());
        transformations.add(new BlurTransformation(this));

        Picasso.with(this).load(URL)
                .placeholder(R.drawable.default_bg)
                .error(R.drawable.error_iamge)
                .fit()
                .centerCrop()
                .transform(transformations)
                .into(mBlurImage);

效果图:

gray_blur.png

如上图,第一张为度灰操作,第二张为 度灰+高斯模糊

另外发现了一个开源库,专门写了很多好玩的Transformation,有兴趣的可以看一下:
picasso-transformations

6. 请求优先级

Picasso 为请求设置有优先级,有三种优先级,LOW、NORMAL、HIGH。默认情况下都是NORMAL,除了调用fetch 方法,fetch 方法的优先级是LOW。

public enum Priority {
    LOW,
    NORMAL,
    HIGH
  }

可以通过priority方法设置请求的优先级,这会影响请求的执行顺序,但是这是不能保证的,它只会往高的优先级靠拢。代码如下:

 Picasso.with(this).load(URL)
                .placeholder(R.drawable.default_bg)
                .error(R.drawable.error_iamge)
                .priority(Picasso.Priority.HIGH)
               // .priority(Picasso.Priority.LOW)
                .into(mImageView);

7. Tag管理请求

Picasso 允许我们为一个请求设置tag来管理请求,看一下对应的几个方法:
下面3个方法是Picasso这个类的:

  • cancelTag(Object tag) 取消设置了给定tag的所有请求
  • pauseTag(Object tag) 暂停设置了给定tag 的所有请求
  • resumeTag(Object tag) resume 被暂停的给定tag的所有请求

还有一个方法是RequestCreator的:

  • tag(Object tag) 为请求设置tag

几个方法的意思也很明确,就是我们可以暂停、resume、和取消请求,可以用在哪些场景呢?

场景一: 比如一个照片流列表,当我们快速滑动列表浏览照片的时候,后台会一直发起请求加载照片的,这可能会导致卡顿,那么我们就可以为每个请求设置一个相同的Tag,在快速滑动的时候,调用pauseTag暂停请求,当滑动停止的时候,调用resumeTag恢复请求,这样的体验是不是就会更好一些呢。

Adapter中添加如下代码:

Picasso.with(this).load(mData.get(position))
                .placeholder(R.drawable.default_bg)
                .error(R.drawable.error_iamge)
                .tag("PhotoTag")
                .into(holder.mImageView);

Activity中为RecyclerView添加滑动监听:

mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                final Picasso picasso = Picasso.with(MainActivity.this);

                if (newState == SCROLL_STATE_IDLE) {
                    picasso.resumeTag("PhotoTag");
                } else {
                    picasso.pauseTag("PhotoTag");
                }
            }
        });

场景二: 比如一个照片流列表界面,在弱网环境下,加载很慢,退出这个界面时可能会有很多请求没有完成,这个时候我们就可 以通过tag 来取消请求了。

 @Override
    protected void onDestroy() {
        super.onDestroy();
        Picasso.with(this).cancelTag("PhotoTag");
    }

8. 同步/异步加载图片

Picasso 加载图片也有同步/异步两种方式
**1,get() 同步 **
很简单,同步加载使用get() 方法,返回一个Bitmap 对象,代码如下:

 try {
           Bitmap bitmap =  Picasso.with(this).load(URL).get();
        } catch (IOException e) {
            e.printStackTrace();
        }

注意:使用同步方式加载,不能放在主线程来做。

2,异步的方式加载图片,fetch()
一般直接加载图片通过into显示到ImageView 是异步的方式,除此之外,还提供了2个异步的方法:

  • fetch() 异步方式加载图片
  • fetch(Callback callback) 异步方式加载图片并给一个回调接口。

  Picasso.with(this).load(URL).fetch(new Callback() {
            @Override
            public void onSuccess() {
                //加载成功
            }

            @Override
            public void onError() {
              //加载失败
            }
        });

这里就要吐槽一下接口设计了,回调并没有返回Bitmap, 不知道作者是怎么考虑的,只是一个通知效果,知道请求失败还是成功。
**fetch 方法异步加载图片并没有返回Bitmap,这个方法在请求成功之后,将结果存到了缓存,包括磁盘和内存缓存。所以使用这种方式加载图片适用于这种场景:知道稍后会加载图片,使用fetch 先加载缓存,起到一个预加载的效果。 **

9. 缓存(Disk 和 Memory)

Picasso 有内存缓存(Memory)和磁盘缓存( Disk), 首先来看一下源码中对于缓存的介绍:

  • LRU memory cache of 15% the available application RAM
  • Disk cache of 2% storage space up to 50MB but no less than 5MB. (Note: this is only
    available on API 14+ <em>or</em> if you are using a standalone library that provides a disk cache on all API levels like OkHttp)
  • Three download threads for disk and network access.

可以看出,内存缓存是使用的LRU 策略的缓存实现,它的大小是内存大小的15%,可以自定义它的大小,最后在扩展那一章节再讲,磁盘缓存是磁盘容量的2%但是不超过50M,不少于5M。处理一个请求的时候,按照这个顺讯检查:memory->disk->network 。先检查有木有内存缓存,如果命中,直接返回结果,否则检查磁盘缓存,命中则返回结果,没有命中则从网上获取。

默认情况下,Picasso 内存缓存和磁盘缓存都开启了的,也就是加载图片的时候,内存和磁盘都缓存了,但是有些时候,我们并不需要缓存,比如说:加载一张大图片的时候,如果再内存中保存一份,很容易造成OOM,这时候我们只希望有磁盘缓存,而不希望缓存到内存,因此就需要我们设置缓存策略了。Picasso 提供了这样的方法。

1,memoryPolicy 设置内存缓存策略
就像上面所说的,有时候我们不希望有内存缓存,我们可以通过 memoryPolicy 来设置。MemoryPolicy是一个枚举,有两个值

NO_CACHE:表示处理请求的时候跳过检查内存缓存
**NO_STORE: ** 表示请求成功之后,不将最终的结果存到内存。

示例代码如下:

with(this).load(URL)
                .placeholder(R.drawable.default_bg)
                .error(R.drawable.error_iamge)
                .memoryPolicy(MemoryPolicy.NO_CACHE,MemoryPolicy.NO_STORE) //静止内存缓存
                .into(mBlurImage);

2,networkPolicy 设置磁盘缓存策略
和内存缓存一样,加载一张图片的时候,你也可以跳过磁盘缓存,和内存缓存策略的控制方式一样,磁盘缓存调用方法networkPolicy(NetworkPolicy policy, NetworkPolicy... additional) , NetworkPolicy是一个枚举类型,有三个值:

NO_CACHE: 表示处理请求的时候跳过处理磁盘缓存
** NO_STORE:** 表示请求成功后,不将结果缓存到Disk,但是这个只对OkHttp有效。
OFFLINE: 这个就跟 上面两个不一样了,如果networkPolicy方法用的是这个参数,那么Picasso会强制这次请求从缓存中获取结果,不会发起网络请求,不管缓存中能否获取到结果。

使用示例:

 with(this).load(URL)
                .placeholder(R.drawable.default_bg)
                .error(R.drawable.error_iamge)
                .memoryPolicy(MemoryPolicy.NO_CACHE,MemoryPolicy.NO_STORE)//跳过内存缓存
                .networkPolicy(NetworkPolicy.NO_CACHE)//跳过磁盘缓存
                .into(mBlurImage);

强制从缓存获取:

 with(this).load(URL)
                .placeholder(R.drawable.default_bg)
                .error(R.drawable.error_iamge)
                .networkPolicy(NetworkPolicy.OFFLINE)//强制从缓存获取结果
                .into(mBlurImage);

10. Debug 和日志

1,缓存指示器

上一节说了,Picasso 有内存缓存和磁盘缓存,先从内存获取,没有再去磁盘缓存获取,都有就从网络加载,网络加载是比较昂贵和耗时的。因此,作为一个开发者,我们往往需要加载的图片是从哪儿来的(内存、Disk还是网络),Picasso让我们很容易就实现了。只需要调用一个方法setIndicatorsEnabled(boolean)就可以了,它会在图片的左上角出现一个带色块的三角形标示,有3种颜色,绿色表示从内存加载、蓝色表示从磁盘加载、红色表示从网络加载。

 Picasso.with(this)
        .setIndicatorsEnabled(true);//显示指示器

效果图:

cache_indicator.png

如上图所示,第一张图从网络获取,第二张从磁盘获取,第三张图从内存获取。

看一下源码中定义指示器的颜色:

 /** Describes where the image was loaded from. */
  public enum LoadedFrom {
    MEMORY(Color.GREEN),
    DISK(Color.BLUE),
    NETWORK(Color.RED);

    final int debugColor;

    private LoadedFrom(int debugColor) {
      this.debugColor = debugColor;
    }
  }

可以很清楚看出,对应三种颜色代表着图片的来源。

2,日志
上面的指示器能够很好的帮助我们看出图片的来源,但是有时候我们需要更详细的信息,Picasso,可以打印一些日志,比如一些关键方法的执行时间等等,我们只需要调用setLoggingEnabled(true)方法,然后App在加载图片的过程中,我们就可以从logcat 看到一些关键的日志信息。

   Picasso.with(this)
          .setLoggingEnabled(true);//开启日志打印

11. Picasso 扩展

到目前为止,Picasso的基本使用已经讲得差不多了,但是在实际项目中我们这可能还满足不了我们的需求,我们需要对它做一些自己的扩展,比如我们需要换缓存的位置、我们需要扩大缓存、自定义线程池、自定义下载器等等。这些都是可以的,接下来我们来看一下可以做哪些方面的扩展。

1,用Builder 自己构造一个Picasso Instance
我们来回顾一下前面是怎么用Picasso 加载图片的:

Picasso.with(this)
       .load("http://ww3.sinaimg.cn/large/610dc034jw1fasakfvqe1j20u00mhgn2.jpg")
       .into(mImageView);

总共3步:
1,用with方法获取一个Picasso 示例
2,用load方法加载图片
3,用into 放法显示图片

首先Picasso是一个单例模式,我们每一次获取的示例都是默认提供给我们的实例。但是也可以不用它给的Instance,我们直接用builder来构造一个Picasso:

       Picasso.Builder builder = new Picasso.Builder(this);
        //构造一个Picasso
        Picasso picasso = builder.build();
        //加载图片
        picasso.load(URL)
                .into(mImageView);

这样我们就构造了一个局部的Picasso实例,当然了,我们直接用new 了一个builder,然后build()生成了一个Picasso。这跟默认的通过with方法获取的实例是一样的。那么现在我们就可以配置一些自定义的功能了。
2, 配置自定义下载器 downLoader
如果我们不想用默认提供的Downloader,那么我们可以自定义一个下载器然后配置进去。举个例子:

(1) 先自定义一个Downloader(只是举个例子,并没有实现):

/**
 * Created by zhouwei on 17/2/26.
 */

public class CustomDownloader implements Downloader {

    @Override
    public Response load(Uri uri, int networkPolicy) throws IOException {
        return null;
    }

    @Override
    public void shutdown() {

    }
}

(2) 然后通过builder 配置:

        //配置下载器
        builder.downloader(new CustomDownloader());
        //构造一个Picasso
        Picasso picasso = builder.build();

这样配置后,我们用build()生成的Picasso 实例来加载图片就会使用自定义的下载器来下载图片了。

** 3, 配置缓存**
前面说过,内存缓存是用的LRU Cahce ,大小是手机内存的15% ,如果你想缓存大小更大一点或者更小一点,可以自定义,然后配置。

        //配置缓存
        LruCache cache = new LruCache(5*1024*1024);// 设置缓存大小
        builder.memoryCache(cache);

上面只是一个简单的举例,当然了你可以自定义,也可以使用LRUCache,改变大小,改变存储路径等等。

提示: 很遗憾,好像没有提供改变磁盘缓存的接口,那就只能用默认的了。

4, 配置线程池
Picasso 默认的线程池的核心线程数为3,如果你觉得不够用的话,可以配置自己需要的线程池,举个列子:

        //配置线程池
        ExecutorService executorService = Executors.newFixedThreadPool(8);
        builder.executor(executorService);

**5, 配置全局的 Picasso Instance **
上面说的这些自定义配置项目都是应用在一个局部的Picasso instance 上的,我们不可能每一次使用都要重新配置一下,这样就太麻烦了。我们希望我们的这些自定义配置能在整个项目都应用上,并且只配置一次。其实Picasso 给我们提供了这样的方法。可以调用setSingletonInstance(Picasso picasso)就可以了,看一下这个方法的源码:

 /**
   * Set the global instance returned from {@link #with}.
   * <p>
   * This method must be called before any calls to {@link #with} and may only be called once.
   */
  public static void setSingletonInstance(Picasso picasso) {
    synchronized (Picasso.class) {
      if (singleton != null) {
        throw new IllegalStateException("Singleton instance already exists.");
      }
      singleton = picasso;
    }
  }

设置一个通过with方法返回的全局instance。我们只希望配置一次,所以,我们应该在Application 的onCreate方法中做全局配置就可以了。app一启动就配置好,然后直接和前面的使用方法一样,调用with方法获取Picasso instance 加载图片就OK了。

因此在Application 中添加如下代码:

 @Override
    public void onCreate() {
        super.onCreate();
        
        // 配置全局的Picasso instance 
        
        Picasso.Builder builder = new Picasso.Builder(this);
        //配置下载器
        builder.downloader(new CustomDownloader());
        //配置缓存
        LruCache cache = new LruCache(5*1024*1024);// 设置缓存大小
        builder.memoryCache(cache);
        //配置线程池
        ExecutorService executorService = Executors.newFixedThreadPool(8);
        builder.executor(executorService);

        //构造一个Picasso
        Picasso picasso = builder.build();
        // 设置全局单列instance 
        Picasso.setSingletonInstance(picasso);
        
        
    }

然后应用这些自定义配置加载图片

Picasso.with(this).load("http://ww3.sinaimg.cn/large/610dc034jw1fasakfvqe1j20u00mhgn2.jpg").into(mImageView);

用法和以前的一样,但是我们已经将我们的自定义配置应用上了。

结尾

以上就是对Picasso 用法的全部总结,如有什么问题,欢迎留言指正。Picasso真的是一个强大的图片加载缓存库,API 简单好用,而且是链式调用的(这点我特别喜欢)。官方文档写的比较简单,很多用法都要看源码和注释才知道。希望本文能给才开始使用Picasso 的同学一点帮助。

参考

Picasso官网
Picasso — Getting Started

centos 7 中文乱码问题解决

 centos  centos 7 中文乱码问题解决已关闭评论
6月 082020
 

使用aws的centos ec2, 今天编辑一个文件,发现copy进去的中文字变乱码了,原来需要将默认的en_US.UTF-8编码修改为zh_CN中文编码,操作步骤如下:

1.查看安装中文包:

查看系统是否安装中文语言包 (列出所有可用的公共语言环境的名称,包含有zh_CN)

# locale -a |grep "zh_CN"

没有输出,说明没有安装,输入下面的命令安装:

# yum groupinstall "fonts" -y

安装完成,查看安了哪些中文语言包

# locale -a |grep "zh_CN"
zh_CN
zh_CN.gb18030
zh_CN.gb2312
zh_CN.gbk
zh_CN.utf8

说明系统已安装中文语言包,无需再安装。重要提示,如果按照下面的步骤你的系统仍然无法使用中文,那么请一个一个尝试上面的编码方式。比如把LANG=”zh_CN”修改为LANG=”zh_CN.gb18030″。

2.修改配置文件:

在修改配置文件之前,我们先看看当前系统语言环境:

# echo $LANG
en_US.UTF-8

# locale
LANG=en_US.UTF-8
LC_CTYPE="en_US.UTF-8"
LC_NUMERIC="en_US.UTF-8"
LC_TIME="en_US.UTF-8"
LC_COLLATE="en_US.UTF-8"
LC_MONETARY="en_US.UTF-8"
LC_MESSAGES="en_US.UTF-8"
LC_PAPER="en_US.UTF-8"
LC_NAME="en_US.UTF-8"
LC_ADDRESS="en_US.UTF-8"
LC_TELEPHONE="en_US.UTF-8"
LC_MEASUREMENT="en_US.UTF-8"
LC_IDENTIFICATION="en_US.UTF-8"
LC_ALL=

虽然安装了中文语言包但本机的语言环境并不是中文,需要修改locale.conf配置文件:

# vim /etc/locale.conf
LANG="zh_CN"
# source   /etc/locale.conf

也可以使用命令修改locale.conf配置文件:

#  localectl set-locale LANG=zh_CN

之后查看当前语言环境:

# echo $LANG
zh_CN

# locale
LANG=zh_CN
LC_CTYPE="zh_CN"
LC_NUMERIC="zh_CN"
LC_TIME="zh_CN"
LC_COLLATE="zh_CN"
LC_MONETARY="zh_CN"
LC_MESSAGES="zh_CN"
LC_PAPER="zh_CN"
LC_NAME="zh_CN"
LC_ADDRESS="zh_CN"
LC_TELEPHONE="zh_CN"
LC_MEASUREMENT="zh_CN"
LC_IDENTIFICATION="zh_CN"
LC_ALL=

3. 验证是否成功:

[[email protected] ~]# date
2020年 06月 08日 星期一 11:26:03 CST