作者:snowdream
Email:yanghui1986527#gmail.com
Github: https://github.com/snowdream
QQ 群: 529327615
原文地址:https://snowdream.github.io/blog/2016/11/27/gradle-issues-with-android/

现在,大家都慢慢开始习惯使用Android Studio来开发Android应用了。
在使用过程中,难免碰到一些Gralde相关的使用问题。下面总结我收集整理的一些碰到的问题,以及解决方案:

1. 问题: Maven源仓库不可用,或者访问,下载非常慢。

分析: 由于主要的仓库Maven,Jcenter都在国外,以及众所周知的原因,这些Maven仓库可能比较慢。而我们从新建/导入工程开始,就要和Maven源仓库进行交互,因此,这可能导致我们导入或者打开工程非常的缓慢。

解决方案

  1. 优先使用国内Maven仓库镜像。推荐:阿里云Maven仓库镜像

    1
    maven { url “http://maven.aliyun.com/mvn/repository/“ }
  2. 使用http协议访问Maven仓库。使用下面的仓库来替换默认的 jcenter() 和 mavenCentral()

    1
    2
    jcenter { url “http://jcenter.bintray.com/"}
    maven { url “http://repo1.maven.org/maven2"}
  3. 如果你的项目不需要频繁从Maven仓库更新/下载组件,那么强烈建议你打开offline模式。

2. 问题: Android Studio导入工程非常慢,甚至卡死在导入阶段。

分析:Android Studio导入工程,默认会采用Gradle Wrapper的方式。也就是当你在本地没有下载过相应版本的Gradle,在导入工程前,就会去尝试下载相应版本的Gradle。

关于Gradle Wrapper的配置文件,在项目根目录下gradle/wrapper下。通常是两个文件:gradle-wrapper.jar和gradle-wrapper.properties

我们来看看gradle-wrapper.properties文件:

1
2
3
4
5
6
#Mon Dec 28 10:00:20 PST 2015
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip

没错,如果你之前没有通过Gradle Wrapper方式下载过这个版本Gradle,那么你导入工程后,第一件事情就是通过distributionUrl去下载gradle。然而,我们从这个来自Gradle官方的下载地址下载是非常慢的。

解决方案
1.搭建局域网HTTP服务器,镜像Gradle主流版本安装包。
参考:腾讯的方案:http://android-mirror.bugly.qq.com:8080/gradle/

2.在项目根目录build.gradle配置一个gradle wrapper任务。

1
2
3
4
task wrapper(type: Wrapper) {
gradleVersion = '3.1'
distributionUrl = "http://android-mirror.bugly.qq.com:8080/gradle/gradle-${gradleVersion}-bin.zip"
}

其中:gradleVersion对应gradle版本号,而distributionUrl则是对应版本的Gradle安装包下载地址。

3.在根目录下执行gradle wrapper 命令,然后将根目录下的gradle目录提交至git仓库。

3. 问题: 导入工程后,在Gradle Sync阶段卡死。

具体表现为:你以前已经在Android Studio中打开该工程,但是当你升级了本地Gradle版本,又或者使用Intellij IDEA打开,你会发现导入很慢。即使进去主界面,Gradle Sync也会处于假死状态,好像一直在加载什么。。。

分析: 不明

解决方案:根据网上的建议,排除以上原因之后,你可以这么干:删除工程下的.gradle文件夹,然后重新导入工程。

作者:snowdream
Email:yanghui1986527#gmail.com
Github: https://github.com/snowdream
QQ 群: 529327615
原文地址:https://snowdream.github.io/blog/2016/10/27/wifi-adb-without-usb/

简介

大家在开发调试Android应用的时候,都需要使用USB连接电脑和测试手机。
那么如何通过WIFI来连接电脑和测试手机呢?

通常的做法是这样:
安装idea插件AndroidWiFiADB。通过这个插件,只需要用USB连接一次电脑和测试手机,之后就可以只通过WIFI来连接了。

那么有没有方法,完全通过WIFI来进行连接呢?
下面就要介绍这样一款工具:WIFIADB

前提

Android手机需要Root

安装

WIFIADBIntelliJPlugin

在Idea/Android studio插件安装对话框输入“WIFI ADB ULTIMATE”,安装插件,然后重启IDE。
WIFIADBIntelliJPlugin

WIFIADBAndroid

  1. 点击,下载安装WIFIADBANDROID-RELEASE-1.0.APK
  2. 测试手机连接wifi
  3. 启动应用,点击主界面中间的圆形按钮。如果启动成功,底部会提示IP地址和端口号。
    WIFIADBAndroid

运行

在Idea/Android studio界面右侧,点击标签““WIFI ADB ULTIMATE””,输入IP地址和端口号,点击“Connect”即可。

WIFI ADB ULTIMATEWIFI ADB ULTIMATE

参考

  1. WIFIADB
  2. AndroidWiFiADB
  3. ADBWIFI

作者:snowdream
Email:yanghui1986527#gmail.com
Github: https://github.com/snowdream
QQ 群: 529327615
原文地址:https://snowdream.github.io/blog/2016/10/26/gradle-goodness-check-task-dependencies/

翻译自: http://mrhaki.blogspot.com/2014/11/gradle-goodness-check-task-dependencies.html

我们可以运行Gradle的任务,但是不实际执行动作。这就是所谓的模拟运行。我们可以通过模拟运行来查看,我们定义的任务依赖关系是否正确。因为,我们在进行模拟运行时,我们可以看到所有的任务和任务依赖关系输出日志。

例如,我们定义了一个简单的build文件,包含三个任务和一些任务依赖关系:

1
2
3
4
5
6
7
8
9
def printTaskNameAction = {
println "Running ${it.name}"
}
task first << printTaskNameAction
task second(dependsOn: first) << printTaskNameAction
task third(dependsOn: [first, second]) << printTaskNameAction

我们通过添加命令行选项 -m or –dry-run来进行模拟运行。因此,我们这样执行第三个任务:

1
2
3
4
5
6
7
8
9
$ gradle -m third
:first SKIPPED
:second SKIPPED
:third SKIPPED
BUILD SUCCESSFUL
Total time: 2.242 secs
$

我们在输出中可以看到,没有任何任务是实际被执行的,在输出日志中显示SKIPPED,但是我们可以看到所有被执行任务的名字。

作者:snowdream
Email:yanghui1986527#gmail.com
Github: https://github.com/snowdream
QQ 群: 529327615
原文地址:https://snowdream.github.io/blog/2016/10/26/gradle-goodness-changing-name-of/

翻译自: http://mrhaki.blogspot.com/2014/10/gradle-goodness-changing-name-of.html

Gradle默认使用build.gradle作为默认的配置文件文件名。如果我们在build.gradle文件中编写代码,那么我们在运行任务的时候,不需要指定build文件名。我们也可以不使用build.gradle,而用另外的文件名来创建build配置文件。例如:我们可以在一个名字为sample.gradle的文件中编写代码。为了运行这个文件中的任务,我们需要在命令行,使用选项 -b 或者 –build-file,后面指定build文件名。但是,我们可以改变项目设置,并且设置项目的默认build文件名。这样设置,我们不需要再使用前面的命令行选项。

假设我们现在有一个build文件,sample.gradle:

1
2
3
4
5
6
// File: sample.gradle
task sample(description: 'Sample task') << {
println 'Sample task'
}
defaultTasks 'sample'

为了运行sample任务,我们可以使用命令行选项 -b 或者 –build-file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ gradle -b sample.gradle
:sample
Sample task
BUILD SUCCESSFUL
Total time: 3.168 secs
$ gradle --build-file sample.gradle
:sample
Sample task
BUILD SUCCESSFUL
Total time: 2.148 secs
$

我们也可以改变项目默认的build文件名。首先,我们在项目根目录下创建项目设置文件settings.gradle。 在settings.gradle文件中,我们给rootProject修改属性值buildFileName

1
2
3
// File: settings.gradle
// Change default build file name for this project.
rootProject.buildFileName = 'sample.gradle'

现在,我们执行sample.gradle中的任务,不再需要使用命令行选项-b 或者 –build-file了。

1
2
3
4
5
6
7
8
$ gradle
:sample
Sample task
BUILD SUCCESSFUL
Total time: 3.312 secs
$

作者:snowdream
Email:yanghui1986527#gmail.com
Github: https://github.com/snowdream
QQ 群: 529327615
原文地址:https://snowdream.github.io/blog/2016/10/22/android-optimalize-series-log/

简介

在Android应用开发过程中,通过Log类输出日志是一种很重要的调试手段。
大家对于Log类的使用,一般会形成几点共识:

  1. 在Debug模式下打印日志,在Release模式下不打印日志
  2. 避免滥用Log类进行输出日志。因为这样可能造成日志刷屏,淹没真正有用的日志。
  3. 封装Log类,以提供同时输出日志到文件等功能

具体细化为以下几点建议:

  1. 禁用System.out.println
    Android应用中,一般通过封装过的Log类来输出日志,方便控制。而System.out.println是标准的Java输出方法,使用不当,可能造成Release模式下输出日志的结果。
  2. 禁用e.printStackTrace
    禁用理由同上
    建议通过封装过的Log类来输出异常堆栈信息

  3. Debug模式下,通过一个静态变量,控制日志的显示隐藏。
    我一般习惯直接使用BuildConfig.DEBUG,当然,你也可以自己定义一个。

1
2
3
4
5
6
7
private static final boolean isDebug = BuildConfig.DEBUG;
public static void i(String tag, String msg) {
if (isDebug) {
Log.i(tag, msg);
}
}

4.Release模式下,通过Proguard配置来移除日志
在Proguard配置文件中,确保没有添加 –dontoptimize选项 来禁用优化的前提下,
添加以下代码:

1
2
3
4
5
6
7
8
9
-assumenosideeffects class android.util.Log {
public static *** d(...);
public static *** e(...);
public static *** i(...);
public static *** v(...);
public static *** println(...);
public static *** w(...);
public static *** wtf(...);
}

那么,是否我们按照上面的做,就真的一劳永逸呢?
我的脑海中浮现出几个相关问题:

  1. Proguard配置中添加的配置,真的可以在Release模式下,移除日志吗?
  2. 如果我们用的是封装过的Log工具类,应该怎么配置?
  3. 移除日志后,原来在日志方法中的拼接字符串参数,是否还会申请/占用内存?

本着大胆假设,小心求证的原则,下面我们通过实践来探索上面的问题答案。

本文基于以下项目进行测试实践:
https://github.com/snowdream/test/tree/master/android/test/logtest
反编译工具:JD-GUI

验证Proguard配置清理日志的有效性

CASE

1
2
3
4
Log.i(TAG,"这样使用,得到的LOGTAG的值就是DroidSettings," +
"然而并非如此,当DroidSettings这个类进行了混淆之后,类名变成了类似a,b,c这样的名称," +
"LOGTAG则不再是DroidSettings这个值了。这样可能造成的问题就是,内部混淆有日志的包,我们去过滤DroidSettings " +
"却永远得不到任何信息。");

在添加上述Proguard配置前后,编译打包Release模式的正式包,使用JD-GUI进行反编译,对比上述代码的编译后代码。

结果

添加配置前
[case1-a]

添加配置后
[case1-b]

结论

通过比对结果,我们可以得出结论:
通过添加Proguard配置,可以在Release模式下,移除掉日志。

验证封装过的Log工具类,是否有必要进行而外配置

CASE

1
2
3
4
LogUtil.i(TAG,"这样使用,得到的LOGTAG的值就是DroidSettings," +
"然而并非如此,当DroidSettings这个类进行了混淆之后,类名变成了类似a,b,c这样的名称," +
"LOGTAG则不再是DroidSettings这个值了。这样可能造成的问题就是,内部混淆有日志的包,我们去过滤DroidSettings " +
"却永远得不到任何信息。");

在添加上述Proguard配置前后,编译打包Release模式的正式包,使用JD-GUI进行反编译,对比上述代码的编译后代码。

结果

添加配置前
[case2-a]

添加配置后
[case2-b]

结论

通过比对结果,我们可以得出结论:
在这种简单封装的情况下,我们不需要额外的配置,也可以将封装过的Log工具类调用日志一起移除。

当然,实际使用过程中,可能封装更复杂。为了保险起见,可以也添加上Log工具类的配置。示例如下:

1
2
3
4
5
6
7
-assumenosideeffects class com.github.snowdream.logtest.LogUtil {
public static *** d(...);
public static *** e(...);
public static *** i(...);
public static *** v(...);
public static *** w(...);
}

验证移除日志后,字符串拼接是否还存在?

CASE

1
2
3
4
5
6
7
8
9
Log.i(TAG,"这样使用,得到的LOGTAG的值就是DroidSettings," +
"然而并非如此,当DroidSettings这个类进行了混淆之后,类名变成了类似a,b,c这样的名称," +
"LOGTAG则不再是DroidSettings这个值了。这样可能造成的问题就是,内部混淆有日志的包,我们去过滤DroidSettings " +
"却永远得不到任何信息。");
Log.i(TAG, "这样使用,得到的LOGTAG的值就是DroidSettings," +
"然而并非如此,当DroidSettings这个类进行了混淆之后,类名变成了类似a,b,c这样的名称," +
"LOGTAG则不再是DroidSettings这个值了。这样可能造成的问题就是,内部混淆有日志的包,我们去过滤DroidSettings " +
"却永远得不到任何信息。" + index ++);

上面代码的区别是:
前面是简单的字符串相加,而后面是字符串和变量的相加
在添加上述Proguard配置的前提下,分别针对以上两段代码,编译打包Release模式的正式包,使用JD-GUI进行反编译,对比上述代码的编译后代码。

结果

简单字符串相加
[case1-b]

字符串和变量相加
[case3.png]

结论

通过比对结果,我们可以得出结论:
如果只是简单字符串相加,是会彻底移除的,并且字符串拼接也不见了,不会占用内存。
而如果是字符串和变量相加,日志会移除,但是字符串拼接还在,还会占用内存。

验证日志中使用函返回值的情况

CASE

1
2
3
4
5
6
7
8
9
10
11
LogUtil.i(TAG, getMessage());
LogUtil.i(TAG, "FROM FUNCTION " + getMessage());
private String getMessage() {
return "这样使用,得到的LOGTAG的值就是DroidSettings," +
"然而并非如此,当DroidSettings这个类进行了混淆之后,类名变成了类似a,b,c这样的名称," +
"LOGTAG则不再是DroidSettings这个值了。这样可能造成的问题就是,内部混淆有日志的包,我们去过滤DroidSettings " +
"却永远得不到任何信息。";
}

上面代码的区别是:
前面是直接使用函数返回值,而后面是字符串和函数返回值的相加
在添加上述Proguard配置的前提下,分别针对以上两段代码,编译打包Release模式的正式包,使用JD-GUI进行反编译,对比上述代码的编译后代码。

结果

直接使用函数返回值
[case1-b]

字符串和函数返回值相加
[case4.png]

结论

通过比对结果,我们可以得出结论:
以上两种场景下,日志移除,拼接字符串不在了,也不会占用内存。

经过大量实践后的结论

如果你以为上面就是全部真相的话,就错了。
经过大量的测试实践,实际上真相更复杂。

以下是开启Proguard前提下,各种情况下的测试结论:

  1. Log.i(简单字符串)
  2. Log.i(局部变量)
  3. Log.i(成员变量)
  4. Log.i(简单字符串+局部变量)
    以上四种情况,日志被彻底移除,不会额外增加内存。
  5. Log.i(简单字符串+成员变量)
    日志被移除,但是字符串拼接会存在,并占用内存。
  6. Log.i(成员函数) 其中,成员函数返回值为: 简单字符串
  7. Log.i(成员函数) 其中,成员函数返回值为: 简单字符串+局部变量
    以上两种情况,日志被彻底移除,不会额外增加内存。
  8. Log.i(成员函数) 其中,成员函数返回值为: 简单字符串+成员变量
    日志被移除,但是字符串拼接会存在,并占用内存。

注:以上所有情况,参数都是指第二个或者后面的参数。第一个参数,我都使用了静态成员变量:
private static final String TAG = MainActivity.class.getSimpleName();

优化建议

  1. 确保没有开启 –dontoptimize选项的前提下,添加Proguard优化日志配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    -assumenosideeffects class android.util.Log {
    public static *** d(...);
    public static *** e(...);
    public static *** i(...);
    public static *** v(...);
    public static *** println(...);
    public static *** w(...);
    public static *** wtf(...);
    }
  2. 针对这种情况“Log.i(成员函数) 其中,成员函数返回值为: 简单字符串+成员变量”
    目前并没有办法规避,不建议这么使用。

  3. 针对这种情况”Log.i(简单字符串+成员变量)”
    我们的解决方案是,在封装的Log工具类方法中,使用变长参数。
    下面是一个简单的示例:
    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
    48
    49
    50
    51
    52
    package com.github.snowdream.logtest;
    import android.text.TextUtils;
    import android.util.Log;
    /**
    * Created by snowdream on 16-10-22.
    */
    public class LogUtil {
    private static final boolean isDebug = BuildConfig.DEBUG;
    public static void i(String tag, String... args) {
    if (isDebug) {
    Log.i(tag, getLog(tag,args));
    }
    }
    public static void d(String tag, String... args) {
    if (isDebug) {
    Log.i(tag, getLog(tag,args));
    }
    }
    public static void v(String tag, String... args) {
    if (isDebug) {
    Log.i(tag, getLog(tag,args));
    }
    }
    public static void w(String tag, String... args) {
    if (isDebug) {
    Log.i(tag, getLog(tag,args));
    }
    }
    public static void e(String tag, String... args) {
    if (isDebug) {
    Log.i(tag, getLog(tag,args));
    }
    }
    private static String getLog(String tag, String... args){
    StringBuilder builder = new StringBuilder();
    for (String arg : args){
    if (TextUtils.isEmpty(arg)) continue;
    builder.append(arg);
    }
    return builder.toString();
    }
    }

参考

  1. 如何安全地打印日志
  2. 关于Android Log的一些思考
  3. Androrid应用打包release版时关闭log日志输出

作者:snowdream
Email:yanghui1986527#gmail.com
Github: https://github.com/snowdream
QQ 群: 529327615
原文地址:https://snowdream.github.io/blog/2016/10/20/mobile-app-develop-flow/

众所周知,移动应用一般都是按照预定周期进行迭代的,比如:一月发三版,两周发一版。

每一个迭代周期内,都会有产品,UI,研发,测试的逐步介入。

各种角色之间可能会有不同的依赖关系,他们并不能完全并行的工作。

本文主要从研发环节,以一个Android RD的视角,探索移动应用的开发流程。

名词解释

API

本文主要泛指服务端

SDK

本文主要泛指本地模块端。在模块化的背景下,移动应用被分隔成不同的模块。这些模块有可能是独立的,也有可能依赖于服务端(API)。

Client

本文主要泛指客户端

开发流程

串行开发

[串行开发流程图]

串行开发比较好理解,就是每个环节依次进行,每个环节都给出完整,可用的输出,提供给下一个环节,直至最后给测试。

优点

每个环节给出的输出都是经过充分测试,可用的成果。

缺点

每个环节要等上一个环节完成开发,之后,再进行开发。

结果往往是,下游环节的同学,在迭代初期,开发进程被阻塞,迭代后期,因为工期紧,而没日没夜的加班。

浪费时间和人力。

并行开发

[并行开发流程图]

并行开发就是API,SDK,Client各类开发人员约定接口/协议,同时进行开发。

至少分为两个阶段:

第一阶段:
上游环节和下游环节约定好接口/协议,并提供有接口但是无实现的依赖成果,输出给下游环节。

下游环节根据这些进行并行开发。

第二阶段:
上游环节完成接口/协议的实现,经过充分测试,输出完整的成果。

等待下游环节完成开发时,进行一次连接调试,确保业务流程完整,功能正常。

优点

各个环节约定好接口/协议之后,可以并行开发。

要求

这个需要各个环节的人员,进行及时,有效的沟通。

总结

根据之前的项目实践,我比较偏向于并行开发。

当然,对于移动应用开发流程的理解,大家见仁见智,欢迎讨论。

作者:snowdream
Email:yanghui1986527#gmail.com
Github: https://github.com/snowdream
QQ 群: 529327615
原文地址:https://snowdream.github.io/blog/2016/10/17/android-genymotion-install-and-settings/

注: 由snowdream收集整理

简介

Genymotion是一款基于x86架构的Android模拟器,由于系统启动速度,应用运行速度远远快于Android SDK自带模拟器而受到广泛应用。

优缺点

优点

  • 系统启动速度快
  • 应用运行速度快
  • 跨平台
  • IDE支持

缺点

  • 与真机相比,无法支持一些硬件相关的传感器特性等
  • 由于市场上大部分应用都是基于ARM架构来编译的,因此,与默认模拟器,真机相比,对于包含仅支持ARM架构的so的应用,默认不支持。

注:基于x86架构的模拟器/真机,兼容ARM指令有两个解决方案:

  1. 对于x86真机,x86处理器已经能够基本兼容ARM指令了。参考《涨姿势!x86处理器兼容ARM架构App的秘密》
  2. 对于Genymotion模拟器,则通过安装ARM_Translation_Android来进行兼容。

安装Genymotion

安装步骤

  1. 安装虚拟机VirtualBox

  2. 注册Genymotion帐号

  3. 登录,下载并安装Genymotion

安装指南

详细安装步骤,请参考以下文章:

  1. Installation
  2. Genymotion安装方法
[Genymotion效果图]

安装ARM_Translation_Android系列包

由于genymotion是基于x86的,而大部分应用都是基于ARM的,因此,我们需要安装一个ARM_Translation_Android系列包来增强兼容性。

安装步骤

  1. 点击下载ARM_Translation_Android系列包
  2. 将下载的zip包,拖进Genymotion模拟器窗口,按照提示安装
  3. 安装成功后,重启Genymotion模拟器即可。

安装指南

  1. Genymotion with Google Play Services
  2. Use ARM Translation on 5.x image
  3. Use ARM Translation on 6.x image

注:以上步骤,便可满足大部分的开发测试需求。以下的步骤,都是可选步骤。

下面是安装微信的效果

[wechat]

安装Google Apps

  1. 根据平台,android版本等选择不同的安装包,下载。
    http://opengapps.org/
    https://github.com/opengapps/opengapps
  2. 将下载的zip包,拖进Genymotion模拟器窗口,按照提示安装
  3. 安装成功后,重启Genymotion模拟器即可。

安装Xposed

  1. 根据平台,android版本等选择不同的安装包,下载。
    http://dl-xda.xposed.info/framework/
    其中,sdk21,sdk22,sdk23,分别对应Android 5.0,5.1, 6.0.
  2. 将下载的zip包,拖进Genymotion模拟器窗口,按照提示安装
  3. 安装成功后,重启Genymotion模拟器即可。
  4. 对于Android 5.0以上的手机,请前往XDA论坛主题贴下载附件 XposedInstaller_3.0_alpha4.apk,并安装。
    下载地址:http://forum.xda-developers.com/showthread.php?t=3034811
    如果你看到以下界面,恭喜你,Xposed Framework安装完成。
[xposed3]

参考

  1. Android模拟器Genymotion
  2. Genymotion安装方法
  3. 快到极致的 Android 模拟器Genymotion
  4. Genymotion那点事儿
  5. Xposed 官网
  6. Xposed XDA论坛
  7. [OFFICIAL] Xposed for Lollipop/Marshmallow [Android 5.0/5.1/6.0, v86, 2016/10/16]
  8. Xposed框架的安装
  9. Genymotion with Google Play Services
  10. Use ARM Translation on 5.x image
  11. Use ARM Translation on 6.x image

作者:snowdream
Email:yanghui1986527#gmail.com
Github: https://github.com/snowdream
QQ 群: 529327615
原文地址:https://snowdream.github.io/blog/2016/10/11/android-secret-codes/

注: 本文根据《Android-SecretCodes》翻译整理。

什么是暗码?

在android系统中,暗码就是类似这种样式的字符串: *#*#<code>#*#*

如果这样的系统暗码执行,系统会触发下面的方法:(来自 AOSP Android Open Source Project)

1
2
3
4
5
6
7
8
9
10
11
static private boolean handleSecretCode(Context context, String input) {
int len = input.length();
if (len > 8 && input.startsWith("*#*#") && input.endsWith("#*#*")) {
Intent intent = new Intent(TelephonyIntents.SECRET_CODE_ACTION,
Uri.parse("android_secret_code://" + input.substring(4, len - 4)));
context.sendBroadcast(intent);
return true;
}
return false;
}

如何运行暗码?

有两种方式可以执行运行暗码:

直接在手机电话拨号界面输入暗码,例如:*#*#123456789#*#*

或者直接在代码中进行调用。

1
2
3
4
String secretCode = "123456789";
Intent intent = new Intent(Intent.ACTION_DIAL);
intent.setData(Uri.parse("tel:*#*#" + secretCode + "#*#*"));
startActivity(intent);
1
2
3
4
5
String secretCode = "123456789";
String action = "android.provider.Telephony.SECRET_CODE";
Uri uri = Uri.parse("android_secret_code://" + secretCode);
Intent intent = new Intent(action, uri);
sendBroadcast(intent);

如何创建自定义暗码?

在你的应用AndroidManifest.xml文件中,增加以下代码,用来定义手机暗码。

不管什么时候暗码 *#*#123456789#*#* 被触发,你都将收到对应的广播。

1
2
3
4
5
6
<receiver android:name=".MySecretCodeReceiver">
<intent-filter>
<action android:name="android.provider.Telephony.SECRET_CODE" />
<data android:scheme="android_secret_code" android:host="123456789" />
</intent-filter>
</receiver>

参考

  1. Android Secret Codes
  2. 话说android系统之暗码

作者:snowdream
Email:yanghui1986527#gmail.com
Github: https://github.com/snowdream
QQ 群: 529327615
原文地址:https://snowdream.github.io/blog/2016/09/02/android-develop-xposed-module/

注: 根据Development tutorial 整理完成

创建Android项目

如果准备从零开始创建Xposed模块,首先应该创建一个Android应用工程。

引入 Xposed Framework API

app/build.gradle文件中声明Xposed Framework API 的jar包依赖。

1
2
3
4
5
6
7
8
9
repositories {
jcenter();
}
dependencies {
provided 'de.robv.android.xposed:api:82'
//如果需要引入文档,方便查看的话
provided 'de.robv.android.xposed:api:82:sources'
}

说明:

  1. 请留意,这个82是Xposed Framework API的版本号,叫做xposedminversion。
  2. xposedminversion可以在这里进行查询:
    https://bintray.com/rovo89/de.robv.android.xposed/api
  3. Xposed Framework API文档请参考:http://api.xposed.info/reference/packages.html

修改AndroidManifest.xml

在AndroidManifest.xml文件中添加以下代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="de.robv.android.xposed.mods.tutorial"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk android:minSdkVersion="15" />
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name" >
<meta-data
android:name="xposedmodule"
android:value="true" />
<meta-data
android:name="xposeddescription"
android:value="Easy example which makes the status bar clock red and adds a smiley" />
<meta-data
android:name="xposedminversion"
android:value="53" />
</application>
</manifest>

说明:

  1. xposedmodule: 一般设置为true,表示这是一个xposed模块
  2. xposeddescription: 一句话描述该模块的用途,可以引用string.xml中的字符串
  3. xposedminversion: 没错,这个就是上面提到的xposedminversion。我理解为要求支持的Xposed Framework最低版本。

模块实现

创建一个或者几个类,并实现IXposedHookLoadPackage,IXposedHookZygoteInit或者其他IXposedMod的子接口。

1
2
3
4
5
6
7
8
9
10
11
package de.robv.android.xposed.mods.tutorial;
import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam;
public class Tutorial implements IXposedHookLoadPackage {
public void handleLoadPackage(final LoadPackageParam lpparam) throws Throwable {
XposedBridge.log("Loaded app: " + lpparam.packageName);
}
}

注: XposedBridge.log会将日志输出到logcat,并写入日志文件”/data/data/de.robv.android.xposed.installer/log/debug.log”.

好了,现在可以开始Hook了。
大部分的Hook工作,主要通过XposedHelpers类的一些辅助函数来实现。比如:findAndHookMethod

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package de.robv.android.xposed.mods.tutorial;
import static de.robv.android.xposed.XposedHelpers.findAndHookMethod;
import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam;
public class Tutorial implements IXposedHookLoadPackage {
public void handleLoadPackage(final LoadPackageParam lpparam) throws Throwable {
if (!lpparam.packageName.equals("com.android.systemui"))
return;
findAndHookMethod("com.android.systemui.statusbar.policy.Clock", lpparam.classLoader, "updateClock", new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
// this will be called before the clock was updated by the original method
}
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
// this will be called after the clock was updated by the original method
}
});
}
}

