使用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/

使用Intellij IDEA阅读android源码

一直使用Ubuntu+Intellig IDEA进行android开发,并且android源码已经花了两三个星期下载回来了,但是linux平台,没有好用的source insight,所以一直阅读都是需要看哪个了才去搜索那一个。原来发现,原来android提供了eclipse,idea等工具进行阅读的方法。

在android源码目录有一个目录development/tools/idegen,这个就是用来生成idea的project文件的。

那么就开始生成吧!

首先在源码根目录执行这个文件
bash
sh ./development/tools/idegen/idegen.sh

发现需要idegen.jar文件,我这里贡献给大家,把这个文件复制到out/host/linux-x86/framework/目录下,然后重新执行刚才的命令即可。文件下载地址

给我的提示是:

Read excludes: 25ms
Traversed tree: 592981ms

即,总共花费这么多时间,当然电脑快的话,可能也会更快一点。

在我的源码目录生成了android.imlandroid.ipr两个文件。

然后在idea中点击file>open,选择刚刚生成的android.ipr文件即可。

android中网络操作使用总结(http)

Android是作为智能手机的操作系统,我们开发的应用,大多数也都需要连接网络,通过网络发送数据、获取数据,因此作为一个应用开发者必须熟悉怎么进行网络访问与连接。通常android中进行网络连接一般是使用scoket或者http,http是最多的情况,这里,我来总结下,怎么进行http网络访问操作。

android是采用java语言进行开发的,android的包中包含java的URLConnection和apache 的httpclient,因此我们可以使用这两个工具进行网络连接和操作。同时,为了控制是否允许程序连接网络,我们开发应用时,需要在Manifest文件中配置申请网络连接的权限,代码如下。
xml
<uses-permission android:name="android.permission.INTERNET"/>

使用URLConnection连接网络

URLConnection为java.net包中提供的网络访问,支持http,https,ftp等,进行http连接时,使用HttpURLConnection即可,示例代码如下:

URL url = new URL("http://www.android.com/");
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
try {
    InputStream in = new BufferedInputStream(urlConnection.getInputStream());
    readStream(in);   //该方法是我们自己写的,从流中取数据保存到本地,实现从网络获取数据。
finally {
    urlConnection.disconnect();
}

向服务器提交数据时,需要采用POST传输
“`java
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();<br/>
try {<br/>
urlConnection.setDoOutput(true); //设置可以上传数据<br/>
urlConnection.setChunkedStreamingMode(0);</p>

<pre><code>OutputStream out = new BufferedOutputStream(urlConnection.getOutputStream());
writeStream(out); //该方法时我们自己写的,向输出流中写数据,实现数据上传。

InputStream in = new BufferedInputStream(urlConnection.getInputStream());
readStream(in);
</code></pre>

<p>finally {<br/>
urlConnection.disconnect();<br/>
}<br/>
“`
更多使用方法和函数请查看:http://developer.android.com/reference/java/net/HttpURLConnection.html

同时HttpsURLConnection的使用方法请查看:http://developer.android.com/reference/javax/net/ssl/HttpsURLConnection.html

使用HttpClient

这个呢,我们也可以使用AndroidHttpClient页可以使用apache默认的DeafultHttpClient,两者之间仅仅常见Client不同,其他都一样

创建AndroidHttpClient
java
AndroidHttpClient httpClient = AndroidHttpClient.newInstance("user-agent");
java
创建DefaultHttpClient
java
HttpClient client = new DefaultHttpClient();

GET请求
“`java
HttpGet getRequest = new HttpGet("<a href="http://blog.isming.me%22">http://blog.isming.me"</a>);<br/>
try {<br/><br/>
HttpResponse response = httpClient.execute(getMethod); //发起GET请求 </p>

<pre><code>Log.i(TAG, "resCode = " + response.getStatusLine().getStatusCode()); //获取响应码<br />
Log.i(TAG, "result = " + EntityUtils.toString(response.getEntity(), "utf-8"));//获取服务器响应内容<br />
</code></pre>

<p>} catch (ClientProtocolException e) {<br/><br/>
// TODO Auto-generated catch block<br/><br/>
e.printStackTrace();<br/><br/>
} catch (IOException e) {<br/><br/>
// TODO Auto-generated catch block<br/><br/>
e.printStackTrace();<br/><br/>
}<br/><br/>

<pre><code><br />发起POST请求:<br />
“`java<br />
//先将要传的数据放入List<br/><br/>
params = new LinkedList<BasicNameValuePair>();<br/><br/>
params.add(new BasicNameValuePair(“param1”, “Post方法”));<br/><br/>
params.add(new BasicNameValuePair(“param2”, “第二个参数”)); </p>

<p>try {<br/><br/>
HttpPost postMethod = new HttpPost(baseUrl);<br/><br/>
postMethod.setEntity(new UrlEncodedFormEntity(params, “utf-8”)); //将参数填入POST Entity中 </p>

<pre><code>HttpResponse response = httpClient.execute(postMethod); //执行POST方法
Log.i(TAG, “resCode = ” + response.getStatusLine().getStatusCode()); //获取响应码
Log.i(TAG, “result = ” + EntityUtils.toString(response.getEntity(), “utf-8”)); //获取响应内容
</code></pre>

<p>} catch (UnsupportedEncodingException e) {<br/><br/>
// TODO Auto-generated catch block<br/><br/>
e.printStackTrace();<br/><br/>
} catch (ClientProtocolException e) {<br/><br/>
// TODO Auto-generated catch block<br/><br/>
e.printStackTrace();<br/><br/>
} catch (IOException e) {<br/><br/>
// TODO Auto-generated catch block<br/><br/>
e.printStackTrace();<br/><br/>
}<br/><br/>

说明

从上面看到,使用HttpClient更加方便,明了。不过Android官方给我们的文档说明是建议android2.2及以下版本建议使用HttpClient,android2.3及以上版本建议使用HttpURLConnection。

原因是HttpURLConnection时一个轻量级的Http客户端,提供的API比较简单,方便扩展,但是在2.2及以前版本中,这个API存在一些bug,在2.3的版本中,给修改了。

而HttpClient虽然稳定,bug少,但是其提供的API很多,进行升级和扩展不方便,android团队在提升和优化其方面的积极性不高。

因此,在Android2.2版本以前,使用HttpClient时最好的选择。

Android2.3版本及以后,使用HttpURLConnection时最佳选择。

框架

网络连接是耗时的操作,通常我们不会将其放在UI线程操作,会编写异步线程,将其放在后台线程执行(异步线程操作看这里)。

而网上有一些线程的网络操作框架,可以减少我们的工作量,同时我们自己写的好,可能很多异常处理之类的,没有其向的更加全面。

有轻量的android-async-http

谷歌官方的volley

Square提供的Retrofit

这个可以根据自己项目的需要选择不同的框架,有空我来写写volley的使用(PS:网上已经有使用方法了,自己百度去),另外两个我的连接中已经有使用方法了。

ok,完成!

写的东西不少,但是介绍的不够详细。如果不清楚的,欢迎与我交流

ubuntu快速变装mac

其实,我喜欢Mac的,想要有个MacBook,喜欢其婀娜多姿的身材,妩媚的脸庞,最终要的是有一个UNIX的心。可惜,屌丝买不起啊,只好用Ubuntu来装装Mac了,有什么办法变装呢,那就是安装主题,哈哈。

之前的时候,还是Ubuntu13.04的时候,用过一个主题,让我的桌面变得真的很像Mac,但是升级到14.04之后,发现那个主题安装不了了。今天偶然发现,原来是作者对其升级了,针对不同版本安装不同的主题包,然后我又恢复原来的那个界面了。遂分享之。

先来晒晒我的界面,(__) 嘻嘻……

佛说,万物皆有源!首先,我们要先将该软件的源加到我们的源列表中。


sudo add-apt-repository ppa:noobslab/themes
sudo apt-get update

然后针对不同版本,安装不同的主题

Ubuntu13.04(更低版本,可以用这个试一下,不保证可以用)


sudo apt-get install mac-icons-noobslab
sudo apt-get install mac-ithemes-noobslab

Ubuntu13.10

sudo apt-get install mac-icons-v2-noobslab
sudo apt-get install mac-ithemes-v2-noobslab

Ubuntu14.04

sudo apt-get install mac-icons-v3
sudo apt-get install mac-ithemes-v3

执行完之后,需要安装Ubuntu Tweak ,在theme中进行更多的设置,我的配置如下图所示。

Android消息循环分析

我们的常用的系统中,程序的工作通常是有事件驱动和消息驱动两种方式,在Android系统中,Java应用程序是靠消息驱动来工作的。

消息驱动的原理就是:

1. 有一个消息队列,可以往这个队列中投递消息;

2. 有一个消息循环,不断从消息队列中取出消息,然后进行处理。

在Android中通过Looper来封装消息循环,同时在其中封装了一个消息队列MessageQueue。

另外Android给我们提供了一个封装类,来执行消息的投递,消息的处理,即Handler。

在我们的线程中实现消息循环时,需要创建Looper,如:

class LooperThread extends Thread {
    public Handler mHandler;
    public void run() {
        Looper.prepare(); //1.调用prepare
        ......
        Looper.loop();  //2.进入消息循环
    }
}

看上面的代码,其实就是先准备Looper,然后进入消息循环。

1. 在prepare的时候,创建一个Looper,同时在Looper的构造方法中创建一个消息队列MessageQueue,同时将Looper保存到TLV中(这个是关于ThreadLocal的,不太懂,以后研究了再说)

2. 调用loop进入消息循环,此处其实就是不断到MessageQueue中取消息Message,进行处理。

然后再看我们如何借助Handler来发消息到队列和处理消息

Handler的成员(非全部):


final MessageQueue mQueue;
final Looper mLooper;
final Callback mCallback;

Message的成员(非全部):


Handler target;
Runnable callback;

可以看到Handler的成员包含Looper,通过查看源代码,我们可以发现这个Looper是有两种方式获得的,1是在构造函数传进来,2是使用当前线程的Looper(如果当前线程无Looper,则会报错。我们在Activity中创建Handler不需要传Handler是因为Activity本身已经有一个Looper了),MessageQueue也就是Looper中的消息队列。

然后我们看怎么向消息队列发送消息,Handler有很多方法发送队列(这个自己可以去查),比如我们看sendMessageDelayed(Message msg, long delayMillis)

public final boolean sendMessageDelayed(Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
// SystemClock.uptimeMillis() 获取开机到现在的时间
}
//最终所有的消息是通过这个发,uptimeMillis是绝对时间(从开机那一秒算起)
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
boolean sent = false;
MessageQueue queue = mQueue;
if (queue != null) {
msg.target = this;
sent = queue.enqueueMessage(msg, uptimeMillis);
}
return sent;
}

