Android背锅侠


  • Startseite

  • Archiv

  • Tags
Android背锅侠

RecyclerView的下拉刷新与上拉加载更多

Veröffentlicht am 2017-02-04

下拉刷新和上拉加载更多,从设计层面上来说,就不是同一个层面上的,从Google官方推出的下拉刷新控件SwipeRefreshLayout就可以看出。
一般来说,下拉刷新是以包裹整个列表控件的容器来实现的,而上拉加载更多是以列表控件的一部分扩展(footerView)来实现的。

下拉刷新

  1. Google官方的SwipeRefreshLayout
    xml中控件初始化:
    <android.support.v4.widget.SwipeRefreshLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <android.support.v7.widget.RecyclerView
    android:id="@+id/recyclerView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>
    </android.support.v4.widget.SwipeRefreshLayout>

java类中实现

// 设置下拉出现小圆圈是否是缩放出现,出现的位置,最大的下拉位置
mySwipeRefreshLayout.setProgressViewOffset(true, 50, 200);
// 设置下拉圆圈的大小,两个值 LARGE, DEFAULT
mySwipeRefreshLayout.setSize(SwipeRefreshLayout.LARGE);
// 设置下拉圆圈上的颜色,蓝色、绿色、橙色、红色
mySwipeRefreshLayout.setColorSchemeResources(
android.R.color.holo_blue_bright,
android.R.color.holo_green_light,
android.R.color.holo_orange_light,
android.R.color.holo_red_light);
// 通过 setEnabled(false) 禁用下拉刷新
mySwipeRefreshLayout.setEnabled(false);
// 设定下拉圆圈的背景
mSwipeLayout.setProgressBackgroundColor(R.color.red);
/*
* 设置手势下拉刷新的监听
*/
mySwipeRefreshLayout.setOnRefreshListener(
new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
// 刷新动画开始后回调到此方法
}
}
);

  1. 使用第三方下拉刷新控件,可以更好地支持自定义下拉样式。
    这里推荐一个比较好的第三方控件:android-Ultra-Pull-To-Refresh
    compile 'in.srain.cube:ultra-ptr:1.0.10'

xml中控件初始化:

<in.srain.cube.views.ptr.PtrFrameLayout
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/refreshView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:ptr_duration_to_close="200"
app:ptr_duration_to_close_header="1000"
app:ptr_keep_header_when_refresh="true"
app:ptr_pull_to_fresh="false"
app:ptr_ratio_of_header_height_to_refresh="1.2"
app:ptr_resistance="1.7">
<com.zdf.zhihu.view.widget.LoadMoreRecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</com.zdf.zhihu.view.widget.LoadMoreRecyclerView>
</in.srain.cube.views.ptr.PtrFrameLayout>

java类中实现:

private void initRefreshView() {
// 这里可以完全自定义各种样式,只要实现PtrUIHandler,
// 具体请参考官方文档也源码
MaterialHeader header = new MaterialHeader(getContext());
refreshView.setHeaderView(header);
refreshView.addPtrUIHandler(header);
refreshView.setPtrHandler(new PtrDefaultHandler() {
@Override
public void onRefreshBegin(PtrFrameLayout ptrFrameLayout) {
page = 0;
requestTopMovies();
}
});
}
@Override
public void autoRefresh() {
handler.postDelayed(new Runnable() {
@Override
public void run() {
refreshView.autoRefresh();
}
}, 100);
}
@Override
public void refreshComplete() {
handler.post(new Runnable() {
@Override
public void run() {
refreshView.refreshComplete();
}
});
}

上拉加载更多

以列表控件的一部分扩展实现,这里以RecyclerView为例,新建一个类LoadMoreRecyclerView,继承至RecyclerView,主要的功能就是实现加载更多逻辑和显示控件:

/**
* 集成了上拉加载更多的RecyclerView
*
* Created by XiaoFeng on 2017/2/3.
*/
public class LoadMoreRecyclerView extends RecyclerView {
private final int STATE_NONE = 0x00;
private final int STATE_LOADING = 0x01;
private final int STATE_FAILURE = 0x02;
private final int STATE_COMPLETE = 0x03;
// 滑到底部里最后一个个数的阀值
private static final int VISIBLE_THRESHOLD = 1;
private int state;
private boolean loadMoreEnabled = true;
private OnLoadMoreListener listener;
private SuperAdapter superAdapter;
private LoadMoreView loadMoreView;
private LinearLayoutManager layoutManager;
public LoadMoreRecyclerView(Context context) {
this(context, null);
}
public LoadMoreRecyclerView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public LoadMoreRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public void initLoadMore(@NonNull OnLoadMoreListener listener) {
this.listener = listener;
if (getAdapter() instanceof SuperAdapter) {
superAdapter = (SuperAdapter) getAdapter();
}
loadMoreView = new LoadMoreView(getContext());
layoutManager = (LinearLayoutManager) getLayoutManager();
addOnScrollListener(new OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (canLoadMore() && dy >= 0) {
int totalItemCount = layoutManager.getItemCount();
int lastVisibleItem = layoutManager.findLastVisibleItemPosition();
if (lastVisibleItem + VISIBLE_THRESHOLD >= totalItemCount) {
onLoadMore(recyclerView);
}
}
}
});
}
public void setLoadMoreEnabled(boolean enabled) {
this.loadMoreEnabled = enabled;
}
private boolean canLoadMore() {
return state != STATE_LOADING && loadMoreEnabled;
}
private void onLoadMore(RecyclerView recyclerView) {
state = STATE_LOADING;
if (loadMoreView != null) {
loadMoreView.loading();
}
recyclerView.post(new Runnable() {
@Override
public void run() {
if (state == STATE_LOADING) {
if (superAdapter != null && loadMoreView != null) {
superAdapter.addFooterView(loadMoreView);
}
}
}
});
if (listener != null) {
listener.onLoadMore();
}
}
public void loadMoreFailed() {
state = STATE_FAILURE;
if (loadMoreView != null) {
loadMoreView.failure();
}
if (superAdapter != null) {
superAdapter.removeFooterView();
}
}
public void loadMoreComplete() {
state = STATE_COMPLETE;
if (loadMoreView != null) {
loadMoreView.complete();
}
if (superAdapter != null) {
superAdapter.removeFooterView();
}
}
}

上面出现的SuperAdapter是使用了一个第三方封装的万能Adapter,同时支持ListView和RecyclerView,个人感觉比鸿洋大神封装的万能Adapter要好一些,但是基本原理都是一样的。原理可以看他们各自的文章介绍,已经写的很详细了,这里就不多说了。

http://www.jianshu.com/p/d6a76fd3ea5b
http://blog.csdn.net/lmj623565791/article/details/38902805
http://blog.csdn.net/lmj623565791/article/details/51118836

关于分页加载

伴随着上拉加载更多功能的,往往是分页加载,关于分页加载的页数是在列表控件内部自动控制?还是在列表控件外部自己控制?我个人认为应该放在列表控件外部自己来控制页数,因为这个页数不光是在上拉加载更多时会使用到,在下拉刷新的时候也会使用到,并且对页数需要清零。
这里提供一种在列表控件外部控制页数的实现逻辑:

