关于标点符号

凑数的标题

由于实在苦于中英文标点的切换,以及如何控制字数呈现比较舒服的文字排列非常困难,所以我从前几篇笔记开始统一使用英文标点,再也没人担心我中英文标点切换有时候会根据上下文自动判别而超出预期的状况,所以终于可以好好的打字,诚恳的吐槽和愉悦的凑字数:P

2018-05-10更新

其实我后来又反悔了,因为中文标点在方块字的字里行间还是看起来更加舒服,随缘吧哈哈。(然后这篇文章的逗号又被我改了回去)

偷懒的日子

0x80 如何科学的偷懒

距离上一次好好写写文章的日子已经很久了,大约一个半月以前的时候,自己再次产生了换个思路搞搞有意思的东西的想法,刚好又得意于某个环境(其实就是公司提供机会),便研究起了Hadoop生态圈,兴致勃勃地搞了几天.然而没过多久,我接到了重要的差遣,原本空闲的时间要拿出一部分甚至全部来投入到工作上,我觉得这是作为一个称职的员工应该做的.当然事实是我相当于出差一段时间,每天也是早出晚归,除了拿出点时间阅读点文章,真的没有时间再去玩点技术上的东西,同样的,我的笔记也就自然停止输入新的内容了.

说句实在话,最近这段时间不劳累是骗人的,但是也有一个好处,我不得不强迫自己按时吃早饭:P虽然我这段时间还是瘦了3/4斤= =.和socket打了大半个月交道,其实也算巩固了自己的知识面,到没大有什么太遗憾的,唯一要说的可能也就是这个自己所应坚持的笔记长时间停更,其实还是蛮内疚地.所以接下来一段时间我打算过一下之前写的东西,该补充补充,毕竟温故而知新,当然有啥有意思或好玩的,我还是会记下来,工作中的感悟可能也会写下来,毕竟我觉得我的工作性质已经有所变化了,它既不是我曾经坚持的,也不是我梦想拥有的,而人生就是这样,很多时候由不得己.

当然,还有很重要的一点,那就是我要重新开始学习安全相关的知识,我也要做ctf!!!

JUnit下缺失MockJar的解决方法

0x81 TestCase执行失败

最近升级几次Android Studio的beta版本经常遇到几个问题,JUnit执行单元测试时总是失败,原因是android包下的类找不到.虽然原则上讲JUnit的测试类中应该尽可能避免Android Framework的类出现,但有时候为了方便(偷懒)也会出现这种状况.然而神奇的是在之前的状况下是可以正常运行测试类的,因为IDE会生成对应的MockJar来模拟实际上不存的类,经过排查,发现Root Project下build文件夹中原应该生成的mockable-android-[api]-*.jar并没有出现,这也是导致TestCase执行失败的原因.

0x82 手动重新生成MockJar

我们在执行Gradle的Clear任务或Rebuild任务时,会删除build文件夹的内容重新生成,但是不管是配置有问题或者是IDE本身存在的bug,都有可能导致MockJar的生成失败,从而导致Android JUnit执行失败,如果要保证正常的执行测试用例,我们需要重新生成对应的文件.

修复方式通常有两种:

  1. 通过拷贝其他工程生成的MockJar,因为对应api平台下IDE生成的MockJar通常是一致的,我们可以从其他工程中拷贝该文件到对应工程下,但是一但触发Clear任务会导致该文件被删除.

  2. 使用Gradle任务重新生成MockJar,可能个别版本的IDE或Gradle存在bug,导致MockJar没有生成,我们可以使用以下命令强制运行生成任务:

  • gradle mockableAndroidJar --info 查看MockJar的状态信息

  • gradle mockableAndroidJar --rerun-tasks 重新执行生成任务

Read More

Hadoop,Hbase,Hive的伪分布式配置

0x81 伪分布式Hadoop

Hadoop提供三种配置方式:单机模式,伪分布式模式和完全分布式.其中单机模式是指Hadoop运行在一个Jvm进程当中,通常是我们开发时用于调试MapReduce作业.而伪分布式是指我们在同一台节点机器上运行Hadoop集群的多个Jvm进程,意在本机上启用完整的Hadoop运行环境,完全分布式与之相对,是指在正确的网络拓扑环境中,各个节点由自己的任务进程组成,从而实现HA的服务器支持.