注:根据名称不难发现,beforeHookedMethod/afterHookedMethod会在被Hook函数之前/之后执行。

关于使用Xposed来进行Hook的更多知识,这里就不展开了。大家可以参考以下两篇文章:

  1. Helpers
  2. android-hook框架xposed篇

声明实现

在assets目录下创建一个空文件,命名为xposed_init。
在这个文件中,每一行记录一个类的完整路径,来声明实现类。
在这里,我们声明 “de.robv.android.xposed.mods.tutorial.Tutorial”

好了,到这里,一个简单的Xposed模块应用项目就构建好了。

模块安装与使用

  1. 将这个工程,编译,打包,安装到已经支持Xposed的手机中。
  2. 打开Xposed Installer应用,切换到模块界面,你可以看到你开发的Xposed模块。
  3. 通过勾选/取消,来启用/禁用模块。然后,重启手机,进行生效。

模块发布

Xposed模块开发完成后,你可以按照以下步骤发布分享。

  1. 你首先需要一个XDA论坛帐号。如果没有,请前往论坛注册:
    http://forum.xda-developers.com/
  2. 使用XDA论坛帐号,登录xposed官网,按照操作提示进行发布。
    http://repo.xposed.info/

Xposed模块开发优势和不足

优势

  1. 功能强大,既可以修改系统应用,也可以修改其他应用。hook android,hook everything.
  2. 使用灵活,既可以针对一款应用进行Hook,也可以针对所有应用进行Hook。

