屏蔽Nougat的文件共享检测

0x81 更改StrictMode检测

我之前做过Nougat上FileProvider的笔记,并且这种方式是存在很多坑的,其实网上也有很多办法关闭这种ExposedUri的检测,虽然这不是Google推荐的办法,但是在某些时候也能起到特殊的作用。这第一种办法就是修改严格模式的策略:

1
2
3
4
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { 
StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
StrictMode.setVmPolicy(builder.build());
}

判断如果运行环境的API版本大于Android Nougat,就将虚拟机政策置空。

0x82 利用反射关闭检测

不知道Google既然想要强制处理文件共享,为什么还要允许关闭检测,也许是为了兼容性考虑,既然提供了判断那我们就可以依托Java强大的反射完成这件事情:

1
2
3
4
5
6
try {
Method ddfu = StrictMode.class.getDeclaredMethod("disableDeathOnFileUriExposure");
ddfu.invoke(null);
} catch (Exception e) {
e.printStackTrace();
}

代码也很简单,利用反射取出StrictMode的disableDeathOnFileUriExposure方法,然后调用就可以了。

Read More

dnf更新时如何排除软件包

0x81 为什么要这么做

最早在使用ubuntu的时候,我使用hostapd来开启AP共享,但是较新的软件包有一个bug,会导致AP一直卡在启动中的状态,如果手动将软件包降级不做任何处理的话,每次升级软件包都会连同它一起升级,因此就需要将它在dpkg重标记为hold,这样检测更新就会跳过这种软件包。

由于我升级了Fedora 26的branch版本,每天的更新非常多,尤其是kernel,几乎就是跟着内核站同步更新,每天都会经历着rc1到rc8更新的折磨,恰巧昨天更新了update-testing源里的4.11.0正式版内核,我就想着把kernel加入忽略更新的列表。

0x82 如何忽略

方法很简单,使用dnf命令更新的时候提供了一个叫做exclude的参数可以用于排除软件包的更新,因此只要使用一下这个命令就可以了:

1
2
sudo dnf makecache
sudo dnf upgrade --exclude=kernel*

其中kernel*使用通配符过滤所有以kernel开头的软件包,到然如果每次都这样肯定会非常麻烦,dnf提供了配置文件来让我们方便的配置这个参数:

1
2
3
4
5
6
7
8
9
10
fionera@fionera:~|⇒  cat /etc/dnf/dnf.conf
[main]
gpgcheck=1
installonly_limit=2
clean_requirements_on_remove=True
exclude=kernel*

fastestmirror=true
deltarpm=true
fionera@fionera:~|⇒

这样exclude后面的软件包就会自动被忽略。

npm升级失败的处理方式

0x81 惨痛遭遇

系统上的npm是fedora仓库里的3.10.10,使用dnf安装nodejs的时候作为弱依赖被同时安装的,我从来只是用它安装其他的工具,一直没有手动升级过它,它一直是跟随着包管理工具升级。今天用outdated看来一下哪些模块可以升级,发现npm有4.5.0可用,于是我就尝试升级了它sudo npm update npm -g,然后悲剧出现了,升级失败后npm用不了了。

0x82 问题根源

我便去npm全局模块的安装目录看了一下,目录是/usr/lib/node_modules/,进入npm文件夹我发现什么都没有了,仔细阅读了升级日志,发现升级的时候先把原来的文件删除掉再放入新的文件,应该就是这个时候出现了问题。于是我尝试用dnf重新安装了npm,然后npm就正常了。

0x83 一个可行的升级方案

npm工具实质上就是一个nodejs的脚本,我们用它来管理node模块,Fedora包管理器安装的npm和全局安装处理的方式差不多,在/usr/bin/npm生成了/usr/lib/node_modules/bin/npm-cli.js的软链接,我们的npm就是它。由于升级会导致npm被破坏,所以我就拷贝了一份npm3,卸载了包管理器安装的npm,然后使用绝对路径的npm-cli.js脚本全局安装了npm4,之后为新安装的npm手动建立软链接就可以使用了。