看上面的的代码,可以看到Handler将自己设为Message的target,然后然后将msg放到队列中,并且指定执行时间。

消息处理

处理消息,即Looper从MessageQueue中取出队列后,调用msg.target的dispatchMessage方法进行处理,此时会按照消息处理的优先级来处理:

1. 若msg本身有callback,则交其处理;

2. 若Handler有全局callback,则交由其处理;

3. 以上两种都没有,则交给Handler子类实现的handleMessage处理,此时需要重载handleMessage。

我们通常采用第三种方式进行处理。

注意!!!!我们一般是采用多线程,当创建Handler时,LooperThread中可能还未完成Looper的创建,此时,Handler中无Looper,操作会报错。

我们可以采用Android为我们提供的HandlerThread来解决,该类已经创建了Looper,并且通过wait/notifyAll来避免错误的发生,减少我们重复造车的事情。我们创建该对象后,调用getLooper()即可获得Looper(Looper未创建时会等待)。

补充

本文所属为Android中java层的消息循环机制,其在Native层还有消息循环,有单独的Looper。并且2.3以后MessageQueue的核心向Native层下移,native层java层均可以使用。这个我没有过多的研究了!哈哈

PS:本文参考《深入理解Android:卷I》

管理Activity的生命周期

Activity是android的四大组件之一,我们编写程序时,主要通过Activity来显示我们的UI。我们需要了解他的生命周期,以及 每个周期可以做什么。
一个Activity存在三种状态:

Resumed:

activity显示在屏幕的最前面,并且获取用户焦点。

Paused:

其他activity在当前activity之前,并获得焦点。当前activity还能够部分显示,仍然维护着所有状态,当内存低的时候才会被系统杀死。

Stopped:

当前activity完全不可见。但是仍然存在,其他应用需要内存的时候会被杀死(不一定是低内存的时候)。

具体生命周期见图:

  1. 启动Activity
    执行onCreate() –> onStart() –> onResumed()
    这个过程执行完,我们的activity就显示在屏幕上了。
    我们可以重写相应的方法,在其中实现我们需要的操作。
  2. 销毁Activity
    当我们执行finish()或者被系统强制杀死时,我们的activity会被销毁。
    我们的activity内部会执行 onPause()–>onStop()->onDestory()
  3. 暂停和继续
    当我们的界面被部分挡住时,会进入暂停状态。
    此时会执行onPause()
    界面重新完全显示后又会回到继续状态(Resumed),会执行onResumed()
    在即将pause时,我应应该在onPause中执行一些释放操作,比如停止正在进行的动画,一些用户的状态(确定用户会保存的,比如邮件草稿),释放系统支援(如广播接收者,传感器),以及其他会消耗电池并且在paused时用户不需要用到的。
    同时这些释放或者保存的,我们在onResumed时候需要恢复。
  4. 停止和重启
    用户进去其他界面之后,如接听电话,或者打开其他activity,我们的界面会停止,进入stoped状态。此时会执行onPause()–>onStop()
    重启时会执行onRestart()–>onStart()–>onResume()
    重启时候和我们创建时相比,多一步onRestart()。
    ​在stop的时候,我们需要执行一些比在onPause中更加消耗CPU的更大的任务,比如写数据库。
    ​同时建议,在onStart()中恢复,而不是在onRestart()中恢复。
  5. 重新创建Activity
    ​ ​当我们的进程被destory(不是用户主动调用finish),可以返回。返回的时候会重新创建。执行过程和创建activity一样。
    ​当activity被系统kill之前,会调用onSaveInstanceState()去保存UI状态,如果我们有信息需要保存,也可以去重写这个方法去做。
    同时我们可以重写onRestoreInstanceState()去恢复状态。不重写,系统会恢复系统保存的那一部分UI。或者我们可以在onCreate中恢复,onCreate的参数savedInstanceState就是我们保存的信息,可以判断该参数是否为空,来恢复界面。

解决Ubuntu下Sublime text 3中文输入的问题

好久之前便听朋友说起Sublime Text这款软件很好用,终于这几天有空折腾,把软件给装起来了。用起来确实很不错,写代码很爽。

但是用了一段时间之后,我需要输入中文了,无论怎么切换输入法,都无法切换到中文。
网上搜索了一下,原来这是Bug。找解决方法吧。下面介绍我的解决方案,是大神cjacker解决成功的啦,我只是copy一下,方便大家在遇到这个问题的时候可以方便解决。

    我的系统:ubuntu 13.04  
    我的输入法:fcitx   
    sublime版本:3059    

理论上支持 sublime text2/3

1.保存代码sublime-imfix.c

/*
sublime-imfix.c
Use LD_PRELOAD to interpose some function to fix sublime input method support for linux.
By Cjacker Huang <jianzhong.huang at i-soft.com.cn>

gcc -shared -o libsublime-imfix.so sublime_imfix.c  `pkg-config --libs --cflags gtk+-2.0` -fPIC
LD_PRELOAD=./libsublime-imfix.so sublime_text
*/
#include <gtk/gtk.h>
#include <gdk/gdkx.h>
typedef GdkSegment GdkRegionBox;

struct _GdkRegion
{
    long size;
    long numRects;
    GdkRegionBox *rects;
    GdkRegionBox extents;
};

GtkIMContext *local_context;

