git中merge和rebase的区别

最开始实习的时候是使用svn,之后正式工作就一直在使用git,这样算起来,使用git也有两年的时间了。以前带我的同事,让我在拉代码的时候要我使用git pull --rebase,一直很纳闷为什么要那样做,后来遇到拉代码的时候有许多冲突要解决,然后去查找资料,才了解到其中的一些事情。今天分享一下,顺便自己也梳理一下。

git pull

git pull 是 git fetch + git merge FETCH_HEAD 的缩写。所以,默认情况下,git pull就是先fetch,然后执行merge 操作,如果加–rebase 参数,就是使用git rebase 代替git merge。

merge 和 rebase

merge 是合并的意思,rebase是复位基底的意思。

现在我们有这样的两个分支,test和master,提交如下:

      D---E test
     /
A---B---C---F master

在master执行git merge test,然后会得到如下结果:

      D--------E
     /          \
A---B---C---F----G   test, master

在master执行git rebase test,然后得到如下结果:

A---B---D---E---C'---F'   test, master  

可以看到,merge操作会生成一个新的节点,之前的提交分开显示。而rebase操作不会生成新的节点,是将两个分支融合成一个线性的提交。

 其他内容放这里

通过上面可以看到,想要更好的提交树,使用rebase操作会更好一点。这样可以线性的看到每一次提交,并且没有增加提交节点。

在我们操作过程中。merge 操作遇到冲突的时候,当前merge不能继续进行下去。手动修改冲突内容后,add 修改,commit 就可以了。

而rebase 操作的话,会中断rebase,同时会提示去解决冲突。解决冲突后,将修改add后执行git rebase –continue继续操作,或者git rebase –skip忽略冲突。

原文地址:http://isming.me/2014/09/26/git-rebase-merge/,转载请注明出处。

在android中画圆形图片的几种办法

在开发中经常会有一些需求,比如显示头像,显示一些特殊的需求,将图片显示成圆角或者圆形或者其他的一些形状。但是往往我们手上的图片或者从服务器获取到的图片都是方形的。这时候就需要我们自己进行处理,将图片处理成所需要的形状。正如茴香豆的的“茴”写法大于一种,经过我的研究,画出特殊图片的方法也不是一种,我发现了三种,且听我一一道来。

使用Xfermode 两图相交方式

通过查找资料发现android中可以设置画笔的Xfermode即相交模式,从而设置两张图相交之后的显示方式,具体模式见下图,源码可以去android apidemo。(SRC 为我们要画到目标图上的图即原图,DST为目标图)

由上图可以看到,如果我们需要画一个圆形的图,可以在画布上面先画一个跟目标大小一样的圆,然后xfermode选择SRC_IN,再讲我们的头像或者其他图画上去就可以了。同样也可以先画我们的图,再画圆,不过xfermode要选择DST_IN。两种都可以实现我们需要的效果。示例代码如下:

Paint p = new Paint();
p.setAntiAlias(true); //去锯齿
p.setColor(Color.BLACK);
p.setStyle(Paint.Style.STROKE);
Canvas canvas = new Canvas(bitmap);  //bitmap就是我们原来的图,比如头像
p.setXfermode(new PorterDuffXfermode(Mode.DST_IN));  //因为我们先画了图所以DST_IN
int radius = bitmap.getWidth; //假设图片是正方形的
canvas.drawCircle(radius, radius, radius, p); //r=radius, 圆心(r,r)

以上就是简单的示例,根据以上的16种模式你其实还可以做出更多效果。另外,只要你给一张相交图,那张图形状什么样,我们的图就可以显示成什么样。

通过裁剪画布区域实现指定形状的图形

Android中Canvas提供了ClipPath, ClipRect, ClipRegion 等方法来裁剪,通过Path, Rect ,Region 的不同组合,Android几乎可以支持任意形状的裁剪区域。因此,我们几乎可以获取任意形状的区域,然后在这个区域上画图,就可以获得,我们要的图片了,直接看示例。

