一个使用Toolbar的例子

<android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:background="?attr/colorPrimary"
            app:titleTextAppearance="@style/MyTitleTextStyle"
            app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:minHeight="?attr/actionBarSize"/>

about apptheme

app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"

more
这个使用的主题必须要和Actionbar使用的主题对应,比如的我用的Light.DarkAction.那么这个主题必须也要是这个,不然会连带ActionBar那边的按钮也会出现问题

about titleTextAppearance

app:titleTextAppearance="@style/MyTitleTextStyle"

ActionBar 主题修改

下面XML演示如何在v21 appcompact上修改Actionbar title,颜色,字体颜色

<!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">#FF3333</item>
        <!--   darker variant for the status bar and contextual app bars -->
        <item name="colorPrimaryDark">#FF3333</item>
        <!--   theme UI controls like checkboxes and text fields -->
        <item name="colorAccent">#FFF</item>
    </style>


    <!--<style name="pallte_btn">-->
        <!--<item name="android:layout_width">60dp</item>-->
        <!--<item name="android:layout_height">60dp</item>-->
    <!--</style>-->


    <!--<style name="tabStyle">-->
    <!--</style>-->


    <style name="TestTheme" parent="AppTheme">
        <item name="actionBarStyle">@style/actionBarStyle</item>
        <item name="actionMenuTextAppearance">@style/myActionMenuTextAppearance</item>
    </style>


    <style name="myActionMenuTextAppearance" parent="@style/TextAppearance.AppCompat.Widget.ActionBar.Menu">
        <item name="android:textSize">20sp</item>
        <item name="android:shadowRadius">4</item>
        <item name="android:shadowColor">#000</item>
        <item name="android:shadowDx">2</item>
        <item name="android:shadowDy">2</item>
    </style>


    <style name="actionBarStyle" parent="@style/Widget.AppCompat.Light.ActionBar.Solid">
        <item name="titleTextStyle">@style/MyTitleTextStyle</item>
    </style>


    <style name="MyTitleTextStyle" parent="TextAppearance.AppCompat.Widget.ActionBar.Title">
        <item name="android:shadowRadius">4</item>
        <item name="android:shadowColor">#000</item>
        <item name="android:shadowDx">2</item>
        <item name="android:shadowDy">2</item>
    </style>

Git 工作流

场景

项目已经上线,有用户反馈说我们的程序有bug。同时,产品狗扔下了两个发布时间不一样的需求修复,拍照功能和美颜功能,拍照功能要下一版本发布,美颜功能要下下版本发布

问题

如何修复Bug同时,不影响新功能的开发,并且把两次的需求隔离开来.

解决方案

建立完善的工作流

项目初始化的时候我们新建三个分支,分别是release ,develop和master。同时,项目发布的时候我们要建立tag,通过tag我们找回对应版本的代码。

release 分支

保存可以发布的或者已经发布的代码分支

master 分支

保存着开发好测试过的代码,每次develop上开发好的功能都可以合并到master分支

develop 分支

我们的开发分支,开发过程中分享的代码

有这三个分支的前提下我们就可以从容的应对需求了。

修复线上的bug

从v1.0.0(假设出现问题的就是v1.0.0版本) tag 中 checkout出新的分支,hotfix_xxx, 修复好之后直接合并回去发布

开发拍照功能

直接在develop上开发,开发完成后合并到master上,测试过后就可以合并到release上了

开发美颜功能

从master中新建一个分支,叫feature_pretty ,在这个分支上同步开发,功能要上线的时候再合并到release分支上

这样就比较好的方式解决了上面这个问题了

参考

Git快速入门

初始化项目

直接clone已经有的项目

git clone https://github.com/hangox/git-use-demo.git

或者初始化本地项目

移动到项目的地址

cd /Users/hangox/Desktop/AndroidWorkspace/GitUseDemo

再敲入

git init

这个时候敲入git status 就会看到

On branch master

Initial commit

Untracked files:
  (use "git add <file>..." to include in what will be committed)

    .gitignore
    .idea/
    app/
    build.gradle
    gradle.properties
    gradle/
    gradlew
    gradlew.bat
    settings.gradle

nothing added to commit but untracked files present (use "git add" to track)

这是时候就会表示成功了

设置代码提交者信息

如果不设置代码提交者信息当然也可以提交,但是谁不希望提交上去的代码有个有个牛逼的名字呢😈

设置名字:

git config user.name "NiuBi"

设置邮箱:(出问题了人家可以找到你😂)

git config user.email "hangox.liang@gmail.com"

如果你想所有项目都是应用这个名字和邮箱只需要加上参数-- global
比如:

git config --global user.name "NiuBi"
git config user.email "hangox.liang@gmail.com"