// 分页页数
private int page = 0;
// 初始化下拉刷新控件
private void initRefreshView() {
MaterialHeader header = new MaterialHeader(getContext());
refreshView.setHeaderView(header);
refreshView.addPtrUIHandler(header);
refreshView.setPtrHandler(new PtrDefaultHandler() {
@Override
public void onRefreshBegin(PtrFrameLayout ptrFrameLayout) {
page = 0;
requestTopMovies();
}
});
}
// 初始化RecyclerView,包含上拉加载更多
private void initRecyclerView() {
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
adapter = new SubjectAdapter(getContext());
recyclerView.setAdapter(adapter);
recyclerView.initLoadMore(new OnLoadMoreListener() {
@Override
public void onLoadMore() {
page++;
requestTopMovies();
}
});
}
// 具有分页操作的网络请求
private void requestTopMovies() {
Subscriber subscriber = new RxCallback<List<Movie>>() {
@Override
public void onSuccess(List<Movie> movieList) {
if (adapter != null) {
if (page == 0) {
adapter.clear();
} else {
recyclerView.loadMoreComplete();
}
adapter.addAll(movieList);
}
}
@Override
public void onFinished() {
refreshComplete();
}
};
RxRetrofitClient.getInstance().requestTop250Movies(subscriber, page, 10);
}

参考:
https://github.com/hanks-zyh/SwipeRefreshLayout
http://www.jianshu.com/p/d6a76fd3ea5b
http://blog.csdn.net/lmj623565791/article/details/38902805
http://blog.csdn.net/lmj623565791/article/details/51118836

Android背锅侠

RxJava + Retrofit2 + OkHttp3 封装及踩坑(续)

Veröffentlicht am 2017-01-26

前一篇文章(也是我在简书上的第一篇技术文章^.^)讲了Android三剑客的基础用法和简单封装,有一些封装只是一笔带过,还有些用法被遗漏没讲到的,所以在这篇里统一做下查漏补缺。

0x00 先做一下纠正:

https和失败重连,OkHttp默认是支持的,并不用手动去设置(在OkHttpClient.Builder中已默认设置),所以OkHttpClient.Builder的初始化可以简化为:

// 创建OkHttpClient
OkHttpClient.Builder builder = new OkHttpClient.Builder()
// 超时设置
.connectTimeout(DEFAULT_CONNECT_TIMEOUT, TimeUnit.SECONDS)
.readTimeout(DEFAULT_READ_TIMEOUT, TimeUnit.SECONDS)
.writeTimeout(DEFAULT_WRITE_TIMEOUT, TimeUnit.SECONDS)
// cookie管理
.cookieJar(new PersistentCookieJar(new SetCookieCache(), new SharedPrefsCookiePersistor(App.getInstance())));

0x01 Cookie持久化管理

这部分主要参考了这篇文章。

  • 不带持久化
    builder.cookieJar(new CookieJar() {
    private final HashMap<HttpUrl, List<Cookie>> cookieStore = new HashMap<>();
    @Override
    public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
    cookieStore.put(url, cookies);
    }
    @Override
    public List<Cookie> loadForRequest(HttpUrl url) {
    List<Cookie> cookies = cookieStore.get(url);
    return cookies != null ? cookies : new ArrayList<Cookie>();
    }
    });

这种简单的实现,每次重启App都会需要重新登录,不可取。

  • 带持久化
    CookieHandler cookieHandler = new CookieManager(
    new PersistentCookieStore(context), CookiePolicy.ACCEPT_ALL);
    builder.cookieJar(new JavaNetCookieJar(cookieHandler));

这里出现了两个类:JavaNetCookieJar和PersistentCookieStore

  • JavaNetCookieJar就是对CookieJar的封装实现,里面实现了对Cookie持久化存储和获取的调用逻辑,OkHttp已经帮我们实现了这个类,需要引入下面这个包:

    compile 'com.squareup.okhttp3:okhttp-urlconnection:3.5.0'
  • PersistentCookieStore是具体实现Cookie持久化的类,使用的是SharedPreferences,具体代码实现可参考这篇。
    当然,如果你想通过数据库实现持久化,也可以自己封装一个类似的类去实现。

  • 再介绍一个封装了Cookie持久化的第三方库(推荐)
    ClearableCookieJar cookieJar = new PersistentCookieJar(
    new SetCookieCache(), new SharedPrefsCookiePersistor(context));
    builder.cookieJar(cookieJar);

需要引入下面这个包:

compile 'com.github.franmontiel:PersistentCookieJar:v1.0.0

0x02. 拦截器

  • addInterceptor和addNetworkInterceptor的区别
    前一篇文章有同学问到两者的区别,okHttp官方对拦截器做了解释,并给了一张图,感觉挺一目了然的。
    Paste_Image.png
    两种拦截器简单来说就是调用时机的区别,应用拦截器调用时机较早,也就是进入chain.proceed的递归较早,相应的完成递归得到response会较晚;而网络拦截器则相反,request请求调用时机较晚,会较早完成chain.proceed递归调用,得到response的时机较早。
    简单来说就是应用拦截器较上层,而网络拦截器较底层,所有拦截器就是一个由浅入深的递归调用。具体还是得看源码。

  • Http Header
    可以通过这个拦截器为Request添加全局统一的Header。

    /**
    * 网络请求公共头信息插入器
    *
    * Created by XiaoFeng on 17/1/18.
    */
    public class HttpHeaderInterceptor implements Interceptor {
    @Override
    public Response intercept(Chain chain) throws IOException {
    Request original = chain.request();
    Request request = original.newBuilder()
    .header("User-Agent", "Android, xxx")
    .header("Accept", "application/json")
    .header("Content-type", "application/json")
    .method(original.method(), original.body())
    .build();
    return chain.proceed(request);
    }
    }
  1. 公共参数
    主要参考这篇

    /**
    * 网络请求公共参数插入器
    * <p>
    * Created by XiaoFeng on 2017/1/25.
    */
    public class CommonParamsInterceptor implements Interceptor {
    @Override
    public Response intercept(Chain chain) throws IOException {
    Request request = chain.request();
    if (request.method().equals("GET")) {
    HttpUrl httpUrl = request.url().newBuilder()
    .addQueryParameter("version", "xxx")
    .addQueryParameter("device", "Android")
    .addQueryParameter("timestamp", String.valueOf(System.currentTimeMillis()))
    .build();
    request = request.newBuilder().url(httpUrl).build();
    } else if (request.method().equals("POST")) {
    if (request.body() instanceof FormBody) {
    FormBody.Builder bodyBuilder = new FormBody.Builder();
    FormBody formBody = (FormBody) request.body();
    for (int i = 0; i < formBody.size(); i++) {
    bodyBuilder.addEncoded(formBody.encodedName(i), formBody.encodedValue(i));
    }
    formBody = bodyBuilder
    .addEncoded("version", "xxx")
    .addEncoded("device", "Android")
    .addEncoded("timestamp", String.valueOf(System.currentTimeMillis()))
    .build();
    request = request.newBuilder().post(formBody).build();
    }
    }
    return chain.proceed(request);
    }
    }
  2. 缓存策略

    /**
    * 网络请求缓存策略插入器
    *
    * Created by XiaoFeng on 17/1/17.
    */
    public class HttpCacheInterceptor implements Interceptor {
    @Override
    public Response intercept(Chain chain) throws IOException {
    Request request = chain.request();
    // 无网络时,始终使用本地Cache
    if (!NetworkUtil.isNetworkConnected()) {
    request = request.newBuilder()
    .cacheControl(CacheControl.FORCE_CACHE)
    .build();
    }
    Response response = chain.proceed(request);
    if (NetworkUtil.isNetworkConnected()) {
    // 有网络时,设置缓存过期时间0个小时
    int maxAge = 0;
    response.newBuilder()
    .header("Cache-Control", "public, max-age=" + maxAge)
    .removeHeader("Pragma") // 清除头信息,因为服务器如果不支持,会返回一些干扰信息,不清除下面无法生效
    .build();
    } else {
    // 无网络时,设置缓存过期超时时间为4周
    int maxStale = 60 * 60 * 24 * 28;
    response.newBuilder()
    .header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale)
    .removeHeader("Pragma")
    .build();
    }
    return response;
    }
    }
  3. 调试工具
    使用的是Facebook推出的一个集成到Chrome中的调试工具,需要引入下面两个库:

    compile 'com.facebook.stetho:stetho:1.4.1'
    compile 'com.facebook.stetho:stetho-okhttp3:1.4.1'

