四、后记
最后更新于:2022-04-01 03:09:19
## 四、后记
我们终于完成了用一行代码写反射,避免了很多无意义的模板式代码。需要再次说明的是,本文是依据[jOOR](https://github.com/jOOQ/jOOR) 进行编写的,[这里](https://github.com/tianzhijiexian/Android-Best-Practices/blob/master/2015.9/reflect/README%20-%20chinese.md)有原项目readme的中文翻译。
jOOR是我无意中遇到的开源库,第一次见到它时我就知道这个是我想要的,因为那时候我被反射搞的很乱,而它简洁的编码方式给我带来了新的思考,大大提高了代码可读性。顺便一说,作者人比较好(就是死活不愿意让我放入中文的readme),技术也很不错。该项目有很详细的测试用例,并且还给出了几个类似的反射调用封装库。可见作者在写库时做了大量的调研和测试工作,让我们可以放心的运用该库*(其实就两个类)*。
本文希望带给大家一个反射的新思路,给出一个最简单实用的反射写法,希望能被大家迅速运用到实践中去。更加重要的是,通过对jOOR的分析,让我知道了写库前应该调研类似的库,而不是完全的创造新轮子,调研和测试是代码稳定性的重要保障。
## 参考自
[http://www.cnblogs.com/tianzhijiexian/p/3906774.html](http://www.cnblogs.com/tianzhijiexian/p/3906774.html)
[https://github.com/tianzhijiexian/HttpAnnotation/blob/master/lib/src/main/java/kale/net/http/util/HttpReqAdapter.java](https://github.com/tianzhijiexian/HttpAnnotation/blob/master/lib/src/main/java/kale/net/http/util/HttpReqAdapter.java)
三、解决方案
最后更新于:2022-04-01 03:09:17
## 三、解决方案
### 3.1 一行代码写反射
作为一个Android程序员,索性就拿`TextView`这个类开刀吧。首先定义一个类变量:
~~~
TextView mTv;
~~~
**通过反射得到实例:**
~~~
// 有参数,建立类
mTv = Reflect.on(TextView.class).create(this).get();
// 通过类全名得到类
String word = Reflect.on("java.lang.String").create("Reflect TextView").get();
// 无参数,建立类
Fragment fragment = Reflect.on(Fragment.class).create().get();
~~~
**通过反射调用方法:**
~~~
// 调用无参数方法
L.d("call getText() : " + Reflect.on(mTv).call("getText").toString());
// 调用有参数方法
Reflect.on(mTv).call("setTextColor", 0xffff0000);
~~~
**通过反射get、set类变量**
TextView中有个mText变量,来看看我们怎么接近它。
~~~
// 设置参数
Reflect.on(mTv).set("mText", "---------- new Reflect TextView ----------");
// 获得参数
L.d("setgetParam is " + Reflect.on(mTv).get("mText"));
~~~
### 3.2 什么时候该用反射,什么时候不用反射
又到了这样权衡利弊的时候了,首先我们明确,在日常开发中尽量不要用反射,除非遇到了必须要通过反射才能调用的方法。比如我在做一个下拉通知中心功能的时候就遇到了这样的情况。系统没有提供api,所以我们只能通过反射进行调用,所以我自己写了这样一段代码:
~~~
<uses-permission android:name="android.permission.EXPAND_STATUS_BAR"/>
~~~
~~~
private static void doInStatusBar(Context mContext, String methodName) {
try {
Object service = mContext.getSystemService("statusbar");
Method expand = service.getClass().getMethod(methodName);
expand.invoke(service);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 显示消息中心
*/
public static void openStatusBar(Context mContext) {
// 判断系统版本号
String methodName = (VERSION.SDK_INT <= 16) ? "expand" : "expandNotificationsPanel";
doInStatusBar(mContext, methodName);
}
/**
* 关闭消息中心
*/
public static void closeStatusBar(Context mContext) {
// 判断系统版本号
String methodName = (VERSION.SDK_INT <= 16) ? "collapse" : "collapsePanels";
doInStatusBar(mContext, methodName);
}
~~~
先来看看利用jOOR写的`doInStatusBar`方法会简洁到什么程度:
~~~
private static void doInStatusBar(Context mContext, String methodName) {
Object service = mContext.getSystemService("statusbar");
Reflect.on(service).call(methodName);
}
~~~
哇,就一行代码啊,很爽吧~
爽完了,我们就来看看反射问题吧。因为不是系统给出的api,所以谷歌在不同的版本上用了不同的方法名来做处理,用反射的话我们就必须进行版本的判断,这是需要注意的,此外反射在性能方面确实不好,这里需要谨慎。
我的建议:
如果一个类中有很多地方都是private的,而你的需求都需要依赖这些方法或者变量,那么比起用反射,推荐把这个类复制出来,变成自己的类,像是toolbar这样的类就可以进行这样的操作。
在自己写框架的时候,我们肯定会用到反射,很简单的例子就是事件总线和注解框架,翔哥就说过一句话:**无反射,无框架**。也正因为是自己写的框架,所以通过反射调用的方法名和参数一般不会变,更何况做运行时注解框架的话,反射肯定会出现。在这种情况下千万不要害怕反射,索性放心大胆的做。因为它会让你完成很多不可能完成的任务。
总结下来就是:
实际进行日常开发的时候尽量少用反射,可以通过复制原始类的形式来避免反射。在写框架时,不避讳反射,在关键时利用反射来助自己一臂之力。
二、分析
最后更新于:2022-04-01 03:09:15
## 二、分析
当我们接到上面需求后,我长舒一口气,因为这回的需求还比较简单。
我相信有人会说:“反射就那几个api,一直没变过,你就不会自己去查啊,觉得麻烦就别写代码啊,不知道反射的内部细节你怎么去提高呢?”
其实不然,重复写麻烦的代码和提高自己的代码能力是完全无关的,我实在不知道我们写了成千上万行的`findViewById`后除了知道类要和xml文件绑定外,还学到了什么。
那么这回我们继续来挑战传统思维和模板式代码,来看看新一代的反射代码应该怎么写,如何用一行代码来反射出类。
在做之前,来看看我们一般用反射来干嘛?
~~~
1\. 反射构建出无法直接访问的类
2\. set或get到无法访问的类变量
3\. 调用不可访问的方法
~~~
一、需求
最后更新于:2022-04-01 03:09:12
## 一、需求
今天一个“懒”程序员突然跑过来说:“反射好麻烦,我要提点需求。”
听到这句话后我就知道,今天一定不好过了,奇葩需求又来了。
我们之前写反射都是要这么写:
~~~
public static <T> T create(HttpRequest httpRequest) {
Object httpRequestEntity = null;
try {
Class<T> httpRequestEntityCls = (Class<T>) Class.forName(HttpProcessor.PACKAGE_NAME + "." + HttpProcessor.CLASS_NAME);
Constructor con = httpRequestEntityCls.getConstructor(HttpRequest.class);
httpRequestEntity = con.newInstance(httpRequest);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return (T) httpRequestEntity;
}
~~~
因为反射在开发中很少用(做普通的业务开发的话),仅仅在自己写一些框架和注解框架时会用到,所以对api总是不熟悉。每次用到api都要去网上查,查了后又得自己实验下,很不爽。更差劲的是这样写法可读性十分低下。我不希望这样写反射,我希望反射能像
~~~
String str = new String();
~~~
这样简单,**一行代码搞定!**。
此外,有很多人都说反射影响性能,在开发的时候要避免用反射。那么什么时候该用反射,什么时候不用反射呢?用什么方式来避免反射呢?如果不明白**什么时候用反射**,就很难将反射活学活用了。
Java反射最佳实践
最后更新于:2022-04-01 03:09:10
原文出处:[https://github.com/tianzhijiexian/Android-Best-Practices/blob/master/2015.9/reflect/reflect.md](https://github.com/tianzhijiexian/Android-Best-Practices/blob/master/2015.9/reflect/reflect.md)
**概要:最简单优雅的使用反射。**
本文的例子都可以在示例代码中看到并下载,如果喜欢请star,如果觉得有纰漏请提交issue,如果你有更好的点子可以提交pull request。本文的示例代码主要是基于[jOOR](https://github.com/jOOQ/jOOR)行编写的,如果想了解更多请查看[这里](https://github.com/jOOQ/jOOR/tree/master/jOOR/src/test/java/org/joor/test)的测试代码。
参考资料
最后更新于:2022-04-01 03:09:08
## 参考自
[http://ihongqiqu.com/blog/2014/10/16/android-log/](http://ihongqiqu.com/blog/2014/10/16/android-log/)
[https://github.com/pengwei1024/LogUtils](https://github.com/pengwei1024/LogUtils)
[https://github.com/orhanobut/logger](https://github.com/orhanobut/logger)
## 作者
[![Jack Tony](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-09-11_55f22cd539622.jpg%3Fraw%3Dtrue)](https://github.com/tianzhijiexian/Android-Best-Practices/blob/master/2015.8/log/avatar.jpg?raw=true)
[developer_kale@.com](mailto:developer_kale@.com)
@天之界线2010
后记
最后更新于:2022-04-01 03:09:05
## 后记
我们可以看到即使一个最简单的log都有很多点是可优化的,而且看到了我们之前一直写的模板式代码是多么枯燥乏味。通过这篇文章,大家可以看到一个优化的过程,相信大家都会喜欢最终的简单、美观、方便的log类去调试应用。当然,我知道还是有很多人不喜欢,那么不妨提出更好的解决方案来一起讨论。宁信书则不如无书,我在实际过程中会将L和Log混合使用。我在调试那些会重复调用的方法的时候(比如for循环,onScroll),会利用原生的log,因为log量少,并且可以很方便的进行上下的比对,在调试其他信息的时候会用L的方式,因为更加直观,可以很方便的从系统日志中快速找到我们的log信息。
同学们,相信我们的最终目的是一致的,那就是让开发越来越简便,越来越优雅~
最终结果
最后更新于:2022-04-01 03:09:03
## 最终结果
首先建立一个activity,在里面输出各种类型的数据。为了测试Inner class和Object的效果,我专门建立了一个很简单的内部类User:
~~~
class User {
private String name;
private String sex;
User(String name, String sex) {
this.name = name;
this.sex = sex;
}
public void log() {
show();
}
private void show() {
L.d("user");
}
}
~~~
激动人心的测试开始了:
~~~
// string
String str = fromIntent("key");
L.d(str != null ? str : "hello world");
// json
L.json("[{\"CityId\":18,\"CityName\":\"西安\",\"ProvinceId\":27,\"CityOrder\":1},{\"CityId\":53,\"CityName\":\"广州\",\"ProvinceId\":27,\"CityOrder\":1}]'");
// object
L.Object(new User("jack", "f"));
// list
L.Object(TestUtil.getLongStringList(this));
// array
L.Object(TestUtil.getShortStringArr());
// arrays
double[][] doubles = {
{1.2, 1.6, 1.7, 30, 33},
{1.2, 1.6, 1.7, 30, 33},
{1.2, 1.6, 1.7, 30, 33},
{1.2, 1.6, 1.7, 30, 33}
};
L.Object(doubles);
// sub class
new User("name", "sex").log();
~~~
结果如下:
简单的string类型: [![此处输入图片的描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-09-11_55f22c958348e.png%3Fraw%3Dtrue)](https://github.com/tianzhijiexian/Android-Best-Practices/blob/master/2015.8/log/string.png?raw=true)
Json字符串:
[![此处输入图片的描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-09-11_55f22c9763758.png%3Fraw%3Dtrue)](https://github.com/tianzhijiexian/Android-Best-Practices/blob/master/2015.8/log/json.png?raw=true)
Object对象:
[![此处输入图片的描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-09-11_55f22c998826d.png%3Fraw%3Dtrue)](https://github.com/tianzhijiexian/Android-Best-Practices/blob/master/2015.8/log/pojo.png?raw=true)
数组类型:
[![此处输入图片的描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-09-11_55f22c9b621c9.png%3Fraw%3Dtrue)](https://github.com/tianzhijiexian/Android-Best-Practices/blob/master/2015.8/log/array.png?raw=true)
[![此处输入图片的描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-09-11_55f22c9d22cb4.png%3Fraw%3Dtrue)](https://github.com/tianzhijiexian/Android-Best-Practices/blob/master/2015.8/log/arrayes.png?raw=true)
内部类:
[![此处输入图片的描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-09-11_55f22c9f60a40.png%3Fraw%3Dtrue)](https://github.com/tianzhijiexian/Android-Best-Practices/blob/master/2015.8/log/innerCls.png?raw=true)
解决方案
最后更新于:2022-04-01 03:09:01
## 解决方案
### 2.1 消灭TAG
我们用TAG就是做定位,同时方便过滤无意义的log。那么索性把当前类名作为这样一个TAG的标识。于是,在我们自定义的log类中就用如下代码设置tag:
~~~
/**
* @return 当前的类名(simpleName)
*/
private static String getClassName() {
String result;
StackTraceElement thisMethodStack = (new Exception()).getStackTrace()[2];
result = thisMethodStack.getClassName();
int lastIndex = result.lastIndexOf(".");
result = result.substring(lastIndex + 1, result.length());
return result;
}
~~~
这样我们就轻易的摆脱了tag的纠缠。
> 这个方法来自于豪哥的建议,这里感谢豪哥的意见。
### 2.2 将Log简化
有人说我们IDE不都有代码提示了么,为啥还用一个L来做简化。首先用L比log能更快的得到提示,输入一个l.d就会直接显示提示,并且不会和原本的log类混淆。其次就是调用更方便。简化log这个东西太简单了,直接自定义一个L类,用作Log的输出即可。
### 2.3 在终端能显示当前类名并且增加超链
这个功能其实ide是支持的,只不过我们可以通过一些神奇的方法来做到更好的效果。下面就给出两个可行的方法:
~~~
/**
* @return 当前的类名(全名)
*/
private static String getClassName() {
String result;
StackTraceElement thisMethodStack = (new Exception()).getStackTrace()[1];
result = thisMethodStack.getClassName();
return result;
}
/**
* log这个方法就可以显示超链
*/
private static String callMethodAndLine() {
String result = "at ";
StackTraceElement thisMethodStack = (new Exception()).getStackTrace()[1];
result += thisMethodStack.getClassName()+ ".";
result += thisMethodStack.getMethodName();
result += "(" + thisMethodStack.getFileName();
result += ":" + thisMethodStack.getLineNumber() + ") ";
return result;
}
~~~
### 2.4 让log更加美观
人们对美的追求真是无止境,更加美丽的log也能方便我们一下子区分什么是系统打印的,什么是我们自己应用打印的。做到这点也比较简单,就是在输出前做点字符串拼接的工作。
~~~
private static final char TOP_LEFT_CORNER = '╔';
private static final char BOTTOM_LEFT_CORNER = '╚';
private static final char MIDDLE_CORNER = '╟';
private static final char HORIZONTAL_DOUBLE_LINE = '║';
private static final String DOUBLE_DIVIDER = "════════════════════════════════════════════";
private static final String SINGLE_DIVIDER = "────────────────────────────────────────────";
private static final String TOP_BORDER = "╔════════════════════════════════════════════════════════════════════════════════════════";
private static final String BOTTOM_BORDER = "╚════════════════════════════════════════════════════════════════════════════════════════";
private static final String MIDDLE_BORDER = "╟────────────────────────────────────────────────────────────────────────────────────────";
private static String TAG = "PRETTYLOGGER";
~~~
因为打印log也是消耗性能的,所以我建议最多只保留出现某些异常(这些异常轻于Exception)时打印的log,在调试时打印的log在提交代码前请全部清除。
### 2.5 让log支持输出object、map、list、array、jsonStr等对象
这个需求实现起来也比较容易,如果是简单的POJO的对象,我们用反射得到对象的类变量,通过字符串拼接的方式最终输出值。如果是map等数组结构,那么就用其内部的遍历依次输出值和内容。如果是json的字符串,就需要判断json的`{}`,`[]`这样的特殊字符进行换行处理。
### 2.6 增加log自动化和强制开关
区分release和debug版本有系统自带的BuildConfig.DEBUG变量,用这个就可以控制是否显示log了。强制开关也很简单,在log初始化的最后判断强制开关是否打开,如果打开那么就覆盖之前的显示设置,直接显示log。转为代码就是这样:
~~~
public class BaseApplication extends Application {
// 定义是否是强制显示log的模式
protected static final boolean LOG = false;
@Override
public void onCreate() {
super.onCreate();
L.init() // default PRETTYLOGGER or use just init()
//.setMethodCount(2); // default 2
//.hideThreadInfo() // default shown
.setMethodOffset(1); // default 0
// 在debug下,才显示log
L.init().setLogLevel(BuildConfig.DEBUG ? LogLevel.FULL : LogLevel.NONE);
// 如果是强制显示log,那么无论在什么模式下都显示log
if (BaseApplication.LOG) {
L.init().setLogLevel(LogLevel.FULL);
}
}
}
~~~
分析
最后更新于:2022-04-01 03:08:58
## 分析
当我们接到了上面需求后就会觉得,产品经理真是恐怖的生物。我们现在需要为这样一个人提供一个很好的API,以满足他这些杂乱的需求。但这个需求不合理么,很合理,我们的宗旨就是让无意义的重复代码去死,如果死不掉就交给机器来做。我们应该做那些真正需要我们做的事情,而不是像一个没思想的猿猴一般整天写模板式代码。
需求
最后更新于:2022-04-01 03:08:56
## 需求
我们都知道android中log是这么写的:
~~~
Log.d(TAG, "This is a debug log");
~~~
我们在调试的时候经常会输出这行代码,这个方法有两个参数,一个是TAG,一个是真正要输出的内容。我们不愿意每次花费时间去思考应该定义什么**TAG**,我们希望有一个东西可以帮我们定义好TAG,我们只需要写正真有意义的内容就行。这才是程序员思维,而不是程序猿思维。其实就是这样一个东西:
~~~
Log.d("This is a debug log");
~~~
如果我们更“懒”*(懒在这里是褒义词)*,可能会希望这样写:
~~~
L.d("This is a debug log");
~~~
等等!是不是还能更进一步,我希望终端输出的log更加**美观**,并且输出的地方是可以有一个**超链接**,这样我们直接点击输出内容的超链就可以直接定位到具体的代码中了。
~~~
D/LoggerActivity﹕ ╔══════════════════════════════════════════════════════════
D/LoggerActivity﹕ ║ Thread: main
D/LoggerActivity﹕ ╟──────────────────────────────────────────────────────────
D/LoggerActivity﹕ ║ BaseActivity.onCreate (BaseActivity.java:32)
D/LoggerActivity﹕ ║ LoggerActivity.setViews (LoggerActivity.java:26)
D/LoggerActivity﹕ ╟──────────────────────────────────────────────────────────
D/LoggerActivity﹕ ║ This is a debug log
D/LoggerActivity﹕ ╚══════════════════════════════════════════════════════════
~~~
对了,我还希望log能帮我们优雅的打印出map、list、json、array等**Object对象**,而不用我们去自己拼接。
~~~
D/LoggerActivity﹕ ╔══════════════════════════════════════════════════════════
D/LoggerActivity﹕ ║ Thread: main
D/LoggerActivity﹕ ╟──────────────────────────────────────────────────────────
D/LoggerActivity﹕ ║ BaseActivity.onCreate (BaseActivity.java:32)
D/LoggerActivity﹕ ║ LoggerActivity.setViews (LoggerActivity.java:38)
D/LoggerActivity﹕ ╟──────────────────────────────────────────────────────────
D/LoggerActivity﹕ ║ String[5] {
D/LoggerActivity﹕ ║ [android, ios, wp, linux, window]
D/LoggerActivity﹕ ║ }
D/LoggerActivity﹕ ╚══════════════════════════════════════════════════════════
~~~
哦,还有。我不希望在release包中出现log,但懒得自己整天设置log的**开关**,能不能自动化。但有时候我们一些东西还必须在release包中调试,比如需要签名的微信登录SDK。我希望能加个**强制显示log的开关**,一旦开启无论如何都显示log信息。
~~~
// 定义是否是强制显示log的模式
protected static final boolean LOG = false;
~~~
Log最佳实践
最后更新于:2022-04-01 03:08:54
> 原文出处:[https://github.com/tianzhijiexian/Android-Best-Practices/blob/master/2015.8/log/log.md](https://github.com/tianzhijiexian/Android-Best-Practices/blob/master/2015.8/log/log.md)
**概要:如何使用更好的log来调试应用。**
本文的例子都可以在示例代码中看到并下载,如果喜欢请star,如果觉得有纰漏请提交issue,如果你有更好的点子可以提交pull request。本文的示例代码主要是基于[Logger](https://github.com/orhanobut/logger)和[LogUtils](https://github.com/pengwei1024/LogUtils)进行编写的,如果想了解更多请查看他们的详细解释。
前言
最后更新于:2022-04-01 03:08:52
> 原文出处:https://github.com/tianzhijiexian/Android-Best-Practices
本篇内容不断更新中~
## 初衷
起笔写这个项目的原因很简单。面对世面上众多的第三方库,我们很难在短时间内知道什么是最好的。开源平台虽百家争鸣,但落实到开发者的项目中也免不了成王败寇的结局。Android的很多效果已经被人实现了一次又一次,我希望带给大家一些市面上最好的开源库或者解决方案,让大家能快速找到质量较好的第三方库。
我更希望的是,一个初创公司的开发者能在看完这系列文章后,善用轮子,这样能在保证项目开发速度的前提下,还能有点代码质量。当然了,我一个人的力量和知识是有限的,所以我把它放在github上面。希望大家能参与进来,推荐优质的第三方库或者解决方案。
## Developer
[![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-09-11_55f22b751c898.jpg)](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-09-11_55f22b751c898.jpg)
Jack Tony: [developer_kale@.com](mailto:developer_kale@.com)
## License
~~~
Copyright 2015 Jack Tony
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
~~~