这样虽然很拙,但是确实有效,其实和手动下载npm压缩包的效果是一样的,npm升级失败更好的解决方法我还没有找到,找到后再正确的处理它。

写方案的日子

0x80 累得要死

5.1节前接了个艰巨的任务——理需求出方案。对我这种闷头写码,早晨来了就沉浸在愉快的编码生活中的人,写方案对我来说无疑是致命的,从4.27开始,假期也没闲着到今天为止,这么多天,字没写几个,大部分的时间都浪费在了思考上,拿头去想客户到底想要做什么。

业务跟加油站有关,可我作为一个工作刚刚2年零1个月,从海大逃出不到一年的码彪子来说我根本不懂业务= =你说我作为一个没车的人,都没有加油的机会,更不用说什么加油卡了,但是花了几天时间了解一下另一个行业也算是丰富知识了,懂了帐户额度帐,备付金,圈存的整个业务流程(虽然并没有什么卵用)。

不想思考了,太累了就说这么多,汇成一句话,风华正茂自己又热爱,还是想安安心心写自己觉得有价值的代码~

正确配置Sudoers

0x81 软件包升级机制

之前我们在《Sudoers-Configuration》说过如何去配置Sudoers,以达到自己临时提权的目的,并且提到我们理应使用visudo这个命令去修改配置文件,这个文件默认修改的文件路径是/etc/sudoers。使用包管理升级过软件包的都知道,包管理器在升级软件包时除了分析数据库中的依赖之外,还会对当前已安装软件包的文件状况进行统计,比如我使用的Fedora的rpm工具就能检测到那些文件发生了,在我使用dnf升级sudo软件包时,由于我修改了sudoers这个文件,因此rpm会产生一个/etc/sudoers.rpmnew文件,这个文件就是新版版本的默认配置与旧版本不一致为避免丢失用户设置而产生的。

0x82 分散配置文件夹

和大部分带有可配置文件的软件工具一样,sudo提供了/etc/sudoers.d文件夹给用户加载自定义配置,这样就可以在不修改源配置文件的情况下添加我们自己的配置。当然这个文件夹也是可以配置的,但是这样就需要修改我们的默认文件夹,如果你想自定义配置路径,可以在默认的sudoers.d中作一次中转。

默认配置文件夹

0x83 添加自定义配置

起初我使用vim直接写入了一个文件,后来发现没有效果,于是我使用visudo -f path重新添加了一个文件,发现后者是有效的。经过对比我发现他们的权限是不一样的:

权限差异与检查结果

所以前者不好用也就很好理解了,把权限修改正确就能用了。

静态库与动态库

0x81 静态库与动态库

库的概念在我们日常开发和使用软件中是非常常见的,它们通常是一组经过编译的代码的组合,比如Linux发行版当中就存在大量的共享库提供给操作系统其他软件使用,它们可能存在多个版本,我们平时安装的依赖包有80%是程序二进制运行所需要的库文件。按一般说法,库通常分为静态库和动态库,前者是编译进了最终的二进制文件,后者以一种动态加载的方式提供功能。以C++为例,静态库通常是.a/.lib结尾,动态库通常是.so/.dll结尾。

0x82 如何编译一个静态库

静态库一般是一组代码的集合,它们可以独立于main函数之外,专于提供相应的功能,链接器在链接静态库时会将所有的内容链接到可执行二进制文件或其他库中。下面我们举个简单的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// StaticMath.h
#pragma once
class StaticMath {
public:
StaticMath(void);
~StaticMath(void);

static double add(double a, double b); // 加法
static double sub(double a, double b); // 减法
static double mul(double a, double b); // 乘法
static double div(double a, double b); // 除法

void print();
};
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
// StaticMath.cpp
#include "StaticMath.h"

#include <iostream>

StaticMath::StaticMath() {
}