在Application中初始化就可以用了

Stetho.initializeWithDefaults(this);

如何调试?

  • 打开Chrome浏览器
  • 地址栏输入chrome://inspect
  • 进入页面后,在左边的DevTools -> Devices -> Remote Target下,可以找到你连接的手机设备,点开后就会出现调试页面了,后面就自己研究吧,不光可以调试网络请求,还可以查看手机中的数据库和SharePreference等持久化文件,而且不用root,很强大!

0x03. FastJson解析库封装

网上很多介绍retrofit的文章,对网络请求返回的结果,使用的都是默认的Gson库,虽然可以满足大部分人的需求,但是有些对性能要求高一点的人,还是习惯使用FastJson库做解析,这里就讲讲如何把默认的Gson库替换成FastJson库。

首先,默认Gson库的设置是这样的:

Retrofit retrofit = new Retrofit.Builder()
.client(builder.build())
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.baseUrl(BASE_URL)
.build();

用FastJson库替换后是这样的:

Retrofit retrofit = new Retrofit.Builder()
.client(builder.build())
.addConverterFactory(FastJsonConvertFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.baseUrl(BASE_URL)
.build();

是不是很像,没错,就是把ConverterFactory替换了一下而已。

至于FastJsonConvertFactory的实现,其实就是仿造GsonConverterFactory的源码来写的,并不复杂。

主要有三个类:

  1. 工厂类:FastJsonConvertFactory,里面就是分别创建了Request和Response的转换类。

    /**
    *
    * Created by XiaoFeng on 2017/1/17.
    */
    public class FastJsonConvertFactory extends Converter.Factory {
    public static FastJsonConvertFactory create() {
    return new FastJsonConvertFactory();
    }
    @Override
    public Converter<?, RequestBody> requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
    return new FastJsonRequestConverter<>();
    }
    @Override
    public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
    return new FastJsonResponseConverter<>(type);
    }
    }
  2. Request转换类:FastJsonRequestConverter

    /**
    *
    * Created by XiaoFeng on 2017/1/17.
    */
    public class FastJsonRequestConverter<T> implements Converter<T, RequestBody> {
    private static final MediaType MEDIA_TYPE = MediaType.parse("application/json; charset=UTF-8");
    private static final Charset UTF_8 = Charset.forName("UTF-8");
    @Override
    public RequestBody convert(T value) throws IOException {
    return RequestBody.create(MEDIA_TYPE, JSON.toJSONBytes(value));
    }
    }
  3. Response转换类:FastJsonResponseConverter

    /**
    *
    * Created by XiaoFeng on 2017/1/17.
    */
    public class FastJsonResponseConverter<T> implements Converter<ResponseBody, T> {
    private final Type type;
    public FastJsonResponseConverter(Type type) {
    this.type = type;
    }
    @Override
    public T convert(ResponseBody value) throws IOException {
    BufferedSource buffer = Okio.buffer(value.source());
    String s = buffer.readUtf8();
    buffer.close();
    return JSON.parseObject(s, type);
    }
    }

是不是很简单,如果想再换成别的第三方json解析库,照着这个写就可以了。

0x04. 生命周期

上一篇中还有同学提到RxJava的生命周期管理,防止内存泄漏,这个可以直接使用第三方库,参考这篇。
一般引用下面两个库就够了:

compile 'com.trello:rxlifecycle:1.0'
compile 'com.trello:rxlifecycle-components:1.0'

有两种使用方式:
1. 自动取消订阅,使用bindToLifecycle。
需要继承至RxActivity或者RxFragment等基类。

@Override
protected void onStart() {
super.onStart();
// Using automatic unsubscription, this should determine that the correct time to
// unsubscribe is onStop (the opposite of onStart).
Observable.interval(1, TimeUnit.SECONDS)
.doOnUnsubscribe(new Action0() {
@Override
public void call() {
Log.i(TAG, "Unsubscribing subscription from onStart()");
}
})
// 因为bindToLifecycle是在onStart的时候调用,所以在onStop的时候自动取消订阅
.compose(this.<Long>bindToLifecycle())
.subscribe(new Action1<Long>() {
@Override
public void call(Long num) {
Log.i(TAG, "Started in onStart(), running until in onStop(): " + num);
}
});
}

从下面这段核心函数可以看清自动取消订阅的规则,就是在哪个生命周期内调用bindToLifecycle,就在与其对应的结束生命周期函数调用时自动取消订阅。

private static final Func1<ActivityEvent, ActivityEvent> ACTIVITY_LIFECYCLE =
new Func1<ActivityEvent, ActivityEvent>() {
@Override
public ActivityEvent call(ActivityEvent lastEvent) {
switch (lastEvent) {
case CREATE:
return ActivityEvent.DESTROY;
case START:
return ActivityEvent.STOP;
case RESUME:
return ActivityEvent.PAUSE;
case PAUSE:
return ActivityEvent.STOP;
case STOP:
return ActivityEvent.DESTROY;
case DESTROY:
throw new OutsideLifecycleException("Cannot bind to Activity lifecycle when outside of it.");
default:
throw new UnsupportedOperationException("Binding to " + lastEvent + " not yet implemented");
}
}
};

2. 手动取消订阅,使用bindUntilEvent。
需要继承至RxActivity或者RxFragment等基类。

@Override
protected void onResume() {
super.onResume();
// `this.<Long>` is necessary if you're compiling on JDK7 or below.
// If you're using JDK8+, then you can safely remove it.
Observable.interval(1, TimeUnit.SECONDS)
.doOnUnsubscribe(new Action0() {
@Override
public void call() {
Log.i(TAG, "Unsubscribing subscription from onResume()");
}
})
// 手动设置在Activity onDestroy的时候取消订阅
.compose(this.<Long>bindUntilEvent(ActivityEvent.DESTROY))
.subscribe(new Action1<Long>() {
@Override
public void call(Long num) {
Log.i(TAG, "Started in onResume(), running until in onDestroy(): " + num);
}
});
}