0x82 伪分布式配置Hadoop

  1. 创建hadoop帐户
    为了保证我们的环境足够纯净,我们将hadoop运行在hadoop用户下,可以保证权限等不会出现奇怪的问题.创建用户的方式很简单,和创建一般的用户一样:useradd hadoop,之后用户相关的操作就见仁见智了.

  2. 下载hadoop的tar包
    hadoop是apache基金会的明星项目,我们可以在官网下载到tar包,截至到目前为止最新版本是3.0.0-alpha4,这里我下载的是2.6.5.

  3. 配置hadoop
    我们将tar包移动到hadoop用户的Home目录下,执行tar zxvf hadoop-2.6.5-bin.tar.gz将hadoop软件包解压,然后进入hadoop-2.6.5/etc/hadoop下对相应的配置文件进行更改(必要时拷贝template文件),主要修改的几个文件如下:

    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
    core-site.xml
    <configuration>
    <property>
    <name>fs.defaultFS</name>
    <value>hdfs://single:9000</value>
    </property>
    </configuration>

    hdfs-site.xml
    <configuration>
    <property>
    <name>dfs.replication</name>
    <value>1</value>
    </property>
    <property>
    <name>dfs.namenode.secondary.http-address</name>
    <value>single:50090</value>
    </property>
    <property>
    <name>dfs.namenode.name.dir</name>
    <value>/home/hadoop/hadoop-2.x/dfs/name</value>
    </property>
    <property>
    <name>dfs.datanode.data.dir</name>
    <value>/home/hadoop/hadoop-2.x/dfs/data</value>
    </property>
    </configuration>

    mapred-site.xml
    <configuration>
    <property>
    <name>mapreduce.framework.name</name>
    <value>yarn</value>
    </property>
    </configuration>

    yarn-site.xml
    <configuration>
    <property>
    <name>yarn.resourcemanager.hostname</name>
    <value>single</value>
    </property>
    <property>
    <name>yarn.nodemanager.aux-services</name>
    <value>mapreduce_shuffle</value>
    </property>
    </configuration>
    1
    2
    3
    4
    hadoop.env
    mapred.env
    yarn.env
    export JAVA_HOME=/home/hadoop/jdk
  4. 启动hadoop
    hadoop-2.6.5/sbin中提供了启动脚本,其中startall.sh可以直接启动hadoop,但是该脚本已被视为废弃,官方推荐使用start-dfs.sh和start-yarn.sh来启动NameNode,DataNode和Node/ResourceManager.

Read More

重新打包Proguard规避StackTable异常

0x81 Unknown verification type

Unknown verification type [17] in stack map frame是最近打包App时遇到的一个问题,然而很有意思的是如果不开启Multidex支持,便不存在这个问题,minApi>21也不存在这个问题.如果我们在release包的时候开启了proguard,也会出现这个问题,虽然发生这种状况的条件非常多,但其实归根结底都是proguard的问题,在minApi<21时开启Multidex也会进行Proguard操作,因为Multidex的功能实质上就是一个特殊的Proguard过程,它作为中间件处理将要生成的字节码文件.

0x82 问题原因

出现这种问题时完整的错误信息通常是Can't read [\build\intermediates\transforms\jarMerging\debug\jars\1\1f\xxx.jar] (Can't process class [xxx/xxx/xxx/a$b$c.class] (Unknown verification type [xx] in stack map frame)),其根源是某个jar在做jarMerge操作时某个类的StackMapTable attributes有问题,通常的做法是我们需要修正这个被混淆的jar包的问题,但是某些第三方库的jar包我们时不可控的,因此我们只能让Proguard自己忽略掉StackMapTable的问题.

Read More

正确的配置StartupWMClass

0x81 启动器与窗口分组

众所周知,Linux各大发行版默认使用的桌面环境不一,所以为了尽可能的规范化应用Launcher的使用,各大窗口管理器和桌面环境按照WM_CLASS区分启动器以及进行窗口分组操作.以gnome-desktop为例,系统内置的Nautilus文件管理器能够很好的处理窗口分组:

Nautilus的窗口分组

但是如果我们使用menulibre或者手动创建.desktop启动器描述文件,很多程序却没办法正确的分组,甚至桌面环境无法正确的识别程序的启动,这个时候就需要修改WM_CLASS以保证程序的各个窗口归入一个分组.

0x82 Desktop描述文件

.desktop是GNU下默认的程序描述启动器描述文件,通过包管理安装的包的二进制启动器通常在/usr/share/applications目录下有对应的描述文件,它会显示在LauncherPad当中供用户点击启动程序.相对应的,在个人用户目录的.local/share/applications当中可以添加用户自定以的启动器,而menulibre就是一个图形化编辑器,方便我们创建或修改启动器.

