From f3eedbe7027de415ace825b9a6d6c4005402d184 Mon Sep 17 00:00:00 2001 From: MarkFrank01 <1123212438@qq.com> Date: Thu, 5 Jan 2017 18:35:20 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BA=86=E4=B8=80=E4=BA=9BUt?= =?UTF-8?q?ils=E5=B9=B6=E5=8A=A0=E5=85=A5=E7=BC=93=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/AndroidManifest.xml | 14 +- .../java/com/tufei/architecturedemo/App.java | 31 +- .../base/mvp/BaseActivity.java | 34 +- .../base/mvp/BaseFragment.java | 2 +- .../constants/NetConstants.java | 16 +- .../core/retrofit/RxUtil.java | 182 ++++ .../retrofit/function/RetryWithDelay.java | 71 ++ .../interceptor/HeaderInterceptor.java | 41 + .../interceptor/ParamsInterceptor.java | 56 ++ .../core/retrofit/result/Result.java | 37 + .../core/retrofit/result/ResultCallBack.java | 31 + .../core/retrofit/result/ResultObserver.java | 60 ++ .../di/ActivityBindingModule.java | 11 - .../architecturedemo/di/AppComponent.java | 12 +- .../mvp/main/MainPresenter.java | 3 +- .../architecturedemo/mvp/model/LoginTask.java | 22 + .../mvp/model/VersionTask.java | 41 + .../mvp/model/bean/VersionBean.java | 63 ++ .../mvp/model/\345\244\207\346\263\250" | 53 + .../mvp/splash/SplashActivity.java | 21 +- .../mvp/splash/SplashContract.java | 4 - .../mvp/splash/SplashPresenter.java | 10 +- .../mvp/splash/\345\244\207\346\263\250" | 1 + .../architecturedemo/net/DownNetConfig.java | 12 +- .../architecturedemo/net/HttpService.java | 56 +- .../tufei/architecturedemo/net/NetModule.java | 16 +- .../architecturedemo/net/NetRepository.java | 38 + .../net/\345\244\207\346\263\250" | 23 + .../architecturedemo/utils/CacheUtil.java | 952 ++++++++++++++++++ .../architecturedemo/utils/CloseUtil.java | 50 + .../architecturedemo/utils/DateUtil.java | 45 + .../utils/RetrofitFactory.java | 59 ++ .../tufei/architecturedemo/utils/RxUtil.java | 40 - .../architecturedemo/utils/StorageUtil.java | 82 ++ .../architecturedemo/utils/SystemUtil.java | 110 ++ .../tufei/architecturedemo/utils/Utils.java | 88 ++ .../widget/DateTimePicker.java | 108 ++ app/src/main/res/layout/activity_splash.xml | 51 +- app/src/main/res/values/arrays.xml | 16 + app/src/main/res/values/strings.xml | 12 + build.gradle | 14 +- 41 files changed, 2351 insertions(+), 237 deletions(-) create mode 100644 app/src/main/java/com/tufei/architecturedemo/core/retrofit/RxUtil.java create mode 100644 app/src/main/java/com/tufei/architecturedemo/core/retrofit/function/RetryWithDelay.java create mode 100644 app/src/main/java/com/tufei/architecturedemo/core/retrofit/interceptor/HeaderInterceptor.java create mode 100644 app/src/main/java/com/tufei/architecturedemo/core/retrofit/interceptor/ParamsInterceptor.java create mode 100644 app/src/main/java/com/tufei/architecturedemo/core/retrofit/result/Result.java create mode 100644 app/src/main/java/com/tufei/architecturedemo/core/retrofit/result/ResultCallBack.java create mode 100644 app/src/main/java/com/tufei/architecturedemo/core/retrofit/result/ResultObserver.java create mode 100644 app/src/main/java/com/tufei/architecturedemo/mvp/model/LoginTask.java create mode 100644 app/src/main/java/com/tufei/architecturedemo/mvp/model/VersionTask.java create mode 100644 app/src/main/java/com/tufei/architecturedemo/mvp/model/bean/VersionBean.java create mode 100644 "app/src/main/java/com/tufei/architecturedemo/mvp/model/\345\244\207\346\263\250" create mode 100644 "app/src/main/java/com/tufei/architecturedemo/mvp/splash/\345\244\207\346\263\250" create mode 100644 app/src/main/java/com/tufei/architecturedemo/net/NetRepository.java create mode 100644 "app/src/main/java/com/tufei/architecturedemo/net/\345\244\207\346\263\250" create mode 100644 app/src/main/java/com/tufei/architecturedemo/utils/CacheUtil.java create mode 100644 app/src/main/java/com/tufei/architecturedemo/utils/CloseUtil.java create mode 100644 app/src/main/java/com/tufei/architecturedemo/utils/DateUtil.java create mode 100644 app/src/main/java/com/tufei/architecturedemo/utils/RetrofitFactory.java create mode 100644 app/src/main/java/com/tufei/architecturedemo/utils/StorageUtil.java create mode 100644 app/src/main/java/com/tufei/architecturedemo/utils/SystemUtil.java create mode 100644 app/src/main/java/com/tufei/architecturedemo/utils/Utils.java create mode 100644 app/src/main/java/com/tufei/architecturedemo/widget/DateTimePicker.java create mode 100644 app/src/main/res/values/arrays.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 54cba75..e58a8af 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,30 +2,24 @@ - - - + - - - - - + - + diff --git a/app/src/main/java/com/tufei/architecturedemo/App.java b/app/src/main/java/com/tufei/architecturedemo/App.java index b8526ad..f84bf1e 100644 --- a/app/src/main/java/com/tufei/architecturedemo/App.java +++ b/app/src/main/java/com/tufei/architecturedemo/App.java @@ -2,6 +2,7 @@ import com.tufei.architecturedemo.di.AppComponent; import com.tufei.architecturedemo.di.DaggerAppComponent; +import com.tufei.architecturedemo.utils.Utils; import dagger.android.AndroidInjector; import dagger.android.DaggerApplication; @@ -10,7 +11,7 @@ * 我们创建一个定制的{@link App}类,继承自{@link DaggerApplication}。 * 然后覆盖{@link DaggerApplication#applicationInjector()}方法,告诉Dagger如何为我们创建一个单例的Component。 * 我们完全不用调用`component.inject(this)`,因为{@link DaggerApplication}将会帮我们完成这些操作。 - * + *