3. 自定义RxActivity/RxFragment
直接继承RxActivity/RxFragment有时会碰到问题,因为有可能本身已经有一个基类需要继承,java不能多继承。不过不要慌,我们可以自定义一个自己的基类,实现方式参考RxActivity。

public abstract class RxActivity extends Activity implements LifecycleProvider<activityevent> {
private final BehaviorSubject<activityevent> lifecycleSubject = BehaviorSubject.create();
public RxActivity() {
}
@NonNull
@CheckResult
public final Observable<activityevent> lifecycle() {
return this.lifecycleSubject.asObservable();
}
@NonNull
@CheckResult
public final <t> LifecycleTransformer<t> bindUntilEvent(@NonNull ActivityEvent event) {
return RxLifecycle.bindUntilEvent(this.lifecycleSubject, event);
}
@NonNull
@CheckResult
public final <t> LifecycleTransformer<t> bindToLifecycle() {
return RxLifecycleAndroid.bindActivity(this.lifecycleSubject);
}
@CallSuper
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.lifecycleSubject.onNext(ActivityEvent.CREATE);
}
@CallSuper
protected void onStart() {
super.onStart();
this.lifecycleSubject.onNext(ActivityEvent.START);
}
@CallSuper
protected void onResume() {
super.onResume();
this.lifecycleSubject.onNext(ActivityEvent.RESUME);
}
@CallSuper
protected void onPause() {
this.lifecycleSubject.onNext(ActivityEvent.PAUSE);
super.onPause();
}
@CallSuper
protected void onStop() {
this.lifecycleSubject.onNext(ActivityEvent.STOP);
super.onStop();
}
@CallSuper
protected void onDestroy() {
this.lifecycleSubject.onNext(ActivityEvent.DESTROY);
super.onDestroy();
}
}

突然发现写文章真是一个知识梳理,自我学习的好方法,比没有目的性的看很多技术文章有用很多倍,极力推荐有能力的同学都去尝试写属于自己的技术博客。^ ^

参考:
https://gold.xitu.io/entry/572ed42ddf0eea0063186e1f
https://gist.github.com/franmontiel/ed12a2295566b7076161
https://gold.xitu.io/entry/5825300b2f301e005c47fac5
http://www.codexiu.cn/android/blog/39432/
http://androidxx.ren/forum.php?mod=viewthread&tid=19
https://gold.xitu.io/entry/58290ea2570c35005878ce8f

Android背锅侠

RxJava + Retrofit2 + OkHttp3 封装及踩坑

Veröffentlicht am 2017-01-21

网上对这三个开源库的组合框架代码已经一搜一大把了,但是都很零碎,需要搜索很多东拼西凑才能写一套完整的复合自己需求的框架。这篇文章就是我自己在封装过程遇到的各种问题的记录和总结,免得很多人重复踩坑。。

一、封装后的效果

private void requestTopMovies(int page) {
showWaitingDialog();
Subscriber subscriber = new RxCallback<List<Movie>>() {
@Override
public void onSuccess(List<Movie> movieList) {
if (adapter != null) {
adapter.updateData(movieList);
}
}
@Override
public void onFinished() {
dismissWaitingDialog();
}
};
RxRetrofitClient.getInstance().requestTop250Movies(subscriber, page, 10);
}

二、封装过程

1. 全局Client管理类封装 RxRetrofitClient(单例)

在这里初始化各种网络设置

/// RxRetrofitClient.java
private void initClient() {
// 创建OkHttpClient
OkHttpClient.Builder builder = new OkHttpClient.Builder()
// 超时设置
.connectTimeout(DEFAULT_CONNECT_TIMEOUT, TimeUnit.SECONDS)
.readTimeout(DEFAULT_READ_TIMEOUT, TimeUnit.SECONDS)
.writeTimeout(DEFAULT_WRITE_TIMEOUT, TimeUnit.SECONDS)
// 错误重连
.retryOnConnectionFailure(true)
// 支持HTTPS
.connectionSpecs(Arrays.asList(ConnectionSpec.CLEARTEXT, ConnectionSpec.MODERN_TLS)) //明文Http与比较新的Https
// cookie管理
.cookieJar(new PersistentCookieJar(new SetCookieCache(), new SharedPrefsCookiePersistor(App.getInstance())));
// 添加各种插入器
addInterceptor(builder);
// 创建Retrofit实例
Retrofit doubanRetrofit = new Retrofit.Builder()
.client(builder.build())
.addConverterFactory(GsonConverterFactory.create())
// .addConverterFactory(FastJsonConvertFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.baseUrl(BASE_URL_DOUBAN)
.build();
// 创建API接口类
doubanApi = doubanRetrofit.create(IDoubanApi.class);
}
private void addInterceptor(OkHttpClient.Builder builder) {
// 添加Header
builder.addInterceptor(new HttpHeaderInterceptor());
// 添加缓存控制策略
File cacheDir = App.getInstance().getExternalCacheDir();
Cache cache = new Cache(cacheDir, DEFAULT_CACHE_SIZE);
builder.cache(cache).addInterceptor(new HttpCacheInterceptor());
// 添加http log
HttpLoggingInterceptor logger = new HttpLoggingInterceptor();
logger.setLevel(HttpLoggingInterceptor.Level.BODY);
builder.addInterceptor(logger);
// 添加调试工具
builder.networkInterceptors().add(new StethoInterceptor());
}

2. Get和Post请求封装
/**
* 豆瓣 Retrofit API
*
* Created by XiaoFeng on 16/12/20.
*/
public interface IDoubanApi {
@GET("top250")
Observable<DoubanResult<List<Movie>>> getTopMovies(@Query("start") int start, @Query("count") int count);
/**
* Json格式的Post请求(application/json)
*/
@POST("account/update_user_info")
Observable<RESTResult<String>> updateUserInfo(@Body RequestBody body);
/**
* Form格式的Post请求(application/x-www-form-urlencoded)
*/
@FormUrlEncoded
@POST("account/update_user_info")
Observable<RESTResult<String>> updateUserInfo(@FieldMap Map<String, String> params);
}
/// RxRetrofitClient.java
/**
* 获取豆瓣电影Top250的列表数据
*
* @param subscriber 由调用者传过来的观察者对象
* @param page 页码
* @param count 每页个数
*/
public void requestTop250Movies(Subscriber<List<Movie>> subscriber, int page, int count) {
doubanApi.getTopMovies(page * count, count)
.map(RxUtil.<List<Movie>>handleDoubanResult())
.compose(RxUtil.<List<Movie>>normalSchedulers())
.subscribe(subscriber);
}
/**
* 修改用户信息
* Json格式
*
* @param subscriber
* @param params
*/
public void updateUserInfo(Subscriber<String> subscriber, Map<String, Object> params) {
xzApi.updateUserInfo(toRequestBody(params))
.map(RxUtil.<String>handleRESTFulResult())
.compose(RxUtil.<String>normalSchedulers())
.subscribe(subscriber);
}
/**
* 修改用户信息
* Form格式
*
* @param subscriber
* @param params
*/
public void updateUserInfo(Subscriber<String> subscriber, Map<String, Object> params) {
xzApi.updateUserInfo(params)
.map(RxUtil.<String>handleRESTFulResult())
.compose(RxUtil.<String>normalSchedulers())
.subscribe(subscriber);
}