典型的.desktop

0x83 WM_CLASS与StartupWMClass

WM_CLASS是一个特殊的运行时变量,用来指示xdg程序的窗口管理器识别类型,桌面环境通常读取窗口的这一变量来区分某些窗口所属,将相同的WM_CLASS窗口归为一组并与启动器关联.我们可以使用.desktop文件的StartupWMClass属性来手动控制这一行为,那我们如何获取窗口的类型呢?使用xprop WM_CLASS可以读出窗口的X11属性WM_CLASS,通过获取的值和StartupWMClass一致就可以做到启动器与对应的窗口关联,对于自定义应用,GNOME3的收藏夹在也不是单纯的快捷方式了.

RxJavaPlugins Error: Didn't find class ThrowableExtension

0x81 问题来源

其实这个问题本身是不存在的,在昨天更新到Android Studio 3.0 Beta3 之后,在一些特殊情况下会抛出异常RxJavaPlugins Error Didn't find class "com.google.devtools.build.android.desugar.runtime.ThrowableExtension",经过查阅资料(Google一番)后,发现这个问题曾经在Beta1出现过,不过Google之后迅速放出了Beta2更新修正了这个问题,而如今Beta3居然又出现了这个问题,看来是没有做好回归测试啊。

0x82 解决方法

参考StackOverFlow上的解决方式,大概共有这么几种:

  1. 降级gradle-plugin
    根据那个哥们的说法,这其实就是gradle-plugin的问题,故而降级可以解决这个问题,但是降级容易导致不兼容等不可预料的问题,Beta3的IDE也不允许使用Beta2的gradle-plugin,会强制要求升级。

  2. 手动添加ThrowableExtension.java
    根据异常信息可以发现,google/devtools种的一个类在运行时找不到,有人提出了可以手动添加该类就可以避免这个问题,当然这不是一个好的方案,毕竟这其实是一种额外的操作。

  3. minApi降级至19以下
    根据@Xavier Rubio Jansana的回答,这个Issue Google在Beta1时已经意识到并标记为P0,并且在Beta2时修正了这个问题,但在Beta3 reopen了这个Issue。一个Googler给出了一个Workaround:

    Temporary workaround is to set min sdk version below 19. Issue is that Desugar will process try-with-resources for API 19+, although platform supports it, but we will not package those classes.

    是Desugar导致了api19+的问题,因此minApi降到18可以暂时避免这个问题。还有一点,如果没有使用Java8 的特性可以考虑关闭Java8支持,它同样也对kotlin工程有效。

到目前为止,Google没有放出新的更新,只能先这样用着,相信Google很快会放出更新解决这个问题。

有关Java数字格式化的示例

0x81 浮点数带来的问题

平时我们在使用数字的时候,直接使用往往不太会出现什么问题,但是如果我们需要对浮点数进行格式化输出或者是进行数学运算,就有可能遇到各种各样的问题。导致这些问题的原因有很多,根本原因就是浮点数的二进制保存机制问题,所以出现0.1 != 0.1的状况很正常,而有一些特殊的情况下数字会被使用科学计数法表示,这也不是面向普通用户应该出现的状况。这篇文章不是剖析Java的浮点数机制,而是一个浮点数使用示例总结,所以可能文章内容会不断的发生变化。

0x82 几种格式化方法

对于浮点数的显示,我们有很多格式化方法,最基本的StringFormat和数学运算、BigDecimal、DecimalFormat等等,但在使用它们时我还是会遇到各种各样的问题,下面就是一些示例和结果。

Read More

windowIsTranlucent 踩坑之旅

0x81 windowIsTranslucent

android:windowIsTranslucent是Android提供的一个非常有用的属性,它可以将应用了该Style的Activity所在的Window设置成透明的。当我们有一些特殊的需求,比如我们需要一个透明的Activity,它的Theme可能是Theme.Transparent,这个Style将android:windowIsTranslucentandroid:windowIsFloating都设置成了true,后面这个属性也有很多坑。通常状况下,我们不会使用windowIsTranslucent这个属性,因为它将Activity设置为透明,那之前打开这个Activity的Activity如果不finish就会处于可见状态,从而导致Acitivity的onStop hook不会被调用,有可能会导致逻辑上的失误。

0x82 windowIsTranslucent为生命周期带来的影响