不足

  1. 无法调试。只能通过打印日志进行跟踪。(例如:XposedBridge.log)
  2. 无法即时生效。启用/禁用模块,你需要重启手机。
  3. multidex支持不足。详见Multidex support

Xposed模块开发实例

Xposed-Keylogger的基础上,我稍作修改,制作了一个Xposed模块开发实例。
这个模块的作用就是监听键盘按键,记录所有你设置到EditText控件的字符串。

建议

Xposed是如此的强大,因此,建议重视手机安全的用户,坚决不要root,不要安装xposed,发烧友,土豪随意。

参考

  1. Xposed 官网
  2. Xposed XDA论坛
  3. Development tutorial
  4. Helpers
  5. Replacing resources
  6. Using the Xposed Framework API
  7. DingDingUnrecalled
  8. FakeXX
  9. WechatLuckyMoney
  10. Xposed Framework API
  11. Xposed Framework API in bintray
  12. android-hook框架xposed篇
  13. JustTrustMe

作者:snowdream
Email:yanghui1986527#gmail.com
Github: https://github.com/snowdream
QQ 群: 529327615
原文地址:https://snowdream.github.io/blog/2016/09/02/android-install-xposed-framework/

简介

提到Xposed框架时,人们总会用到一个词“神器”。
是的,安装Xposed后,我们似乎脑洞大开,以前不能干的事件,现在都能干了。
对此,我的理解是:hook android,hook everything