void
gdk_region_get_clipbox (const GdkRegion *region,
        GdkRectangle    *rectangle)
{   
    g_return_if_fail (region != NULL);
    g_return_if_fail (rectangle != NULL);

    rectangle->x = region->extents.x1;
    rectangle->y = region->extents.y1;
    rectangle->width = region->extents.x2 - region->extents.x1;
    rectangle->height = region->extents.y2 - region->extents.y1;
    GdkRectangle rect;
    rect.x = rectangle->x;
    rect.y = rectangle->y;
    rect.width = 0;
    rect.height = rectangle->height; 
    //The caret width is 2; 
    //Maybe sometimes we will make a mistake, but for most of the time, it should be the caret.
    if(rectangle->width == 2 && GTK_IS_IM_CONTEXT(local_context)) {
        gtk_im_context_set_cursor_location(local_context, rectangle);
    }
}

//this is needed, for example, if you input something in file dialog and return back the edit area
//context will lost, so here we set it again.

static GdkFilterReturn event_filter (GdkXEvent *xevent, GdkEvent *event, gpointer im_context)
{
    XEvent *xev = (XEvent *)xevent;
    if(xev->type == KeyRelease && GTK_IS_IM_CONTEXT(im_context)) {
        GdkWindow * win = g_object_get_data(G_OBJECT(im_context),"window");
        if(GDK_IS_WINDOW(win))
            gtk_im_context_set_client_window(im_context, win);
    }
    return GDK_FILTER_CONTINUE;
}

void gtk_im_context_set_client_window (GtkIMContext *context,
      GdkWindow    *window)
{
    GtkIMContextClass *klass;
    g_return_if_fail (GTK_IS_IM_CONTEXT (context));
    klass = GTK_IM_CONTEXT_GET_CLASS (context);
    if (klass->set_client_window)
    klass->set_client_window (context, window);

    if(!GDK_IS_WINDOW (window))
    return;
    g_object_set_data(G_OBJECT(context),"window",window);
    int width = gdk_window_get_width(window);
    int height = gdk_window_get_height(window);
    if(width != 0 && height !=0) {
        gtk_im_context_focus_in(context);
        local_context = context;
    }
    gdk_window_add_filter (window, event_filter, context); 
}

2.安装C/C++的编译环境和gtk libgtk2.0-dev

sudo    apt-get install build-essential
sudo apt-get install libgtk2.0-dev

3.编译共享内存

gcc -shared -o libsublime-imfix.so sublime_imfix.c  `pkg-config --libs --cflags gtk+-2.0` -fPIC

4.启动测试

LD_PRELOAD = ./libsublime-imfix.so sublime_text

正常的话这样是没有问题的。

然后我们在修改我们的desktop文件,使图标也可以使用

sudo vi /usr/share/applications/sublime-text.desktop

先将so文件移动到sublime text的目录

然后按照如下替换(主要是每次执行之前,去预加载我们的libsublime-imfix.so库)

[Desktop Entry]
Version=1.0
Type=Application
Name=Sublime Text
GenericName=Text Editor
Comment=Sophisticated text editor for code, markup and prose
Exec=bash -c 'LD_PRELOAD=/opt/sublime_text/libsublime-imfix.so /opt/sublime_text/sublime_text' %F
Terminal=false
MimeType=text/plain;
Icon=sublime-text
Categories=TextEditor;Development;
StartupNotify=true
Actions=Window;Document;

[Desktop Action Window]
Name=New Window
Exec=bash -c 'LD_PRELOAD=/opt/sublime_text/libsublime-imfix.so /opt/sublime_text/sublime_text' -n
OnlyShowIn=Unity;

[Desktop Action Document]
Name=New File
Exec=bash -c 'LD_PRELOAD=/opt/sublime_text/libsublime-imfix.so /opt/sublime_text/sublime_text' --command new_file
OnlyShowIn=Unity;

Toast的使用详解

Android中提供一种简单的Toast消息提示框机制,可以在用户点击了某些按钮后,提示用户一些信息,提示的信息不能被用户点击,Toast的提示信息根据用户设置的显示时间后自动消失。Toast的提示信息可以在调试程序的时候方便的显示某些想显示的东西,或者给用户提供友好的界面显示效果。