要想查看现在的config信息可以这样

git config --list

你就会看到

user.name=47
user.email=liang.hanguang93@gmail.com
core.autocrlf=input
core.excludesfile=/Users/hangox/.gitignore_global
core.ignorecase=false
difftool.sourcetree.cmd=opendiff "$LOCAL" "$REMOTE"
difftool.sourcetree.path=
mergetool.sourcetree.cmd=/Applications/SourceTree.app/Contents/Resources/opendiff-w.sh "$LOCAL" "$REMOTE" -ancestor "$BASE" -merge "$MERGED"
mergetool.sourcetree.trustexitcode=true
core.repositoryformatversion=0
core.filemode=true
core.bare=false
core.logallrefupdates=true
core.ignorecase=true
core.precomposeunicode=true
user.name=NiuBi

如果你发现了两个user.name和user.email ,这是其中一个是global的,一个是项目的而已

当然git config 还有很多可以设置的,包括合并工具什么的,自己挖掘吧!

编写.gitignore

什么是.gitignore

.gitignore 就好像它的名字一样

告诉 git忽略一些不需要的文件

.gitignore 编写规则

项目文件结构是这样的

.
├── GitUseDemo.iml
├── app
│   ├── app.iml
│   ├── build
│   ├── build.gradle
│   ├── libs
│   ├── proguard-rules.pro
│   └── src
├── build
│   └── generated
├── build.gradle
├── gradle
│   └── wrapper
├── gradle.properties
├── gradlew
├── gradlew.bat
├── local.properties
└── settings.gradle

我想要忽略build文件夹,.iml 结尾文件和local.properties

#忽略build 文件夹
/build
#忽略local.properties 文件
local.properties
#忽略.xml 结尾的文件,使用正则
*.iml
#不忽略GitUseDemo.xml,这里只是为了演示,其实应该忽略的
!GitUseDemo.iml
#gradle 缓存文件,不需要提交
.gradle
#idea 的配置文件,不需要提交
.idea


这个时候我们敲入git status

On branch master

Initial commit

Untracked files:
  (use "git add <file>..." to include in what will be committed)

    .gitignore
    GitUseDemo.iml
    app/
    build.gradle
    gradle.properties
    gradle/
    gradlew
    gradlew.bat
    settings.gradle

nothing added to commit but untracked files present (use "git add" to track)

可以看到不需要的东西都被忽略了

添加需要提交的文件到暂存区

添加当前文件夹所有文件

git add .

移除追踪的文件

##去掉--cached就是同时删除文件
git rm --cached build.gradle

使用简单的匹配添加文件

添加后缀为.gradle的文件

git add *.gradle

提交

简单的提交方法

git commit -m 'first commit'

如果你写的东西比较长,可以这样

git commit 

这样将会出现一个vim 编辑器来编写,像这样

推送到远端

以上我们做的都这是发生在本地(这也是为什么Git快的原因了),如果需要和小伙伴们合作的话,就需要推送到远端了。

如果你是clone下来的可以跳过以下这一步了

  • 添加远端
# 添加远端,origin 只是一个名字而已,只是默认都是叫这个
git remote add origin https://github.com/hangox/git-use-demo.git

# 查看远端信息
git remote -v

  • 更新以下本地的代码(每次同步之前建议这样做,虽然我老是忘记/(ㄒoㄒ)/~~)
# pull 其实是包含了两步操作,从remote 中fetch(拿来)代码,和当前分支(也就是master) 
# merge(合并),如果你们公司使用的是rebase的话,就不能这样干了。但是大部分情况下都是
# 使用merge的
git pull origin master
  • 上传代码
# git push <remote-name> <remote-branch-name>
git push origin master

等待片刻,代码就上传完毕,你就可以和小伙伴们愉快的玩耍了O(∩_∩)O~~

参考

Android App网络通信安全

前提

没有100%的安全,我们做的只是提高破解的成本,当成本比破解得到的金钱高的时候,自然没有人愿意破解了

场景

我们的接口协议被破,破解者通过程序直接刷我们的新增用户,通过邀请机制获取积分。

问题引出

如何保证和接口通信的就是我们的客户端,而不是别人,或者更深的一层,如何保证客户端和服务器通信的就是彼此,没有第三方。

根据问题得出要做到以下几点

  1. 数据不被篡改
  2. 数据只有双方才能看懂
  3. 数据具有时效性

如何保证数据不被篡改

加上数据完整性的校验签名(前提是只有客户端才能生成这个检验签名)

如何使数据只有双方才能看懂

使用加密算法(前提是只有客户端才能生成校验签名)


能做到以上两点,网络通信就没有基本问题了。
但是,以上两点都有个非常重要的前提,也是一个非常困难的前提