Xposed框架是什么???

官方对此的解释是这样的:
“Xposed是一个适用于Android的框架。基于这个框架开发的模块可以改变系统和app应用的行为,而不需要修改APK。这是一个很棒的特性,意味着Xposed模块可以不经过任何修改,安装在各种不同的ROM上。Xposed模块可以很容易的开启和关闭。你只需要激活或者禁用Xposed模块,然后重启手机即可。”

[xposed1]

在手机发烧友的眼中,Xposed是这样子的:

修改手机主题,权限控制,阻止广告,禁用各种APP滥用权限,微信,游戏等相关的各种外挂…

在开发者的眼中,Xposed是这样子的:

渗透测试,测试数据构造,环境监控,动态埋点,热补丁,自动化录制…

关于Xposed框架的基本原理以及更多介绍,请参考文末链接,或者自行百度。

风险声明

[xposed2]

在安装Xposed框架之前,我必须把风险告诉你:

  1. 软变砖
  2. 无限重启

简单解释下:

  1. 软砖: 手机能启动,但是进不去桌面
  2. 硬砖/黑砖: 手机在按电源键,或者连接电脑没反应,一直黑屏。
  3. 软砖可以救 硬砖只能修。
  4. 无限重启: 就是手机快要进入桌面的时候,又自动重启。周而复始,无限重启。