这里有几个 坑

  1. 写Form格式的Post请求时,需要添加 @FormUrlEncoded 注解,否则编译器会报错
  2. 写Json格式的Post请求时,不使用@FieldMap注解,而是使用 @Body 注解,并声明 RequestBody 类型变量
3. RequestBody封装
private RequestBody toRequestBody(Map params) {
return RequestBody.create(JSON, toJsonStr(params));
}
private String toJsonStr(Map params) {
return new JSONObject(params).toString();
}
4. 对请求结果统一封装 RESTResult
/**
* RESTFul 返回值封装类
*
* Created by XiaoFeng on 16/12/21.
*/
public class RESTResult<T> {
public static final int FAILURE = 0;
public static final int SUCCESS = 1;
@SerializedName("res")
private int res;
@SerializedName("msg")
private String msg;
@SerializedName("data")
T data;
public int getRes() {
return res;
}
public void setRes(int res) {
this.res = res;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
5. 对请求结果进行转换和预处理(map)
/// RxUtil.java
/**
* 对RESTful返回结果做预处理,对逻辑错误抛出异常
*
* @param <T>
* @return
*/
public static <T> Func1<RESTResult<T>, T> handleRESTFulResult() {
return new Func1<RESTResult<T>, T>() {
@Override
public T call(RESTResult<T> restResult) {
if (restResult.getRes() != RESTResult.SUCCESS) {
throw new ApiException(restResult.getRes(), restResult.getMsg());
}
return restResult.getData();
}
};
}
6. rxjava线程切换封装(compose)
/// RxUtil.java
/**
* 普通线程切换: IO -> Main
*
* @param <T>
* @return
*/
public static <T> Observable.Transformer<T, T> normalSchedulers() {
return new Observable.Transformer<T, T>() {
@Override
public Observable<T> call(Observable<T> source) {
return source.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
};
}

这里有一个 坑 :
我把handleRESTFulResult和normalSchedulers封装到了单独的帮助类RxUtil中,这样在使用map或者compose做转换时,需要显式写明返回类型,不然编译器会报错,这点在很多网上查找的资料中都没有提及

.map(RxUtil.handleRESTFulResult())
.compose(RxUtil.normalSchedulers())

7. 对服务器返回的逻辑错误进行统一拦截和封装,抛出异常
/**
* 对服务器返回的逻辑错误值进行封装
*
* Created by XiaoFeng on 16/12/21.
*/
public class ApiException extends RuntimeException {
private static final int USER_NOT_EXIST = 100;
private static final int WRONG_PASSWORD = 101;
private int errorCode;
public ApiException(String detailMessage) {
super(detailMessage);
}
public ApiException(int resultCode) {
this(resultCode, toApiExceptionMessage(resultCode));
}
public ApiException(int resultCode, String detailMessage) {
super(detailMessage);
this.errorCode = resultCode;
}
public int getErrorCode() {
return errorCode;
}
/**
* 映射服务器返回的自定义错误码,
* (此时的http状态码在[200, 300) 之间)
*
* @param resultCode
* @return
*/
private static String toApiExceptionMessage(int resultCode) {
String message;
switch (resultCode) {
case USER_NOT_EXIST:
message = "该用户不存在";
break;
case WRONG_PASSWORD:
message = "密码错误";
break;
default:
message = "未知错误";
}
return message;
}
}
8. 对Subscriber的封装,同时对onError异常再次封装
/**
* 暴露给最上层的网络请求回调处理类
*
* Created by XiaoFeng on 16/12/28.
*/
public abstract class RxCallback<T> extends Subscriber<T> {
/**
* 成功返回结果时被调用
*
* @param t
*/
public abstract void onSuccess(T t);
/**
* 成功或失败到最后都会调用
*/
public abstract void onFinished();
@Override
public void onCompleted() {
onFinished();
}
@Override
public void onError(Throwable e) {
String errorMsg;
if (e instanceof IOException) {
/** 没有网络 */
errorMsg = "Please check your network status";
} else if (e instanceof HttpException) {
/** 网络异常,http 请求失败,即 http 状态码不在 [200, 300) 之间, such as: "server internal error". */
errorMsg = ((HttpException) e).response().message();
} else if (e instanceof ApiException) {
/** 网络正常,http 请求成功,服务器返回逻辑错误 */
errorMsg = e.getMessage();
} else {
/** 其他未知错误 */
errorMsg = !TextUtils.isEmpty(e.getMessage()) ? e.getMessage() : "unknown error";
}
Toast.makeText(App.getInstance(), errorMsg, Toast.LENGTH_SHORT).show();
onFinished();
}
@Override
public void onNext(T t) {
onSuccess(t);
}
}

参考:
https://gank.io/post/56e80c2c677659311bed9841
http://tech.glowing.com/cn/glow-android-performance-optimization/
http://www.jianshu.com/p/f3f0eccbcd6f
http://wuxiaolong.me/2016/06/18/retrofits/
http://stackoverflow.com/questions/35243785/rxjava-static-generic-utility-method-with-transformer

Android背锅侠

Android 编码规范

Veröffentlicht am 2017-01-21

Android 编码规范

任何一个傻瓜都能写出计算机可以理解的代码。唯有写出人类容易理解的代码,才是优秀的程序员。 – Martin Flower



团队协作项目,为了保持大家的代码一致性,进行一些代码格式的规范。

1. 基本要求

  • 代码缩进使用4个空格,不是Tab键。
  • 统一使用UTF-8编码,避免乱码问题。
  • 除了注释,代码中不出现中文。
  • 每个类写上必要的注释,类的说明,作者,修改时间,联系方式(可选)。
  • 为了让他人可以容易看懂你的代码,请在关键地方做好注释。
  • 变量和函数名命名使用“驼峰命名法”,资源相关文件命名使用“下划线命名法”。
  • 代码中用到的字符串、颜色值和控件尺寸等资源,都统一写到对应的xml文件中(strings.xml, colors.xml, values.xml),方便代码维护和今后的国际化支持。
  • 强烈建议使用Android Studio或者Intellij IDEA。
  • 使用 Ctrl+Shift+F 来格式化代码, 使用 Ctrl+Shift+O 来格式化 Import 包。

2. 项目架构

  • 基本遵循MVP架构(Model/View/Presenter),把数据源,用户界面和业务逻辑各个层次解耦,增加代码可读性和维护性,也增强了代码的扩展性。
  • 详细代码可参考Google官方MVP架构示例。

3. 分包管理

  • 按照组件类型来分包,而不是按业务模块来分包。业务有可能会变,但组件类型是基本不变的。另外,新加入的开发人员,对业务不熟悉,但对组件是很清楚的,理解快,入手也快。
  • 对于稍微大一点的项目,可以先按模块分包,在子模块中再按照组件类型分包。
  • 分包格式也可参考Google官方MVP架构示例。

    base:存放基础类的包,里面的类以Base为前缀,例如BaseActivity;
    activity:存放activity的包,每个activity命名以Activity结尾,例如MainActivity;
    fragment:存放fragment的包,每个fragment命名以Fragment结尾,例如ChatFragment;
    receiver:存放receiver的包;
    service:存放service的包;
    adapter:存放adapter的包,每个adapter命名以Adapter结尾,例如EventItemAdapter;
    common:存放一些公共常量,例如后端接口、SharedPreferenceKey、IntentExtra等;
    utils:存放工具类的包,比如常见的工具类:LogUtils、DateUtils;
    entity:存放实体类的包;
    widget:存放自定义View的包;
    

4. 命名规范

包命名

  • 采用反域名命名规则,包名全部小写,连续的单词只是简单地连接起来,不使用下划线。
  • 一级包名为com,二级包名为xxx(可以是公司域名或者个人命名),三级包名根据应用进行命名,四级包名为模块名或层级名。如com.isa.crm.activity。

类命名

  • 大驼峰命名。除常见的缩写单词以外,不使用缩写,缩写的单词每个字母都大写。
  • 公共的工具类建议以Utils、Manager为后缀,如LogUtils。接口命名遵循以上原则,以I为前缀,或以able或ible为后缀。

变量命名

  • 成员变量命名
    小驼峰命名。不推荐使用谷歌的前面加m的编码风格(如果使用团队中使用m,则统一使用)。

  • 临时变量命名
    小驼峰命名。

  • 常量命名
    单词每个字母均大写。单词之间下划线连接。

方法命名

小驼峰命名。使用动词或动名词。

资源文件命名

  • 布局文件命名
    {组件_功能},下划线命名,全部小写。如:activity_login

  • 控件id命名
    {控件_范围_功能}, 下划线命名,全部小写。如:et_login_password

  • 字符串id命名
    {控件类型_功能}, 如:btnlogin
    页面标题,命名格式为:title
    {页面}
    按钮文字,命名格式为:btn{按钮事件}
    标签文字,命名格式为:label
    {标签文字}
    选项卡文字,命名格式为:tab{选项卡文字}
    消息框文字,命名格式为:toast
    {消息}
    编辑框的提示文字,命名格式为:hint{提示信息}
    图片的描述文字,命名格式为:desc
    {图片文字}
    对话框的文字,命名格式为:dialog_{文字}

  • 图片资源命名
    图标资源以ic为前缀,例如ic_chat,指聊天图标;
    背景图片以bg为前缀,例如bg_login,指的是登录页的背景图;
    按钮图片以btn为前缀,例如btn_login,指的是登录按钮的图片,不过这只有一种状态,需要加上状态的可以在后面添加,例如btn_login_pressed,表示登录按钮按下的图片;
    当使用shape和selector文件为背景或者按钮时,命名参照以上说明;

  • 动画文件命名
    {动画类型_动画方向},下划线命名法,全部小写。如:fade_in

常用控件缩写

控件 缩写
Linearlayout ll
RelativeLayout rl
TextView tv
EditText et
Button btn
ImageButton imgBtn
ImageView iv
CheckBox chb
ListView lv
GridView gv
ScrollView sv
WebView wv
ProgressBar proBar
RadioButton rb
Spinner spn

其他

  1. 其他内容参见谷歌编码规范
    中文http://blog.sina.com.cn/s/blog_48d491300100zwzg.html#use-todo-comments
    英文http://source.android.com/source/code-style.html

  2. 其他未写明的欢迎补充,现有的有疑惑的欢迎提问,有缺点的欢迎质疑讨论修改。





新浪微博Android SDK Java代码规范

代码规范的重要性不言而喻,但是一般公司都不太重视代码规范。在代码走读的时候,由于没有统一的规范,导致每个人都认为自己写的是对的。

在查看新浪微博Android SDK代码的时候,发现了个codestyle包。一个短小精悍的CodingRuler类却包含了比较全面的Java代码规范。

贴出来分享一下。当然,如果想要更详细的,写以查看 Google Java Style

  1. weibo_android_sdk/demo-src/WeiboSDK/src/com/sina/weibo/sdk/codestyle/CodingRuler.java

    /*
     * 文件名(可选),如 CodingRuler.java
     * 
     * 版本信息(可选),如:@version 1.0.0
     * 
     * 版权申明(开源代码一般都需要添加),如:Copyright (C) 2010-2013 SINA Corporation.
     */
    
    package com.sina.weibo.sdk.codestyle;
    
    /**
     * 类的大体描述放在这里。
     * 
     * <p>
     * <b>NOTE:以下部分为一个简要的编码规范,更多规范请参考 ORACLE 官方文档。</b><br>
     * 地址:http://www.oracle.com/technetwork/java/codeconventions-150003.pdf<br>
     * 另外,请使用 UTF-8 格式来查看代码,避免出现中文乱码。<br>
     * <b>至于注释应该使用中文还是英文,请自己行决定,根据公司或项目的要求而定,推荐使用英文。</b><br>
     * </p>
     * <h3>1. 整理代码</h3>
     * <ul>
     *    <li>1.1. Java 代码中不允许出现在警告,无法消除的警告要用 @SuppressWarnings。
     *    <li>1.2. 去掉无用的包、方法、变量等,减少僵尸代码。
     *    <li>1.3. 使用 Lint 工具来查看并消除警告和错误。
     *    <li>1.4. 使用 Ctrl+Shift+F 来格式化代码,然后再进行调整。
     *    <li>1.5. 使用 Ctrl+Shift+O 来格式化 Import 包。
     * </ul>
     * 
     * <h3>2. 命名规则</h3>
     *    <h3>2.1. 基本原则</h3>
     *    <ul>
     *         <li>2.1.1. 变量,方法,类命名要表义,严格禁止使用 name1, name2 等命名。
     *         <li>2.1.2. 命名不能太长,适当使用简写或缩写。(最好不要超过 25 个字母)
     *         <li>2.1.3. 方法名以小写字母开始,以后每个单词首字母大写。
     *         <li>2.1.4. 避免使用相似或者仅在大小写上有区别的名字。
     *         <li>2.1.5. 避免使用数字,但可用 2 代替 to,用 4 代替 for 等,如 go2Clean。
     *    </ul>
     *    
     *    <h3>2.2. 类、接口</h3>
     *    <ul>
     *         <li>2.2.1. 所有单词首字母都大写。使用能确切反应该类、接口含义、功能等的词。一般采用名词。
     *         <li>2.2.2. 接口带 I 前缀,或able, ible, er等后缀。如ISeriable。
     *    </ul>
     *    
     *    <h3>2.3. 字段、常量</h3>
     *    <ul>
     *         <li>2.3.1. 成员变量以 m 开头,静态变量以 s 开头,如 mUserName, sInstance。
     *         <li>2.3.2. 常量全部大写,在词与词之前用下划线连接,如 MAX_NUMBER。
     *         <li>2.3.3. 代码中禁止使用硬编码,把一些数字或字符串定义成常用量。
     *         <li>2.3.4. 对于废弃不用的函数,为了保持兼容性,通常添加 @Deprecated,如 {@link #doSomething()}
     *    </ul>
     *         
     * <h3>3. 注释</h3>
     *    请参考 {@link #SampleCode} 类的注释。
     *    <ul>
     *    <li>3.1. 常量注释,参见 {@link #ACTION_MAIN} 
     *    <li>3.2. 变量注释,参见 {@link #mObject0} 
     *    <li>3.3. 函数注释,参见 {@link #doSomething(int, float, String)}
     *    </ul> 
     *    
     * <h3>4. Class 内部顺序和逻辑</h3>
     * <ul>
     *    <li>4.1. 每个 class 都应该按照一定的逻辑结构来排列基成员变量、方法、内部类等,
     *             从而达到良好的可读性。
     *    <li>4.2. 总体上来说,要按照先 public, 后 protected, 最后 private, 函数的排布
     *             也应该有一个逻辑的先后顺序,由重到轻。
     *    <li>4.3. 以下顺序可供参考:
     *         定义 TAG,一般为 private(可选)<br>
     *         定义 public 常量<br>
     *         定义 private 常量、内部类<br>
     *         定义 private 变量<br>
     *         定义 public 方法<br>
     *         定义 protected 方法<br>
     *         定义 private 方法<br>
     * </ul>        
     * 
     * <h3>5. 表达式与语句</h3>
     *    <h3>5.1. 基本原则:采用紧凑型风格来编写代码</h3>
     *    <h3>5.2. 细则</h3>
     *    <ul>
     *         <li>5.2.1. 条件表示式,参见 {@link #conditionFun(boolean)} 
     *         <li>5.2.2. switch 语句,参见 {@link #switchFun(int)}
     *         <li>5.2.3. 循环语句,参见 {@link #circulationFun(boolean)}
     *         <li>5.2.4. 错误与异常,参见 {@link #exceptionFun()}
     *         <li>5.2.5. 杂项,参见 {@link #otherFun()}
     *         <li>5.2.6. 批注,参见 {@link #doSomething(int, float, String)}
     *    </ul>
     * 
     * @author 作者名
     * @since 2013-XX-XX
     */
    @SuppressWarnings("unused")
    public class CodingRuler {
    
        /** 公有的常量注释 */
        public static final String ACTION_MAIN = "android.intent.action.MAIN";
    
        /** 私有的常量注释(同类型的常量可以分块并紧凑定义) */
        private static final int MSG_AUTH_NONE    = 0;
        private static final int MSG_AUTH_SUCCESS = 1;
        private static final int MSG_AUTH_FAILED  = 2;
    
        /** 保护的成员变量注释 */
        protected Object mObject0;
    
        /** 私有的成员变量 mObject1 注释(同类型的成员变量可以分块并紧凑定义) */
        private Object mObject1;
        /** 私有的成员变量 mObject2 注释 */
        private Object mObject2;
        /** 私有的成员变量 mObject3 注释 */
        private Object mObject3;
    
        /**
         * 对于注释多于一行的,采用这种方式来
         * 定义该变量
         */
        private Object mObject4;
    
        /**
         * 公有方法描述...
         * 
         * @param param1  参数1描述...
         * @param param2  参数2描述...
         * @param paramXX 参数XX描述... (注意:请将参数、描述都对齐)
         */
        public void doSomething(int param1, float param2, String paramXX) {
            // 以下注释标签可以通过Eclipse内置的Task插件看到
            // TODO  使用TODO来标记代码,说明标识处有功能代码待编写
            // FIXME 使用FIXME来标记代码,说明标识处代码需要修正,甚至代码是
            //       错误的,不能工作,需要修复
            // XXX   使用XXX来标记代码,说明标识处代码虽然实现了功能,但是实现
            //       的方法有待商榷,希望将来能改进
        }
    
        /**
         * 保护方法描述...
         */
        @Deprecated
        protected void doSomething() {
            // ...implementation
        }
    
        /**
         * 私有方法描述...
         * 
         * @param param1  参数1描述...
         * @param param2  参数2描述...
         */
        private void doSomethingInternal(int param1, float param2) {
            // ...implementation        
        }
    
        /**
         * 条件表达式原则。
         */
        private void conditionFun() {
            boolean condition1 = true;
            boolean condition2 = false;
            boolean condition3 = false;
            boolean condition4 = false;
            boolean condition5 = false;
            boolean condition6 = false;
    
            // 原则: 1. 所有 if 语句必须用 {} 包括起来,即便只有一句,禁止使用不带{}的语句
            //       2. 在含有多种运算符的表达式中,使用圆括号来避免运算符优先级问题
            //       3. 判断条件很多时,请将其它条件换行
            if (condition1) {
                // ...implementation
            }
    
            if (condition1) {
                // ...implementation
            } else {
                // ...implementation
            }
    
            if (condition1)          /* 禁止使用不带{}的语句 */
                condition3 = true;
    
            if ((condition1 == condition2) 
                    || (condition3 == condition4)
                    || (condition5 == condition6)) {
    
            }
        }
    
        /**
         * Switch语句原则。
         */
        private void switchFun() {
    
            // 原则: 1. switch 语句中,break 与下一条 case 之间,空一行
            //       2. 对于不需要 break 语句的,请使用 /* Falls through */来标注
            //       3. 请默认写上 default 语句,保持完整性
            int code = MSG_AUTH_SUCCESS;
            switch (code) {
            case MSG_AUTH_SUCCESS:
                break;
    
            case MSG_AUTH_FAILED:
                break;
    
            case MSG_AUTH_NONE:
                /* Falls through */
            default:
                break;
            }
        }
    
        /**
         * 循环表达式。
         */
        private void circulationFun() {
    
            // 原则: 1. 尽量使用for each语句代替原始的for语句
            //       2. 循环中必须有终止循环的条件或语句,避免死循环
            //       3. 循环要尽可能的短, 把长循环的内容抽取到方法中去
            //       4. 嵌套层数不应超过3层, 要让循环清晰可读
    
            int array[] = { 1, 2, 3, 4, 5 };
            for (int data : array) {
                // ...implementation
            }
    
            int length = array.length;
            for (int ix = 0; ix < length; ix++) {
                // ...implementation
            }
    
            boolean condition = true;
            while (condition) {
                // ...implementation
            }
    
            do {
                // ...implementation
            } while (condition);
        }
    
        /**
         * 异常捕获原则。
         */
        private void exceptionFun() {
    
            // 原则: 1. 捕捉异常是为了处理它,通常在异常catch块中输出异常信息。
            //       2. 资源释放的工作,可以放到 finally 块部分去做。如关闭 Cursor 等。
            try {
                // ...implementation
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
    
            }
        }
    
        /**
         * 其它原则(整理中...)。
         */
        private void otherFun() {
            // TODO
        }    
    }
    
  2. weibo_android_sdk/demo-src/WeiboSDK/src/com/sina/weibo/sdk/codestyle/SampleCode.java

    /*
     * Copyright (C) 2010-2013 The SINA WEIBO Open Source Project
     *
     * 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.
     */
    
    package com.sina.weibo.sdk.codestyle;
    
    /**
     * 类的大体描述放在这里。
     *         
     * @author 作者名
     * @since 2013-XX-XX
     */
    @SuppressWarnings("unused")
    public class SampleCode {
    
        /** 公有的常量注释 */
        public static final String ACTION_MAIN = "android.intent.action.MAIN";
    
        /** 私有的常量注释(同类型的常量可以分块并紧凑定义) */
        private static final int MSG_AUTH_NONE    = 0;
        private static final int MSG_AUTH_SUCCESS = 1;
        private static final int MSG_AUTH_FAILED  = 2;
    
        /** 保护的成员变量注释 */
        protected Object mObject0;
    
        /** 私有的成员变量 mObject1 注释(同类型的成员变量可以分块并紧凑定义) */
        private Object mObject1;
        /** 私有的成员变量 mObject2 注释 */
        private Object mObject2;
        /** 私有的成员变量 mObject3 注释 */
        private Object mObject3;
    
        /**
         * 对于注释多于一行的,采用这种方式来
         * 定义该变量
         */
        private Object mObject4;
    
        /**
         * 公有方法描述...
         * 
         * @param param1  参数1描述...
         * @param param2  参数2描述...
         * @param paramXX 参数XX描述... (注意:请将参数、描述都对齐)
         */
        public void doSomething(int param1, float param2, String paramXX) {
            // ...implementation
        }
    
        /**
         * 保护方法描述...
         */
        protected void doSomething() {
            // ...implementation        
        }
    
        /**
         * 私有方法描述...
         * 
         * @param param1  参数1描述...
         * @param param2  参数2描述...
         */
        private void doSomethingInternal(int param1, float param2) {
            // ...implementation        
        }
    }
    
Android背锅侠

使用Hexo创建和管理Github Pages

Veröffentlicht am 2017-01-21

在Github上创建Github Pages

  • Github上面新建仓库:<username>.github.io,必须以这个格式命名。
  • 此时,你的GitHub个人站点其实已经建成了,没错,就这么简单。。

    如果这时候push一个index.html到站点根目录,就可以通过 https://<username>.github.io 访问你的个人主页了。
    但是一般不这么做,因为这样的站点太丑陋,也不想花时间完全自己去写一个博客网站。
    所以我们需要Hexo帮我们自动建站,请继续往下看。。

安装Hexo

  • 首先你电脑上需要先安装好NodeJS环境,自行百度。
  • 安装Hexo

    npm install hexo-cli -g
    #npm install hexo --save
    #如果命令无法运行,可以尝试更换taobao的npm源
    npm install -g cnpm --registry=https://registry.npm.taobao.org
  • 初始化Hexo配置

    #1. 创建一个Hexo站点目录,如 /blog/hexo,并cd到该目录。
    #2. 初始化Hexo
    $ hexo init
    $ npm install
    #3. 本地生成站点,查看效果
    $ hexo generate
    $ hexo server
    ## 简化命令 ##
    $ hexo g
    $ hexo s
    ## 浏览器中输入localhost:4000,查看效果

部署Hexo站点到Github Pages上

  • 配置SSH秘钥

    1. cd 到 ~/.ssh
    2. 如果目录不存在,需要新建

      $ ssh-keygen -t rsa -C "your_email@example.com"
      #这将按照你提供的邮箱地址,创建一对密钥
      Generating public/private rsa key pair.
      Enter file in which to save the key (/c/Users/you/.ssh/id_rsa): [Press enter]
      #直接回车,之后提示输入密码和确认密码,也是直接回车两次。
      #此时屏幕输出:
      Your identification has been saved in /c/Users/you/.ssh/id_rsa.
      Your public key has been saved in /c/Users/you/.ssh/id_rsa.pub.
      The key fingerprint is:
      01:0f:f4:3b:ca:85:d6:17:a1:7d:f0:68:9d:f0:a2:db your_email@example.com
    3. 在GitHub账户中添加你的公钥

      $ clip < ~/.ssh/id_rsa.pub
      或者手动复制id_rsa.pub文件内容。
      登录GitHub,到个人->设置->SSH keys中粘贴刚才的公钥,title随便。
      点击 [Add SSH key]
    4. 测试SSH秘钥是否添加成功

      $ ssh -T git@github.com
      如果是下面的反馈:
      The authenticity of host 'github.com (207.97.227.239)' can't be established.
      RSA key fingerprint is 16:27:ac:a5:76:28:2d:36:63:1b:56:4d:eb:df:a6:48.
      Are you sure you want to continue connecting (yes/no)?
      不要紧张,输入yes就好,然后会看到:
      Hi cnfeat! You've successfully authenticated, but GitHub does not provide shell access.
    5. 设置用户信息

      $ git config --global user.name "cnfeat" //用户名(将出现在git提交记录中)
      $ git config --global user.email "cnfeat@gmail.com" //自己的邮箱
  • 修改Hexo的部署路径,使其与github关联

    1. 修改Hexo根目录下的_config.yml文件,添加deploy字段

      # Deployment
      ## Docs: https://hexo.io/docs/deployment.html
      deploy:
      type: git
      repository: https://github.com/bbssyyuui/bbssyyuui.github.io.git
      branch: master
    2. 生成并部署站点

      $ hexo clean // 当出现一些奇怪错误的时候,可以尝试清空
      $ hexo generate
      $ hexo deploy
      ## 简化命令 ##
      $ hexo g
      $ hexo d
      ## 更简化命令 ##
      $ hexo g -d
    3. 查看站点
      浏览器输入你的个人站点网址:https://bbssyyuui.github.io.git
      OK,大功告成!!!

安装Hexo的Markdown编辑插件 hexo-hey

hexo-hey这个插件是集成在Hexo站点中的,可以方便的在线后台编辑、管理和发布你的文章。

  • 安装hexo-hey

    $ npm install hexo-hey --save
  • 修改Hexo根目录下的_config.yml文件,添加admin字段

    # Admin
    admin:
    name: hexo
    password: hey
    secret: hey hexo
    expire: 60*1
    # cors: http://localhost:3000
  • 重新生成和预览站点

    $ hexo g
    $ hexo s
    # 通过访问 http://localhost:4000/admin 后台页面查看和管理
  • 重新生成和部署站点

    $ hexo g -d

更换Hexo主题

默认生成的Hexo站点是比较丑陋的,Hexo官网提供了很多主题供你使用。
这里推荐个不错的主题 hexo-theme-next

  • 下载主题

    $ cd hexo/themes
    $ git clone https://github.com/iissnan/hexo-theme-next.git
  • 修改主题配置

    # 设置主题
    # 修改Hexo根目录下的_config.yml文件中的theme字段
    theme: hexo-theme-next
    # 配置主题
    # 修改themes/hexo-theme-next/目录下的_config.yml,使其符合你的喜好。
    scheme: Pisces
    ...
    


参考:
https://hexo.io/zh-cn/docs/
https://my.oschina.net/ryaneLee/blog/638440
https://github.com/nihgwu/hexo-hey

Android背锅侠

Hello World

Veröffentlicht am 2017-01-21

Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.

Quick Start

Create a new post

$ hexo new "My New Post"

More info: Writing

Run server

$ hexo server

More info: Server

Generate static files

$ hexo generate

More info: Generating

Deploy to remote sites

$ hexo deploy

More info: Deployment

XiaoFeng

XiaoFeng

6 Artikel
© 2017 XiaoFeng
Erstellt mit Hexo
Theme - NexT.Pisces