只有客户端能生成一些东西

数据具有时效性

加入加密过的时间戳(前提是只有客户端才能生成这个时间戳)

如何保证客户端生成一些东西

使用C写生成的逻辑,不使用Java

Java是非常容易反编译的语言,即使混淆了,一些常量也是无法混淆的,所以当反编译的时候非常容易通过搜索来找到对应的常量。但是C不一样,C写的东西反编译后是汇编代码,汇编代码并不是多少人可以看得懂的,一定程度上提高了破解的成本。

对调用者进行校验

Android的so库,如果不对使用者进行校验,使用的人可以新建一个一样包名的应用,然后调用我们的so库,一样可以知道对应的输入输出。我们这里在C库里做了对签名的校验,如果发现签名不是我们的,就会用另外一条密钥对数据进行加密。

总结

要想网络协议不被破解,App必须要能够有一个黑匣子一样的东西,让人无法破解,最常用的办法就是把安全逻辑的代码写入到C库中,并对C库进行校验。

延伸和拓展

  1. 对C库代码进行混淆,提高反编译难度
  2. 没有100%的安全,做好行为监控才是王道,如果一个用户压根就没有点开过这个页面,却收到了这个页面的请求,这点足够怀疑这个请求的来源了。

Android 自定义按钮状态

需求描述:

最近做个项目,有一个按钮,有下载,安装,点击,安装中,下载中和使用这六个不同的状态,每个状态对应的按钮颜色也是不一样的,加上按下去的变色,大概有12种状态。

解决方案:

  • 6种状态对应不一样的background,设置为不同的状态的时候,换上对应的背景。
  • 自定义按钮状态

选择方案:

其实第二种和第一种的本质区别就是把所有的状态的文件写到一个xml里,但是更优雅,果断使用第二种

方案实现:

  • 定义自己的状态
  • 重写控件的onCreateDrawableState(int extraSpace) 告诉系统你有多少种状态

就这两个步骤,非常简单

定义自己的状态
   <declare-styleable name="ColorStateButton">
        <attr name="state_download" format="boolean"/>
        <attr name="state_downloading" format="boolean"/>
        <attr name="state_install" format="boolean"/>
        <attr name="state_installing" format="boolean"/>
        <attr name="state_open" format="boolean"/>
    </declare-styleable>

其实我想试试可不可以直接用数字类型的,当时时间比较紧急,就没试了,有兴趣的可以试试

重写onCreateDrawableState
 @Override
    protected int[] onCreateDrawableState(int extraSpace) {
        if (mColorStatus != null && mColorStatus.getMergeState() != null && isEnabled()) {
            final int[] drawableStatus = super.onCreateDrawableState(extraSpace + mColorStatus.getMergeState().length);//告诉系统你要添加拓展的状态数量
            //告诉系统你要合并的状态
            mergeDrawableStates(drawableStatus, mColorStatus.getMergeState());
//            printList(drawableStatus);
            return drawableStatus;
        }¡
        return super.onCreateDrawableState(extraSpace);
    }

详细代码:

按钮的background
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"
          xmlns:app="http://schemas.android.com/apk/res-auto">
    <!-- 需要下载状态-->
    <item
        android:drawable="@drawable/ic_color_button_download_normal"
        android:state_pressed="false"
        app:state_download="true"/>
    <item
        android:drawable="@drawable/ic_color_button_download_pressed"
        android:state_pressed="true"
        app:state_download="true"/>

    <!--需要安装状态-->
    <item
        android:drawable="@drawable/ic_color_button_install"
        android:state_pressed="false"
        app:state_install="true"/>
    <item
        android:drawable="@drawable/ic_color_button_install_pressed"
        android:state_pressed="true"
        app:state_install="true"/>
    <!--按钮不可用-->
    <item
        android:drawable="@drawable/ic_color_button_disable"
        android:state_enabled="false"/>

    <item
        android:drawable="@drawable/ic_color_button_normal_pressed"
        android:state_pressed="true"/>
    <!--正常状态-->
    <item
        android:drawable="@drawable/ic_color_button_normal"/>