其实这个属性带来的不良影响还有不少,最典型的就是Activity生命周期影响
根据windowIsTranslucent有true/false两种状态那两个Activity就有四种情况:

  • A:true,B:false A:onPause->onStop
  • A:true,B:true A:onPause
  • A:false,B:false A:onPause->onStop
  • A:false,B:true A:onPause

以上是AOSP的默认行为,意为只要目标Acitivitytranslucent的那源Acitivity就不会调用onStop,是个别的ROM自己实现了多窗口,可能第二种状况也会用onStop,所以要注意onStop中调用逻辑的可靠性。

Read More

Android中启动多个Activity

[2017-08-09修改]

0x81 Notification

Android操作系统开放给我们Notification的相关API,使得我们可以轻松的控制通知的显示和行为。其中我们想要对通知的点击进行相应的响应时,就会使用到PendingIntent这个东西,PendingIntent有三个非常重要的成员方法getActivity、getBroadcast和getService,根据官方文档的解释,它们分别用点击通知时打开一个Activity、发送一个广播、启动一个Service,从而达到某些目的。

比如,收到一篇新闻的通知,我们想要通过通知进入新闻详情页,这时候我们就可以构造一个打开Activity的PendingIntent来完成这件事,PendingIntent中的Intent就是我们使用startActivity时的Intent参数:

1
2
3
4
5
6
final Notification.Builder builder = new Notification.Builder(this);
builder.setContentTitle("Open One").setContentText("Click Open One Activity").setSmallIcon(
R.mipmap.ic_launcher).setContentIntent(PendingIntent
.getActivity(mContext, 0, new Intent(mContext, ConstraintLayoutActivity.class),
PendingIntent.FLAG_UPDATE_CURRENT));
notificationManager.notify(NOTIFICATION_ID, builder.build());

上述代码在Android O 以下已经可以正常运行了,这种状况对于程序正在后台运行并且Activity栈中存在根Activity时会直接在栈顶创建新打开的Activity,和startActivity行为一致,但是若App已经不再运行,而我们又是使用ApplicationContext开启Activity,则必须在Intent上追加参数Intent.FLAG_ACTIVITY_NEW_TASK才行。对于发送广播或者是启动Service,只需要使用PendingIntent特定的API即可。

如果你的程序tagetApi是Android O,只是这样是无法成功发送通知的,因为Android O 对通知系统又做了改进,添加了NotificationChannel的支持,并支持在Pixel Launcher上添加通道通知显示和iOS的Badge提示。因此我们需要创建一个通知通道:

1
2
3
4
5
NotificationChannel channel = new NotificationChannel("testChannel", "TestChannel", NotificationManager.IMPORTANCE_DEFAULT);
channel.setDescription("Description");
channel.enableLights(true);
channel.setLightColor(Color.RED);
notificationManager.createNotificationChannel(channel);

之后在NotificationBuilder中指定ChannelId就可以弹出通知了。

[修改补充] Android Support Library 更新到了26.0.0,其中的v7.NotificationCompat被废弃,Google推荐使用v4.Notificationompat.Builder(context,channelId)构建通知,支持库对Android O 的通知做了兼容支持。

0x82 startActivities和getActivities

继续之前的例子,打开了新闻详情的页面,如果是正常情况下我们还可以按返回键回到根Activity,但若是我们的Activity是通过重新创建Task后启动的,那按返回键就会直接退出到先前的Task,这种体验有时候不是很好。我们知道startActivities就是用于启动多个Activity的,而PendingIntent提供了类似的方法getActivities,这样PendingIntent就会在触发时按顺序打开Activity,从而做到从新闻详情返回到首页的需求:

1
2
3
4
5
6
7
8
9
10
11
final NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
builder.setAutoCancel(true).setContentTitle("Open One").setContentText(
"Click Open One Activity").setSmallIcon(R.mipmap.ic_launcher).setContentIntent(
PendingIntent.getActivities(mContext, 0, new Intent[]{new Intent(mContext,
ConstraintLayoutActivity.class).addFlags(
Intent.FLAG_ACTIVITY_NEW_TASK), new Intent(mContext,
GameActivity.class),
new Intent(mContext,
OpenGLActivity.class)},
PendingIntent.FLAG_UPDATE_CURRENT)).setChannelId("testChannel");
notificationManager.notify(NOTIFICATION_ID, builder.build());

其中第一个Intent因为众所周知的原因必须带有Flag:Intent.FLAG_ACTIVITY_NEW_TASK,后面的Intent便不是必须的,最后一个Intent的Activity会被作为primary key,整个行为和startActivities一致。

Read More