根据官方的警示和网友的反馈, 三星的手机,以及索尼,戴尔的部分手机 容易导致以上风险。

安装

Xposed框架的安装需要经过root,安装第三方Recovery,安装Xposed框架,安装Xposed Installer等几个步骤。这些步骤都是依次进行的,任何步骤的失败,都会导致Xposed框架的安装过程中止。

因此,建议在 国际国内的主流Android机型 上进行安装。

Root

根据我的个人实践,这里我推荐使用 KingRoot 这款工具进行Root。

官方网址: https://kingroot.net/?myLocale=zh_CN

Root之前,我建议你查询下,你的机型是否被支持: https://kingroot.net/model

TWRP

对于Android 5.0以上的手机,官方提示,必须要先刷入第三方Recovery, 比如: TWRP

官方网址: https://twrp.me/

刷机之前,请先查询下,你的机型是否被支持:
https://twrp.me/Devices/

以Nexus 5 为例, 网站有详细的操作指南。https://twrp.me/devices/lgnexus5.html

当然有些非主流手机,也可以在相关论坛找到TWRP的修改版本。

比如我的手机,中兴 Blade A1(C880U) 16G 灵动白 移动4G手机 双卡双待

我就是参考:中兴小鲜3中兴Blade a1移动版全网通版本TWRP刷写教程@root

