public final class ApiClient {

    private ApiClient() {}

    public static final ApiService service = new Retrofit.Builder()
            .baseUrl(ApiDefine.API_BASE_URL)
            .client(new OkHttpClient.Builder()
                    .addInterceptor(createUserAgentInterceptor())
                    .addInterceptor(createHttpLoggingInterceptor())
                    .build())
            .addConverterFactory(GsonConverterFactory.create(EntityUtils.gson))
            .build()
            .create(ApiService.class);

    @NonNull
    private static Interceptor createUserAgentInterceptor() {
        return new Interceptor() {

            private static final String HEADER_USER_AGENT = "User-Agent";

            @NonNull
            @Override
            public Response intercept(@NonNull Chain chain) throws IOException {
                Request.Builder builder = chain.request().newBuilder();
                OkHttpHackUtils.setRequestHeaderLenient(builder, HEADER_USER_AGENT, ApiDefine.USER_AGENT);
                return chain.proceed(builder.build());
            }

        };
    }

    @NonNull
    private static Interceptor createHttpLoggingInterceptor() {
        HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
        loggingInterceptor.setLevel(BuildConfig.DEBUG ? HttpLoggingInterceptor.Level.BODY : HttpLoggingInterceptor.Level.NONE);
        return loggingInterceptor;
    }

} public class ForegroundCallback<T extends Result> implements Callback<T>, CallbackLifecycle<T> {

    private final WeakReference<Activity> activityWeakReference;

    public ForegroundCallback(@NonNull Activity activity) {
        activityWeakReference = new WeakReference<>(activity);
    }

    @Nullable
    protected final Activity getActivity() {
        return activityWeakReference.get();
    }

    @Override
    public final void onResponse(@NonNull Call<T> call, @NonNull Response<T> response) {
        Activity activity = getActivity();
        if (ActivityUtils.isAlive(activity)) {
            boolean interrupt;
            if (response.isSuccessful()) {
                interrupt = onResultOk(response.code(), response.headers(), response.body());
            } else {
                interrupt = onResultError(response.code(), response.headers(), ErrorResult.from(response));
            }
            if (!interrupt) {
                onFinish();
            }
        }
    }

    @Override
    public final void onFailure(@NonNull Call<T> call, @NonNull Throwable t) {
        Activity activity = getActivity();
        if (ActivityUtils.isAlive(activity)) {
            boolean interrupt;
            if (call.isCanceled()) {
                interrupt = onCallCancel();
            } else {
                interrupt = onCallException(t, ErrorResult.from(t));
            }
            if (!interrupt) {
                onFinish();
            }
        }
    }

    @Override
    public boolean onResultOk(int code, Headers headers, T result) {
        return false;
    }

    @Override
    public boolean onResultError(int code, Headers headers, ErrorResult errorResult) {
        return false;
    }

    @Override
    public boolean onCallCancel() {
        return false;
    }

    @Override
    public boolean onCallException(Throwable t, ErrorResult errorResult) {
        return false;
    }

    @Override
    public void onFinish() {}

} public class DataResult<Data> extends Result {

    private Data data;

    public Data getData() {
        return data;
    }

    public void setData(Data data) {
        this.data = data;
    }

} public class ErrorResult extends Result {

    @NonNull
    public static ErrorResult from(@NonNull Response response) {
        ErrorResult errorResult = null;
        ResponseBody errorBody = response.errorBody();
        if (errorBody != null) {
            try {
                errorResult = EntityUtils.gson.fromJson(errorBody.string(), ErrorResult.class);
            } catch (Throwable ignored) {}
        }
        if (errorResult == null) {
            errorResult = new ErrorResult();
            errorResult.setSuccess(false);
            switch (response.code()) {
                case 400:
                    errorResult.setMessage("非法请求");
                    break;
                case 403:
                    errorResult.setMessage("非法行为");
                    break;
                case 404:
                    errorResult.setMessage("未找到资源");
                    break;
                case 405:
                    errorResult.setMessage("非法请求方式");
                    break;
                case 408:
                    errorResult.setMessage("请求超时");
                    break;
                case 500:
                    errorResult.setMessage("服务器内部错误");
                    break;
                case 502:
                    errorResult.setMessage("服务器网关错误");
                    break;
                case 504:
                    errorResult.setMessage("服务器网关超时");
                    break;
                default:
                    errorResult.setMessage("未知响应：" + response.message());
                    break;
            }
        }
        return errorResult;
    }

    @NonNull
    public static ErrorResult from(@NonNull Throwable t) {
        ErrorResult errorResult = new ErrorResult();
        errorResult.setSuccess(false);
        if (t instanceof UnknownHostException || t instanceof ConnectException) {
            errorResult.setMessage("网络无法连接");
        } else if (t instanceof NoRouteToHostException) {
            errorResult.setMessage("无法访问网络");
        } else if (t instanceof SocketTimeoutException) {
            errorResult.setMessage("网络访问超时");
        } else if (t instanceof JsonSyntaxException) {
            errorResult.setMessage("响应数据格式错误");
        } else {
            errorResult.setMessage("未知错误：" + t.getLocalizedMessage());
        }
        return errorResult;
    }

    @SerializedName("error_msg")
    private String message;

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

} public class Topic extends TopicSimple {

    @SerializedName("author_id")
    private String authorId;

    private Tab tab;

    private String content;

    private boolean good;

    private boolean top;

    @SerializedName("reply_count")
    private int replyCount;

    @SerializedName("visit_count")
    private int visitCount;

    @SerializedName("create_at")
    private DateTime createAt;

    public String getAuthorId() {
        return authorId;
    }

    public void setAuthorId(String authorId) {
        this.authorId = authorId;
    }

    @NonNull
    public Tab getTab() {
        return tab == null ? Tab.unknown : tab; // 接口中有些话题没有 Tab 属性，这里保证 Tab 不为空
    }

    public void setTab(Tab tab) {
        this.tab = tab;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
        cleanContentCache();
    }

    public boolean isGood() {
        return good;
    }

    public void setGood(boolean good) {
        this.good = good;
    }

    public boolean isTop() {
        return top;
    }

    public void setTop(boolean top) {
        this.top = top;
    }

    public int getReplyCount() {
        return replyCount;
    }

    public void setReplyCount(int replyCount) {
        this.replyCount = replyCount;
    }

    public int getVisitCount() {
        return visitCount;
    }

    public void setVisitCount(int visitCount) {
        this.visitCount = visitCount;
    }

    public DateTime getCreateAt() {
        return createAt;
    }

    public void setCreateAt(DateTime createAt) {
        this.createAt = createAt;
    }

    /**
     * Html渲染缓存
     */

    @SerializedName("content_html")
    private String contentHtml;

    @SerializedName("content_summary")
    private String contentSummary;

    public void markSureHandleContent() {
        if (contentHtml == null || contentSummary == null) {
            Document document;
            if (ApiDefine.MD_RENDER) {
                document = FormatUtils.handleHtml(content);
            } else {
                document = FormatUtils.handleHtml(FormatUtils.renderMarkdown(content));
            }
            if (contentHtml == null) {
                contentHtml = document.body().html();
            }
            if (contentSummary == null) {
                contentSummary = document.body().text().trim();
            }
        }
    }

    public void cleanContentCache() {
        contentHtml = null;
        contentSummary = null;
    }

    public String getContentHtml() {
        markSureHandleContent();
        return contentHtml;
    }

    public String getContentSummary() {
        markSureHandleContent();
        return contentSummary;
    }

} public class User {

    @SerializedName("loginname")
    private String loginName;

    @SerializedName("avatar_url")
    private String avatarUrl;

    private String githubUsername;

    @SerializedName("create_at")
    private DateTime createAt;

    private int score;

    @SerializedName("recent_topics")
    private List<TopicSimple> recentTopicList;

    @SerializedName("recent_replies")
    private List<TopicSimple> recentReplyList;

    public String getLoginName() {
        return loginName;
    }

    public void setLoginName(String loginName) {
        this.loginName = loginName;
    }

    public String getAvatarUrl() { // 修复头像地址的历史遗留问题
        return FormatUtils.getCompatAvatarUrl(avatarUrl);
    }

    public void setAvatarUrl(String avatarUrl) {
        this.avatarUrl = avatarUrl;
    }

    public String getGithubUsername() {
        return githubUsername;
    }

    public void setGithubUsername(String githubUsername) {
        this.githubUsername = githubUsername;
    }

    public DateTime getCreateAt() {
        return createAt;
    }

    public void setCreateAt(DateTime createAt) {
        this.createAt = createAt;
    }

    public int getScore() {
        return score;
    }

    public void setScore(int score) {
        this.score = score;
    }

    public List<TopicSimple> getRecentTopicList() {
        return recentTopicList;
    }

    public void setRecentTopicList(List<TopicSimple> recentTopicList) {
        this.recentTopicList = recentTopicList;
    }

    public List<TopicSimple> getRecentReplyList() {
        return recentReplyList;
    }

    public void setRecentReplyList(List<TopicSimple> recentReplyList) {
        this.recentReplyList = recentReplyList;
    }

} public interface IUserDetailPresenter {

    void getUserAsyncTask(@NonNull String loginName);

} public final class ActivityUtils {

    private ActivityUtils() {}

    public static boolean isAlive(@Nullable Activity activity) {
        return activity != null && !(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 && activity.isDestroyed()) && !activity.isFinishing();
    }

} public interface IUserDetailView {

    void onGetUserOk(@NonNull User user);

    void onGetCollectTopicListOk(@NonNull List<Topic> topicList);

    void onGetUserError(@NonNull String message);

    void onGetUserStart();

    void onGetUserFinish();

} public final class HandlerUtils {

    private HandlerUtils() {}

    public static final Handler handler = new Handler(Looper.getMainLooper());

}
import android.app.Activity;
import android.support.annotation.NonNull;
import org.cnodejs.android.md.R;
import org.cnodejs.android.md.model.api.ApiClient;
import org.cnodejs.android.md.model.api.ForegroundCallback;
import org.cnodejs.android.md.model.entity.DataResult;
import org.cnodejs.android.md.model.entity.ErrorResult;
import org.cnodejs.android.md.model.entity.Topic;
import org.cnodejs.android.md.model.entity.User;
import org.cnodejs.android.md.presenter.contract.IUserDetailPresenter;
import org.cnodejs.android.md.ui.util.ActivityUtils;
import org.cnodejs.android.md.ui.view.IUserDetailView;
import org.cnodejs.android.md.util.HandlerUtils;
import java.util.List;
import okhttp3.Headers;
package org.cnodejs.android.md.presenter.implement;





public class UserDetailPresenter implements IUserDetailPresenter {

    private final Activity activity;
    private final IUserDetailView userDetailView;

    private boolean loading = false;

    public UserDetailPresenter(@NonNull Activity activity, @NonNull IUserDetailView userDetailView) {
        this.activity = activity;
        this.userDetailView = userDetailView;
    }

    @Override
    public void getUserAsyncTask(@NonNull String loginName) {
        if (!loading) {
            loading = true;
            userDetailView.onGetUserStart();
            ApiClient.service.getUser(loginName).enqueue(new ForegroundCallback<DataResult<User>>(activity) {

                private long startLoadingTime = System.currentTimeMillis();

                private long getPostTime() {
                    long postTime = 1000 - (System.currentTimeMillis() - startLoadingTime);
                    if (postTime > 0) {
                        return postTime;
                    } else {
                        return 0;
                    }
                }

                @Override
                public boolean onResultOk(int code, Headers headers, final DataResult<User> result) {
                    HandlerUtils.handler.postDelayed(new Runnable() {