StaticMath::~StaticMath() {
}

double StaticMath::add(double a, double b) {
return a + b;
}

double StaticMath::sub(double a, double b) {
return a - b;
}

double StaticMath::mul(double a, double b) {
return a * b;
}

double StaticMath::div(double a, double b) {
return a / b;
}

void StaticMath::print() {
std::cout << "Print" << std::endl;
}

如上定义了StaticMath的一组头文件和实现文件,我们将在main.cpp中使用这个类及其方法,进行如下操作生成静态库并链接到可执行文件main:

1
2
3
g++ -c StaticMath.cpp
ar -crv libstaticmath.a StaticMath.o
g++ main.cpp -L. -lstaticmath -O2 -o main

这三句命令首先生成了StaticMath.o中间文件,然后使用ar打包并令生成libstaticmath.a静态库文件,最后使用g++调用链接器链接staticmath库并生成可执行文件main,我们看下执行结果:

main执行结果和依赖

通过ldd的分析结果我们可以发现,依赖库中并没有staticmath,这说明静态库是编译进了二进制文件当中。

Read More

堆,栈,自由储存区

0x79 假如生活强奸了你,生活还要继续,你也得继续

刚度过了一个疲惫而无意义的周末:)有一个撒币女票真的是为生活增加了难度,4.23世界读书日这一天我们进入了一家图书咖啡馆,馆子不小人也不少,一路上她对于实验室藏书的炫耀间戏剧性的蹦出了mallo这个小玩意,我便问她“你知道malloc在内存里分配了哪一块区域吗?”,虽然我连续对“是在栈上”予以肯定想让她相信,可是她这次出奇的坚持,看来我的气势没以前那么有压迫性了:P

虽然上大学首先学习的是C,然后学习了C++,但是做了这么久Java,我突然发现之前为编程而学的那点C语言知识根本经不住时间的考验。纵使自己后来玩过一段时间Linux系统编程,随着时间的推移,很多东西都抛于脑后了。刚巧最近在补C++的知识,借着这个契机,我打算从0开始重新学习一下C++11,有一些易忘的点就顺便做个记录。由这件小事作为一个引子,我们来看看这个比较基础的概念性问题。

0x80 内容参考

  1. C语言变量声明内存分配
  2. C++ 自由存储区是否等价于堆?
  3. 细说new与malloc的10点区别

0x81 程序运行时的内存组成

我们从C语言说起:

  1. 程序代码区
    这个又叫Code区,其实这个概念在每一个可执行程序中都有,你的程序要运行,把必要的二进制代码加载到内存里是必须的,当然并不代表要一次性把所有可能执行的代码都加载到内存中,它们通常是按需加载。这一部分是程序启动时创建,程序退出时回收。

  2. 常量存储区
    狭义上它也可以称作字符串常量区,之所以这么说是因为这里通常保存的是常量字符串,表面上看起来有点像JVM的String常量池。我们用IDA等静态分析工具也可以发现它可以把字符串分析出来,有的时候可以节省我们很多时间。这一部分是程序启动时创建,程序退出时回收。

  3. 全局/静态区
    这个区域存放全局变量和静态变量,初始化和未初始化的分开存放。这块区域是在编译时决定的,而不是像动态变量一样运行时申请内存区域,其实字符串常量区也可以看作一种特殊的静态变量区域。这一部分是程序启动时创建,程序退出时回收。


  4. 运行时自动分配,通常存放了函数参数和局部变量,比如我们在函数内使用int num = 0;生命一个变量,它便是在栈区。之所以叫栈区是因为他的行为类似于数据结构中的栈,使用时入栈,函数返回时出栈,大名鼎鼎的栈溢出就发生在这个位置。


  5. 和上面的栈一样,属于动态存储区,但是这一部分区域是由程序编写者主动申请和释放,最好的实践是申请空间不使用时释放内存空间,以免发生内存泄露,不能仅仅依靠程序退出时操作系统对其进行回收。malloc关键字申请的内存便在这个区域上,C++很多实现中new的默认实现也是在堆上申请内存。