有两种方式去创建并且显示Toast:

  1. Toast.makeText(Context context, int resId, int duration)

    Toast.makeText(Context context, CharSequence text, int duration)

    Context为上下文,通常为当前activity;resId是string字符串的id,CharSequence为你要显示的字符串,duration为显示的时间,可以选择Toast.LENGTH_SHORT或Toast.LENGTH_LONG,也可自定义时间。
    使用方法:

    Toast.makeText(this, "this is string", Toast.LENGTH_SHORT).show();

  2. 自己创建Toast,并且设置视图,即自定义

    如:

Toast toast = new Toast(this);      
// 定义一个ImageView        
ImageView imageView = new ImageView(this);      
imageView.setImageResource(R.drawable.ic_launcher);     
// 定义一个Layout,这里是Layout     
LinearLayout Layout = new LinearLayout(this);       
Layout.setOrientation(LinearLayout.HORIZONTAL);     
// 将ImageView放到Layout中      
Layout.addView(imageView);      
// 设置View       
toast.setView(Layout);      
//设置显示时间        
toast.setDuration(20);      
toast.show();       

通过上面的代码就可以自己定义一个Toast了,我们还在其中显示了图片。

如何设置Toast显示的位置

方法一:

setGravity(int gravity, int xOffset, int yOffset) 三个参数分别表示(起点位置,水平向右位移,垂直向下位移)

方法二:

setMargin(float horizontalMargin, float verticalMargin)
以横向和纵向的百分比设置显示位置,参数均为float类型(水平位移正右负左,竖直位移正上负下)

注意事项:

Toast中有一个public方法setText(),可以给toast设置resid或者string,该方式尽可以在我们的第一种方法中使用,第二种自定义toast的方式是不可以使用的,使用的话会抛出异常。

原因是使用第一种方式创建,Toast会自己创建一个view,即textview,而我们使用这个setText实际是向这个TextView设置内容,而自定义的View不会有这个控件,因此会报错。

android异步操作总结

Android中经常会有一些操作比如网络请求,文件读写,数据库操作,比较耗时,我们需要将其放在非UI线程去处理,此时,我们需要处理任务前后UI的变化和交互。我们需要通过类似js中异步请求处理,这里总结我所了解到的,方便自己记忆,也方便别人的浏览。

  1. AsyncTask

new AysncTask().execute();

AsyncTask会按照流程执行在UI线程和一个耗时的任务线程。

1.onPreExecute() 执行预处理,它运行于UI线程,可以为后台任务做一些准备工作,比如绘制一个进度条控件。

2.doInBackground(Params…) 后台进程执行的具体计算在这里实现,doInBackground(Params…)是AsyncTask的关键,此方法必须重载。在这个方法内可以使用publishProgress(Progress…)改变当前的进度值。

3.onProgressUpdate(Progress…) 运行于UI线程。如果在doInBackground(Params…) 中使用了publishProgress(Progress…),就会触发这个方法。在这里可以对进度条控件根据进度值做出具体的响应。

4.onPostExecute(Result) 运行于UI线程,可以对后台任务的结果做出处理,结果就是doInBackground(Params…)的返回值。此方法也要经常重载,如果Result为null表明后台任务没有完成(被取消或者出现异常)。

  1. Handler
    创建Handler时需要传Lopper,默认是UI线程的。
    通过Handler发送消息(Message)到主线程或者Handler的线程,

  2. Activity.runOnUiThread(Runnable)
    Runnable即可在UI线程执行

  3. View.post(Runnable)
    Runnable运行在UI线程
    View.post(Runnable)方法。在post(Runnable action)方法里,View获得当前线程(即UI线程)的Handler,然后将action对象post到Handler里。在Handler里,它将传递过来的action对象包装成一个Message(Message的callback为action),然后将其投入UI线程的消息循环中。在Handler再次处理该Message时,有一条分支(未解释的那条)就是为它所设,直接调用runnable的run方法。而此时,已经路由到UI线程里,因此,我们可以毫无顾虑的来更新UI。

所有的异步操作原理本质都是通过Handler

基本上就这几种方法,当然也可自己使用消息循环常见类似的任务处理机制。