按照 TWRP for ZTE Blade Apex 2 强行刷入的。

刷机完成后,重启可以进入Recovery界面。

Xposed Framework

下载

Xposed Framework下载地址:http://dl-xda.xposed.info/framework/

其中,sdk21,sdk22,sdk23,分别对应Android 5.0,5.1, 6.0.
根据,手机ROM版本和处理器类型选择Xposed Framework刷机包。

比如,中兴Blade a1移动版(5.1, arm64),我选择了刷机包xposed-v86-sdk22-arm64.zip 和卸载包xposed-uninstaller-20150831-arm64.zip

下载之后,将这两个压缩包,拷贝到SD卡根目录下。

安装

  1. 重启手机,进入Recovery界面。(adb reboot recovery)
  2. 选择【安装刷机包】进入下级页面,选择【从SD卡选择ZIP文件】
  3. 在SD卡根目录找到Xposed Framework刷机包(xposed-v86-sdk22-arm64.zip),并选择。
  4. 滑动底部的滑动条,确认刷入,等待提示刷机完成。
  5. 重启手机,等待进入桌面。

卸载

如果刷入Xposed Framework刷机包之后,无限重启,进不去桌面怎么办?
那就按照下面提示,卸载掉Xposed Framework。

  1. 重启手机,进入Recovery界面。(adb reboot recovery)
  2. 选择【安装刷机包】进入下级页面,选择【从SD卡选择ZIP文件】
  3. 在SD卡根目录找到Xposed Framework卸载刷机包(xposed-uninstaller-20150831-arm64.zip),并选择。
  4. 滑动底部的滑动条,确认刷入,等待提示刷机完成。
  5. 重启手机,等待进入桌面。

Xposed Installer

这是一个管理Xposed模块的官方应用。通过它,你可以随时禁用或者启用Xposed模块,然后重启手机。

对于Android 5.0以上的手机,请前往XDA论坛主题贴下载附件 XposedInstaller_3.0_alpha4.apk,并安装。

下载地址:http://forum.xda-developers.com/showthread.php?t=3034811

如果你看到以下界面,恭喜你,Xposed Framework安装完成。

[xposed3]

FAQ

  1. Xposed FAQ / Known issues
  2. Xposed in zhihu
  3. Xposed in Stackoverflow

参考

  1. Xposed 官网
  2. Xposed XDA论坛
  3. [OFFICIAL] Xposed for Lollipop/Marshmallow [Android 5.0/5.1/6.0, v86, 2016/07/08]
  4. Xposed framework 作者rovo89 原文(xda)介绍大译
  5. Xposed:不得不说的 Android 神器
  6. Android 系统上的 Xposed 框架中都有哪些值得推荐的模块?
  7. xposed模块整理
  8. 基于Xposed修改微信运动步数
  9. 用黑客思维做测试——神器 Xposed 框架介绍
  10. 安卓注入框架Xposed分析与简单应用
  11. Xposed框架初体验

Fork me on GitHub