然后我们来说一个C++特有的概念——自由存储区:

我们通常会这样说,malloc在堆上分配一块指定大小的内存,用完使用free释放掉这一段内存;使用new 在自由存储区上分配一块对象大小的内存,使用delete删除该对象占用的内存。那自由存储区到底是什么?其实它只是C++里的一个抽象概念,它表示的是对象在new出来时所申请占用的内存区域。

0x82 栈和堆

栈和堆都属于动态分配,它们都是在运行时最终由操作系统负责分配给变量的内存,虽然堆(malloc/free)实际上给了编程人员更大的权利。栈的分配是在程序运行函数调用时由操作系统将局部变量、参数等压栈,在程序返回时出栈,这整个过程编程人员是没有办法直接干预的。使用malloc在堆上分配的内存则由编程人员控制,如果处理不好将产生内存泄露的风险,并且频繁的申请内存还会产生内存碎片影响整个作业系统的运行效率。

0x83 自由存储区与堆的区别

根据前面的说法,自由存储区只是C++里的一个抽象概念,它表示的是对象在new出来时所申请占用的内存区域。

堆,是操作系统术语,它是操作系统维护的一块内存,专门用于动态分配给程序使用,调用malloc时分配,调用free时回收。自由存储,是C++的一个抽象概念,使用new关键字申请的内存就叫做自由存储区内存,然后使用delete释放内存,在大部分情况下C++编译器都使用堆来实现自由存储区的分配。但是做C++开发的都知道,C++提供了强大的重载操作符机制,我们完全可以使用别的区域来实现自由存储区,所以它们其实是不同层面的两种东西,它们完全可以是同一种类型也可以不同。

0x84 malloc/free和new/delete

他们两个有很多相似的地方,但是却又有根本上的不同:

  1. 前面着重提到的内存位置
    malloc/free是在堆上为对象动态分配空间,new/delete是在自由存储区上。

  2. 返回值
    malloc是void 需要做强制类型转换,new是T 对应类型指针无需转换。

  3. 是否显式指定分配大小
    malloc需要显式指定大小,new不用。

  4. 构造函数与析构函数调用状况
    使用new创建的对象才会调用。

  5. 重新分配内存
    当发现内存不足时,我们可以使用realloc重新分配内存,如果连续内存足够则直接扩展,否则重新寻找内存区域。

差别其实有很多,甚至new也可以重载并使用malloc来实现一部分功能,但是不一样的东西过于揪住细节的话是没有意义的,我们需要做的就是在具有相似功能的事物上进行区分和甄别。

Retrofit2 Converter的使用

0x80 前言

今天来说一个比较简单的东西——Converter。看它的名字就知道它是转换器的意思,相信很多人都用过GsonConverter这个库,它的内容很简单并且它一直跟着Retrofit2主库的更新而更新。今天在重构代码的时候遇到了这样一个问题,我需要拿到返回内容的原始字符串而不是经过转换的POJO类,而GsonConverter没有办法做到,于是自己为了节省时间就做了点修改。

0x81 GsonConverter 的使用

用法很简单,先在build.gradle文件中引用该库:

1
2
3
compile 'com.squareup.retrofit2:retrofit:2.2.0'
compile 'com.squareup.retrofit2:converter-gson:2.2.0'
compile 'com.squareup.retrofit2:adapter-rxjava2:2.2.0'

这样我们就可以在RetrofitBuilder构造Retrofit实例时添加相应的功能:

1
2
3
4
5
ApiService apiService = new Retrofit.Builder().client(okHttpBuilder.build())
.baseUrl(HttpConstants.BASE_URL)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.build().create(ApiService.class);

上面代码我们为Retrofit添加了RxJava2的适配器以及使用Gson处理字符串的Converter。

0x82 ApiService