* We create a custom {@link App} class that extends {@link DaggerApplication}. * We then override applicationInjector() which tells Dagger how to make our @Singleton Component * We never have to call `component.inject(this)` as {@link DaggerApplication} will do that for us. @@ -19,11 +20,37 @@ * @date 2017/9/11 */ -public class App extends DaggerApplication { +public abstract class App extends DaggerApplication { + private static App appContext; + @Override protected AndroidInjector applicationInjector() { AppComponent appComponent = DaggerAppComponent.builder().application(this).build(); appComponent.inject(this); return appComponent; } + + @Override + public void onCreate() { + super.onCreate(); + appContext = this; + //初始化工具类 + Utils.init(this, debugMode()); + //初始化数据库 +// Realm.init(this); + } + + public static App getAppContext() { + return appContext; + } + + /** + * 设置debug模式,推荐返回app的BuildConfig.DEBUG + */ + public abstract boolean debugMode(); + + /** + * 配置应用文件保存的根路径 + */ + public abstract String getAppRootPath(); } diff --git a/app/src/main/java/com/tufei/architecturedemo/base/mvp/BaseActivity.java b/app/src/main/java/com/tufei/architecturedemo/base/mvp/BaseActivity.java index 07f0c8a..ccc1273 100644 --- a/app/src/main/java/com/tufei/architecturedemo/base/mvp/BaseActivity.java +++ b/app/src/main/java/com/tufei/architecturedemo/base/mvp/BaseActivity.java @@ -1,6 +1,5 @@ package com.tufei.architecturedemo.base.mvp; -import android.app.Dialog; import android.content.Intent; import android.os.Bundle; import android.support.annotation.LayoutRes; @@ -20,7 +19,7 @@ /** * BaseActivity要继承{@link DaggerAppCompatActivity},而不再是{@link AppCompatActivity} * 或者你点开{@link DaggerAppCompatActivity},把他的不多的代码放到你的BaseActivity里, - * 继续继承AppCompatActivity,或者是你想要继承的类。 + * 继续extends AppCompatActivity(傻吧你?)。 * * @author tufei * @date 2017/6/27 @@ -32,7 +31,6 @@ public abstract class BaseActivity extends DaggerAppCompatActivity { */ protected String TAG = getClass().getSimpleName(); private List presenterList; - private List

mDialogList; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { @@ -74,7 +72,7 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { public void attach(T t) { if (!(this instanceof IBaseView)) { throw new RuntimeException(this.getClass().getSimpleName() + - " didn't implement the interface for View that base on IBaseView!"); + "didn't implement the interface for View that base on IBaseView!"); } //也考虑了 一个view同时绑定多个presenter的情况 (つд⊂)其实基本用不到 if (presenterList == null) { @@ -95,32 +93,6 @@ public void detach(T t) { t.onDetachView(); } - /** - * 存储dialog - * - * @param dialog - */ - public void addDialog(Dialog dialog) { - if (mDialogList == null) { - mDialogList = new ArrayList<>(); - } - mDialogList.add(dialog); - } - - /** - * 关掉所有dialog - */ - public void dismissAllDialog() { - if (mDialogList != null && mDialogList.size() > 0) { - for (Dialog dialog : mDialogList) { - if (dialog != null && dialog.isShowing()) { - dialog.dismiss(); - } - } - mDialogList.clear(); - } - } - /** * 界面跳转,不需要添加额外信息的时候(一切为了偷懒) * @@ -135,8 +107,6 @@ public void startActivity(@NonNull Class clazz) { @Override protected void onDestroy() { ActivityCollector.removeActivity(this); - //关闭所有没有及时关闭的dialog - dismissAllDialog(); //执行presenter里的onDetachView方法 if (presenterList != null && presenterList.size() > 0) { Iterator iterator = presenterList.iterator(); diff --git a/app/src/main/java/com/tufei/architecturedemo/base/mvp/BaseFragment.java b/app/src/main/java/com/tufei/architecturedemo/base/mvp/BaseFragment.java index 0e875fc..1281ece 100644 --- a/app/src/main/java/com/tufei/architecturedemo/base/mvp/BaseFragment.java +++ b/app/src/main/java/com/tufei/architecturedemo/base/mvp/BaseFragment.java @@ -20,7 +20,7 @@ /** * BaseFragment要继承{@link DaggerFragment},而不再是{@link android.support.v4.app.Fragment} *

- * 1)如果你要使用的是{@link android.support.v4.app.Fragment},那就继承{@link dagger.android.support.DaggerFragment} + * 1)如果你要使用的是{@link android.support.v4.app.Fragment},那就继承{@link DaggerFragment} * 2)如果你要使用的是{@link android.app.Fragment},那就继承{@link dagger.android.DaggerFragment} * 3)或者你两者都要用..那就点开上述两个类,把它们的代码都拷贝过来放在你的BaseFragment就是了.. * diff --git a/app/src/main/java/com/tufei/architecturedemo/constants/NetConstants.java b/app/src/main/java/com/tufei/architecturedemo/constants/NetConstants.java index ca550c5..f90d678 100644 --- a/app/src/main/java/com/tufei/architecturedemo/constants/NetConstants.java +++ b/app/src/main/java/com/tufei/architecturedemo/constants/NetConstants.java @@ -5,8 +5,22 @@ * @date 2017/9/11 */ +@SuppressWarnings("unused") public class NetConstants { public static final long HTTP_CONNECT_TIMEOUT = 5000; public static final long HTTP_READ_TIMEOUT = 5000; - public static final String BASE_URL = "https://aip.baidubce.com/rest/2.0/face/v2/"; + public static final String BASE_URL = "http://www.nishabi.com"; + + //add some constants + public static final int NET_CODE_SUCCESS = 0; + public static final int NET_CODE_ERROR = 1; + + public static final int NET_CODE_CONNECT = 400; + public static final int NET_CODE_UNKNOWN_HOST = 401; + public static final int NET_CODE_SOCKET_TIMEOUT = 402; + + public static final String CONNECT_EXCEPTION = "网络连接异常,请检查您的网络状态"; + public static final String SOCKET_TIMEOUT_EXCEPTION = "网络连接超时,请检查您的网络状态,稍后重试"; + public static final String UNKNOWN_HOST_EXCEPTION = "与服务器连接失败"; + public static final String EMPTY_RESPONSE_EXCEPTION = "无效的返回"; } diff --git a/app/src/main/java/com/tufei/architecturedemo/core/retrofit/RxUtil.java b/app/src/main/java/com/tufei/architecturedemo/core/retrofit/RxUtil.java new file mode 100644 index 0000000..bb1154a --- /dev/null +++ b/app/src/main/java/com/tufei/architecturedemo/core/retrofit/RxUtil.java @@ -0,0 +1,182 @@ +package com.tufei.architecturedemo.core.retrofit; + +import android.app.Dialog; +import android.content.DialogInterface; +import android.support.annotation.NonNull; + +import com.trello.rxlifecycle2.LifecycleProvider; +import com.trello.rxlifecycle2.LifecycleTransformer; +import com.trello.rxlifecycle2.android.ActivityEvent; +import com.trello.rxlifecycle2.android.FragmentEvent; +import com.trello.rxlifecycle2.components.support.RxAppCompatActivity; +import com.trello.rxlifecycle2.components.support.RxFragment; +import com.tufei.architecturedemo.core.retrofit.function.RetryWithDelay; + +import java.util.concurrent.TimeUnit; + +import io.reactivex.Observable; +import io.reactivex.ObservableSource; +import io.reactivex.ObservableTransformer; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.Disposable; +import io.reactivex.functions.Action; +import io.reactivex.functions.Consumer; +import io.reactivex.schedulers.Schedulers; + +/** + * Created by wjc on 2018/1/2.RxLifecycle绑定工具类 + */ +@SuppressWarnings("unused") +public class RxUtil { + private static LifecycleTransformer bindToLifecycle(LifecycleProvider provider) { + if (provider instanceof RxAppCompatActivity) { + return ((RxAppCompatActivity) provider).bindToLifecycle(); + } else if (provider instanceof RxFragment) { + return ((RxFragment) provider).bindToLifecycle(); + } else { + throw new IllegalArgumentException("class must extents RxAppCompatActivity or RxFragment"); + } + } + + private static LifecycleTransformer bindToLifecycle(LifecycleProvider provider, ActivityEvent event) { + if (provider instanceof RxAppCompatActivity) { + return ((RxAppCompatActivity) provider).bindUntilEvent(event); + } else { + throw new IllegalArgumentException("class must extents RxAppCompatActivity"); + } + } + + private static LifecycleTransformer bindToLifecycle(LifecycleProvider provider, FragmentEvent event) { + if (provider instanceof RxFragment) { + return ((RxFragment) provider).bindUntilEvent(event); + } else { + throw new IllegalArgumentException("class must extents RxFragment"); + } + } + + public static ObservableTransformer applySchedulers(final LifecycleProvider provider) { + return new ObservableTransformer() { + @Override public ObservableSource apply(@NonNull Observable upstream) { + return upstream + .retryWhen(new RetryWithDelay()) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .compose(RxUtil.bindToLifecycle(provider)); + + } + }; + } + + public static ObservableTransformer applySchedulers(final LifecycleProvider provider, final ActivityEvent event) { + return new ObservableTransformer() { + @Override public ObservableSource apply(@NonNull Observable upstream) { + return upstream + .retryWhen(new RetryWithDelay()) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .compose(RxUtil.bindToLifecycle(provider, event)); + + } + }; + } + + public static ObservableTransformer applySchedulers(final LifecycleProvider provider, final FragmentEvent event) { + return new ObservableTransformer() { + @Override public ObservableSource apply(@NonNull Observable upstream) { + return upstream + .retryWhen(new RetryWithDelay()) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .compose(RxUtil.bindToLifecycle(provider, event)); + + } + }; + } + + public static ObservableTransformer applySchedulers(final LifecycleProvider provider, @NonNull final Dialog dialog) { + return new ObservableTransformer() { + @Override public ObservableSource apply(@NonNull Observable upstream) { + return upstream + .delay(1, TimeUnit.SECONDS) + .retryWhen(new RetryWithDelay()) + .subscribeOn(Schedulers.io()) + .doOnSubscribe(new Consumer() { + @Override + public void accept(@NonNull final Disposable disposable) throws Exception { + dialog.setOnCancelListener(new DialogInterface.OnCancelListener() { + @Override public void onCancel(DialogInterface dialog) { + disposable.dispose(); + } + }); + dialog.show(); + } + }) + .observeOn(AndroidSchedulers.mainThread()) + .doOnTerminate(new Action() { + @Override public void run() throws Exception { + dialog.dismiss(); + } + }) + .compose(RxUtil.bindToLifecycle(provider)); + } + }; + } + + public static ObservableTransformer applySchedulers(final LifecycleProvider provider, final ActivityEvent event, @NonNull final Dialog dialog) { + return new ObservableTransformer() { + @Override public ObservableSource apply(@NonNull Observable upstream) { + return upstream + .delay(1, TimeUnit.SECONDS) + .retryWhen(new RetryWithDelay()) + .subscribeOn(Schedulers.io()) + .doOnSubscribe(new Consumer() { + @Override + public void accept(@NonNull final Disposable disposable) throws Exception { + dialog.setOnCancelListener(new DialogInterface.OnCancelListener() { + @Override public void onCancel(DialogInterface dialog) { + disposable.dispose(); + } + }); + dialog.show(); + } + }) + .observeOn(AndroidSchedulers.mainThread()) + .doOnTerminate(new Action() { + @Override public void run() throws Exception { + dialog.dismiss(); + } + }) + .compose(RxUtil.bindToLifecycle(provider, event)); + } + }; + } + + public static ObservableTransformer applySchedulers(final LifecycleProvider provider, final FragmentEvent event, @NonNull final Dialog dialog) { + return new ObservableTransformer() { + @Override public ObservableSource apply(@NonNull Observable upstream) { + return upstream + .delay(1, TimeUnit.SECONDS) + .retryWhen(new RetryWithDelay(2,5000)) + .subscribeOn(Schedulers.io()) + .doOnSubscribe(new Consumer() { + @Override + public void accept(@NonNull final Disposable disposable) throws Exception { + dialog.setOnCancelListener(new DialogInterface.OnCancelListener() { + @Override public void onCancel(DialogInterface dialog) { + disposable.dispose(); + } + }); + dialog.show(); + } + }) + .observeOn(AndroidSchedulers.mainThread()) + .doOnTerminate(new Action() { + @Override public void run() throws Exception { + dialog.dismiss(); + } + }) + .compose(RxUtil.bindToLifecycle(provider, event)); + } + }; + } +} diff --git a/app/src/main/java/com/tufei/architecturedemo/core/retrofit/function/RetryWithDelay.java b/app/src/main/java/com/tufei/architecturedemo/core/retrofit/function/RetryWithDelay.java new file mode 100644 index 0000000..4ae73c0 --- /dev/null +++ b/app/src/main/java/com/tufei/architecturedemo/core/retrofit/function/RetryWithDelay.java @@ -0,0 +1,71 @@ +package com.tufei.architecturedemo.core.retrofit.function; + + +import java.net.ConnectException; +import java.net.SocketTimeoutException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import io.reactivex.Observable; +import io.reactivex.ObservableSource; +import io.reactivex.annotations.NonNull; +import io.reactivex.functions.BiFunction; +import io.reactivex.functions.Function; +import retrofit2.HttpException; + +/** + * Created by wjc on 2018/1/2.网络请求失败重试 + */ + +public class RetryWithDelay implements Function, Observable> { + private int maxRetries = 3; + private long retryDelayMillis = 5000; + private long increaseDelay = 5000; + + public RetryWithDelay() { + } + + public RetryWithDelay(int maxRetries, int retryDelayMillis) { + this.maxRetries = maxRetries; + this.retryDelayMillis = retryDelayMillis; + } + + public RetryWithDelay(int maxRetries, long retryDelayMillis, long increaseDelay) { + this.maxRetries = maxRetries; + this.retryDelayMillis = retryDelayMillis; + this.increaseDelay = increaseDelay; + } + + @Override + public Observable apply(@NonNull Observable observable) throws Exception { + return observable + .zipWith(Observable.range(1, maxRetries + 1), new BiFunction() { + @Override + public Wrapper apply(Throwable throwable, Integer integer) { + return new Wrapper(throwable, integer); + } + }).flatMap(new Function>() { + @Override + public ObservableSource apply(Wrapper wrapper) throws Exception { + if ((wrapper.throwable instanceof ConnectException + || wrapper.throwable instanceof SocketTimeoutException + || wrapper.throwable instanceof TimeoutException + || wrapper.throwable instanceof HttpException) + && wrapper.index < maxRetries + 1) { //如果超出重试次数也抛出错误,否则默认是会进入onCompleted + return Observable.timer(retryDelayMillis + (wrapper.index - 1) * increaseDelay, TimeUnit.MILLISECONDS); + } + return Observable.error(wrapper.throwable); + } + }); + } + + private class Wrapper { + private int index; + private Throwable throwable; + + Wrapper(Throwable throwable, int index) { + this.index = index; + this.throwable = throwable; + } + } +} diff --git a/app/src/main/java/com/tufei/architecturedemo/core/retrofit/interceptor/HeaderInterceptor.java b/app/src/main/java/com/tufei/architecturedemo/core/retrofit/interceptor/HeaderInterceptor.java new file mode 100644 index 0000000..aa05e2f --- /dev/null +++ b/app/src/main/java/com/tufei/architecturedemo/core/retrofit/interceptor/HeaderInterceptor.java @@ -0,0 +1,41 @@ +package com.tufei.architecturedemo.core.retrofit.interceptor; + +import android.text.TextUtils; +import android.util.ArrayMap; + +import java.io.IOException; + +import okhttp3.Interceptor; +import okhttp3.Request; +import okhttp3.Response; + +/** + * Created by wjc on 2017/12/29.请求头拦截器 + */ + +public class HeaderInterceptor implements Interceptor { + private ArrayMap headers; + + public HeaderInterceptor(ArrayMap headers) { + this.headers = headers; + } + + @Override public Response intercept(Chain chain) throws IOException { + Request.Builder requestBuilder = chain.request().newBuilder(); + //如果公共请求头不为空,则构建新的请求 + if (headers != null) { + for (String key : headers.keySet()) { + requestBuilder.addHeader(key, headers.get(key)); + } + } + Request request = requestBuilder.build(); + Response.Builder responseBuilder = chain.proceed(request).newBuilder(); + if (!TextUtils.isEmpty(request.cacheControl().toString())) { + responseBuilder + .removeHeader("Pragma") + .removeHeader("Cache-Control") + .header("Cache-Control", "public, max-age=" + request.cacheControl().maxAgeSeconds()); + } + return responseBuilder.build(); + } +} diff --git a/app/src/main/java/com/tufei/architecturedemo/core/retrofit/interceptor/ParamsInterceptor.java b/app/src/main/java/com/tufei/architecturedemo/core/retrofit/interceptor/ParamsInterceptor.java new file mode 100644 index 0000000..1418531 --- /dev/null +++ b/app/src/main/java/com/tufei/architecturedemo/core/retrofit/interceptor/ParamsInterceptor.java @@ -0,0 +1,56 @@ +package com.tufei.architecturedemo.core.retrofit.interceptor; + +import android.util.ArrayMap; + +import java.io.IOException; + +import okhttp3.FormBody; +import okhttp3.HttpUrl; +import okhttp3.Interceptor; +import okhttp3.Request; +import okhttp3.Response; + +/** + * Created by wjc on 2017/12/29. 公共请求参数拦截器 + */ + +public class ParamsInterceptor implements Interceptor { + private ArrayMap params; + + public ParamsInterceptor(ArrayMap params) { + this.params = params; + } + + @Override public Response intercept(Chain chain) throws IOException { + Request oldRequest = chain.request(); + //如果公共请求参数不为空,则构建新的请求 + if (params != null) { + Request.Builder newRequestBuilder = oldRequest.newBuilder(); + //GET请求则使用HttpUrl.Builder来构建 + if ("GET".equalsIgnoreCase(oldRequest.method())) { + HttpUrl.Builder httpUrlBuilder = oldRequest.url().newBuilder(); + for (String key : params.keySet()) { + httpUrlBuilder.addQueryParameter(key, params.get(key)); + } + newRequestBuilder.url(httpUrlBuilder.build()); + } else { + //如果原请求是表单请求 + if (oldRequest.body() instanceof FormBody) { + FormBody.Builder formBodyBuilder = new FormBody.Builder(); + for (String key : params.keySet()) { + formBodyBuilder.add(key, params.get(key)); + } + FormBody oldFormBody = (FormBody) oldRequest.body(); + int size = oldFormBody.size(); + for (int i = 0; i < size; i++) { + formBodyBuilder.add(oldFormBody.name(i), oldFormBody.value(i)); + } + newRequestBuilder.post(formBodyBuilder.build()); + } + // TODO: 2017/3/24 处理其它类型的request.body + } + return chain.proceed(newRequestBuilder.build()); + } + return chain.proceed(oldRequest); + } +} diff --git a/app/src/main/java/com/tufei/architecturedemo/core/retrofit/result/Result.java b/app/src/main/java/com/tufei/architecturedemo/core/retrofit/result/Result.java new file mode 100644 index 0000000..815c051 --- /dev/null +++ b/app/src/main/java/com/tufei/architecturedemo/core/retrofit/result/Result.java @@ -0,0 +1,37 @@ +package com.tufei.architecturedemo.core.retrofit.result; + +import java.io.Serializable; + +/** + * Created by wjc on 2018/1/2. 网络请求结果 + */ +@SuppressWarnings("unused") +public class Result implements Serializable { + private int code; + private String msg; + private T result; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getMsg() { + return msg; + } + + public void setMsg(String msg) { + this.msg = msg; + } + + public T getResult() { + return result; + } + + public void setResult(T result) { + this.result = result; + } +} diff --git a/app/src/main/java/com/tufei/architecturedemo/core/retrofit/result/ResultCallBack.java b/app/src/main/java/com/tufei/architecturedemo/core/retrofit/result/ResultCallBack.java new file mode 100644 index 0000000..b72fa14 --- /dev/null +++ b/app/src/main/java/com/tufei/architecturedemo/core/retrofit/result/ResultCallBack.java @@ -0,0 +1,31 @@ +package com.tufei.architecturedemo.core.retrofit.result; + +import com.tufei.architecturedemo.constants.NetConstants; + +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; + +/** + * Created by wjc on 2018/1/2.ResultCallBack + */ + +public abstract class ResultCallBack implements Callback> { + @Override public void onResponse(Call> call, Response> response) { + if (response != null && response.body()!=null) { + if (response.body().getCode() == NetConstants.NET_CODE_SUCCESS) { + handlerResult(true, null, response.body().getResult()); + } else { + handlerResult(false, new Throwable(response.body().getMsg()), null); + } + } else { + handlerResult(false, new Throwable(NetConstants.EMPTY_RESPONSE_EXCEPTION), null); + } + } + + @Override public void onFailure(Call> call, Throwable t) { + handlerResult(false, t, null); + } + + public abstract void handlerResult(boolean success, Throwable throwable, T t); +} diff --git a/app/src/main/java/com/tufei/architecturedemo/core/retrofit/result/ResultObserver.java b/app/src/main/java/com/tufei/architecturedemo/core/retrofit/result/ResultObserver.java new file mode 100644 index 0000000..2514c0c --- /dev/null +++ b/app/src/main/java/com/tufei/architecturedemo/core/retrofit/result/ResultObserver.java @@ -0,0 +1,60 @@ +package com.tufei.architecturedemo.core.retrofit.result; + + +import com.tufei.architecturedemo.constants.NetConstants; +import java.net.ConnectException; +import java.net.SocketTimeoutException; +import java.net.UnknownHostException; + +import io.reactivex.Observer; +import io.reactivex.disposables.Disposable; + +/** + * Created by wjc on 2018/1/2. 网络请求结果处理 + */ +@SuppressWarnings("unused") +public abstract class ResultObserver implements Observer> { + @Override public void onSubscribe(Disposable d) { + + } + + @Override public void onNext(Result result) { + if (result != null) { + if (result.getCode() == NetConstants.NET_CODE_SUCCESS) { + handlerResult(result.getResult()); + } else { + handlerError(result.getCode(), result.getMsg()); + } + } else { + handlerError(NetConstants.NET_CODE_ERROR, NetConstants.EMPTY_RESPONSE_EXCEPTION); + } + } + + @Override public void onError(Throwable e) { + if (e instanceof SocketTimeoutException) { + handlerError(NetConstants.NET_CODE_SOCKET_TIMEOUT, NetConstants.SOCKET_TIMEOUT_EXCEPTION); + } else if (e instanceof ConnectException) { + handlerError(NetConstants.NET_CODE_CONNECT, NetConstants.CONNECT_EXCEPTION); + } else if (e instanceof UnknownHostException) { + handlerError(NetConstants.NET_CODE_UNKNOWN_HOST, NetConstants.UNKNOWN_HOST_EXCEPTION); + } else { + handlerError(NetConstants.NET_CODE_ERROR, e.getMessage()); + } + } + + @Override public void onComplete() { + + } + + /** + * 返回正常数据 + */ + public abstract void handlerResult(T t); + + /** + * 返回业务错误 + */ + public void handlerError(int code, String msg) { + } + +} diff --git a/app/src/main/java/com/tufei/architecturedemo/di/ActivityBindingModule.java b/app/src/main/java/com/tufei/architecturedemo/di/ActivityBindingModule.java index 5ba1c53..f7f510b 100644 --- a/app/src/main/java/com/tufei/architecturedemo/di/ActivityBindingModule.java +++ b/app/src/main/java/com/tufei/architecturedemo/di/ActivityBindingModule.java @@ -1,9 +1,6 @@ package com.tufei.architecturedemo.di; import com.tufei.architecturedemo.mvp.EmptyActivity; -import com.tufei.architecturedemo.mvp.Single.SingleActivity; -import com.tufei.architecturedemo.mvp.face.FaceActivity; -import com.tufei.architecturedemo.mvp.face.FaceModule; import com.tufei.architecturedemo.mvp.main.MainActivity; import com.tufei.architecturedemo.mvp.main.MainModule; import com.tufei.architecturedemo.mvp.splash.SplashActivity; @@ -39,14 +36,6 @@ public abstract class ActivityBindingModule { @ContributesAndroidInjector(modules = {MainModule.class}) abstract MainActivity mainActivity(); - @ActivityScoped - @ContributesAndroidInjector(modules = {FaceModule.class}) - abstract FaceActivity faceActivity(); - - @ActivityScoped - @ContributesAndroidInjector() - abstract SingleActivity singleActivity(); - /** * 如果你的Activity不需要任何Module,即Activity简单到不需要注入任何东西的时候,也要在这里声明它 * 除非你的Activity不再是继承自{@link dagger.android.support.DaggerAppCompatActivity} diff --git a/app/src/main/java/com/tufei/architecturedemo/di/AppComponent.java b/app/src/main/java/com/tufei/architecturedemo/di/AppComponent.java index 2d255f3..8a0a4e9 100644 --- a/app/src/main/java/com/tufei/architecturedemo/di/AppComponent.java +++ b/app/src/main/java/com/tufei/architecturedemo/di/AppComponent.java @@ -3,8 +3,6 @@ import android.app.Application; import com.tufei.architecturedemo.App; -import com.tufei.architecturedemo.mvp.Single.SingleModule; -import com.tufei.architecturedemo.net.HttpService; import com.tufei.architecturedemo.net.NetModule; import javax.inject.Singleton; @@ -28,19 +26,15 @@ * is the module from Dagger.Android that helps with the generation * and location of subcomponents. * - * @author tufei - * @date 2017/9/11 + * Created by tufei on 2017/9/11. */ @Singleton -@Component(modules = {SingleModule.class, - NetModule.class, +@Component(modules = {NetModule.class, ApplicationModule.class, ActivityBindingModule.class, AndroidSupportInjectionModule.class}) public interface AppComponent extends AndroidInjector { - HttpService httpService(); - /** * 必须写的模板代码。 * 给予我们语法糖,让我们可以做DaggerAppComponent.builder().application(this).build().inject(this); @@ -55,7 +49,7 @@ public interface AppComponent extends AndroidInjector { interface Builder { @BindsInstance - AppComponent.Builder application(Application application); + Builder application(Application application); AppComponent build(); } diff --git a/app/src/main/java/com/tufei/architecturedemo/mvp/main/MainPresenter.java b/app/src/main/java/com/tufei/architecturedemo/mvp/main/MainPresenter.java index 3095c0c..7face17 100644 --- a/app/src/main/java/com/tufei/architecturedemo/mvp/main/MainPresenter.java +++ b/app/src/main/java/com/tufei/architecturedemo/mvp/main/MainPresenter.java @@ -1,6 +1,6 @@ package com.tufei.architecturedemo.mvp.main; -import com.tufei.architecturedemo.data.LoginTask; +import com.tufei.architecturedemo.mvp.model.LoginTask; import com.tufei.architecturedemo.utils.LogUtil; import javax.inject.Inject; @@ -35,7 +35,6 @@ * 你会发现,{@link MainActivity#mPresenter}和{@link MainFragment#mPresenter} * 不是同一个实例。也就是你想要在MainActivity的范围内,实现单例,办不到。 * 但是...现在不会了... - * */ //@ActivityScoped final class MainPresenter implements MainContract.Presenter { diff --git a/app/src/main/java/com/tufei/architecturedemo/mvp/model/LoginTask.java b/app/src/main/java/com/tufei/architecturedemo/mvp/model/LoginTask.java new file mode 100644 index 0000000..2d48f83 --- /dev/null +++ b/app/src/main/java/com/tufei/architecturedemo/mvp/model/LoginTask.java @@ -0,0 +1,22 @@ +package com.tufei.architecturedemo.mvp.model; + +import com.tufei.architecturedemo.net.NetRepository; +import com.tufei.architecturedemo.utils.LogUtil; + +import javax.inject.Inject; + +/** + * 登陆登出 + * @author tufei + * @date 2017/9/12 + */ +public class LoginTask { + private static final String TAG = "LoginTask"; + private NetRepository mNetRepository; + + @Inject + public LoginTask(NetRepository netRepository) { + mNetRepository = netRepository; + LogUtil.d(TAG,"netRepository = "+netRepository); + } +} diff --git a/app/src/main/java/com/tufei/architecturedemo/mvp/model/VersionTask.java b/app/src/main/java/com/tufei/architecturedemo/mvp/model/VersionTask.java new file mode 100644 index 0000000..eaf421d --- /dev/null +++ b/app/src/main/java/com/tufei/architecturedemo/mvp/model/VersionTask.java @@ -0,0 +1,41 @@ +package com.tufei.architecturedemo.mvp.model; + +import com.tufei.architecturedemo.mvp.model.bean.VersionBean; +import com.tufei.architecturedemo.net.DownNetConfig; +import com.tufei.architecturedemo.net.NetRepository; +import com.tufei.architecturedemo.net.download.OnDownListener; +import com.tufei.architecturedemo.utils.LogUtil; +import com.tufei.architecturedemo.utils.RxUtil; + +import javax.inject.Inject; + +import io.reactivex.Flowable; +import io.reactivex.Observable; +import okhttp3.ResponseBody; + +/** + * + * @author tufei + * @date 2017/9/11 + */ +public class VersionTask { + private static final String TAG = "VersionTask"; + private NetRepository mNetRepository; + + @Inject + public VersionTask(NetRepository netRepository) { + LogUtil.d(TAG, "netRepository = " + netRepository); + mNetRepository = netRepository; + } + + + public Observable getVersion() { + return mNetRepository.getVersion() + .compose(RxUtil.io_main_handleHttpResult()); + } + + public Flowable update(String path, OnDownListener listener) { + NetRepository repository = DownNetConfig.initNetConfig(path, listener); + return repository.update(path); + } +} diff --git a/app/src/main/java/com/tufei/architecturedemo/mvp/model/bean/VersionBean.java b/app/src/main/java/com/tufei/architecturedemo/mvp/model/bean/VersionBean.java new file mode 100644 index 0000000..95117e6 --- /dev/null +++ b/app/src/main/java/com/tufei/architecturedemo/mvp/model/bean/VersionBean.java @@ -0,0 +1,63 @@ +package com.tufei.architecturedemo.mvp.model.bean; + +/** + * @author tufei + * @date 2017/9/12 + */ +public class VersionBean { + /** + * { + "id": 49, + "version": "1.0", + "type": null, + "description": "初始版本", + "path": "http://www.sb.com/app.apk" + } + */ + + private int id; + private String type; + private String description; + private String version; + private String path; + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } +} diff --git "a/app/src/main/java/com/tufei/architecturedemo/mvp/model/\345\244\207\346\263\250" "b/app/src/main/java/com/tufei/architecturedemo/mvp/model/\345\244\207\346\263\250" new file mode 100644 index 0000000..b456f61 --- /dev/null +++ "b/app/src/main/java/com/tufei/architecturedemo/mvp/model/\345\244\207\346\263\250" @@ -0,0 +1,53 @@ +1.命名 +以前我的model,都是诸如,SplashModel、MainModel这样命名的。似乎没毛病,SplashModel对应SplashActivity嘛。 +但有一次,我发现我想拿SplashModel的更新功能,拿到设置界面用的使用。设置界面SettingActivity的presenter的 +构造方法可能会变成了下面这样: +SettingPresenter(SplashModel splashModel) { + mSplashModel = splashModel; +} +莫名的傻逼... + +2.Model的相关类,都没有提供相关的接口。而网上的教程,基本都有提供接口。 +第一,懒。 +第二,RxJava为我们带来了便利。比如,以前如果我们请求一个登陆接口,Model可能得提供一个回调接口,告诉Presenter, +请求的结果:失败或者成功。 +必须用回调来做的理由,是为了解耦,Model层不应该知道哪个Presenter会来调它。也就是Model不应该持有Presenter的引用。 +你查看,谷歌的todo-mvp和todo-mvp-dagger,你会发现他们的model层,都有这么一个接口类: +public interface TasksDataSource { + interface LoadTasksCallback { + //... + } + interface GetTaskCallback { + //... + } + //LoadTasksCallback的回调作用于此 + void getTasks(@NonNull LoadTasksCallback callback); + //GetTaskCallback的回调作用于此 + void getTask(@NonNull String taskId, @NonNull GetTaskCallback callback); + ...... +} + +但在todo-mvp-rxjava里,变成了这样: +public interface TasksDataSource { + Flowable> getTasks(); + Flowable> getTask(@NonNull String taskId); + ...... +} +这样可以说是使用RxJava的好处之一。 +至于我,更懒,连这个接口都懒得写了。理由下面说。 + +第三,如果View和Presenter的接口还有点用处的话。我觉得没事给Model都写一个接口,简直是闲得慌。 +当然,谷歌的todo-mvp-rxjava的Model还是有保留接口的必要,但也是因为他们的业务场景不同, +有本地数据库、远程数据库两种情况,它的接口的保留,我觉得更多是因为“多继承”,为了编写复用性更好的代码。 + +当像我这里,版本更新VersionTask,只针对请求后台数据库一种情况。有没有写这个接口的必要? + +下面是一段摘自《Java编程思想》第9章 接口 第188-189页的话: +“‘确定接口是理想选择,因而应该总是选择接口而不是具体的类。'这其实是一种引诱。当然,对于创建类, +几乎在任何时刻,都可以替代为创建一个接口和一个工厂。许多人都掉进了这种陷阱,只要有可能就去创建接口和工厂。 +这种逻辑看起来好像是因为需要使用不同的具体实现,因此总是应该添加这种抽象性。这实际上已经变成了一种草率的设计优化。 +任何抽象性都应该是应真正的需求而产生的。如果没有足够的说服力,只是为了以防万一添加新接口, +并由此带来了额外的复杂性。那么应该质疑一下这样的设计了。恰当的原则,应该是优先选择类而不是接口。 +接口是一种重要工具,但它们容易被滥用。” + +所以,我个人更倾向于,如无特别的必要,不提供Model的接口。 diff --git a/app/src/main/java/com/tufei/architecturedemo/mvp/splash/SplashActivity.java b/app/src/main/java/com/tufei/architecturedemo/mvp/splash/SplashActivity.java index a0abbc0..e9010bb 100644 --- a/app/src/main/java/com/tufei/architecturedemo/mvp/splash/SplashActivity.java +++ b/app/src/main/java/com/tufei/architecturedemo/mvp/splash/SplashActivity.java @@ -1,14 +1,11 @@ package com.tufei.architecturedemo.mvp.splash; -import android.app.AlertDialog; import android.content.Intent; import android.view.View; import android.widget.Toast; import com.tufei.architecturedemo.R; import com.tufei.architecturedemo.base.mvp.BaseActivity; -import com.tufei.architecturedemo.mvp.Single.SingleActivity; -import com.tufei.architecturedemo.mvp.face.FaceActivity; import com.tufei.architecturedemo.mvp.main.MainActivity; import com.tufei.architecturedemo.utils.LogUtil; @@ -40,7 +37,7 @@ protected void initData() { LogUtil.d(TAG, "splashPresenter = " + mPresenter); } - @OnClick({R.id.btn_send_msg, R.id.btn_face, R.id.btn_check_update, R.id.btn_single}) + @OnClick({R.id.btn_send_msg, R.id.btn_check_update}) public void onViewClicked(View view) { switch (view.getId()) { case R.id.btn_send_msg: @@ -48,16 +45,10 @@ public void onViewClicked(View view) { intent.putExtra(MainActivity.TASK_ID, "你好!"); startActivity(intent); break; - case R.id.btn_face: - startActivity(FaceActivity.class); - break; case R.id.btn_check_update: //接口是假的。只是演示一下,使用rxjava2+retrofit2时候的示例代码。 //mPresenter.checkUpdate(); break; - case R.id.btn_single: - startActivity(SingleActivity.class); - break; default: break; } @@ -73,16 +64,6 @@ public void showDownProgress(int progress) { //做相关操作 } - @Override - public void showUpdateTipDialog(String path) { - AlertDialog updateTipDialog = new AlertDialog.Builder(this).setPositiveButton("更新", (dialog, which) -> { - mPresenter.update(path); - }).setNegativeButton("取消", (dialog, which) -> { - - }).show(); - addDialog(updateTipDialog); - } - @Override public void showToast(String tip) { Toast.makeText(this, tip, Toast.LENGTH_SHORT).show(); diff --git a/app/src/main/java/com/tufei/architecturedemo/mvp/splash/SplashContract.java b/app/src/main/java/com/tufei/architecturedemo/mvp/splash/SplashContract.java index e71a80b..67ddfc9 100644 --- a/app/src/main/java/com/tufei/architecturedemo/mvp/splash/SplashContract.java +++ b/app/src/main/java/com/tufei/architecturedemo/mvp/splash/SplashContract.java @@ -15,13 +15,9 @@ interface View extends IBaseView { void showMainActivity(); void showDownProgress(int progress); - - void showUpdateTipDialog(String path); } interface Presenter extends IBasePresenter { void checkUpdate(); - - void update(String path); } } diff --git a/app/src/main/java/com/tufei/architecturedemo/mvp/splash/SplashPresenter.java b/app/src/main/java/com/tufei/architecturedemo/mvp/splash/SplashPresenter.java index e8e9daa..0d5508c 100644 --- a/app/src/main/java/com/tufei/architecturedemo/mvp/splash/SplashPresenter.java +++ b/app/src/main/java/com/tufei/architecturedemo/mvp/splash/SplashPresenter.java @@ -4,8 +4,8 @@ import android.content.Context; import com.tufei.architecturedemo.base.mvp.BasePresenter; -import com.tufei.architecturedemo.data.VersionTask; import com.tufei.architecturedemo.di.ActivityScoped; +import com.tufei.architecturedemo.mvp.model.VersionTask; import com.tufei.architecturedemo.net.NetModule; import com.tufei.architecturedemo.utils.AppUtil; import com.tufei.architecturedemo.utils.LogUtil; @@ -76,12 +76,11 @@ public void checkUpdate() { .getVersion() .subscribe( version -> { + LogUtil.d(TAG, "查询版本信息成功。"); if (version.getVersion().equals(AppUtil.getVersion(mContext))) { - LogUtil.d(TAG, "版本号一致,不需要更新。"); mView.showMainActivity(); } else { - LogUtil.d(TAG, "版本号不一致,询问用户是否需要更新。"); - mView.showUpdateTipDialog(version.getPath()); + update(version.getPath()); } }, throwable -> { @@ -110,8 +109,7 @@ public void checkUpdate() { * * @param path */ - @Override - public void update(String path) { + private void update(String path) { Disposable disposable = mVersionTask .update(path, (bytesLoaded, total, done, url) -> { //下载进度 diff --git "a/app/src/main/java/com/tufei/architecturedemo/mvp/splash/\345\244\207\346\263\250" "b/app/src/main/java/com/tufei/architecturedemo/mvp/splash/\345\244\207\346\263\250" new file mode 100644 index 0000000..0ec43ba --- /dev/null +++ "b/app/src/main/java/com/tufei/architecturedemo/mvp/splash/\345\244\207\346\263\250" @@ -0,0 +1 @@ +1.lambda的使用 diff --git a/app/src/main/java/com/tufei/architecturedemo/net/DownNetConfig.java b/app/src/main/java/com/tufei/architecturedemo/net/DownNetConfig.java index 801a757..7f94b21 100644 --- a/app/src/main/java/com/tufei/architecturedemo/net/DownNetConfig.java +++ b/app/src/main/java/com/tufei/architecturedemo/net/DownNetConfig.java @@ -23,20 +23,21 @@ public class DownNetConfig { /** - * 要监听下载进度时,无法使用全局的HttpService + * 要监听下载进度时,无法使用全局的Repository * * @param url * @param listener * @return */ - public static HttpService initNetConfig(String url, OnDownListener listener) { + public static NetRepository initNetConfig(String url, OnDownListener listener) { Retrofit retrofit = new Retrofit.Builder() .baseUrl(NetConstants.BASE_URL) .client(createOkHttpClient(url, listener)) .addConverterFactory(createGsonConverterFactory()) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .build(); - return retrofit.create(HttpService.class); + NetRepository repository = new NetRepository(retrofit); + return repository; } /** @@ -45,14 +46,15 @@ public static HttpService initNetConfig(String url, OnDownListener listener) { * @param url * @return */ - public static HttpService initNetConfigForRxbus(final String url) { + public static NetRepository initNetConfigForRxbus(final String url) { Retrofit retrofit = new Retrofit.Builder() .baseUrl(NetConstants.BASE_URL) .client(createOkHttpClient(url, null)) .addConverterFactory(createGsonConverterFactory()) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .build(); - return retrofit.create(HttpService.class); + NetRepository repository = new NetRepository(retrofit); + return repository; } private static OkHttpClient createOkHttpClient(String url, OnDownListener listener) { diff --git a/app/src/main/java/com/tufei/architecturedemo/net/HttpService.java b/app/src/main/java/com/tufei/architecturedemo/net/HttpService.java index 8b320ae..87e3f17 100644 --- a/app/src/main/java/com/tufei/architecturedemo/net/HttpService.java +++ b/app/src/main/java/com/tufei/architecturedemo/net/HttpService.java @@ -1,30 +1,19 @@ package com.tufei.architecturedemo.net; -import com.tufei.architecturedemo.data.bean.AccessToken; -import com.tufei.architecturedemo.data.bean.RecognizeResult; -import com.tufei.architecturedemo.data.bean.VersionBean; - -import java.util.List; -import java.util.Map; +import com.tufei.architecturedemo.mvp.model.bean.VersionBean; import io.reactivex.Flowable; import io.reactivex.Observable; -import okhttp3.FormBody; import okhttp3.ResponseBody; -import retrofit2.http.Body; -import retrofit2.http.FieldMap; -import retrofit2.http.FormUrlEncoded; import retrofit2.http.GET; -import retrofit2.http.POST; import retrofit2.http.Query; import retrofit2.http.Streaming; -import retrofit2.http.Url; /** * @author tufei * @date 2017/9/12 */ -public interface HttpService { +interface HttpService { /** * 获取当前最新的软件版本 @@ -43,45 +32,4 @@ public interface HttpService { @Streaming @GET("software/upgrade/apk") Flowable update(@Query("apkPath") String path); - - /** - * 获取使用百度人脸识别时,必须的access_token - * 当BaseUrl不适用时,用@Url标注,传一个完整的url即可 - * - * @param accessTokenUrl - * @param params - * @return - */ - @FormUrlEncoded - @POST - Observable getAccessToken(@Url String accessTokenUrl, @FieldMap Map params); - - - /** - * 保存人脸到百度 - * - * @return - * @param body - */ - @POST("faceset/user/add") - Observable saveFace(@Body FormBody body); - - /** - * 识别人脸 - * - * @param photoBytes - * @return - */ - @POST("identify") - Observable>> recognizeFace(@Body FormBody photoBytes); - - /** - * 删除人脸 - * - * @param body - * @return - */ - @POST("faceset/user/delete") - Observable deleteFace(@Body FormBody body); - } diff --git a/app/src/main/java/com/tufei/architecturedemo/net/NetModule.java b/app/src/main/java/com/tufei/architecturedemo/net/NetModule.java index 006cb46..86891c5 100644 --- a/app/src/main/java/com/tufei/architecturedemo/net/NetModule.java +++ b/app/src/main/java/com/tufei/architecturedemo/net/NetModule.java @@ -1,9 +1,12 @@ package com.tufei.architecturedemo.net; -import com.tufei.architecturedemo.di.AppComponent; +import com.tufei.architecturedemo.utils.RetrofitFactory; + +import javax.inject.Singleton; import dagger.Module; import dagger.Provides; +import retrofit2.Retrofit; /** * @author tufei @@ -12,19 +15,16 @@ @Module public class NetModule { - /** - * 如果是通过@Provides提供实例,要实现单例,只需要在此标注@Singleton - * 当然,{@link AppComponent}里,也要标注上@Singleton - */ + @Singleton @Provides - HttpService provideHttpService() { - return RetrofitFactory.createHttpService(); + Retrofit provideRetrofit() { + return RetrofitFactory.createRetrofit(); } // @Singleton // @Provides // @Nullable -// HttpService provideHttpService() { +// Retrofit provideRetrofit() { // return null; // } } diff --git a/app/src/main/java/com/tufei/architecturedemo/net/NetRepository.java b/app/src/main/java/com/tufei/architecturedemo/net/NetRepository.java new file mode 100644 index 0000000..a92d6f5 --- /dev/null +++ b/app/src/main/java/com/tufei/architecturedemo/net/NetRepository.java @@ -0,0 +1,38 @@ +package com.tufei.architecturedemo.net; + +import com.tufei.architecturedemo.mvp.model.bean.VersionBean; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import io.reactivex.Flowable; +import io.reactivex.Observable; +import okhttp3.ResponseBody; +import retrofit2.Retrofit; + +/** + * 这里的@Singleton不能去掉。不然NetRepository无法保证全局单例。 + * @author tufei + * @date 2017/6/28 + */ +@Singleton +public class NetRepository { + private HttpService httpService; + + @Inject + NetRepository(Retrofit retrofit) { + httpService = retrofit.create(HttpService.class); + } + + /** + * 注意,这里的返回值是Observable>,而不是Observable + * @return + */ + public Observable> getVersion() { + return httpService.getVersion(); + } + + public Flowable update(String path) { + return httpService.update(path); + } +} diff --git "a/app/src/main/java/com/tufei/architecturedemo/net/\345\244\207\346\263\250" "b/app/src/main/java/com/tufei/architecturedemo/net/\345\244\207\346\263\250" new file mode 100644 index 0000000..a187edb --- /dev/null +++ "b/app/src/main/java/com/tufei/architecturedemo/net/\345\244\207\346\263\250" @@ -0,0 +1,23 @@ +1.后台返回json数据的预处理 +我们后台返回json的统一格式是: +参 数 类 型 说 明 +success boolean 请求是否成功若,请求失败,则为 false;若请求成功,则为 true; +errmsg string 错误消息或错误码,若请求失败,该字段为失败提示信息;若请求成功,则该字段没有任何内容; +data object 若请求失败,则该字段没有任何内容;若请求成功,则该字段为请求结果的数据内容; + +参考json: +{ + "data" : { + "id" : "123456789" + }, + "errmsg" : null, + "success" : true +} + +所以我将网络请求结果的json用HttpResult封装了。还使用工具类,对返回的json结果,进行了预处理,简化了Model的重复代码。 +详见:RxUtil的io_main_handleHttpResult和io_main_handleNoData方法。 +参考例子:版本更新功能:SplashPresenter + VersionTask + +2.下载 +rxjava2搭配retrofit2实现下载功能,并要求监听下载进度时,实现比较麻烦。这里也给出了具体代码。 +注意,不能使用全局的NetRepository来实现带进度的下载。 \ No newline at end of file diff --git a/app/src/main/java/com/tufei/architecturedemo/utils/CacheUtil.java b/app/src/main/java/com/tufei/architecturedemo/utils/CacheUtil.java new file mode 100644 index 0000000..1bf5035 --- /dev/null +++ b/app/src/main/java/com/tufei/architecturedemo/utils/CacheUtil.java @@ -0,0 +1,952 @@ +package com.tufei.architecturedemo.utils; + +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.Process; +import android.support.annotation.NonNull; + +import org.json.JSONArray; +import org.json.JSONObject; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.RandomAccessFile; +import java.io.Serializable; +import java.nio.ByteBuffer; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel; +import java.util.Collections; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; + +/** + * Created by wjc on 2017/12/28. 缓存相关工具类 + */ +@SuppressWarnings("unused") +public class CacheUtil { + + private static final long DEFAULT_MAX_SIZE = Long.MAX_VALUE; + private static final int DEFAULT_MAX_COUNT = Integer.MAX_VALUE; + + public static final int SEC = 1; + public static final int MIN = 60; + public static final int HOUR = 360; + public static final int DAY = 8640; + + private static Map sCacheMap = new HashMap<>(); + private CacheManager mCacheManager; + + /** + * 获取缓存实例 + *

在/data/data/com.xxx.xxx/cache/cacheUtils目录

+ *

缓存尺寸不限

+ *

缓存个数不限

+ * + * @return {@link CacheUtil} + */ + public static CacheUtil getInstance() { + return getInstance("", DEFAULT_MAX_SIZE, DEFAULT_MAX_COUNT); + } + + /** + * 获取缓存实例 + *

在/data/data/com.xxx.xxx/cache/cacheName目录

+ *

缓存尺寸不限

+ *

缓存个数不限

+ * + * @param cacheName 缓存目录名 + * @return {@link CacheUtil} + */ + public static CacheUtil getInstance(String cacheName) { + return getInstance(cacheName, DEFAULT_MAX_SIZE, DEFAULT_MAX_COUNT); + } + + /** + * 获取缓存实例 + *

在/data/data/com.xxx.xxx/cache/cacheUtils目录

+ * + * @param maxSize 最大缓存尺寸,单位字节 + * @param maxCount 最大缓存个数 + * @return {@link CacheUtil} + */ + public static CacheUtil getInstance(long maxSize, int maxCount) { + return getInstance("", maxSize, maxCount); + } + + /** + * 获取缓存实例 + *

在/data/data/com.xxx.xxx/cache/cacheName目录

+ * + * @param cacheName 缓存目录名 + * @param maxSize 最大缓存尺寸,单位字节 + * @param maxCount 最大缓存个数 + * @return {@link CacheUtil} + */ + public static CacheUtil getInstance(String cacheName, long maxSize, int maxCount) { + if (isSpace(cacheName)) cacheName = "cacheUtils"; + File file = new File(Utils.getContext().getCacheDir(), cacheName); + return getInstance(file, maxSize, maxCount); + } + + /** + * 获取缓存实例 + *

在cacheDir目录

+ *

缓存尺寸不限

+ *

缓存个数不限

+ * + * @param cacheDir 缓存目录 + * @return {@link CacheUtil} + */ + public static CacheUtil getInstance(@NonNull File cacheDir) { + return getInstance(cacheDir, DEFAULT_MAX_SIZE, DEFAULT_MAX_COUNT); + } + + /** + * 获取缓存实例 + *

在cacheDir目录

+ * + * @param cacheDir 缓存目录 + * @param maxSize 最大缓存尺寸,单位字节 + * @param maxCount 最大缓存个数 + * @return {@link CacheUtil} + */ + public static CacheUtil getInstance(@NonNull File cacheDir, long maxSize, int maxCount) { + final String cacheKey = cacheDir.getAbsoluteFile() + "_" + Process.myPid(); + CacheUtil cache = sCacheMap.get(cacheKey); + if (cache == null) { + cache = new CacheUtil(cacheDir, maxSize, maxCount); + sCacheMap.put(cacheKey, cache); + } + return cache; + } + + private CacheUtil(@NonNull File cacheDir, long maxSize, int maxCount) { + if (!cacheDir.exists() && !cacheDir.mkdirs()) { + throw new RuntimeException("can't make dirs in " + cacheDir.getAbsolutePath()); + } + mCacheManager = new CacheManager(cacheDir, maxSize, maxCount); + } + + /////////////////////////////////////////////////////////////////////////// + // byte 读写 + /////////////////////////////////////////////////////////////////////////// + + /** + * 缓存中写入字节数组 + * + * @param key 键 + * @param value 值 + */ + public void put(@NonNull String key, @NonNull byte[] value) { + put(key, value, -1); + } + + /** + * 缓存中写入字节数组 + * + * @param key 键 + * @param value 值 + * @param saveTime 保存时长,单位:秒 + */ + public void put(@NonNull String key, @NonNull byte[] value, int saveTime) { + if (value.length <= 0) return; + if (saveTime >= 0) value = CacheHelper.newByteArrayWithTime(saveTime, value); + File file = mCacheManager.getFileBeforePut(key); + CacheHelper.writeFileFromBytes(file, value); + mCacheManager.updateModify(file); + mCacheManager.put(file); + + } + + /** + * 缓存中读取字节数组 + * + * @param key 键 + * @return 存在且没过期返回对应值,否则返回{@code null} + */ + public byte[] getBytes(@NonNull String key) { + return getBytes(key, null); + } + + /** + * 缓存中读取字节数组 + * + * @param key 键 + * @param defaultValue 默认值 + * @return 存在且没过期返回对应值,否则返回默认值{@code defaultValue} + */ + public byte[] getBytes(@NonNull String key, byte[] defaultValue) { + final File file = mCacheManager.getFileIfExists(key); + if (file == null) return defaultValue; + byte[] data = CacheHelper.readFile2Bytes(file); + if (CacheHelper.isDue(data)) { + mCacheManager.removeByKey(key); + return defaultValue; + } + mCacheManager.updateModify(file); + return CacheHelper.getDataWithoutDueTime(data); + } + + /////////////////////////////////////////////////////////////////////////// + // String 读写 + /////////////////////////////////////////////////////////////////////////// + + /** + * 缓存中写入String + * + * @param key 键 + * @param value 值 + */ + public void put(@NonNull String key, @NonNull String value) { + put(key, value, -1); + } + + /** + * 缓存中写入String + * + * @param key 键 + * @param value 值 + * @param saveTime 保存时长,单位:秒 + */ + public void put(@NonNull String key, @NonNull String value, int saveTime) { + put(key, CacheHelper.string2Bytes(value), saveTime); + } + + /** + * 缓存中读取String + * + * @param key 键 + * @return 存在且没过期返回对应值,否则返回{@code null} + */ + public String getString(@NonNull String key) { + return getString(key, null); + } + + /** + * 缓存中读取String + * + * @param key 键 + * @param defaultValue 默认值 + * @return 存在且没过期返回对应值,否则返回默认值{@code defaultValue} + */ + public String getString(@NonNull String key, String defaultValue) { + byte[] bytes = getBytes(key); + if (bytes == null) return defaultValue; + return CacheHelper.bytes2String(bytes); + } + + /////////////////////////////////////////////////////////////////////////// + // JSONObject 读写 + /////////////////////////////////////////////////////////////////////////// + + /** + * 缓存中写入JSONObject + * + * @param key 键 + * @param value 值 + */ + public void put(@NonNull String key, @NonNull JSONObject value) { + put(key, value, -1); + } + + /** + * 缓存中写入JSONObject + * + * @param key 键 + * @param value 值 + * @param saveTime 保存时长,单位:秒 + */ + public void put(@NonNull String key, @NonNull JSONObject value, int saveTime) { + put(key, CacheHelper.jsonObject2Bytes(value), saveTime); + } + + /** + * 缓存中读取JSONObject + * + * @param key 键 + * @return 存在且没过期返回对应值,否则返回{@code null} + */ + public JSONObject getJSONObject(@NonNull String key) { + return getJSONObject(key, null); + } + + /** + * 缓存中读取JSONObject + * + * @param key 键 + * @param defaultValue 默认值 + * @return 存在且没过期返回对应值,否则返回默认值{@code defaultValue} + */ + public JSONObject getJSONObject(@NonNull String key, JSONObject defaultValue) { + byte[] bytes = getBytes(key); + if (bytes == null) return defaultValue; + return CacheHelper.bytes2JSONObject(bytes); + } + + + /////////////////////////////////////////////////////////////////////////// + // JSONArray 读写 + /////////////////////////////////////////////////////////////////////////// + + /** + * 缓存中写入JSONArray + * + * @param key 键 + * @param value 值 + */ + public void put(@NonNull String key, @NonNull JSONArray value) { + put(key, value, -1); + } + + /** + * 缓存中写入JSONArray + * + * @param key 键 + * @param value 值 + * @param saveTime 保存时长,单位:秒 + */ + public void put(@NonNull String key, @NonNull JSONArray value, int saveTime) { + put(key, CacheHelper.jsonArray2Bytes(value), saveTime); + } + + /** + * 缓存中读取JSONArray + * + * @param key 键 + * @return 存在且没过期返回对应值,否则返回{@code null} + */ + public JSONArray getJSONArray(@NonNull String key) { + return getJSONArray(key, null); + } + + /** + * 缓存中读取JSONArray + * + * @param key 键 + * @param defaultValue 默认值 + * @return 存在且没过期返回对应值,否则返回默认值{@code defaultValue} + */ + public JSONArray getJSONArray(@NonNull String key, JSONArray defaultValue) { + byte[] bytes = getBytes(key); + if (bytes == null) return defaultValue; + return CacheHelper.bytes2JSONArray(bytes); + } + + + /////////////////////////////////////////////////////////////////////////// + // Bitmap 读写 + /////////////////////////////////////////////////////////////////////////// + + /** + * 缓存中写入Bitmap + * + * @param key 键 + * @param value 值 + */ + public void put(@NonNull String key, @NonNull Bitmap value) { + put(key, value, -1); + } + + /** + * 缓存中写入Bitmap + * + * @param key 键 + * @param value 值 + * @param saveTime 保存时长,单位:秒 + */ + public void put(@NonNull String key, @NonNull Bitmap value, int saveTime) { + put(key, CacheHelper.bitmap2Bytes(value), saveTime); + } + + /** + * 缓存中读取Bitmap + * + * @param key 键 + * @return 存在且没过期返回对应值,否则返回{@code null} + */ + public Bitmap getBitmap(@NonNull String key) { + return getBitmap(key, null); + } + + /** + * 缓存中读取Bitmap + * + * @param key 键 + * @param defaultValue 默认值 + * @return 存在且没过期返回对应值,否则返回默认值{@code defaultValue} + */ + public Bitmap getBitmap(@NonNull String key, Bitmap defaultValue) { + byte[] bytes = getBytes(key); + if (bytes == null) return defaultValue; + return CacheHelper.bytes2Bitmap(bytes); + } + + /////////////////////////////////////////////////////////////////////////// + // Drawable 读写 + /////////////////////////////////////////////////////////////////////////// + + /** + * 缓存中写入Drawable + * + * @param key 键 + * @param value 值 + */ + public void put(@NonNull String key, @NonNull Drawable value) { + put(key, CacheHelper.drawable2Bytes(value)); + } + + /** + * 缓存中写入Drawable + * + * @param key 键 + * @param value 值 + * @param saveTime 保存时长,单位:秒 + */ + public void put(@NonNull String key, @NonNull Drawable value, int saveTime) { + put(key, CacheHelper.drawable2Bytes(value), saveTime); + } + + /** + * 缓存中读取Drawable + * + * @param key 键 + * @return 存在且没过期返回对应值,否则返回{@code null} + */ + public Drawable getDrawable(@NonNull String key) { + return getDrawable(key, null); + } + + /** + * 缓存中读取Drawable + * + * @param key 键 + * @param defaultValue 默认值 + * @return 存在且没过期返回对应值,否则返回默认值{@code defaultValue} + */ + public Drawable getDrawable(@NonNull String key, Drawable defaultValue) { + byte[] bytes = getBytes(key); + if (bytes == null) return defaultValue; + return CacheHelper.bytes2Drawable(bytes); + } + + /////////////////////////////////////////////////////////////////////////// + // Parcelable 读写 + /////////////////////////////////////////////////////////////////////////// + + /** + * 缓存中写入Parcelable + * + * @param key 键 + * @param value 值 + */ + public void put(@NonNull String key, @NonNull Parcelable value) { + put(key, value, -1); + } + + /** + * 缓存中写入Parcelable + * + * @param key 键 + * @param value 值 + * @param saveTime 保存时长,单位:秒 + */ + public void put(@NonNull String key, @NonNull Parcelable value, int saveTime) { + put(key, CacheHelper.parcelable2Bytes(value), saveTime); + } + + /** + * 缓存中读取Parcelable + * + * @param key 键 + * @param creator 建造器 + * @return 存在且没过期返回对应值,否则返回{@code null} + */ + public T getParcelable(@NonNull String key, @NonNull Parcelable.Creator creator) { + return getParcelable(key, creator, null); + } + + /** + * 缓存中读取Parcelable + * + * @param key 键 + * @param creator 建造器 + * @param defaultValue 默认值 + * @return 存在且没过期返回对应值,否则返回默认值{@code defaultValue} + */ + public T getParcelable(@NonNull String key, @NonNull Parcelable.Creator creator, T defaultValue) { + byte[] bytes = getBytes(key); + if (bytes == null) return defaultValue; + return CacheHelper.bytes2Parcelable(bytes, creator); + } + + /////////////////////////////////////////////////////////////////////////// + // Serializable 读写 + /////////////////////////////////////////////////////////////////////////// + + /** + * 缓存中写入Serializable + * + * @param key 键 + * @param value 值 + */ + public void put(@NonNull String key, @NonNull Serializable value) { + put(key, value, -1); + } + + /** + * 缓存中写入Serializable + * + * @param key 键 + * @param value 值 + * @param saveTime 保存时长,单位:秒 + */ + public void put(@NonNull String key, @NonNull Serializable value, int saveTime) { + put(key, CacheHelper.serializable2Bytes(value), saveTime); + } + + /** + * 缓存中读取Serializable + * + * @param key 键 + * @return 存在且没过期返回对应值,否则返回{@code null} + */ + public Object getSerializable(@NonNull String key) { + return getSerializable(key, null); + } + + /** + * 缓存中读取Serializable + * + * @param key 键 + * @param defaultValue 默认值 + * @return 存在且没过期返回对应值,否则返回默认值{@code defaultValue} + */ + public Object getSerializable(@NonNull String key, Object defaultValue) { + byte[] bytes = getBytes(key); + if (bytes == null) return defaultValue; + return CacheHelper.bytes2Object(getBytes(key)); + } + + /** + * 获取缓存大小 + *

单位:字节

+ * + * @return 缓存大小 + */ + public long getCacheSize() { + return mCacheManager.getCacheSize(); + } + + /** + * 获取缓存个数 + * + * @return 缓存个数 + */ + public int getCacheCount() { + return mCacheManager.getCacheCount(); + } + + /** + * 根据键值移除缓存 + * + * @param key 键 + * @return {@code true}: 移除成功
{@code false}: 移除失败 + */ + public boolean remove(@NonNull String key) { + return mCacheManager.removeByKey(key); + } + + /** + * 清除所有缓存 + * + * @return {@code true}: 清除成功
{@code false}: 清除失败 + */ + public boolean clear() { + return mCacheManager.clear(); + } + + private class CacheManager { + private final AtomicLong cacheSize; + private final AtomicInteger cacheCount; + private final long sizeLimit; + private final int countLimit; + private final Map lastUsageDates = Collections.synchronizedMap(new HashMap()); + private final File cacheDir; + + private CacheManager(File cacheDir, long sizeLimit, int countLimit) { + this.cacheDir = cacheDir; + this.sizeLimit = sizeLimit; + this.countLimit = countLimit; + cacheSize = new AtomicLong(); + cacheCount = new AtomicInteger(); + calculateCacheSizeAndCacheCount(); + } + + private void calculateCacheSizeAndCacheCount() { + new Thread(new Runnable() { + @Override + public void run() { + int size = 0; + int count = 0; + final File[] cachedFiles = cacheDir.listFiles(); + if (cachedFiles != null) { + for (File cachedFile : cachedFiles) { + size += cachedFile.length(); + count += 1; + lastUsageDates.put(cachedFile, cachedFile.lastModified()); + } + cacheSize.getAndAdd(size); + cacheCount.getAndAdd(count); + } + } + }).start(); + } + + private long getCacheSize() { + return cacheSize.get(); + } + + private int getCacheCount() { + return cacheCount.get(); + } + + private File getFileBeforePut(String key) { + File file = new File(cacheDir, String.valueOf(key.hashCode())); + if (file.exists()) { + cacheCount.addAndGet(-1); + cacheSize.addAndGet(-file.length()); + } + return file; + } + + private File getFileIfExists(String key) { + File file = new File(cacheDir, String.valueOf(key.hashCode())); + if (!file.exists()) return null; + return file; + } + + private void put(File file) { + cacheCount.addAndGet(1); + cacheSize.addAndGet(file.length()); + while (cacheCount.get() > countLimit || cacheSize.get() > sizeLimit) { + cacheSize.addAndGet(-removeOldest()); + cacheCount.addAndGet(-1); + } + } + + private void updateModify(File file) { + Long millis = System.currentTimeMillis(); + if (file.setLastModified(millis)) + lastUsageDates.put(file, millis); + } + + private boolean removeByKey(String key) { + File file = getFileIfExists(key); + if (file == null) return true; + if (!file.delete()) return false; + cacheSize.addAndGet(-file.length()); + cacheCount.addAndGet(-1); + lastUsageDates.remove(file); + return true; + } + + private boolean clear() { + File[] files = cacheDir.listFiles(); + if (files == null || files.length <= 0) return true; + boolean flag = true; + for (File file : files) { + if (!file.delete()) { + flag = false; + continue; + } + cacheSize.addAndGet(-file.length()); + cacheCount.addAndGet(-1); + lastUsageDates.remove(file); + } + if (flag) { + lastUsageDates.clear(); + cacheSize.set(0); + cacheCount.set(0); + } + return flag; + } + + /** + * 移除旧的文件 + * + * @return 移除的字节数 + */ + private long removeOldest() { + if (lastUsageDates.isEmpty()) return 0; + Long oldestUsage = Long.MAX_VALUE; + File oldestFile = null; + Set> entries = lastUsageDates.entrySet(); + synchronized (lastUsageDates) { + for (Map.Entry entry : entries) { + Long lastValueUsage = entry.getValue(); + if (lastValueUsage < oldestUsage) { + oldestUsage = lastValueUsage; + oldestFile = entry.getKey(); + } + } + } + if (oldestFile == null) return 0; + long fileSize = oldestFile.length(); + if (oldestFile.delete()) { + lastUsageDates.remove(oldestFile); + return fileSize; + } + return 0; + } + } + + private static class CacheHelper { + + static final int timeInfoLen = 14; + + private static byte[] newByteArrayWithTime(int second, byte[] data) { + byte[] time = createDueTime(second).getBytes(); + byte[] content = new byte[time.length + data.length]; + System.arraycopy(time, 0, content, 0, time.length); + System.arraycopy(data, 0, content, time.length, data.length); + return content; + } + + /** + * 创建过期时间 + * + * @param second 秒 + * @return _$millis$_ + */ + private static String createDueTime(int second) { + return String.format(Locale.getDefault(), "_$%010d$_", System.currentTimeMillis() / 1000 + second); + } + + private static boolean isDue(byte[] data) { + long millis = getDueTime(data); + return millis != -1 && System.currentTimeMillis() > millis; + } + + private static long getDueTime(byte[] data) { + if (hasTimeInfo(data)) { + String millis = new String(copyOfRange(data, 2, 12)); + try { + return Long.parseLong(millis) * 1000; + } catch (NumberFormatException e) { + return -1; + } + } + return -1; + } + + private static byte[] getDataWithoutDueTime(byte[] data) { + if (hasTimeInfo(data)) { + return copyOfRange(data, timeInfoLen, data.length); + } + return data; + } + + private static byte[] copyOfRange(byte[] original, int from, int to) { + int newLength = to - from; + if (newLength < 0) throw new IllegalArgumentException(from + " > " + to); + byte[] copy = new byte[newLength]; + System.arraycopy(original, from, copy, 0, Math.min(original.length - from, newLength)); + return copy; + } + + private static boolean hasTimeInfo(byte[] data) { + return data != null + && data.length >= timeInfoLen + && data[0] == '_' + && data[1] == '$' + && data[12] == '$' + && data[13] == '_'; + } + + private static void writeFileFromBytes(File file, byte[] bytes) { + FileChannel fc = null; + try { + fc = new FileOutputStream(file, false).getChannel(); + fc.write(ByteBuffer.wrap(bytes)); + fc.force(true); + } catch (IOException e) { + e.printStackTrace(); + } finally { + CloseUtil.closeIO(fc); + } + } + + private static byte[] readFile2Bytes(File file) { + FileChannel fc = null; + try { + fc = new RandomAccessFile(file, "r").getChannel(); + int size = (int) fc.size(); + MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_ONLY, 0, size).load(); + byte[] data = new byte[size]; + mbb.get(data, 0, size); + return data; + } catch (IOException e) { + e.printStackTrace(); + return null; + } finally { + CloseUtil.closeIO(fc); + } + } + + private static byte[] string2Bytes(String string) { + if (string == null) return null; + return string.getBytes(); + } + + private static String bytes2String(byte[] bytes) { + if (bytes == null) return null; + return new String(bytes); + } + + private static byte[] jsonObject2Bytes(JSONObject jsonObject) { + if (jsonObject == null) return null; + return jsonObject.toString().getBytes(); + } + + private static JSONObject bytes2JSONObject(byte[] bytes) { + if (bytes == null) return null; + try { + return new JSONObject(new String(bytes)); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + private static byte[] jsonArray2Bytes(JSONArray jsonArray) { + if (jsonArray == null) return null; + return jsonArray.toString().getBytes(); + } + + private static JSONArray bytes2JSONArray(byte[] bytes) { + if (bytes == null) return null; + try { + return new JSONArray(new String(bytes)); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + private static byte[] parcelable2Bytes(Parcelable parcelable) { + if (parcelable == null) return null; + Parcel parcel = Parcel.obtain(); + parcelable.writeToParcel(parcel, 0); + byte[] bytes = parcel.marshall(); + parcel.recycle(); + return bytes; + } + + private static T bytes2Parcelable(byte[] bytes, Parcelable.Creator creator) { + if (bytes == null) return null; + Parcel parcel = Parcel.obtain(); + parcel.unmarshall(bytes, 0, bytes.length); + parcel.setDataPosition(0); + T result = creator.createFromParcel(parcel); + parcel.recycle(); + return result; + } + + private static byte[] serializable2Bytes(Serializable serializable) { + if (serializable == null) return null; + ByteArrayOutputStream baos; + ObjectOutputStream oos = null; + try { + oos = new ObjectOutputStream(baos = new ByteArrayOutputStream()); + oos.writeObject(serializable); + return baos.toByteArray(); + } catch (Exception e) { + e.printStackTrace(); + return null; + } finally { + CloseUtil.closeIO(oos); + } + } + + private static Object bytes2Object(byte[] bytes) { + if (bytes == null) return null; + ObjectInputStream ois = null; + try { + ois = new ObjectInputStream(new ByteArrayInputStream(bytes)); + return ois.readObject(); + } catch (Exception e) { + e.printStackTrace(); + return null; + } finally { + CloseUtil.closeIO(ois); + } + } + + private static byte[] bitmap2Bytes(Bitmap bitmap) { + if (bitmap == null) return null; + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + bitmap.compress(Bitmap.CompressFormat.PNG, 100, baos); + return baos.toByteArray(); + } + + private static Bitmap bytes2Bitmap(byte[] bytes) { + return (bytes == null || bytes.length == 0) ? null : BitmapFactory.decodeByteArray(bytes, 0, bytes.length); + } + + private static byte[] drawable2Bytes(Drawable drawable) { + return drawable == null ? null : bitmap2Bytes(drawable2Bitmap(drawable)); + } + + private static Drawable bytes2Drawable(byte[] bytes) { + return bytes == null ? null : Bitmap2Drawable(bytes2Bitmap(bytes)); + } + + private static Bitmap drawable2Bitmap(Drawable drawable) { + if (drawable == null) return null; + if (drawable instanceof BitmapDrawable) { + BitmapDrawable BitmapDrawable = (BitmapDrawable) drawable; + if (BitmapDrawable.getBitmap() != null) { + return BitmapDrawable.getBitmap(); + } + } + Bitmap bitmap; + if (drawable.getIntrinsicWidth() <= 0 || drawable.getIntrinsicHeight() <= 0) { + bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888); + } else { + bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); + } + Canvas canvas = new Canvas(bitmap); + drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); + drawable.draw(canvas); + return bitmap; + } + + private static Drawable Bitmap2Drawable(Bitmap Bitmap) { + return Bitmap == null ? null : new BitmapDrawable(Utils.getContext().getResources(), Bitmap); + } + } + + private static boolean isSpace(String s) { + if (s == null) return true; + for (int i = 0, len = s.length(); i < len; ++i) { + if (!Character.isWhitespace(s.charAt(i))) { + return false; + } + } + return true; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tufei/architecturedemo/utils/CloseUtil.java b/app/src/main/java/com/tufei/architecturedemo/utils/CloseUtil.java new file mode 100644 index 0000000..bbddf47 --- /dev/null +++ b/app/src/main/java/com/tufei/architecturedemo/utils/CloseUtil.java @@ -0,0 +1,50 @@ +package com.tufei.architecturedemo.utils; + +import java.io.Closeable; +import java.io.IOException; + +/** + * Created by wjc on 2017/12/28. 关闭相关工具类 + */ +@SuppressWarnings("unused") +public final class CloseUtil { + + private CloseUtil() { + throw new UnsupportedOperationException("u can't instantiate me..."); + } + + /** + * 关闭IO + * + * @param closeables closeables + */ + public static void closeIO(Closeable... closeables) { + if (closeables == null) return; + for (Closeable closeable : closeables) { + if (closeable != null) { + try { + closeable.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } + + /** + * 安静关闭IO + * + * @param closeables closeables + */ + public static void closeIOQuietly(Closeable... closeables) { + if (closeables == null) return; + for (Closeable closeable : closeables) { + if (closeable != null) { + try { + closeable.close(); + } catch (IOException ignored) { + } + } + } + } +} diff --git a/app/src/main/java/com/tufei/architecturedemo/utils/DateUtil.java b/app/src/main/java/com/tufei/architecturedemo/utils/DateUtil.java new file mode 100644 index 0000000..8944789 --- /dev/null +++ b/app/src/main/java/com/tufei/architecturedemo/utils/DateUtil.java @@ -0,0 +1,45 @@ +package com.tufei.architecturedemo.utils; + +import android.annotation.SuppressLint; +import android.content.Context; + + +import com.tufei.architecturedemo.R; + +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.Locale; + +/** + * Created by dugang on 2017/12/28. 日期时间工具类 + */ +@SuppressWarnings("unused") +public class DateUtil { + + /** + * 按照格式获取当前的日期 + */ + public static String currentDate(String pattern) { + SimpleDateFormat sdf = new SimpleDateFormat(pattern, Locale.getDefault()); + return sdf.format(new Date().getTime()); + } + + /** + * 格式化Long型日期,secondLevel为true时会转换为毫秒级再格式化 + */ + public static String formatDate(String pattern, long date, boolean secondLevel) { + SimpleDateFormat sdf = new SimpleDateFormat(pattern, Locale.getDefault()); + return sdf.format(new Date(date * (secondLevel ? 1000L : 1L))); + } + + /** + * 获取今天星期几 + */ + public static String getDayOfWeek(Context context) { + String[] weekDays = context.getResources().getStringArray(R.array.week); + Calendar calendar = Calendar.getInstance(); + @SuppressLint("WrongConstant") int w = calendar.get(Calendar.DAY_OF_WEEK) - 1; + return weekDays[w < 0 ? 0 : w]; + } +} diff --git a/app/src/main/java/com/tufei/architecturedemo/utils/RetrofitFactory.java b/app/src/main/java/com/tufei/architecturedemo/utils/RetrofitFactory.java new file mode 100644 index 0000000..5893975 --- /dev/null +++ b/app/src/main/java/com/tufei/architecturedemo/utils/RetrofitFactory.java @@ -0,0 +1,59 @@ +package com.tufei.architecturedemo.utils; + +import com.google.gson.Gson; +import com.tufei.architecturedemo.constants.NetConstants; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import okhttp3.Interceptor; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.logging.HttpLoggingInterceptor; +import retrofit2.Retrofit; +import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; +import retrofit2.converter.gson.GsonConverterFactory; + +/** + * @author tufei + * @date 2017/9/11 + */ + +public class RetrofitFactory { + + public static Retrofit createRetrofit(){ + Retrofit retrofit = new Retrofit.Builder() + .baseUrl(NetConstants.BASE_URL) + .client(createOkHttpClient()) + .addConverterFactory(createGsonConverterFactory()) + .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) + .build(); + return retrofit; + } + + private static OkHttpClient createOkHttpClient(){ + HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(); + loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY); + OkHttpClient client = new OkHttpClient.Builder() + .addInterceptor(loggingInterceptor) + .addInterceptor(new Interceptor() { + @Override + public Response intercept(Chain chain) throws IOException { + Request request = chain.request() + .newBuilder() + .addHeader("Content-Type", "application/json; charset=UTF-8") + .build(); + return chain.proceed(request); + } + }) + .connectTimeout(NetConstants.HTTP_CONNECT_TIMEOUT, TimeUnit.MILLISECONDS) + .readTimeout(NetConstants.HTTP_READ_TIMEOUT, TimeUnit.MILLISECONDS) + .build(); + return client; + } + + private static GsonConverterFactory createGsonConverterFactory(){ + return GsonConverterFactory.create(new Gson()); + } +} diff --git a/app/src/main/java/com/tufei/architecturedemo/utils/RxUtil.java b/app/src/main/java/com/tufei/architecturedemo/utils/RxUtil.java index a31b0b6..8d73521 100644 --- a/app/src/main/java/com/tufei/architecturedemo/utils/RxUtil.java +++ b/app/src/main/java/com/tufei/architecturedemo/utils/RxUtil.java @@ -1,6 +1,5 @@ package com.tufei.architecturedemo.utils; -import com.tufei.architecturedemo.net.FaceHttpResult; import com.tufei.architecturedemo.net.HttpResult; import io.reactivex.Observable; @@ -66,43 +65,4 @@ public static ObservableTransformer io_main_handleNoData }); } - - /** - * 1)实现了线程切换io->main - * 2)用于预处理百度人脸相关接口返回的json - * 注意: - * 如果对{@link FaceHttpResult#result}不关心,即使result为空也无所谓的时候, - * 只在乎网络请求的结果{@link FaceHttpResult#error_code}或者{@link FaceHttpResult#error_msg}, - * 那么请用{@link #io_main_handleFaceNoData()},因为RxJava不允许发送null - * - * @param - * @return - */ - public static ObservableTransformer, T> io_main_handleFaceHttpResult() { - return httpResultObservable -> - httpResultObservable.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()). - flatMap(httpResult->{ - if (httpResult.getErrorCode() != 0 || httpResult.getErrorMsg() != null) { - return Observable.error(new Exception(httpResult.getErrorMsg())); - } else { - return Observable.just(httpResult.getResult()); - } - }); - } - - /** - * 使用的时候,这么写:Observable - * @return - */ - public static ObservableTransformer io_main_handleFaceNoData() { - return httpResultObservable -> - httpResultObservable.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()). - flatMap(httpResult -> { - if (httpResult.getErrorCode() != 0 || httpResult.getErrorMsg() != null) { - return Observable.error(new Exception(httpResult.getErrorMsg())); - } else { - return Observable.just(httpResult); - } - }); - } } diff --git a/app/src/main/java/com/tufei/architecturedemo/utils/StorageUtil.java b/app/src/main/java/com/tufei/architecturedemo/utils/StorageUtil.java new file mode 100644 index 0000000..1f9a6e8 --- /dev/null +++ b/app/src/main/java/com/tufei/architecturedemo/utils/StorageUtil.java @@ -0,0 +1,82 @@ +package com.tufei.architecturedemo.utils; + +import android.content.SharedPreferences; +import android.os.Environment; +import android.support.annotation.NonNull; +import android.text.TextUtils; +import android.util.Base64; + +import com.google.gson.Gson; +import com.tufei.architecturedemo.App; + +import java.io.File; + +/** + * Created by wjc on 2017/12/28. App应用目录管理 + */ +@SuppressWarnings("unused") +public class StorageUtil { + /** + * 判断是否是文件路径 + */ + public static boolean isFilePath(String path) { + return new File(path).isDirectory(); + } + + /** + * 获取应用的根目录 + */ + public static String getRoot() { + String rootPath = App.getAppContext().getAppRootPath(); + return Environment.getExternalStorageDirectory() + "/" + rootPath + "/"; + } + + /** + * 在应用根路径下创建文件夹(可以多级) + */ + public static String create(@NonNull String dirName) { + return createDir(getRoot() + dirName); + } + + /** + * 在某个路径下创建文件夹(可以多级) + */ + public static String create(@NonNull String root, @NonNull String dirName) { + return createDir(root + dirName); + } + + private static String createDir(String path) { + File dir = new File(path); + if (!dir.exists() && dir.mkdirs()) { + return path; + } + return path; + } + + /** + * 存储对象到SharePreferences文件中 + */ + public static void saveObjectToFile(String key, Object obj, boolean isEncrypt) { + SharedPreferences sp = Utils.getDefaultSharePreferences(); + //本地化账户信息 + String json = new Gson().toJson(obj); + if (isEncrypt) { + json = Base64.encodeToString(json.getBytes(), Base64.DEFAULT); + } + sp.edit().putString(key, json).apply(); + } + + /** + * 从SharePreferences文件中获取对象 + */ + public static Object parseObjectFromFile(String key, Class clazz, boolean isEncrypt) { + SharedPreferences sp = Utils.getDefaultSharePreferences(); + String json = sp.getString(key, null); + if (TextUtils.isEmpty(json)) return null; + + if (isEncrypt) { + json = new String(Base64.decode(json.getBytes(), Base64.DEFAULT)); + } + return new Gson().fromJson(json, clazz); + } +} diff --git a/app/src/main/java/com/tufei/architecturedemo/utils/SystemUtil.java b/app/src/main/java/com/tufei/architecturedemo/utils/SystemUtil.java new file mode 100644 index 0000000..6718940 --- /dev/null +++ b/app/src/main/java/com/tufei/architecturedemo/utils/SystemUtil.java @@ -0,0 +1,110 @@ +package com.tufei.architecturedemo.utils; + +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.view.View; +import android.view.WindowManager; +import android.view.inputmethod.InputMethodManager; + + +import java.security.MessageDigest; +import java.util.Random; + +/** + * Created by wjc on 2017/12/28. 系统工具类 + */ +@SuppressWarnings("unused") +public class SystemUtil { + + /** + * 关闭软键盘 + */ + public static void closeInputMethod(Context context, View view) { + InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); + if (imm.isActive()) + imm.hideSoftInputFromWindow(view.getWindowToken(), 0); + + } + + /** + * 获取屏幕宽度 + */ + public static int getScreenWidth(Context context) { + WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + return wm.getDefaultDisplay().getWidth(); + } + + /** + * 获取屏幕宽度 + */ + public static int getScreenHeight(Context context) { + WindowManager wm = (WindowManager) Utils.getContext().getSystemService(Context.WINDOW_SERVICE); + return wm.getDefaultDisplay().getHeight(); + } + + /** + * 获取应用的版本名称 + */ + public static String getVersionName() { + try { + PackageManager manager = Utils.getContext().getPackageManager(); + PackageInfo info = manager.getPackageInfo(Utils.getContext().getPackageName(), 0); + return info.versionName; + } catch (PackageManager.NameNotFoundException e) { +// Logger.e(e.getMessage()); + return "1.0.0"; + } + } + + /** + * 获取应用的内部版本号 + */ + public static int getVersionCode() { + try { + PackageManager manager = Utils.getContext().getPackageManager(); + PackageInfo info = manager.getPackageInfo(Utils.getContext().getPackageName(), 0); + return info.versionCode; + } catch (PackageManager.NameNotFoundException e) { +// Logger.e(e.getMessage()); + return 1; + } + } + + /** + * md5加密 + */ + public static String md5(String info) { + try { + MessageDigest md5 = MessageDigest.getInstance("MD5"); + md5.update(info.getBytes("UTF-8")); + byte[] encryption = md5.digest(); + StringBuilder strBuf = new StringBuilder(); + for (byte anEncryption : encryption) { + if (Integer.toHexString(0xff & anEncryption).length() == 1) { + strBuf.append("0").append(Integer.toHexString(0xff & anEncryption)); + } else { + strBuf.append(Integer.toHexString(0xff & anEncryption)); + } + } + return strBuf.toString(); + } catch (Exception e) { + return ""; + } + } + + /** + * 获取临时文件的文件名称 + */ + public static String getTempFileName() { + char[] str = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', + 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', + '7', '8', '9'}; + Random random = new Random(); + StringBuilder sb = new StringBuilder(); + while (sb.length() <= 32) { + sb.append(str[random.nextInt(str.length)]); + } + return sb.toString(); + } +} diff --git a/app/src/main/java/com/tufei/architecturedemo/utils/Utils.java b/app/src/main/java/com/tufei/architecturedemo/utils/Utils.java new file mode 100644 index 0000000..0780f2d --- /dev/null +++ b/app/src/main/java/com/tufei/architecturedemo/utils/Utils.java @@ -0,0 +1,88 @@ +package com.tufei.architecturedemo.utils; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.SharedPreferences; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.preference.PreferenceManager; +import android.support.annotation.NonNull; + + +/** + * Created by wjc on 2017/12/28. Utils初始化相关 + */ +@SuppressWarnings("unused") +public final class Utils { + + @SuppressLint("StaticFieldLeak") + private static Context mContext; + private static boolean debug; + + private Utils() { + throw new UnsupportedOperationException("u can't instantiate me..."); + } + + /** + * 初始化工具类 + * + * @param context 上下文 + */ + public static void init(@NonNull Context context) { + init(context, false); + } + + /** + * 初始化工具类 + * + * @param context 上下文 + * @param debug 调试模式 默认-false + */ + public static void init(@NonNull Context context, boolean debug) { + Utils.mContext = context.getApplicationContext(); + Utils.debug = debug; +// if (debug) Logger.init().methodCount(1).hideThreadInfo(); + } + + /** + * 获取AppContext + * + * @return AppContext + */ + public static Context getContext() { + if (mContext != null) return mContext; + throw new NullPointerException("u should init first"); + } + + /** + * 判断是否在Debug模式 + * + * @return true | false + */ + public static boolean isDebug() { + return debug; + } + + /** + * 获取默认的SharedPreferences + */ + public static SharedPreferences getDefaultSharePreferences() { + return PreferenceManager.getDefaultSharedPreferences(mContext); + } + + /** + * 获取App名称 + * + * @return App名称 + */ + public static String getAppName() { + try { + PackageManager pm = Utils.getContext().getPackageManager(); + PackageInfo pi = pm.getPackageInfo(mContext.getPackageName(), 0); + return pi == null ? null : pi.applicationInfo.loadLabel(pm).toString(); + } catch (PackageManager.NameNotFoundException e) { +// Logger.e(e.getMessage()); + return null; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tufei/architecturedemo/widget/DateTimePicker.java b/app/src/main/java/com/tufei/architecturedemo/widget/DateTimePicker.java new file mode 100644 index 0000000..bda13b9 --- /dev/null +++ b/app/src/main/java/com/tufei/architecturedemo/widget/DateTimePicker.java @@ -0,0 +1,108 @@ +package com.tufei.architecturedemo.widget; + +import android.app.AlertDialog; +import android.app.DatePickerDialog; +import android.app.TimePickerDialog; +import android.content.Context; +import android.support.annotation.NonNull; +import android.text.TextUtils; +import android.widget.DatePicker; +import android.widget.TimePicker; +import android.widget.Toast; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.Locale; + +/** + * Created by wjc on 2017/12/28.日期,时间选择器 + */ +@SuppressWarnings("ResourceType,unused") +public class DateTimePicker { + private Context mContext; + + + public DateTimePicker(Context mContext) { + this.mContext = mContext; + } + + /** + * 显示日期选择器 + * + * @param pattern 日期格式化字符串 + */ + public void showDatePickDialog(final String pattern, String dateStr, final @NonNull OnPickListener onPickListener) { + try { + final SimpleDateFormat sdf = new SimpleDateFormat(pattern, Locale.getDefault()); + final Calendar calendar = Calendar.getInstance(); + if (!TextUtils.isEmpty(dateStr)) { + calendar.setTime(sdf.parse(dateStr)); + } + int year = calendar.get(Calendar.YEAR); + int month = calendar.get(Calendar.MONTH); + int day = calendar.get(Calendar.DAY_OF_MONTH); + DatePickerDialog.OnDateSetListener onDateSetListener = new DatePickerDialog.OnDateSetListener() { + @Override + public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth) { + calendar.set(year, monthOfYear, dayOfMonth, 0, 0, 0); + Date date = calendar.getTime(); + long timeInMillis = calendar.getTimeInMillis(); + + onPickListener.onPick(sdf.format(date), timeInMillis / 1000); + } + }; + DatePickerDialog dialog = new DatePickerDialog(mContext, AlertDialog.THEME_HOLO_LIGHT, onDateSetListener, year, month, day); + dialog.show(); + } catch (ParseException e) { + Toast.makeText(mContext, "Date format error", Toast.LENGTH_SHORT).show(); + } + } + + + /** + * 显示时间选择器 + * + * @param pattern 时间格式化字符串 + * @param needSecond 是否需要精确到秒,影响最后返回的long值; + * true-11:37:23 false-11:37:00 + */ + public void showTimePickerDialog(final String pattern, final boolean needSecond, final @NonNull OnPickListener onPickListener) { + final Calendar calendar = Calendar.getInstance(); + int hour = calendar.get(Calendar.HOUR_OF_DAY); + int minute = calendar.get(Calendar.MINUTE); + + final int year = calendar.get(Calendar.YEAR); + final int month = calendar.get(Calendar.MONTH); + final int day = calendar.get(Calendar.DAY_OF_MONTH); + + TimePickerDialog.OnTimeSetListener onTimeSetListener = new TimePickerDialog.OnTimeSetListener() { + @Override + public void onTimeSet(TimePicker view, int hourOfDay, int minute) { + if (needSecond) { + calendar.set(year, month, day, hourOfDay, minute); + } else { + calendar.set(year, month, day, hourOfDay, minute, 0); + } + + Date date = calendar.getTime(); + long timeInMillis = calendar.getTimeInMillis(); + SimpleDateFormat sdf = new SimpleDateFormat(pattern, Locale.getDefault()); + onPickListener.onPick(sdf.format(date), timeInMillis / 1000); + } + }; + TimePickerDialog timePickerDialog = new TimePickerDialog(mContext, AlertDialog.THEME_HOLO_LIGHT, onTimeSetListener, hour, minute, true); + timePickerDialog.show(); + } + + public interface OnPickListener { + /** + * 日期或时间选择确认时间 + * + * @param formatStr 格式化后的日期或时间 + * @param timeInSeconds 精确到秒的长整形时间 + */ + void onPick(String formatStr, long timeInSeconds); + } +} diff --git a/app/src/main/res/layout/activity_splash.xml b/app/src/main/res/layout/activity_splash.xml index d27c396..3b3478c 100644 --- a/app/src/main/res/layout/activity_splash.xml +++ b/app/src/main/res/layout/activity_splash.xml @@ -1,39 +1,36 @@ - - -