</selector>
按钮的源码
package com.bingwish.bibao.ui.fragment;
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.support.annotation.StringRes;
import android.util.AttributeSet;
import android.widget.Button;
import com.bingwish.bibao.R;
import com.hangox.xlog.XLog;
/**
* Created With Android Studio
* User hangox
* Date 16/1/11
* Time 下午8:58
* 下载显示按钮
*/
public class ColorStateButton extends Button {
public static final int[] STATUS_DOWNLOAD = {R.attr.state_download};
public static final int[] STATUS_INSTALL = {R.attr.state_install};
private ColorStatus mColorStatus = ColorStatus.DOWNLOAD;
public ColorStateButton(Context context) {
super(context);
init(context);
}
public ColorStateButton(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public ColorStateButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public ColorStateButton(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init(context);
}
private void init(Context context) {
setColorState(ColorStatus.DOWNLOAD);
}
/***
* 设置颜色类型
*
* @param colorState
*/
public void setColorState(ColorStatus colorState) {
setText(colorState.getButtonTextRes());
switch (colorState) {
case DOWNLOADING:
case INSTALLING:
setEnabled(false);
return;
}
setEnabled(true);
mColorStatus = colorState;
refreshDrawableState();
invalidate();
}
@Override
protected int[] onCreateDrawableState(int extraSpace) {
if (mColorStatus != null && mColorStatus.getMergeState() != null && isEnabled()) {
final int[] drawableStatus = super.onCreateDrawableState(extraSpace + mColorStatus.getMergeState().length);
mergeDrawableStates(drawableStatus, mColorStatus.getMergeState());
//            printList(drawableStatus);
return drawableStatus;
}
return super.onCreateDrawableState(extraSpace);
}
private void printList(int[] ints) {
StringBuilder builder = new StringBuilder();
for (int state : ints) {
builder.append(state).append("#");
}
XLog.i(builder.toString());
}
public enum ColorStatus {
DOWNLOAD(STATUS_DOWNLOAD, R.string.state_download),
CLICK(STATUS_INSTALL, R.string.state_click),
OPEN(null, R.string.state_open),
DOWNLOADING(null, R.string.state_downloading),
INSTALL(STATUS_INSTALL, R.string.state_install),
INSTALLING(null, R.string.state_installing);
int[] mergeState;
@StringRes
int mButtonTextRes;
ColorStatus(int[] statusDownload, @StringRes int buttonTextRes) {
mergeState = statusDownload;
mButtonTextRes = buttonTextRes;
}
public int getButtonTextRes() {
return mButtonTextRes;
}
public int[] getMergeState() {
return mergeState;
}
}
}

Sock代理转换为http 代理

为了应对日益强大的防火墙,大部分人开始专用ShadowSock 作为代理,但是ShadowSock并不支持http 代理,而很多软件非常依赖于http 代理,比如Android SDK ,终端等,所以我们需要Privoxy

以下以Mac系统为例演示安装过程

安装
brew install proxivy

如果你看到这个了

表示你已经安装成功了

配置

配置其实很简单,上面的截图已经告诉你配置文件在哪里,就是/usr/local/etc/privoxy/的config 文件
敲入

vi /usr/local/etc/privoxy/config

在最下面加入

# 监听端口为8118,如果需要其他设备访问,则需要将ip改为路由器的IP 192.168.1.1 或 0.0.0.0 或者直接 :8118
listen-address  0.0.0.0:8118 #如果要使用默认配置,可以不需要这行
forward-socks5 / 127.0.0.1:1080 .
#forward-socks5 表示监听sock5 ,127.0.0.1:1080是本地shadowsock 地址, "." 表示监听所有的URL

配置完成后如图

启动

还记得安装成功后的截图不?

这是直接运行,但是关机后就没了

privoxy /usr/local/etc/privoxy/config

下面是加入系统启动项

ln -sfv /usr/local/opt/privoxy/*.plist ~/Library/LaunchAgents
launchctl load ~/Library/LaunchAgents/homebrew.mxcl.privoxy.plist

如果你选择了其中一个敲入,并没有任何提示,那就是表示成功了

验证
  • 首先验证这个进程是否已经启动了
ps aux  | grep privoxy

截图如下:

  • 验证provixy 是否在监听8118这个端口
netstat -an | grep 8118

结果如下:

这样就是表示正在监听8118端口了

Linux 笔记

内核查看

uname -a

上传或者下载文件

#下载
scp -r -P {SSH端口号} {用户名}@{主机IP地址}:{主机文件目录}   {本地文件目录}
#上传
scp -r -P {SSH端口号}  {本地文件目录} {用户名}@{主机IP地址}:{主机文件目录}

查看当前的IP地址

ip addr show

开启某个服务开机启动

sudo chkconfig [服务名] on
sudo chkconfig docker on

Centos7 添加服务器端口

firewall-cmd --zone=public --add-port=80/tcp --permanent

Firewall 移除port

firewall-cmd --zone=public  --remove-port=19999/tcp --permanent

关闭SELinux

修改/etc/selinux/config 文件
将SELINUX=enforcing改为SELINUX=disabled

添加环境变量

不要直接修改/etc/profile 文件,把需要执行的代码加入到xx.sh 文件中,移动到/etc/profile.d/中就好了