int radius = src.getWidth() / 2; //src为我们要画上去的图,跟上一个示例中的bitmap一样。
Bitmap dest = Bitmap.createBitmap(src.getWidth(), src.getHeight(), Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(dest);
Paint paint = new Paint();
paint.setColor(Color.BLACK);
paint.setAntiAlias(true);
Path path = new Path();
path.addCircle(radius, radius, radius, Path.Direction.CW);
c.clipPath(path);   //裁剪区域
c.drawBitmap(src, 0, 0, paint);  //把图画上去

使用BitmapShader

直接先看示例
java
int radius = src.getWidth() / 2;
BitmapShader bitmapShader = new BitmapShader(src, Shader.TileMode.REPEAT,
Shader.TileMode.REPEAT);
Bitmap dest = Bitmap.createBitmap(src.getWidth(), src.getHeight(), Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(dest);
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setShader(bitmapShader);
c.drawCircle(radius,radius, radius, paint);

Shader就是画笔的渲染器,本质上这中方法其实是画圆,但是渲染采用了我们的图片,然后就可以获得指定的形状了。但是我觉得,这个不适合画很复杂的图形,但是在内存消耗上,应该比第一种小很多。同时呢,设置Shader.TileMode.MIRROR,还可以实现镜面效果,也是极好的。

上面就是实现的三种方法,三种方法都可以画很多的形状,当然遇到非常非常非常非常复杂的情况,我是建议使用第一种,这时候可以让美工给一张末班形状图,省自己去代码绘制了。大家根据自己的需求选择。

在github上面CustomShapeImageView就是用了我们所说的第一种方法绘制。RoundedImageViewCircleImageView则使用bitmapshader完成,当然可能还有一些其他的控件,也许还有其他的一些实现方法,如果你知道,可以回复告诉我^_^。

原文地址:http://isming.me/2014/09/19/draw-circle-image-in-android/,转载请注明出处。

常用有用的android adb命令

adb在开发中真的是时时都会用到,熟练使用可以帮助我们提高开发效率。前段时间看到一个外国程序员分享了一篇文章,觉得写的很好,介绍了一些很有意思的adb的命令,我就寻思着有空也写一篇,对于自己是一个总结,同时方便自己或者其他人以后查阅。

想要能够在命令行里面敲adb能够使用,先要保证你的adb所在的目录,一般是sdk/platform-tools目录,要在环境变量中,想要怎么配环境变量,自己google去。

首先要学会使用帮助,遇到不会用的命令可以直接敲adb 或者adb --help看帮助,帮助文档基本的命令都有。

adb devices 查看adb已经连接上的手机列表

adb install -r /path/to/apk 安装apk到手机, 带参数-r的话就卸载手机中已有的程序

adb uninstall package_name 卸载手机中的程序

adb push -p local remote 复制电脑中的文件到手机,-p显示进度

adb pull -p -a remote local 复制手机中的文件或者文件夹到电脑

adb shell 进入手机的shell命令行环境,可以进去后执行手机中支持的命令,也可以在本句之后加命令

adb shell pm uninstall package_name 卸载手机中的程序

adb shell pm install /path/of/apk/in/phone 安装手机sd卡中的程序

adb shell am start package_name/activity_in_package

adb shell am start package_name/activity_flly_qualified 启动activity

adb shell screencap -p | perl -pe 's/\x0D\x0A/\x0A/g' > screen.png 截屏保存到screen.png

adb logcat 查看log

adb logcat -s TAG_NAME

adb logcat -s TAG_NAME_1 TAG_NAME_2

adb logcat ":PRIORITY" eg:adb logcat ":D"

adb logcat -s TAG_NAME:PRIORITY

adb logcat -c 清空log

ok。我能想到,和参考的别的文章上面,目前使用最多的一些命令就是这些了。更加详细的adb介绍,请参看谷歌官方文档.http://developer.android.com/tools/help/adb.html

PS:最新因为项目的需要,因此研究了一下滤镜,在gpuimage的基础上模仿instargram做了一套滤镜,放在github。上面了,欢迎取阅。https://github.com/sangmingming/android-instagram-filter

android推送技术方案与原理

推送服务现在广泛的使用,几乎成了每个app的必备,现在呢,苹果上面有APNs,android上面游GCM(中国不可用)。我也是经常使用第三方的推送,比如百度云推送,个推等等。但是一直想知道推送的原理,想着自己也能做出来,到网上搜到一些推送的方案(大段内容来自网上)。

方案一:使用GCM服务(Google Cloud Messaging)

简介:Google在Android上标配了自己的推送GCM(Google Cloud Messageing),可以帮助开发人员给他们的Android应用程序发送数据。它是一个轻量级的消息,告诉Android应用程序有新的数据要获取从服务器,或者它可能是一个消息,其中包含了4KB的payload data(像即时通讯这类应用程序可以直接使用该payload消息)。GCM服务处理排队的消息,并把消息传递到目标设备上运行的Android应用程序。

优点:Google提供的服务、原生、简单,无需实现和部署服务端。

缺点:

1.GCM要求Android系统必须是2.2以上的版本,所以对于不少2.2以前的系统没法推送

2.国内服务不稳定。而且不少国内的终端厂商纷纷把Google的服务去掉,替换上自己的。

3.需要用户绑定Google账号,但不少国内用户没有Google账号。

方案二:使用XMPP协议(Openfire + Spark + Smack)

简介:XMPP是一种基于XML的协议,它继承了在XML环境中灵活的发展性,有很强的可扩展性。包括上面讲的GCM服务器底层也是采用XMPP协议封装的。

优点:协议成熟、强大、可扩展性强、目前主要应用于许多聊天系统中,且已有开源的Java版的开发实例androidpn。

缺点:协议较复杂、冗余(基于XML)、费流量、费电,部署硬件成本高。

而androidpn(Android Push Notification)就是基于 XMPP 开源组件的一套整合方案,服务端基于Openfire、客户端基于Smack。到AndroidPN项目主页( http://sourceforge.net/projects/androidpn/ ) 下载2个文件: androidpn-server-0.5.0-bin.zip 和 androidpn-client-0.5.0.zip 分别是服务器和客户端的代码。详细的实现方式网上有不少文章。

androidpn有如下一些不足,开发的时候需要权衡:

1、androidpn服务端重启后客户端不会重连,这个非常悲剧

2、由于服务器不保存消息,造成了如果客户端当前离线就收不到消息。

3、androidpn发送完消息就不管了,所以没有消息回执报表之类,造成没法做应用后续的数据分析用户体验的改善,这对于企业级的应用是个致命伤。

XMPP协议比较费电费流量,这个对当前智能机的消耗太大,在窄带网络和不稳定的(手机)网络都不是最优的选择。但总体来说,XMPP协议还是比较成熟的。

方案三:使用MQTT协议(更多信息见: http://mqtt.org/)

简介:轻量级的、基于代理的“发布/订阅”模式的消息传输协议。

优点:协议简洁、小巧、可扩展性强、省流量、省电,目前已经应用到企业领域(参考: http://mqtt.org/software),且已有C++版的服务端组件rsmb。

缺点:不够成熟、实现较复杂、服务端组件rsmb不开源,部署硬件成本较高。

方案四:使用HTTP轮循方式

简介:定时向HTTP服务端接口(Web Service API)获取最新消息。

优点:实现简单、可控性强,部署硬件成本低。

缺点:实时性差。

方案五:采用第三方服务

目前有不少第三方提供了类似服务,客户端只需要嵌入第三方提供的lib库,由第三方建立长连接,负责消息的接收/发送。同时对于消息都有比较详细的报表数据,可以用于做数据分析挖掘和用户体验的改善。目前国内提供推送服务的有好几家,比较成熟的主要有百度云推送, 极光推送, 个推服务。

本文只是罗列现有的方案,文章内容大段来自网上,我希望有一天可以做出一个demo。

在Ubuntu下配置Android开发环境

主要包括:jdk idea android sdk

安装JDK

sudo add-apt-repository ppa:webupd8team/java   #添加源
sudo apt-get update                             #更新仓库
sudo apt-get install oracle-java7-installer     #安装java7

执行

java -version

检查java版本,确保已经正确安装jdk
最后执行

sudo apt-get install oracle-java7-set-default

将设置java7到系统的环境变量中(这样就不需要自己配置了)

安装JetBrains IntelliJIDEA

打开这个页面http://www.jetbrains.com/idea/download/ 下载Community Edition。
下载回来后,解压即可。

安装Android SDK

到这个页面https://developer.android.com/sdk/index.html,下载SDK Tools,选择最后一个Download For Other Platforms,下面的SDK Tools Only,选择对应平台,linux的是http://dl.google.com/android/android-sdk_r22.6.2-linux.tgz

下载回来后,解压即可。

第一次启动

启动IDEA,创建一个Android 项目,会提示你设置JDK和Android SDK。这就OK了。

原文地址:http://isming.me/2014/06/28/install-android-in-ubuntu,转载请注明出处。

一个程序员的Ubuntu安装的那些软件

鄙人程序猿一枚,Android开发,常年使用Ubuntu(主要是买不起Mac,O(∩_∩)O哈哈~)。分享一下自己使用的那些软件,如果你有什么好的软件。欢迎与我交流。

输入法:开始的时候是用的fcitx,后来搜狗出了linux版本,选择之。下载链接

办公软件:WPS Linux,真心很好用,比libreOffice好用不止一点.下载链接 注意:下载alpah版本.

浏览器:chrome 和 FireFox,不解释,一个浏览器不够用的。这两个,开发调试都够了。关于支付宝的话,可以安装支付宝官方给的一个脚本就可以了。

邮件客户端: ThunderBird Mail 火狐家的,算比较好用的了。

笔记:为知笔记 ,为知真是业界良心,唯一一家提供linux版本的,做的还挺不错的,虽然有一些不好,但是相信以后会越做越好的。安装方法:

\( sudo add-apt-repository ppa:wiznote-team
\) sudo apt-get update

$ sudo apt-get install wiznote

常用编辑器:普通的编辑器就是用Vim,或者用sublime text,sb在ubuntu下中文输入会有问题,看这篇文章。http://blog.isming.me/blog/2014/03/15/jie-jue-ubuntuxia-sublime-text-3zhong-wen-shu-ru-de-wen-ti/.markdown 编辑器,我用retext,ubuntu仓库中有,直接下载就可以了。

开发工具:工欲善其事,必先利其器,作为android开发者,一个好用的工具也很重要,我用IntelliJ IDEA CE,同时配合Android SDK,推荐你们也用这个。

仓库:git,不解释。

抓包工具: windows 下面有很好用的工具Fiddler,在linux 下面我也找到一款好用的工具,Charles下载地址.

先写这么多了,以后有新发现,继续分享。

使用Intent启动组件

android应用程序的三大组件——Activities、Services、Broadcast Receiver,通过消息触发,这个消息就是Intent,中文又翻译为"意图"(我感觉读着不顺畅,还是读英文)。我们可以通过Intent去启动三大组件,并且通过Intent携带数据到其他组件中。本文来看一下怎么使用Intent启动组件,以及Intent的过滤规则。

Intent对象

首先来看Intent对象中包含的成员。

private String mAction;   //动作
private Uri mData;        //数据
private String mType;
private String mPackage;   //包名
private ComponentName mComponent;  //组件名 包含程序包名+类名,以及应用包名
private int mFlags;         //标志
private HashSet<String> mCategories;   //种类
private Bundle mExtras;    //附加信息
private Rect mSourceBounds;
private Intent mSelector;
private ClipData mClipData;

看Intent的源码,主要包含以上成员。

Intent解析

Intent解析有两种方式:显式解析和隐式解析。

显式解析,我们直接传组件进入,打开这个指定的组件,比较简单,通常应用程序内使用。

比如我们创建一个显式的Intent:

Intent intent = new Intent(context, OtherActivity.class);

隐式解析,没有指定具体的组件,通过规则去匹配组件。通常用于多个程序之间的互相调用比较多。我们使用隐式解析式,action、data(包括URI和数据类型)、category都必须有。比如我们启动浏览器去打开一个网址,intent可以这样创建:

Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("http://blog.isming.me"));

上面没有填写category,创建Intent的时候会自动填写为default。

等待补充吧。

乱扯

好吧,本来像,会写的很长的,但是真正想写的时候,发现就这么简单,也没什么好写的。下次多看看源码,再看有没有要补充的。就酱紫了!

android中JSON的解析

android中网络数据传输是经常被用到的,通常我们使用xml或者json,而json更加轻量,便捷,我们使用的更多。我自己在项目中使用很多,今天就说说android中怎么去解析JSON,帮助自己总结内容,同时帮助别人少走弯路。

JSON语法

首先看JSON的语法和结构,这样我们才知道怎么去解析它。JSON语法时JavaScript对象表示语法的子集。

JSON的值可以是:

  • 数字(整数或者浮点数)

  • 字符串(在双引号内)

  • 逻辑值(true 或 false)

  • 数组(使用方括号[]包围)

  • 对象( 使用花括号{}包围)

  • null

JSON中有且只有两种结构:对象和数组。

1、对象:对象在js中表示为“{}”括起来的内容,数据结构为 {key:value,key:value,…}的键值对的结构,在面向对象的语言中,key为对象的属性,value为对应的属性值,所以很容易理解,取值方法为 对象.key 获取属性值,这个属性值的类型可以是 数字、字符串、数组、对象几种。

2、数组:数组在js中是中括号“[]”括起来的内容,数据结构为 ["java","javascript","vb",…],取值方式和所有语言中一样,使用索引获取,字段值的类型可以是 数字、字符串、数组、对象几种。

andoroid解析JSON

android sdk中为我们提供了org.json,可以用来解析json,在android3.0又为在android.util包JsonReader和JsonWriter来进行json的解析和生成。

使用org.json包JSONObject和JSONArray进行解析

我们知道json中就两种结构array和object,因此就有这个两个类进行解析。

如我们有下面几个json字符串:

{"name":"sam","age":18,"weight":60} //json1 一个json对象
[12,13,15]                    //json2 一个数字数组
[{"name":"sam","age":18},{"name":"leo","age":19},{"name":"sky", "age":20}] //json3 json array中有object

第一个json对象json1的解析

JSONObject jsonObj = new JSONObject(json1);
String name = jsonObj.optString("name");
int age = jsonObj.optInt("age");
int weight = jsonObj.optInt("weight");

另外还有

Object opt(String name)
boolean optBoolean(String name)
double optDouble(String name)
JSONArray optJSONArray(String name)
JSONObject optJSONObject(String name)

等方法,我推荐用这些方法,这些方法在解析时,如果对应字段不存在会返回空值或者0,不会报错。

当然如果你使用以下方法
java
Object get(String name)
boolean getBoolean(String name)
int getInt(String name)

等方法时,代码不会去判断是否存在该字段,需要你自己去判断,否则的话会报错。自己判断的话使用has(String name)来判断。

<

p>再来看解析数组,简单的数组。第二个json2
“`java
JSONArray jsonArray = new JSONArray(json2);<br/>
for (int = 0; i < jsonArray.length();i++) {<br/>
int age = jsonArray.optInt(i); <br/>
}

<pre><code>
解析复杂数组,包含对象,json3
“`java
JSONArray jsonArray = new JSONArray(json3);
for (int = 0; i < jsonArray.length();i++) {
JSONObject jsonObject = jsonArray.optJSONObject(i);
String name = jsonObject.optString("name");
int age = jsonObject.optInt("age");
}

从上面可以看到数组的话解析方法和对象差不多,只是将键值替换为在数组中的下标。另外也是有optXXX(int index)和getXXX(int index)方法的,opt也是安全的,即对应index无值的时候,不会报错,返回空,推荐使用。

使用JsonReader进行解析

JsonReader的使用其实和xml解析中的pull是有一点类似的,我们来看示例。

前面的JSONObject和JSONArray创建时传的是String,而JsonReader需要传入的时候是Reader,网络访问中,我们可以直接拿输入流传进来,转成Reader。我们这里假设我们上面的String已经转成InputStream了,分别为jsonIs1,jsonIs2,jsonIs3。

上面的json1解析:

JsonReader reader = new JsonReader(new InputStreamReader(jsonIs1));
try {积分:49排名:第1495693名上传资源:1
    reader.beginObject();
    while (reader.hasNext()) {
        String keyName = reader.nextName();
        if (keyName.equals("name")) { 
            String name = reader.nextString();
        } else if (keyName.equals("age")) {
            int age = reader.nextInt();
        } else if (keyName.equals("weight")) {
            int weight = reader.nextInt();
        }
    }
    reader.endObject();
} finally {
    reader.close();
}

上面json2的解析:

JsonReader reader = new JsonReader(new InputStreamReader(jsonIs2));
try {
    List<Integer> ages = new ArrayList<Integer>();
    reader.beginArray();
    while (reader.hasNext()) {
        ages.add(reader.nextInt());
    }
    reader.endArray();
} finally {
    reader.close();
}

第三个我就不写示例了。

看到这个的话是通过去遍历我们的json,去取得我们的内容,我觉得在效率上面会比第一种效率高一些。具体使用的话就按照我这两个例子可以写出来的。

使用GSON解析JSON

GSON是谷歌出品,很好用。同时呢还有一些其他的第三方的比如fastjson等,都和Gson差不多,传一个string和要转换的对象,帮你直接直接解析出来,不再需要我们自己一个字段一段字段的解析。这里就以Gson为例吧,因为我只用过Gson,对其最熟悉。_^

使用Gson,我们需要导入Gson的包,下载地址https://code.google.com/p/google-gson/downloads/list

现在开始看看,怎么解析上面的三个json字符串例子。

第一个对象json1

首先我们需要一个实体类
“`java
public class People{</p>

<p>public String name;</p>

<p>@SerializedName(age)<br/>
pubic int mAge; //如果我们类中成员的名称和json对象中的键名不同,可以通过注解来设置名字</p>

<p>public int weight;<br/>
}<br/>
“`

然后就可以解析了

Gson gson = new Gson();
Poeple people = gson.fromJson(json1, People.class);

对于第二个json2,我们可以解析成int数组,也可以解析成Integer的List。
解析成数组:
java
Gson gson = new Gson();
int[] ages = gson.fromJson(json2, int[].class);

解析成List:

Gson gson = new Gson();
List<Integer> ages = gson.fromJson(json2, new TypeToken<List<Integer>>(){}.getType);

第三个同样可以解析成List或者数组,我们就直接解析成List.
java
Gson gson = new Gson();
List<People> peoples = gson.fromJson(json3, new TypeToke<List<People>>(){}.getType);

从上面的代码看到,使用Gson解析的话就非常简单了。需要注意的是如果对应的键值和成员名称不同的话可以使用注解来标记。

闲扯

上面就说清了主流的一些解析,可以看到使用Gson之后解析是十分简单,这可以为我们的开发节约很多的时间。同时呢android本身为我们提供的也挺够用的,并且3.0后增加的JsonReader操作更加流畅,和其他如Pull解析xml的方式有些类似。

本文中仅仅介绍了使用,还有一些api以及高级用法没有提到,毕竟一篇文章能描写的有限,请自行查看官方文档。

上面说的只是解析,其实还有json的生成,JSONObject,JSONArray,都可以直接生成json的,3.0也提供了JsonWriter来生成json。而Gson中,直接gson.toJson(),就可以生成json了,一切都是这么的美妙。不过我们平时,生成json使用比较少,我这里就不写了(或者单独写一篇生成)_

参考资料地址:

使用proguard混淆android代码

当前是有些工具比如apktool,dextojar等是可以对我们android安装包进行反编译,获得源码的。为了减少被别人破解,导致源码泄露,程序被别人盗取代码,等等。我们需要对代码进行混淆,android的sdk中为我们提供了ProGrard这个工具,可以对代码进行混淆(一般是用无意义的名字来重命名),以及去除没有使用到的代码,对程序进行优化和压缩,这样可以增加你想的难度。最近我做的项目,是我去配置的混淆配置,因此研究了一下,这里分享一下。

如何启用ProGuard

ant项目和eclipse项目启用方法

在项目的project.properties文件中添加一下代码

proguard.config=proguard.cfg //proguard.cfg为proguard的配置文件

proguard.config=/path/to/proguard.cfg //路径不在项目根目录时,填写实际路径

填写这句配置后,在release打包时就会按照我们的配置进行混淆,注意,在我们平时的debug时是不会进行混淆的。

Gradle项目(以及Android Studio)

在build.gradle中进行配置

android {
buildTypes {
release {
runProguard true
proguardFiles getDefaultProguardFile('proguard-android.txt'),'some-other-rules.txt'
//proguardFile 'some-other-rules.txt' 配置单个文件这样
}
}
}

如上面代码所示,我们可以使用runProguard true开启,并且对其配置混淆配置,可以配置多个文件或单个文件。

android的sdk中已经为我们提供了两个默认的配置文件,我们可以拿过来进行使用,proguard-android.txt和proguard-android-optimize.txt。

ProGuard配置

上面说到android为我们提供了两个默认的配置文件,在其中,我们可以看到他的一些语法。本节进行描述。

保留选项(配置不进行处理的内容)

-keep {Modifier} {class_specification} 保护指定的类文件和类的成员

-keepclassmembers {modifier} {class_specification} 保护指定类的成员,如果此类受到保护他们会保护的更好

-keepclasseswithmembers {class_specification} 保护指定的类和类的成员,但条件是所有指定的类和类成员是要存在。

-keepnames {class_specification} 保护指定的类和类的成员的名称(如果他们不会压缩步骤中删除)

-keepclassmembernames {class_specification} 保护指定的类的成员的名称(如果他们不会压缩步骤中删除)

-keepclasseswithmembernames {class_specification} 保护指定的类和类的成员的名称,如果所有指定的类成员出席(在压缩步骤之后)

-printseeds {filename} 列出类和类的成员-keep选项的清单,标准输出到给定的文件

压缩

-dontshrink 不压缩输入的类文件

-printusage {filename}

-whyareyoukeeping {class_specification}

优化

-dontoptimize 不优化输入的类文件

-assumenosideeffects {class_specification} 优化时假设指定的方法,没有任何副作用

-allowaccessmodification 优化时允许访问并修改有修饰符的类和类的成员

混淆

-dontobfuscate 不混淆输入的类文件

-obfuscationdictionary {filename} 使用给定文件中的关键字作为要混淆方法的名称

-overloadaggressively 混淆时应用侵入式重载

-useuniqueclassmembernames 确定统一的混淆类的成员名称来增加混淆

-flattenpackagehierarchy {package_name} 重新包装所有重命名的包并放在给定的单一包中

-repackageclass {package_name} 重新包装所有重命名的类文件中放在给定的单一包中

-dontusemixedcaseclassnames 混淆时不会产生形形色色的类名

-keepattributes {attribute_name,…} 保护给定的可选属性,例如LineNumberTable, LocalVariableTable, SourceFile, Deprecated, Synthetic, Signature, and InnerClasses.

-renamesourcefileattribute {string} 设置源文件中给定的字符串常量

后面的文件名,类名,或者包名等可以使用占位符代替

?表示一个字符
* 可以匹配多个字符,但是如果是一个类,不会匹配其前面的包名
** 可以匹配多个字符,会匹配前面的包名。

在android中在android Manifest文件中的activity,service,provider, receviter,等都不能进行混淆。一些在xml中配置的view也不能进行混淆,android提供的默认配置中都有。

ProGuard的输出文件及用处

混淆之后,会给我们输出一些文件,在gradle方式下是在/build/proguard/目录下,ant是在/bin/proguard目录,eclipse构建在/proguard目录像。

分别有以下文件:

+ dump.txt 描述apk文件中所有类文件间的内部结构。

+ mapping.txt 列出了原始的类,方法,和字段名与混淆后代码之间的映射。
+ seeds.txt 列出了未被混淆的类和成员
+ usage.txt 列出了从apk中删除的代码

当我们发布的release版本的程序出现bug时,可以通过以上文件(特别时mapping.txt)文件找到错误原始的位置,进行bug修改。同时,可能一开始的proguard配置有错误,也可以通过错误日志,根据这些文件,找到哪些文件不应该混淆,从而修改proguard的配置。

注意:重新release编译后,这些文件会被覆盖,所以每次发布程序,最好都保存一份配置文件。

调试Proguard混淆后的程序

上面说了输出的几个文件,我们在改bug时可以使用,通过mapping.txt,通过映射关系找到对应的类,方法,字段等。

另外Proguard文件中包含retrace脚本,可以将一个被混淆过的堆栈跟踪信息还原成一个可读的信息,window下时retrace.bat,linux和mac是retrace.sh,在/tools/proguard/文件夹下。语法为:

retrace.bat|retrace.sh [-verbose] mapping.txt []

例如:

retrace.bat -verbose mapping.txt obfuscated_trace.txt

如果你没有指定,retrace工具会从标准输入读取。

一些常用包的Proguard配置

下面再写一些我在项目中使用到的一些第三方包需要单独配置的混淆配置

sharesdk混淆注意

-keep class android.net.http.SslError
-keep class android.webkit.**{*;}
-keep class cn.sharesdk.**{*;}
-keep class com.sina.**{*;}
-keep class m.framework.**{*;}

Gson混淆配置

-keepattributes *Annotation*
-keep class sun.misc.Unsafe { *; }
-keep class com.idea.fifaalarmclock.entity.***
-keep class com.google.gson.stream.** { *; } 

Umeng sdk混淆配置

-keepclassmembers class * {
   public <init>(org.json.JSONObject);
}

-keep class com.umeng.**

-keep public class com.idea.fifaalarmclock.app.R$*{
    public static final int *;
}

-keep public class com.umeng.fb.ui.ThreadView {
}

-dontwarn com.umeng.**

-dontwarn org.apache.commons.**

-keep public class * extends com.umeng.**

-keep class com.umeng.** {*; }

关于配置方面,我写的不够详细,可以去看参考资料第二条,proguard官方文档。也欢迎大家交流使用遇到的问题和心得。

资料参考:

1.http://proguard.sourceforge.net/

2.http://developer.android.com/tools/help/proguard.html

使用Gradle构建Android项目

新项目中,使用了Google I/O 2013发布的新工具,使用Gradle构建android项目,并且在新版的Intellig IDEA以及google的Android Studio对其支持。本文就介绍一下怎么使用gradle构建android项目,进行多个版本编译。

Gradle是什么?

Gradle是以Groovy为基础,面向java应用,基于DSL语法的自动化构建工具。是google引入,替换ant和maven的新工具,其依赖兼容maven和ivy。

使用gradle的目的:

  • 更容易重用资源和代码;
  • 可以更容易创建不同的版本的程序,多个类型的apk包;
  • 更容易配置,扩展;
  • 更好的IDE集成;

环境需求

Gradle1.10或者更高版本,grale插件0.9或者更高版本.

android SDK需要有Build Tools 19.0.0以及更高版本

Gradle基本结构

使用ide创建的gradle构建的项目,会自动创建一个build.gradle,如下:
“`
buildscript {<br/>
repositories {<br/>
mavenCentral()<br/>
}</p>

<pre><code>dependencies {
classpath 'com.android.tools.build:gradle:0.9.0'
}
</code></pre>

<p>}</p>

<p>apply plugin: 'android'</p>

<p>android {<br/>
compileSdkVersion 19<br/>
buildToolsVersion "19.0.0"<br/>
}<br/>

<pre><code><br />可以看到,构建文件主要有三个区域:

buildscript{…}

<blockquote>
Configures the build script classpath for this project. 设置脚本的运行环境
</blockquote>

apply plugin: 'android'

<blockquote>
设置使用android插件构建项目
</blockquote>

android{…}

<blockquote>
设置编译android项目的参数
</blockquote>

<h3 id="toc_3">任务task的执行</h3>

通常会有以下任务:

<ul>
<li>assemble The task to assemble the output(s) of the project(输出一个项目文件,android就是打包apk)</li>
<li>check The task to run all the checks.(运行检查,检查程序的错误,语法,等等)</li>
<li>build This task does both assemble and check (执行assemble和check)</li>
<li>clean This task cleans the output of the project(清理项目输出文件)</li>
</ul>

<blockquote>
上面的任务assemble,check,build实际上什么都不做,他们其实都是其他任务的集合。
</blockquote>

执行一个任务的方式为<code>gradle 任务名</code>, 如<code>gradle assemble</code>

在android项目中还有connectedCheck(检查连接的设备或模拟器),deviceCheck(检查设备使用的api版本)

通常我们的项目会有至少生成两个版本,debug和release,我们可以用两个任务assembleDebug和assembleRelease去分别生成两个版本,也可以使用assemble一下子生成两个版本。

gradle支持任务名缩写,在我们执行<em>gradle assembleRelease</em>的时候,可以用<em>gradle aR</em>代替。

<h3 id="toc_4">基本的构建定制</h3>

我们可以在build.gradle文件中配置我们的程序版本等信息,从而可以生成多个版本的程序。<br /><br />
支持的配置有:

<ul>
<li>minSdkVersion 最小支持sdk版本 </li>
<li>targetSdkVersion 编译时的目标sdk版本</li>
<li>versionCode 程序版本号</li>
<li>versionName 程序版本名称</li>
<li>packageName 程序包名</li>
<li>Package Name for the test application 测试用的程序包名</li>
<li>Instrumentation test runner 测试用的instrumentation实例</li>
</ul>

例如:<br />
“`<br />
android {<br/>
compileSdkVersion 19<br/>
buildToolsVersion “19.0.0”</p>

<pre><code>defaultConfig {
versionCode 12
versionName “2.0”
minSdkVersion 16
targetSdkVersion 16
}
</code></pre>

<p>}<br/>

目录配置

默认情况下项目目录是这样的
有两个组件source sets,一个main,一个test,对应下面两个文件夹。
src/main/
src/androidTest/

然后对于每个组件目录都有两个目录,分别存储java代码和资源文件
java/
resources/

对于android项目中呢,对应的目录和文件是
AndroidManifest.xml //该文件src/androidTest/目录下不需要,程序执行时会自动构建
res/
assets/
aidl/
rs/
jni/

如果需要上面这些文件,但是不是在上面所说的目录,则需要设置。

sourceSets {
main {
java {
srcDir 'src/java'
}
resources {
srcDir 'src/resources'
}
}
}

可以给main或者test设置根目录,如

sourceSets {
androidTest.setRoot('tests')
}

可以指定每种文件的存储路径

sourceSets {
main {
manifest.srcFile 'AndroidManifest.xml'
java.srcDirs = ['src']
resources.srcDirs = ['src']
aidl.srcDirs = ['src']
renderscript.srcDirs = ['src']
res.srcDirs = ['res']
assets.srcDirs = ['assets']
}
}

特别是我们的ndk生成的.so文件,通常我们不是放到jni目录中的,我们需要设置一下

sourceSets {
main {
jniLibs.srcDirs = ['libs']
}
}

签名配置

可以给不同类型进行不同的配置,先看示例:

“`
android {<br/>
signingConfigs {<br/>
debug {<br/>
storeFile file("debug.keystore")<br/>
}</p>

<pre><code> myConfig {
storeFile file("other.keystore")
storePassword "android"
keyAlias "androiddebugkey"
keyPassword "android"
}
}

buildTypes {
foo {
debuggable true
jniDebugBuild true
signingConfig signingConfigs.myConfig
}
}
</code></pre>

<p>}<br/>

<pre><code><br />上面的配置文件配置两个类型,一个时debug类型,一个时自己的自定义类型。两个分别使用了不同的签名,同时对于生成密钥,要填写设置的密码。

<h3 id="toc_7">代码混淆设置</h3>

直接看代码:<br /><br />
<code><br />
android {<br />
buildTypes {<br />
release {<br />
runProguard true<br />
proguardFile getDefaultProguardFile('proguard-android.txt')<br />
}<br />
}<br />
}<br />
</code>

以上是使用默认的proguard文件去进行混淆,也可以使用自己编写的规则进行混淆,<code>proguardFile 'some-other-rules.txt'</code>

<h3 id="toc_8">依赖配置</h3>

程序中会依赖别的包,或者程序,需要引入别的类库。前面也说到了,支持maven。<br />
对于本地的类库,可以这样引入:<br />
“`<br />
dependencies {<br/>
compile files(‘libs/foo.jar’) //单个文件<br/>
compile fileTree(dir: ‘libs’, include: [‘*.jar’]) //多个文件<br/>
}</p>

<p>android {<br/>
…<br/>
}<br/>

对于maven仓库文件:
“`
repositories {<br/>
mavenCentral()<br/>
}</p>

<p>dependencies {<br/>
compile 'com.google.guava:guava:11.0.2'<br/>
}</p>

<p>android {<br/>
…<br/>
}<br/>
“`

输出不同配置的应用

看代码:

“`
android {<br/>
…</p>

<pre><code>defaultConfig {
minSdkVersion 8
versionCode 10
}

productFlavors {
flavor1 {
packageName "com.example.flavor1"
versionCode 20
}

flavor2 {
packageName "com.example.flavor2"
minSdkVersion 14
}
}
</code></pre>

}<br/>
“`
通过以上设置,我们可以输出不同的保命不同的版本号,以及最小sdk的程序包。当然我们可以根据自己的需求去做其他的不同

生成多个渠道包(以Umeng为例)

我的完整配置文件

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:0.9.+'
    }
}
apply plugin: 'android'

repositories {
        mavenCentral()
}

android {
    compileSdkVersion 19
    buildToolsVersion "19.0.3"

    defaultConfig {
        minSdkVersion 8
        targetSdkVersion 19
        versionCode 1
        versionName "1.0"
    }

    sourceSets {
        main {
            jniLibs.srcDirs = ['libs']
        }
    }

    lintOptions {
        abortOnError false
    }


//签名
    signingConfigs {
        //你自己的keystore信息
        release {
            storeFile file("×.keystore")
            storePassword "×××"
            keyAlias "××××"
            keyPassword "×××"
        }
    }

    buildTypes {

        debug {
            signingConfig signingConfigs.release
        }

        release {
            signingConfig signingConfigs.release
            runProguard false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
        }
    }

    //渠道Flavors,我这里写了一些常用的,你们自己改
    productFlavors {
        //GooglePlay{}
        //Store360{}
        //QQ{}
        //Taobao{}
        //WanDouJia{}
        //AnZhuo{}
        //AnZhi{}
        //BaiDu{}
        //Store163{}
        //GFeng{}
        //AppChina{}
        //EoeMarket{}
        //Store91{}
        //NDuo{}
        xiaomi{}
        umeng{}
    }

}


//替换AndroidManifest.xml的UMENG_CHANNEL_VALUE字符串为渠道名称
android.applicationVariants.all{ variant ->
    variant.processManifest.doLast{

        //之前这里用的copy{},我换成了文件操作,这样可以在v1.11版本正常运行,并保持文件夹整洁
        //${buildDir}是指./build文件夹
        //${variant.dirName}是flavor/buildtype,例如GooglePlay/release,运行时会自动生成
        //下面的路径是类似这样:./build/manifests/GooglePlay/release/AndroidManifest.xml
        def manifestFile = "${buildDir}/manifests/${variant.dirName}/AndroidManifest.xml"

        //将字符串UMENG_CHANNEL_VALUE替换成flavor的名字
        def updatedContent = new File(manifestFile).getText('UTF-8').replaceAll("UMENG_CHANNEL_VALUE", "${variant.productFlavors[0].name}")
        new File(manifestFile).write(updatedContent, 'UTF-8')

        //将此次flavor的AndroidManifest.xml文件指定为我们修改过的这个文件
        variant.processResources.manifestFile = file("${buildDir}/manifests/${variant.dirName}/AndroidManifest.xml")
    }
}

以上的功能就是替换我的Manifest中的umeng渠道标示,同时去生成不同的apk包。

最后说明

程序在buid的时候,会执行lint检查,有任何的错误或者警告提示,都会终止构建,我们可以将其关掉。

lintOptions {
abortOnError false
}

最后PS一下

本人使用gradle确实方便很多,虽然电脑配置低,build的时候比较慢。

内容呢,主要时参考谷歌官方的文档。

关于怎么安装,没有讲到,可以参考这篇文章:http://stormzhang.github.io/android/2014/02/28/android-gradle/

附上我参考的文档,没看懂的可以去看看。http://tools.android.com/tech-docs/new-build-system/user-guide

gradle升级了,我又写了一篇,地址这里:http://blog.isming.me/2014/11/21/use-gradle-new/