編輯:關於Android編程
在開發領域有一句很流行的話就是不要重復造輪子, 因為我們在開發中用到的很多東西早已有很多人去實現了, 而且這些實現都是經過時間和開發者檢驗過的, 一般不會遇到什麼坑, 而如果我們自己去實現的話, 那不僅會增加工作量, 最大的隱患還是我們並不能預見以後是否會遇到大坑. 不過大家注意了嗎. 上面
不要重復造輪子的一個前提是開發中, 是的, 這句名言在開發中是適用的, 那在學習階段的? 我可以大概的告訴你-忘記這句話!, 為什麼
不要重復造輪子不適合在學習階段使用呢? 如果我們在學習的時候什麼東西都依賴別人的實現, 是不是我們就沒有了自己的核心價值? 而且
重復造輪子還有個好處就是-可以拿我們的代碼和別人的代碼做對比, 這樣我們可以很快的發現自己的不足.
重復造輪子
上面扯了這麼多, 下面我們就開始來造輪子了(話說回來, 我已經造了很多輪子了^_^). 這篇博客我們來仿一個最近很火的android網絡框架的二次封裝-retrofit(這個名字真難記). 新項目的名字我們起個簡單的-glin. 而且項目我已經放github上了, 感興趣的同學可以參考https://github.com/qibin0506/Glin.
如何使用
因為我們是仿retrofit, 所以用法上肯定和retrofit大致相同, 首先是配置.
Glin glin = new Glin.Builder()
.client(new OkClient())
.baseUrl("http://192.168.201.39")
.debug(true)
.parserFactory(new FastJsonParserFactory())
.timeout(10000)
.build();
幾個方法需要簡單的解釋一下,
client指定使用的什麼網絡框架去訪問網絡,
parserFactory指定了我們怎麼去解析返回的數據.
配置完成了以後,我們怎麼去使用呢? 和retrofit一樣,我們需要使用接口來定義業務.
public interface UserApi {
@POST("/users/list")
Call list(@Arg("name") String userName);
}
注解
@POST指定了我們要Post到的api地址, list方法中
@Arg注解制定了這個參數對應在網絡請求中的參數key, 方法的返回值是一個Call類型, 這個Call代表了一個請求.
使用.
UserApi api = glin.create(UserApi.class, getClass().getName());
Call call = api.list("qibin");
call.enqueue(new Callback() {
@Override
public void onResponse(Result result) {
if (result.isOK()) {
Toast.makeText(MainActivity.this, result.getResult().getName(), Toast.LENGTH_SHORT).show();
}else {
Toast.makeText(MainActivity.this, result.getMessage(), Toast.LENGTH_SHORT).show();
}
}
});
熟悉retrofit的同學對這裡應該很熟悉了, 這裡我就不再多嘴了, 下面我們趕緊進入主題, 如果去實現glin!
實現
馬上, 我們就要進去主題啦, 首先我們先來看看Glin這個類是干嘛的.
public class Glin {
private IClient mClient;
private String mBaseUrl;
private CallFactory mCallFactory;
private Glin(IClient client, String baseUrl) {
mClient = client;
mBaseUrl = baseUrl;
mCallFactory = new CallFactory();
}
@SuppressWarnings("unchecked")
public T create(Class klass, Object tag) {
return (T) Proxy.newProxyInstance(klass.getClassLoader(),
new Class[] {klass}, new Handler(tag));
}
public void cancel(String tag) {
mClient.cancel(tag);
}
public void regist(Class key, Class value) {
mCallFactory.regist(key, value);
}
}
Glin這個類還是很簡單的, 構造方法是private的, 因為大家都清楚, 我們強制要用使用建造者模式去實現.
三個變量中CallFactory是我們不熟悉的, 這個CallFactory是干嘛的? 這裡來解釋一下, 還記得我們在定義接口的時候接口中方法的返回值是一個Call嗎? 其實這個Call是一個抽象類, 它有很多實現, 這些實現和方法的注解是對應的, 例如上面的POST注解對應的就是使用
PostCall這個實現, 所以這裡的CallFactory類似一個mapping, 他提供了
注解->call的鍵值對, 這樣Glin就可以根據注解來找到要使用哪個Call了.
create方法貌似是我們使用的一個入口, 我們來看看create方法的實現, 其他Glin是使用了動態代理, 他的代理者, 也是Glin的核心就是Proxy.newProxyInstance的第三個參數-Handler, 我們接著來看看這個
Handler如果實現.
class Handler implements InvocationHandler {
private Object mTag;
public Handler(Object tag) {
mTag = tag;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Class key = null;
String path = null;
HashMap, Class> mapping = mCallFactory.get();
Class item;
Annotation anno;
for (Iterator> iterator = mapping.keySet().iterator();
iterator.hasNext();) {
item = iterator.next();
if (method.isAnnotationPresent(item)) {
key = item;
anno = method.getAnnotation(item);
path = (String) anno.getClass().getDeclaredMethod("value").invoke(anno);
break;
}
}
if (key == null) {
throw new UnsupportedOperationException("cannot find annotations");
}
Class callKlass = mCallFactory.get(key);
if (callKlass == null) {
throw new UnsupportedOperationException("cannot find calls");
}
Constructor constructor = callKlass.getConstructor(IClient.class, String.class, Params.class, Object.class);
Call call = constructor.newInstance(mClient, justUrl(path), params(method, args), mTag);
return call;
}
private String justUrl(String path) {
String url = mBaseUrl == null ? "" : mBaseUrl;
path = path == null ? "" : path;
if (isFullUrl(path)) { url = path;}
else { url += path;}
return url;
}
private boolean isFullUrl(String url) {
if (url == null || url.length() == 0) { return false;}
if (url.toLowerCase().startsWith("http://")) { return true;}
if (url.toLowerCase().startsWith("https://")) {return true;}
return false;
}
private Params params(Method method, Object[] args) {
Params params = new Params();
if (args == null || args.length == 0) {
return params;
}
// method.getParameterAnnotations.length always equals args.length
Annotation[][] paramsAnno = method.getParameterAnnotations();
if (method.isAnnotationPresent(JSON.class)) {
params.add(Params.DEFAULT_JSON_KEY, args[0]);
return params;
}
int length = paramsAnno.length;
for (int i = 0; i < length; i++) {
if (paramsAnno[i].length == 0) { params.add(Params.DEFAULT_JSON_KEY, args[i]);}
else { params.add(((Arg)paramsAnno[i][0]).value(), args[i]);}
}
return params;
}
}
這是Glin類的一個內部類, 雖然看起來很長, 但是基本都是一些輔助方法, 例如:
justUrl是根據baseUrl和注解中指定的地址做一個拼接,
isFullUrl方法是判斷注解中的url是不是一個完成的url, 因為如果是一個完成的url, 我們就不需要在url中拼接上baseUrl了, 這個類中的一個實現的方法
invoke和一個
params是最主要的, 我們接下來就來詳細的說一下這兩個方法.
在invoke方法中, 首先我們獲取所有的
注解->call鍵值對, 然後去遍歷這個map並且判斷我們使用的那個方法是使用了哪個注解, 然後記錄這個注解,並且記錄他的value值, 也就是api提交的地址, 接下來,我們通過得到的注解來從mCallFactory中來獲取這個注解對應的Call, 因為在CallFactory中我們存放的是Call的class, 所以接下來我們是通過反射來實例化這個Call, 並且返回這個call, 其實, 在預先知道目的的情況下,這裡都是很好理解的, 這裡我們的目的就是要得到具體Call的實例.那Call需要什麼參數呢? 我們來看看Call的構造吧.
public abstract class Call {
protected String mUrl;
protected Params mParams;
protected IClient mClient;
protected Object mTag;
public Call(IClient client, String url, Params params, Object tag) {
mClient = client;
mUrl = url;
mParams = params;
mTag = tag;
}
}
client我們知道在哪,
url我們從注解中取到了, 那就剩下一個
params了, 這個
params怎麼獲取呢? 下面我們就來看看上面提到的那個
params方法. 再貼一遍代碼:
private Params params(Method method, Object[] args) {
Params params = new Params();
if (args == null || args.length == 0) {
return params;
}
// method.getParameterAnnotations.length always equals args.length
Annotation[][] paramsAnno = method.getParameterAnnotations();
if (method.isAnnotationPresent(JSON.class)) {
params.add(Params.DEFAULT_JSON_KEY, args[0]);
return params;
}
int length = paramsAnno.length;
for (int i = 0; i < length; i++) {
if (paramsAnno[i].length == 0) { params.add(Params.DEFAULT_JSON_KEY, args[i]);}
else { params.add(((Arg)paramsAnno[i][0]).value(), args[i]);}
}
return params;
}
首先我們先new了一個
Params對象, 這樣做,不至於我們在使用
Params的時候它是一個
null, 接下來, 我們通過
method.getParameterAnnotations來獲取參數中的注解, 這裡返回的是一個二位數組, 為什麼是一個二維的? 很簡單, 因為每個參數可能會有多個注解, 接下來是一個對JSON數據的處理, 我們不用關心, 最後, 我們來遍歷這些參數, 並且將參數的注解value和我們傳遞的參數值存放的
params中, 這樣我們就做到了通過接口來獲取提交參數的目的.
到現在為止, 一個具體的Call我們就實現好了,接下來就是去調用Call的
enqueue方法了, 我們就拿
Post請求來看看
enqueue方法吧.
public class PostCall extends Call {
public PostCall(IClient client, String url, Params params, Object tag) {
super(client, url, params, tag);
}
@Override
public void enqueue(final Callback callback) {
mClient.post(mUrl, mParams, mTag, callback);
}
}
enqueue方法直接調用了
mClient的
post方法! 話說回來, 都到這裡了, 我們還沒看到真正的網絡請求的實現, 是的, 為了提供靈活性, 我們將網絡請求抽象出來, 大家可以任意去實現自己的網絡請求, 我們先來看看這個
IClient接口中都是定義了什麼方法, 然後我們在來看看
post是如何實現的.
public interface IClient {
void get(final String url, final Object tag, final Callback callback);
void post(final String url, final Params params, final Object tag, final Callback callback);
void post(final String url, final String json, final Object tag, final Callback callback);
void put(final String url, final Params params, final Object tag, final Callback callback);
void put(final String url, final String json, final Object tag, final Callback callback);
void delete(final String url, final Object tag, final Callback callback);
void cancel(final Object tag);
void parserFactory(ParserFactory factory);
void timeout(long ms);
void debugMode(boolean debug);
LinkedHashMap headers();
}
其實就是定義了一些基本的http請求方法, 下面我們就來看看一個具體的post請求是如何實現的,
@Override
public void post(String url, Params params, Object tag, Callback callback) {
StringBuilder debugInfo = new StringBuilder();
MultipartBody builder = createRequestBody(params, debugInfo);
Request request = new Request.Builder()
.url(url).post(builder).build();
call(request, callback, tag, debugInfo);
}
這裡使用了
okhttp來作為網絡請求的底層框架, 所以這裡都是和
okhttp相關的代碼. 這裡我們也就不再多說了.
現在我們可以搞定網絡請求了, 還剩下什麼? 數據解析. 數據解析怎麼搞定了? 我們來看看具體的實現代碼.
@Override
public void onResponse(final Call call, Response response) throws IOException {
String resp = response.body().string();
prntInfo("Response->" + resp);
callback(callback, (Result) getParser(callback.getClass()).parse(callback.getClass(), resp));
}
主要的還是getParser方法.
private org.loader.glin.parser.Parser getParser(Class klass) {
Class type = Helper.getType(klass);
if (type.isAssignableFrom(List.class)) {
return mParserFactory.getListParser();
}
return mParserFactory.getParser();
}
這裡有一個淫技, 我們通過Callback的范型類型來判斷要使用什麼方式去解析, 為什麼說是淫技, 因為在java中我們只能獲取到父類的范型類型, 所以這裡的Callback並不是大家印象中的接口, 而是一個抽象類
public abstract class Callback {
public abstract void onResponse(Result result);
}
而且我們在使用Callback的時候, 肯定是要去實現他的, 所以這裡正好就可以獲取到它的范型了.
通過上面的getParser的代碼, 我們還得到了什麼信息? 那就是尼瑪
mParserFactory的實現絕壁簡單, 就是獲取json數組和json對象的解析實現類!
好了, 大體的流程到這裡我們就完成了, 具體的一些實現, 大家可以去github上查看代碼.
項目的地址是:https://github.com/qibin0506/Glin
ListFragment繼承於Fragment。因此它具有Fragment的特性,能夠作為activity中的一部分,目的也是為了使頁面設計更加靈活。相比Fragment
概述: 如果你想要在一個TextView顯示一個被高亮顯示的子字符串。例如,我想讓123456789中的345被高亮顯示。注意,我這裡指的只高亮一部分,而不是全部高亮。你
在Android的應用開發中,我們會用到各種代碼調試;其實在Android的開發之後,我們可能會碰到一些隨機的問題,如cpu過高,內存洩露等,我們無法簡單的進行代碼調試,
效果圖源碼KqwOpenCVBlurDemo銳化也可以看作是一種線性濾波操作,並且錨點像素有較高的權重,而周圍的像素權重較低。因此,我們可以自定義一個這樣的核。/*