我们看下一般情况下的ApiService如何写,首先来看一个GET请求:

1
2
@GET(HttpConstants.HOME_PAGE)
Observable<BaseEntity<HomeBean>> getHomePage(@Query("time") String time);

这个请求很简单,@GET指明了在那个rest-path上执行GET请求,有一个查询参数time,接口方法的返回值是一个Observable,经过GsonConverter转换后的内容是BaseEntity,我们还可以写过滤器取出HomeBean从而让代码更简洁。

再来看一个发送Json数据的POST请求:

1
2
@POST(HttpConstants.CART_ADD)
Observable<BaseEntity<Empty>> addToCart(@Body Map<String, String> info);

这个和上面的GET差不多,只不过注解变成了@POST,参数是@Body,返回值是然被转换成POJO,泛型中的Empty表示我不在意返回内容是什么,我只需要通过返回的规约code进行判断。这一切都工作的很好,知道我发现了有的接口的返回值是没办法通过Gson转换成POJO的,这时候我们需要原始的响应结果。

Read More

PopupWindow 实现暗色背景的坑

0x81 PopupWindow

PopupWindow是一个比较特殊的类,它不继承任何类,是一个相对而言比较独立而又非常具有依赖性的东西。独立自然指的就是它不像Dialog那样继承并实现了很多东西,而具有依赖性则正是因为它不像Dialog那样具有自己的Window,所以我们要操作Window时操作的是它所在的Activity的Window。出于这些原因,PopupWindow有些时候虽然非常实用和简便,我们却有可能遇到一些让人匪夷所思的问题,比如向下事件传递等。

0x82 PopupWindow 暗色背景实现

要实现暗色背景,我们有时候会在PopupWindow的ContentView中使用占有整个屏幕的画布,整个背景是半透明的,而我们真正需要的View只占据了一小部分,这样实现出来的效果和Activity差不多,我们还可以通过处理外部的View点击事件来达到点击外部区域关闭PopupWindow的效果。但是这种用法又一个坏处,当我们使用一个比较缓慢的动画开启它时,会发现背景也跟着参与动画,这样视觉体验就会大打折扣。

在平时的开发当中,PopupWindow出现的几率虽然不是很高,因为有很多中办法可以实现差不多的效果,但是既然Google提供了它,哪怕用的再少我们也要明白如何去用它。无论是网上查阅资料还是参看别人的源码,我们还会看到一种特殊的设置暗色背景的方法,这个方法其是有点trick的意思,没错,这也是我遇到的一个大坑——透明度。

Read More

Android Uri笔记

0x81 Uri

最近一段时间接触到OAuth登录,验证通过后通过一定的规则调起Android页面,而这个规则就是老生常谈的东西——Uri。Uri,即全局资源标识符,有的时候也可以是Url(全局资源定位符,多用于Web),它对某一种资源做了通用的描述,因此,我们通常将资源分配一个独一无二的Uri以帮助使用者准确定位。

0x82 Uri的结构

我们所讲述的Uri是android.net包下的Uri类,在java.net包下有一个URI类,它们之间并没有非常直接的关系,但是它们所要描述的内容是如出一辙的,Uri类是Android平台下可适性更好的类,ContentProvider及Intent等操作的都是这一个Uri。

Uri的结构遵循[scheme:]scheme-specific-part[#fragment],这一部分摘自Android API References中对java.net.URI的介绍。其中scheme-specific-part起主要的标识作用,可拆解成[//authority][path][?query-string],其中authority又可以细分为[host]:[port],path通常以’/‘开头且可以组合,我们在Web中经常打交道的Http Url也符合这一规范,例如https://www.baidu.com:80/search?t=test#top。

1
2
3
4
5
6
7
8
scheme: https;
scheme-specific-part: //www.baidu.com:80/search?t=test
authority: www.baidu.com:80
host: www.baidu.com
port: 80
path: /search
query-string: t=test
fragment: top

Read More