├── settings.gradle ├── templates.zip ├── app ├── src │ ├── main │ │ ├── res │ │ │ ├── values │ │ │ │ ├── strings.xml │ │ │ │ ├── colors.xml │ │ │ │ ├── styles.xml │ │ │ │ └── dimens.xml │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── xml │ │ │ │ ├── network_security_config.xml │ │ │ │ └── bottombar_menu.xml │ │ │ ├── drawable │ │ │ │ ├── ic_home.xml │ │ │ │ └── ic_launcher_background.xml │ │ │ └── layout │ │ │ │ ├── fragment_home.xml │ │ │ │ ├── layout_empty.xml │ │ │ │ ├── fragment_activite.xml │ │ │ │ ├── fragment_mine.xml │ │ │ │ ├── activity_test.xml │ │ │ │ ├── layout_tool_bar.xml │ │ │ │ └── activity_main.xml │ │ ├── java │ │ │ └── me │ │ │ │ └── soushin │ │ │ │ └── tinmvc │ │ │ │ ├── Configure.java │ │ │ │ ├── utils │ │ │ │ ├── DataUtils.java │ │ │ │ ├── StringUtils.java │ │ │ │ ├── DialogUtils.java │ │ │ │ ├── NetUtils.java │ │ │ │ ├── TitleBarUtils.java │ │ │ │ ├── GsonUtils.java │ │ │ │ ├── AppUtils.java │ │ │ │ ├── ActivityUtils.java │ │ │ │ └── SharedUtils.java │ │ │ │ ├── widget │ │ │ │ ├── ToolHolder.java │ │ │ │ ├── ToastStyle.java │ │ │ │ └── InitProvider.java │ │ │ │ ├── module │ │ │ │ ├── demo │ │ │ │ │ ├── TestAdapter.java │ │ │ │ │ ├── ActiviteFragment.java │ │ │ │ │ ├── MineFragment.java │ │ │ │ │ ├── HomeFragment.java │ │ │ │ │ └── TestActivity.java │ │ │ │ └── MainActivity.java │ │ │ │ ├── model │ │ │ │ ├── SubcribeModel.java │ │ │ │ └── ResultModel.java │ │ │ │ ├── FragmentLifecycleCallbacksImpl.java │ │ │ │ ├── base │ │ │ │ ├── BaseDialog.java │ │ │ │ ├── BaseActivity.java │ │ │ │ ├── BaseAdapter.java │ │ │ │ └── BaseFragment.java │ │ │ │ ├── App.java │ │ │ │ ├── ActivityLifeCycleCallBackIml.java │ │ │ │ └── network │ │ │ │ ├── HttpClient.java │ │ │ │ └── TokenInterceptor.java │ │ └── AndroidManifest.xml │ ├── test │ │ └── java │ │ │ └── me │ │ │ └── soushin │ │ │ └── tinmvc │ │ │ └── ExampleUnitTest.java │ └── androidTest │ │ └── java │ │ └── me │ │ └── soushin │ │ └── tinmvc │ │ └── ExampleInstrumentedTest.java ├── proguard-rules.pro └── build.gradle ├── gradle.properties ├── .gitignore ├── gradlew.bat ├── README.md ├── config.gradle └── LICENSE /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | -------------------------------------------------------------------------------- /templates.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/932707629/TinMvc/HEAD/templates.zip -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | TinMvc 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/932707629/TinMvc/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/932707629/TinMvc/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/932707629/TinMvc/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/932707629/TinMvc/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/932707629/TinMvc/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/932707629/TinMvc/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/932707629/TinMvc/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/932707629/TinMvc/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/932707629/TinMvc/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/932707629/TinMvc/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/xml/network_security_config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/java/me/soushin/tinmvc/Configure.java: -------------------------------------------------------------------------------- 1 | package me.soushin.tinmvc; 2 | 3 | 4 | /** 5 | * 全局配置类 6 | * @author SouShin 7 | * @time 2018/11/19 14:45 8 | */ 9 | public class Configure { 10 | // TODO: 2019/1/22 设置 BASE_URL 11 | public static String BASE_URL=""; 12 | 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_home.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #2d8efa 4 | #2d8efa 5 | #2d8efa 6 | #fff 7 | #000 8 | #eeeeee 9 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/test/java/me/soushin/tinmvc/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package me.soushin.tinmvc; 2 | 3 | import org.junit.Test; 4 | 5 | /** 6 | * Example local unit test, which will execute on the development machine (host). 7 | * 8 | * @see Testing documentation 9 | */ 10 | public class ExampleUnitTest { 11 | @Test 12 | public void addition_isCorrect() throws Exception { 13 | //assertEquals(4, 2 + 2); 14 | System.out.println(Math.abs(1.01f - 1.0f) > 0); 15 | } 16 | } -------------------------------------------------------------------------------- /app/src/main/java/me/soushin/tinmvc/utils/DataUtils.java: -------------------------------------------------------------------------------- 1 | package me.soushin.tinmvc.utils; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | /** 7 | * 数据获取工具类 8 | * @auther SouShin 9 | * @time 2019/1/19 18:16 10 | */ 11 | public class DataUtils { 12 | 13 | public static List getActiveList(){ 14 | List activeList=new ArrayList<>(); 15 | for (int i = 0; i < 20; i++) { 16 | activeList.add("动态数组"); 17 | } 18 | return activeList; 19 | } 20 | 21 | 22 | 23 | 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_home.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/java/me/soushin/tinmvc/widget/ToolHolder.java: -------------------------------------------------------------------------------- 1 | package me.soushin.tinmvc.widget; 2 | 3 | import android.view.View; 4 | import android.widget.TextView; 5 | 6 | import me.soushin.tinmvc.R; 7 | 8 | import butterknife.BindView; 9 | import butterknife.ButterKnife; 10 | import butterknife.Unbinder; 11 | 12 | /** 13 | * 标题栏 14 | * @auther SouShin 15 | * @time 2019/1/24 09:11 16 | */ 17 | public class ToolHolder { 18 | @BindView(R.id.view_back) 19 | public View viewBack; 20 | @BindView(R.id.tv_title) 21 | public TextView tvTitle; 22 | @BindView(R.id.toolbar) 23 | public View toolbar; 24 | public Unbinder unbinder; 25 | 26 | public ToolHolder(View view) { 27 | unbinder=ButterKnife.bind(this, view); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_empty.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/xml/bottombar_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 11 | 15 | 16 | 17 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | org.gradle.jvmargs=-Xmx1536m 13 | 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/main/java/me/soushin/tinmvc/utils/StringUtils.java: -------------------------------------------------------------------------------- 1 | package me.soushin.tinmvc.utils; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * author : guof 7 | * Description : 8 | * date : 2018/10/12 9 | */ 10 | public class StringUtils { 11 | 12 | /** 13 | * 字符串判空 14 | * @param str 15 | * @return 16 | */ 17 | public static boolean isNull(String str) { 18 | return str == null || str.trim().length()==0 || "null".equals(str); 19 | } 20 | 21 | /** 22 | * 对象判空 23 | * @param ob 24 | * @return 25 | */ 26 | public static boolean isNull(Object ob) { 27 | return ob == null || ob.toString().length()==0; 28 | } 29 | 30 | /** 31 | * 数组判空 32 | * @param list 33 | * @return 34 | */ 35 | public static boolean isNull(List list){ 36 | return list==null||list.size()==0; 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/java/me/soushin/tinmvc/module/demo/TestAdapter.java: -------------------------------------------------------------------------------- 1 | package me.soushin.tinmvc.module.demo; 2 | 3 | import com.chad.library.adapter.base.BaseViewHolder; 4 | import me.soushin.tinmvc.R; 5 | import me.soushin.tinmvc.base.BaseAdapter; 6 | 7 | import me.soushin.tinmvc.base.BaseAdapter; 8 | 9 | 10 | /** 11 | * baseAdapter的简单使用 12 | * @auther SouShin 13 | * @time 2019/1/19 18:14 14 | */ 15 | public class TestAdapter extends BaseAdapter { 16 | public TestAdapter() { 17 | super(R.layout.layout_tool_bar); 18 | } 19 | 20 | @Override 21 | protected void convert(BaseViewHolder helper, String item) { 22 | helper.setText(R.id.tv_title,item) 23 | .setBackgroundColor(R.id.toolbar,mContext.getResources().getColor(R.color.white)) 24 | .setTextColor(R.id.tv_title,mContext.getResources().getColor(R.color.black)); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_activite.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 17 | -------------------------------------------------------------------------------- /app/src/androidTest/java/me/soushin/tinmvc/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package me.soushin.tinmvc; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static junit.framework.Assert.assertEquals; 11 | /** 12 | * Instrumentation test, which will execute on an Android device. 13 | * 14 | * @see Testing documentation 15 | */ 16 | @RunWith(AndroidJUnit4.class) 17 | public class ExampleInstrumentedTest { 18 | @Test 19 | public void useAppContext() throws Exception { 20 | // Context of the app under test. 21 | Context appContext = InstrumentationRegistry.getTargetContext(); 22 | 23 | assertEquals("com.soushin.android.soushin", appContext.getPackageName()); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/me/soushin/tinmvc/model/SubcribeModel.java: -------------------------------------------------------------------------------- 1 | package me.soushin.tinmvc.model; 2 | 3 | import io.reactivex.disposables.Disposable; 4 | 5 | /** 6 | * 订阅关系 7 | * @auther SouShin 8 | * @time 2019/2/25 14:20 9 | */ 10 | public class SubcribeModel { 11 | 12 | public SubcribeModel() { 13 | } 14 | 15 | public SubcribeModel(String className, Disposable mDisposable) { 16 | this.className = className; 17 | this.mDisposable = mDisposable; 18 | } 19 | 20 | private String className; 21 | private Disposable mDisposable; 22 | 23 | 24 | public String getClassName() { 25 | return className; 26 | } 27 | 28 | public void setClassName(String className) { 29 | this.className = className; 30 | } 31 | 32 | public Disposable getmDisposable() { 33 | return mDisposable; 34 | } 35 | 36 | public void setmDisposable(Disposable mDisposable) { 37 | this.mDisposable = mDisposable; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_mine.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/java/me/soushin/tinmvc/utils/DialogUtils.java: -------------------------------------------------------------------------------- 1 | package me.soushin.tinmvc.utils; 2 | 3 | import android.app.Dialog; 4 | 5 | import java.util.ArrayList; 6 | import java.util.Iterator; 7 | import java.util.List; 8 | 9 | import me.soushin.tinmvc.base.BaseDialog; 10 | 11 | /** 12 | * Dialog工具类 13 | * @auther SouShin 14 | * @time 2019/1/16 09:13 15 | */ 16 | public class DialogUtils { 17 | private static List dialogList = new ArrayList<>(); 18 | 19 | /** 20 | * 关闭弹出框 21 | * 最好在onDestroy里调用一下这个方法 22 | */ 23 | public static void disDialog() { 24 | Iterator it = dialogList.iterator(); 25 | while (it.hasNext()) { 26 | BaseDialog dialog = it.next(); 27 | disDialog(dialog); 28 | it.remove(); 29 | } 30 | } 31 | 32 | public static void addDialog(BaseDialog dialog) { 33 | dialogList.add(dialog); 34 | } 35 | 36 | public static void disDialog(Dialog dialog) { 37 | if (dialog != null && dialog.isShowing()) { 38 | dialog.dismiss(); 39 | } 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/java/me/soushin/tinmvc/FragmentLifecycleCallbacksImpl.java: -------------------------------------------------------------------------------- 1 | package me.soushin.tinmvc; 2 | 3 | import android.os.Bundle; 4 | import android.support.v4.app.Fragment; 5 | import android.support.v4.app.FragmentManager; 6 | 7 | import com.blankj.ALog; 8 | 9 | import me.soushin.tinmvc.base.BaseFragment; 10 | 11 | /** 12 | * @auther SouShin 13 | * @time 2020/3/16 16:24 14 | */ 15 | public class FragmentLifecycleCallbacksImpl extends FragmentManager.FragmentLifecycleCallbacks { 16 | 17 | @Override 18 | public void onFragmentCreated(FragmentManager fm, Fragment f, Bundle savedInstanceState) { 19 | super.onFragmentCreated(fm, f, savedInstanceState); 20 | ALog.e("onFragmentCreated",f.getClass().getSimpleName()); 21 | if (f instanceof BaseFragment &&((BaseFragment) f).useEventBus()){ 22 | //eventbus注册代码 23 | 24 | } 25 | } 26 | 27 | @Override 28 | public void onFragmentDetached(FragmentManager fm, Fragment f) { 29 | super.onFragmentDetached(fm, f); 30 | if (f instanceof BaseFragment &&((BaseFragment) f).useEventBus()){ 31 | //eventbus解绑代码 32 | 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/me/soushin/tinmvc/model/ResultModel.java: -------------------------------------------------------------------------------- 1 | package me.soushin.tinmvc.model; 2 | 3 | import com.zhouyou.http.model.ApiResult; 4 | 5 | /** 6 | * 与服务端约定的返回体 7 | * 根据约定的返回参数更改这个实体类 8 | * result返回0是成功 9 | * 10 | * @auther SouShin 11 | * @time 2019/1/11 11:09 12 | */ 13 | public class ResultModel extends ApiResult { 14 | private int result; 15 | private String message; 16 | private T reData; 17 | 18 | @Override 19 | public String getMsg() { 20 | return this.message; 21 | } 22 | 23 | @Override 24 | public void setMsg(String msg) { 25 | this.message = msg; 26 | } 27 | 28 | @Override 29 | public T getData() { 30 | return this.reData; 31 | } 32 | 33 | @Override 34 | public void setData(T data) { 35 | this.reData = data; 36 | } 37 | 38 | @Override 39 | public boolean isOk() { 40 | // TODO: 2019/1/11 必须设置成功失败 41 | return result == 0; 42 | } 43 | 44 | @Override 45 | public int getCode() { 46 | return this.result; 47 | } 48 | 49 | @Override 50 | public void setCode(int code) { 51 | this.result = code; 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_test.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 17 | 18 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 11 | 12 | 20 | 21 | 22 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /app/src/main/java/me/soushin/tinmvc/module/demo/ActiviteFragment.java: -------------------------------------------------------------------------------- 1 | package me.soushin.tinmvc.module.demo; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.Nullable; 5 | 6 | import com.gyf.barlibrary.ImmersionBar; 7 | import me.soushin.tinmvc.R; 8 | import me.soushin.tinmvc.base.BaseFragment; 9 | 10 | import me.soushin.tinmvc.base.BaseFragment; 11 | 12 | /** 13 | * 动态 14 | * 15 | * @auther SouShin 16 | * @time 2019/1/18 14:42 17 | */ 18 | public class ActiviteFragment extends BaseFragment { 19 | 20 | @Override 21 | public int initLayout() { 22 | return R.layout.fragment_activite; 23 | } 24 | 25 | @Override 26 | protected void initView(@Nullable Bundle savedInstanceState) { 27 | } 28 | 29 | @Override 30 | protected void initListener() { 31 | } 32 | 33 | @Override 34 | public void initImmersionBar() { 35 | // 新手指引: fragment里设置沉浸标题栏之前必须先在宿主Activity里调用ImmersionBar.with(this).init(); 36 | ImmersionBar.with(this)//这里传入的this是fragment的上下文 不能是activity的 37 | .fitsSystemWindows(true) 38 | .statusBarDarkFont(true, 1f) 39 | .init(); 40 | } 41 | 42 | @Override 43 | public boolean immersionBarEnabled() { 44 | // 当为true的时候才可以执行initImmersionBar方法 45 | return true; 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /app/src/main/java/me/soushin/tinmvc/base/BaseDialog.java: -------------------------------------------------------------------------------- 1 | package me.soushin.tinmvc.base; 2 | 3 | import android.app.Dialog; 4 | import android.content.Context; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | 8 | import me.soushin.tinmvc.R; 9 | 10 | import butterknife.ButterKnife; 11 | import butterknife.Unbinder; 12 | import me.soushin.tinmvc.utils.DialogUtils; 13 | 14 | /** 15 | * Created by SouShin on 2018/8/111736. 16 | * 自定义dialog的基类 17 | */ 18 | public class BaseDialog extends Dialog{ 19 | private Context mContext; 20 | private Unbinder unbinder; 21 | public BaseDialog(Context context, int layoutId) { 22 | this(context, layoutId, R.style.CustomDialog); 23 | } 24 | 25 | public BaseDialog(Context context, int layoutId, int styleId) { 26 | super(context, styleId); 27 | this.mContext = context; 28 | LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 29 | View view = inflater.inflate(layoutId, null); 30 | this.setContentView(view); 31 | DialogUtils.addDialog(this); 32 | unbinder=ButterKnife.bind(this); 33 | } 34 | 35 | protected Context getContext_() { 36 | return this.mContext; 37 | } 38 | 39 | @Override 40 | public void dismiss() { 41 | super.dismiss(); 42 | if (unbinder!=null){ 43 | unbinder.unbind(); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | *.aab 5 | 6 | # Files for the ART/Dalvik VM 7 | *.dex 8 | 9 | # Java class files 10 | *.class 11 | 12 | # Generated files 13 | bin/ 14 | gen/ 15 | out/ 16 | 17 | # Gradle files 18 | .gradle/ 19 | build/ 20 | 21 | # Local configuration file (sdk path, etc) 22 | local.properties 23 | 24 | # Proguard folder generated by Eclipse 25 | proguard/ 26 | 27 | # Log Files 28 | *.log 29 | 30 | # Android Studio Navigation editor temp files 31 | .navigation/ 32 | 33 | # Android Studio captures folder 34 | captures/ 35 | 36 | # IntelliJ 37 | *.iml 38 | .idea/workspace.xml 39 | .idea/tasks.xml 40 | .idea/gradle.xml 41 | .idea/assetWizardSettings.xml 42 | .idea/dictionaries 43 | .idea/libraries 44 | .idea/caches 45 | 46 | # Keystore files 47 | # Uncomment the following lines if you do not want to check your keystore files in. 48 | #*.jks 49 | #*.keystore 50 | 51 | # External native build folder generated in Android Studio 2.2 and later 52 | .externalNativeBuild 53 | 54 | # Google Services (e.g. APIs or Firebase) 55 | # google-services.json 56 | 57 | # Freeline 58 | freeline.py 59 | freeline/ 60 | freeline_project_description.json 61 | 62 | # fastlane 63 | fastlane/report.xml 64 | fastlane/Preview.html 65 | fastlane/screenshots 66 | fastlane/test_output 67 | fastlane/readme.md 68 | 69 | # Version control 70 | vcs.xml 71 | 72 | # lint 73 | lint/intermediates/ 74 | lint/generated/ 75 | lint/outputs/ 76 | lint/tmp/ 77 | # lint/reports/ 78 | -------------------------------------------------------------------------------- /app/src/main/java/me/soushin/tinmvc/widget/ToastStyle.java: -------------------------------------------------------------------------------- 1 | package me.soushin.tinmvc.widget; 2 | 3 | import android.view.Gravity; 4 | 5 | import com.hjq.toast.IToastStyle; 6 | 7 | /** 8 | * 自定义Toast样式 9 | * 10 | * @auther SouShin 11 | * @time 2018/12/27 09:45 12 | */ 13 | public class ToastStyle implements IToastStyle { 14 | 15 | @Override 16 | public int getGravity() { 17 | return Gravity.CENTER_HORIZONTAL|Gravity.BOTTOM; 18 | } 19 | 20 | @Override 21 | public int getXOffset() { 22 | return 0; 23 | } 24 | 25 | @Override 26 | public int getYOffset() { 27 | return 240; 28 | } 29 | 30 | @Override 31 | public int getZ() { 32 | return 30; 33 | } 34 | 35 | @Override 36 | public int getCornerRadius() { 37 | return 5; 38 | } 39 | 40 | @Override 41 | public int getBackgroundColor() { 42 | return 0XEE575757; 43 | } 44 | 45 | @Override 46 | public int getTextColor() { 47 | return 0XFFFFFFFF; 48 | } 49 | 50 | @Override 51 | public float getTextSize() { 52 | return 14; 53 | } 54 | 55 | @Override 56 | public int getMaxLines() { 57 | return 3; 58 | } 59 | 60 | @Override 61 | public int getPaddingLeft() { 62 | return 20; 63 | } 64 | 65 | @Override 66 | public int getPaddingTop() { 67 | return 8; 68 | } 69 | 70 | @Override 71 | public int getPaddingRight() { 72 | return getPaddingLeft(); 73 | } 74 | 75 | @Override 76 | public int getPaddingBottom() { 77 | return getPaddingTop(); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /app/src/main/java/me/soushin/tinmvc/module/demo/MineFragment.java: -------------------------------------------------------------------------------- 1 | package me.soushin.tinmvc.module.demo; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.Nullable; 5 | import android.view.View; 6 | import android.widget.RelativeLayout; 7 | import android.widget.TextView; 8 | 9 | import com.gyf.barlibrary.ImmersionBar; 10 | import me.soushin.tinmvc.R; 11 | import me.soushin.tinmvc.base.BaseFragment; 12 | 13 | import butterknife.BindView; 14 | import me.soushin.tinmvc.base.BaseFragment; 15 | 16 | /** 17 | * 我的 18 | * 19 | * @auther SouShin 20 | * @time 2019/1/18 14:44 21 | */ 22 | public class MineFragment extends BaseFragment { 23 | @BindView(R.id.view_back) 24 | TextView tvBack; 25 | @BindView(R.id.tv_title) 26 | TextView tvTitle; 27 | @BindView(R.id.toolbar) 28 | RelativeLayout toolbar; 29 | 30 | @Override 31 | public int initLayout() { 32 | return R.layout.fragment_mine; 33 | } 34 | 35 | @Override 36 | protected void initView(@Nullable Bundle savedInstanceState) { 37 | tvTitle.setText("我的"); 38 | tvBack.setVisibility(View.GONE); 39 | toolbar.setBackgroundResource(R.drawable.ic_launcher_background); 40 | } 41 | 42 | @Override 43 | protected void initListener() {} 44 | 45 | @Override 46 | public void initImmersionBar() { 47 | // 新手指引: fragment里设置沉浸标题栏之前必须先在宿主Activity里调用ImmersionBar.with(this).init(); 48 | ImmersionBar.with(this)//这里传入的this是fragment的上下文 不能是activity的 49 | .titleBar(R.id.toolbar) 50 | .init(); 51 | } 52 | 53 | @Override 54 | public boolean immersionBarEnabled() { 55 | // 当为true的时候才可以执行initImmersionBar方法 56 | return true; 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /app/src/main/java/me/soushin/tinmvc/module/demo/HomeFragment.java: -------------------------------------------------------------------------------- 1 | package me.soushin.tinmvc.module.demo; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.Nullable; 5 | import android.view.View; 6 | import android.widget.TextView; 7 | 8 | import com.gyf.barlibrary.ImmersionBar; 9 | import me.soushin.tinmvc.R; 10 | import me.soushin.tinmvc.base.BaseFragment; 11 | 12 | import butterknife.BindView; 13 | import butterknife.OnClick; 14 | import me.soushin.tinmvc.base.BaseFragment; 15 | 16 | /** 17 | * 首页 18 | * 19 | * @auther SouShin 20 | * @time 2019/1/18 14:40 21 | */ 22 | public class HomeFragment extends BaseFragment { 23 | 24 | @BindView(R.id.view_back) 25 | TextView tvBack; 26 | @BindView(R.id.tv_title) 27 | TextView tvTitle; 28 | 29 | @Override 30 | public int initLayout() { 31 | return R.layout.fragment_home; 32 | } 33 | 34 | @Override 35 | protected void initView(@Nullable Bundle savedInstanceState) { 36 | tvBack.setVisibility(View.GONE); 37 | tvTitle.setText("首页"); 38 | } 39 | 40 | @Override 41 | protected void initListener() { 42 | 43 | } 44 | 45 | @OnClick(R.id.tv_test) 46 | public void onViewClicked() { 47 | startActivity(TestActivity.class); 48 | } 49 | 50 | @Override 51 | public void initImmersionBar() { 52 | // 新手指引: fragment里设置沉浸标题栏之前必须先在宿主Activity里调用ImmersionBar.with(this).init(); 53 | ImmersionBar.with(this)//这里传入的this是fragment的上下文 不能是activity的 54 | .titleBar(R.id.toolbar) 55 | .init(); 56 | } 57 | 58 | @Override 59 | public boolean immersionBarEnabled() { 60 | // 当为true的时候才可以执行initImmersionBar方法 61 | return true; 62 | } 63 | 64 | 65 | 66 | 67 | 68 | } 69 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_tool_bar.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 12 | 13 | 17 | 18 | 26 | 27 | 37 | 38 | 43 | 44 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 14 | 21 | 22 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /app/src/main/java/me/soushin/tinmvc/App.java: -------------------------------------------------------------------------------- 1 | package me.soushin.tinmvc; 2 | 3 | import android.app.Application; 4 | import android.content.Context; 5 | import android.support.annotation.NonNull; 6 | import android.support.multidex.MultiDex; 7 | import com.scwang.smartrefresh.layout.SmartRefreshLayout; 8 | import com.scwang.smartrefresh.layout.api.DefaultRefreshFooterCreator; 9 | import com.scwang.smartrefresh.layout.api.DefaultRefreshHeaderCreator; 10 | import com.scwang.smartrefresh.layout.api.RefreshFooter; 11 | import com.scwang.smartrefresh.layout.api.RefreshHeader; 12 | import com.scwang.smartrefresh.layout.api.RefreshLayout; 13 | import com.scwang.smartrefresh.layout.footer.ClassicsFooter; 14 | import com.scwang.smartrefresh.layout.header.ClassicsHeader; 15 | 16 | /** 17 | * 应用初始化--故事的开始 18 | * 19 | * @auther SouShin 20 | * @time 2019/1/11 11:22 21 | */ 22 | public class App extends Application implements DefaultRefreshHeaderCreator,DefaultRefreshFooterCreator { 23 | private static App app; 24 | 25 | @Override 26 | public void onCreate() { 27 | super.onCreate(); 28 | app=this; 29 | //设置全局的Header构建器 30 | SmartRefreshLayout.setDefaultRefreshHeaderCreator(this); 31 | //设置全局的Footer构建器 32 | SmartRefreshLayout.setDefaultRefreshFooterCreator(this); 33 | } 34 | 35 | public static App getApp() { 36 | return app; 37 | } 38 | 39 | @NonNull 40 | @Override 41 | public RefreshFooter createRefreshFooter(@NonNull Context context, @NonNull RefreshLayout layout) { 42 | //指定为经典Footer 43 | return new ClassicsFooter(context) 44 | .setAccentColor(context.getResources().getColor(R.color.colorAccent)) 45 | .setDrawableSize(20); 46 | } 47 | 48 | @NonNull 49 | @Override 50 | public RefreshHeader createRefreshHeader(@NonNull Context context, @NonNull RefreshLayout layout) { 51 | return new ClassicsHeader(context) 52 | .setAccentColor(context.getResources().getColor(R.color.colorAccent)); 53 | } 54 | 55 | @Override 56 | protected void attachBaseContext(Context base) { 57 | super.attachBaseContext(base); 58 | MultiDex.install(base); 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /app/src/main/java/me/soushin/tinmvc/utils/NetUtils.java: -------------------------------------------------------------------------------- 1 | package me.soushin.tinmvc.utils; 2 | 3 | import android.content.Context; 4 | import android.net.ConnectivityManager; 5 | import android.net.NetworkInfo; 6 | 7 | /** 8 | * 9 | * @author cj 判断网络工具类 10 | * 11 | */ 12 | public class NetUtils { 13 | /** 14 | * 没有连接网络 15 | */ 16 | private static final int NETWORK_NONE = 1; 17 | /** 18 | * 移动网络 19 | */ 20 | private static final int NETWORK_MOBILE = 2; 21 | /** 22 | * 无线网络 23 | */ 24 | private static final int NETWORK_WIFI = 3; 25 | 26 | public static int getNetWorkState(Context context) { 27 | NetworkInfo activeNetworkInfo = getActiveNetworkInfo(context); 28 | if (activeNetworkInfo != null && activeNetworkInfo.isConnected()) { 29 | if (activeNetworkInfo.getType() == (ConnectivityManager.TYPE_WIFI)) { 30 | return NETWORK_WIFI; 31 | } else if (activeNetworkInfo.getType() == (ConnectivityManager.TYPE_MOBILE)) { 32 | return NETWORK_MOBILE; 33 | } 34 | } else { 35 | return NETWORK_NONE; 36 | } 37 | return NETWORK_NONE; 38 | } 39 | 40 | /** 41 | * 获取活动网络信息 42 | * 43 | * @param context 上下文 44 | * @return NetworkInfo 45 | */ 46 | private static NetworkInfo getActiveNetworkInfo(Context context) { 47 | ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); 48 | return cm.getActiveNetworkInfo(); 49 | } 50 | 51 | /** 52 | * 判断网络是否连接 53 | * 需添加权限 54 | * 55 | * @code 56 | */ 57 | public static boolean isConnected(Context context) { 58 | NetworkInfo info = getActiveNetworkInfo(context); 59 | return info != null && info.isConnected(); 60 | } 61 | 62 | /** 63 | * 判断网络是否可用 64 | * 需添加权限 65 | * 66 | * @code 67 | */ 68 | public static boolean isAvailable(Context context) { 69 | NetworkInfo info = getActiveNetworkInfo(context); 70 | return info != null && info.isAvailable(); 71 | } 72 | 73 | 74 | } 75 | -------------------------------------------------------------------------------- /app/src/main/java/me/soushin/tinmvc/base/BaseActivity.java: -------------------------------------------------------------------------------- 1 | package me.soushin.tinmvc.base; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.support.annotation.Nullable; 6 | import android.support.v7.app.AppCompatActivity; 7 | 8 | import com.hjq.toast.ToastUtils; 9 | 10 | import butterknife.ButterKnife; 11 | import butterknife.Unbinder; 12 | import me.yokeyword.fragmentation.SupportActivity; 13 | 14 | /** 15 | * Created by puyanming on 16/5/9. 16 | * 这是Activity的基类 17 | * 继承SupportActivity 解决fragment重叠问题 18 | * 为"单Activity + 多Fragment","多模块Activity + 多Fragment"架构而生,简化开发,轻松解决动画、嵌套、事务相关等问题 19 | * (https://github.com/YoKeyword/Fragmentation) 20 | */ 21 | public abstract class BaseActivity extends SupportActivity { 22 | private Unbinder unbinder; 23 | 24 | @Override 25 | protected void onCreate(@Nullable Bundle savedInstanceState) { 26 | super.onCreate(savedInstanceState); 27 | setContentView(getLayout()); 28 | unbinder = ButterKnife.bind(this); 29 | initView(savedInstanceState); 30 | initListener(); 31 | } 32 | 33 | //这是加载布局的抽象方法 34 | protected abstract int getLayout(); 35 | 36 | //这是加载View的方法 37 | protected abstract void initView(@Nullable Bundle savedInstanceState); 38 | 39 | //这是初始化监听的方法 40 | protected abstract void initListener(); 41 | 42 | /** 43 | * 获取上下文 44 | * 45 | * @return 46 | */ 47 | protected AppCompatActivity getThis() { 48 | return this; 49 | } 50 | 51 | public boolean useEventBus(){ 52 | return false; 53 | } 54 | 55 | /** 56 | * 显示toast 57 | * 58 | * @param msg 59 | */ 60 | protected void showToasty(String msg) { 61 | ToastUtils.show(msg); 62 | } 63 | 64 | /** 65 | * 页面跳转 66 | * 67 | * @param clazz 68 | */ 69 | protected void startActivity(Class clazz) { 70 | startActivity(new Intent(getThis(), clazz)); 71 | } 72 | 73 | /** 74 | * 页面跳转 75 | * 76 | * @param clazz 77 | * @param intent 78 | */ 79 | protected void startActivity(Class clazz, Intent intent) { 80 | intent.setClass(getThis(), clazz); 81 | startActivity(intent); 82 | } 83 | 84 | @Override 85 | protected void onDestroy() { 86 | super.onDestroy(); 87 | unbinder.unbind(); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /app/src/main/java/me/soushin/tinmvc/utils/TitleBarUtils.java: -------------------------------------------------------------------------------- 1 | package me.soushin.tinmvc.utils; 2 | 3 | import android.app.Activity; 4 | import android.view.View; 5 | 6 | import com.gyf.barlibrary.ImmersionBar; 7 | import me.soushin.tinmvc.R; 8 | import me.soushin.tinmvc.widget.ToolHolder; 9 | 10 | /** 11 | * 标题栏工具类 12 | * 标题栏样式较多,可以把所有样式放到这里,方便调用 13 | * 14 | * @auther SouShin 15 | * @time 2019/1/24 09:16 16 | */ 17 | public class TitleBarUtils { 18 | 19 | private ToolHolder toolHolder; 20 | 21 | /** 22 | * 适用于有标题栏有状态栏的情况 23 | * BarParams里面有ImmersionBar各项设置详细说明 24 | * ImmersionBar是设置标题栏和状态栏背景的工具类(https://github.com/gyf-dev/ImmersionBar) 25 | * 支持设置标题栏和状态栏渐变/纯色/白底黑字 26 | * 27 | * @param activity 上下文 28 | * @param isBack 需/不需要返回按钮 29 | * @param title 标题 30 | */ 31 | public void setToolBar(final Activity activity, boolean isBack, String title) { 32 | View toolBar = activity.findViewById(R.id.toolbar); 33 | if (toolBar == null) { 34 | throw new NullPointerException("新手指引:请在activity布局文件里添加"); 35 | } 36 | ImmersionBar.with(activity) 37 | .titleBar(toolBar) 38 | .init(); 39 | toolHolder = new ToolHolder(toolBar); 40 | toolHolder.tvTitle.setText(title); 41 | toolHolder.viewBack.setVisibility(isBack ? View.VISIBLE : View.GONE); 42 | toolHolder.viewBack.setOnClickListener(new View.OnClickListener() { 43 | @Override 44 | public void onClick(View v) { 45 | activity.onBackPressed(); 46 | } 47 | }); 48 | } 49 | 50 | /** 51 | * 适用于无标题栏只有状态栏的情况 52 | * 包括设置状态栏透明 53 | * statusBarDarkFont(true,1) 状态栏黑字 54 | * statusBarColor() 状态栏纯色 55 | * statusBarView(R.id.toolbar) 状态栏背景布局 56 | * fitsSystemWindows(true)解决布局与状态栏重叠问题 57 | * 58 | * @param activity 上下文 59 | */ 60 | public void setToolBar(Activity activity) { 61 | ImmersionBar.with(activity) 62 | .statusBarColor(R.color.colorAccent) 63 | .fitsSystemWindows(true) 64 | .init(); 65 | } 66 | 67 | /** 68 | * 解除绑定 69 | */ 70 | public void Unbind() { 71 | if (toolHolder != null) { 72 | toolHolder.unbinder.unbind(); 73 | toolHolder.unbinder = null; 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16dp 5 | 16dp 6 | 7 | 0dp 8 | 1dp 9 | 2dp 10 | 3dp 11 | 4dp 12 | 5dp 13 | 8dp 14 | 10dp 15 | 12dp 16 | 13dp 17 | 18 | 15dp 19 | 18dp 20 | 21 | 20dp 22 | 25dp 23 | 30dp 24 | 32dp 25 | 35dp 26 | 40dp 27 | 45dp 28 | 50dp 29 | 55dp 30 | 31 | 60dp 32 | 70dp 33 | 74dp 34 | 75dp 35 | 78dp 36 | 80dp 37 | 85dp 38 | 39 | 90dp 40 | 100dp 41 | 105dp 42 | 120dp 43 | 125dp 44 | 150dp 45 | 180dp 46 | 200dp 47 | 240dp 48 | 250dp 49 | 300dp 50 | 51 | 52 | 10sp 53 | 12sp 54 | 13sp 55 | 14sp 56 | 15sp 57 | 16sp 58 | 17sp 59 | 18sp 60 | 20sp 61 | 24sp 62 | 30sp 63 | 64 | 1px 65 | 66 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 20 | 21 | 24 | 25 | 28 | 29 | 32 | 33 | 36 | 39 | 40 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /app/src/main/java/me/soushin/tinmvc/utils/GsonUtils.java: -------------------------------------------------------------------------------- 1 | package me.soushin.tinmvc.utils; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.JsonArray; 5 | import com.google.gson.JsonElement; 6 | import com.google.gson.JsonParser; 7 | import com.google.gson.reflect.TypeToken; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | import java.util.Map; 12 | 13 | /** 14 | * author : guof 15 | * Description : 16 | * date : 2018/8/6 17 | * Copyright (c) 2014-2017 银湾技术二部 18 | */ 19 | 20 | public class GsonUtils { 21 | private static Gson gson; 22 | 23 | public static Gson getGson() { 24 | if (gson == null) { 25 | gson = new Gson(); 26 | } 27 | return gson; 28 | } 29 | 30 | /** 31 | * 将object对象转成json字符串 32 | * 33 | * @param object 34 | * @return 35 | */ 36 | public static String toString(Object object) { 37 | return getGson().toJson(object); 38 | } 39 | 40 | /** 41 | * 将gsonString转成泛型bean 42 | * 43 | * @param gsonString 44 | * @param cls 45 | * @return 46 | */ 47 | public static T toBean(String gsonString, Class cls) { 48 | return getGson().fromJson(gsonString, cls); 49 | } 50 | 51 | /** 52 | * 转成list 53 | * 此方法不可用 54 | * 泛型在编译期类型被擦除导致报错 55 | * @param gsonString 56 | * @param cls 57 | * @return 58 | public static List toList(String gsonString, Class cls) { 59 | return getGson().fromJson(gsonString, new TypeToken>() {}.getType()); 60 | }*/ 61 | 62 | /** 63 | * 转成list 64 | * 解决泛型问题 65 | * @param json 66 | * @param cls 67 | * @param 68 | * @return 69 | */ 70 | public static List toList(String json, Class cls) { 71 | List list = new ArrayList(); 72 | JsonArray array = new JsonParser().parse(json).getAsJsonArray(); 73 | for(final JsonElement elem : array){ 74 | list.add(getGson().fromJson(elem, cls)); 75 | } 76 | return list; 77 | } 78 | 79 | /** 80 | * 转成list中有map的 81 | * 82 | * @param gsonString 83 | * @return 84 | */ 85 | public static List> toListMaps(String gsonString) { 86 | return getGson().fromJson(gsonString, new TypeToken>>(){}.getType()); 87 | } 88 | 89 | /** 90 | * 转成map的 91 | * 92 | * @param gsonString 93 | * @return 94 | */ 95 | public static Map toMaps(String gsonString) { 96 | return getGson().fromJson(gsonString, new TypeToken>(){}.getType()); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'com.jakewharton.butterknife' 3 | 4 | android { 5 | compileSdkVersion rootProject.ext.android["compileSdkVersion"] 6 | buildToolsVersion rootProject.ext.android["buildToolsVersion"] 7 | defaultConfig { 8 | applicationId "me.soushin.tinmvc" 9 | minSdkVersion rootProject.ext.android["minSdkVersion"] 10 | targetSdkVersion rootProject.ext.android["targetSdkVersion"] 11 | versionCode rootProject.ext.android["versionCode"] 12 | versionName rootProject.ext.android["versionName"] 13 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 14 | multiDexEnabled true 15 | } 16 | buildTypes { 17 | debug { 18 | minifyEnabled false 19 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 20 | } 21 | release { 22 | minifyEnabled false 23 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 24 | } 25 | } 26 | android.applicationVariants.all { variant -> 27 | variant.outputs.all { 28 | // 自定义文件名{示例:AppName-Flavor-debug-v1.0.0_201807301409} 29 | outputFileName = "蓝海汇项目框架2.0-${variant.flavorName}-${variant.buildType.name}-v${variant.versionName}_${buildTime()}.apk" 30 | } 31 | } 32 | } 33 | 34 | def buildTime() { 35 | def date = new Date() 36 | def formattedDate = date.format('yyyyMMdd') 37 | return formattedDate 38 | } 39 | 40 | dependencies { 41 | implementation fileTree(include: ['*.jar'], dir: 'libs') 42 | testImplementation rootProject.ext.dependencies["junit"] 43 | androidTestImplementation rootProject.ext.dependencies["runner"] 44 | implementation rootProject.ext.dependencies["BaseRecyclerViewAdapterHelper"] 45 | implementation rootProject.ext.dependencies["rxeasyhttp"] 46 | implementation rootProject.ext.dependencies["ToastUtils"] 47 | implementation rootProject.ext.dependencies["butterknife"] 48 | annotationProcessor rootProject.ext.dependencies["butterknife-compiler"] 49 | implementation rootProject.ext.dependencies["appcompat-v7"] 50 | implementation rootProject.ext.dependencies["recyclerview-v7"] 51 | implementation rootProject.ext.dependencies["constraint-layout"] 52 | implementation rootProject.ext.dependencies["immersionbar"] 53 | implementation rootProject.ext.dependencies["fragmentation"] 54 | implementation rootProject.ext.dependencies["smartrefresh"] 55 | implementation rootProject.ext.dependencies["alog"] 56 | implementation rootProject.ext.dependencies["eventbus"] 57 | implementation rootProject.ext.dependencies["rxpermissions"] 58 | implementation rootProject.ext.dependencies["autosize"] 59 | implementation(rootProject.ext.dependencies["bottom-bar"]) { 60 | exclude group: 'com.android.support' 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## TinMvc ## 2 | 3 | ### 说在前面 ### 4 | 虽然已经在项目中添加了比较详细的代码注释,但是肯定还有很多需要注意的地方没有解释清楚,希望大家在使用的过程中如果发现了什么问题,及时提出来,大家共同解决. 5 | 6 | ### 功能列表 ### 7 | 8 | 1. 解决屏幕适配问题,适配全面屏/刘海屏(AndroidAutoSize是代替AndroidAutoLayout的屏幕适配框架,原理是基于今日头条的适配方案) 9 | 2. 代码解耦,给baseActivity/baseFragment减压 10 | 3. 使用堆栈对activity进行统一管理,ActivityUtil封装了各种常用方法 11 | 4. Activity标题栏统一设置,支持标题栏和状态栏统一设置背景color/shape/drawable 12 | 5. 支持fragment设置状态栏沉浸式,多fragment无缝切换 13 | 6. 解决fragment重叠的bug,fragment任务栈统一管理 14 | 7. 提供懒加载onLazyInitView()/fragment可见性onSupportVisible()/onSupportInvisible()方法回调 15 | 8. SmartRefresh和BaseRecyclerViewAdapterHelper相结合,提供封装的baseAdapter,列表刷新加载更简单 16 | 9. 使用RxEasyHttp网络框架链式调用,与Rxjava2相结合,线程智能控制 17 | 10. 解决Toast禁用通知权限不能弹出的bug,不分主次线程,可自定义Toast样式 18 | 11. 使用bottombar优化用户体验 19 | 12. 支持新手指引,编程中使用不规范的地方,会引导你正确使用 20 | 13. 使用插件一键生成Activity/Fragment 21 | 14. 依赖RxPermission,权限申请更简单 22 | 15. 使用EventBus,事件传递更加清晰 23 | 24 | 25 | ### 开发准备 ### 26 | 27 | 必须的项目配置,框架初始化都已添加,所以直接复制本项目更改报名,即可进行开发使用 28 | 29 | ### 开发指南 ### 30 | 31 | 使用的第三方框架: 32 | 33 | [BottomBar](https://github.com/roughike/BottomBar "BottomBar") 34 | 35 | [Fragmentation](https://github.com/YoKeyword/Fragmentation "Fragmentation") 36 | 37 | [ImmersionBar](https://github.com/gyf-dev/ImmersionBar "ImmersionBar") 38 | 39 | [AndroidAutoSize](https://github.com/JessYanCoding/AndroidAutoSize) 40 | 41 | [RxEasyHttp](https://github.com/zhou-you/RxEasyHttp "RxEasyHttp") 42 | 43 | [ToastUtils](https://github.com/getActivity/ToastUtils "ToastUtils") 44 | 45 | [BaseRecyclerViewAdapterHelper](https://github.com/CymChad/BaseRecyclerViewAdapterHelper "BaseRecyclerViewAdapterHelper") 46 | 47 | [butterknife](https://github.com/JakeWharton/butterknife "butterknife") 48 | 49 | [SmartRefreshLayout](https://github.com/scwang90/SmartRefreshLayout "SmartRefreshLayout") 50 | 51 | [ALog](https://github.com/Blankj/ALog "ALog") 52 | 53 | [RxPermissions](https://github.com/tbruyelle/RxPermissions "RxPermissions") 54 | 55 | [EventBus](https://github.com/greenrobot/EventBus "EventBus") 56 | 57 | ### 使用插件一键生成Activity/Fragment ### 58 | 59 | templates.zip这个压缩文件里放着LhhActivity和LhhFragment两个文件夹 60 | 61 | 首先解压得到这两个文件夹 62 | 63 | LhhActivity放到AS安装目录\plugins\android\lib\templates\activities里 64 | 65 | LhhFragment放到AS安装目录\plugins\android\lib\templates\other里 66 | 67 | 放进去之后重启AS,然后在需要建Activity/Fragment的地方,右键--New--Activity/Fragment--LhhActivity/LhhFragment,然后给Activity/Fragment取个名字,点击Finish,欧克,打完收功! 68 | 69 | ### 注意: ### 70 | 71 | 72 | 1. 设置标题栏和状态栏是在ActivityLifeCycleCallBackIml类里实现的,还进行了其他初始化与销毁业务,实现了对baseActivity的解耦,另外,设置标题栏时要在activity对应的layout里include标题栏布局 73 | 74 | 2. 如果要在fragment里设置状态栏沉浸,可以让该fragment实现SimpleImmersionOwner接口,或者实现ImmersionOwner接口,具体实现可以参考ImmersionBar的demo使用 75 | 76 | 3. 对于一个activity加载多fragment或者一个fragment加载多fragment的需求,可以参考MainActivity里的代码,activity与fragment场景使用方法大同小异,其他需求可参考Fragmentation的demo使用 77 | 78 | 4. 三方库的初始化是在Application里进行的,建议能在子线程初始化的在子线程初始化,RxEasyHttp的初始化默认被注释了,请先设置BASE_URL之后,再取消这段代码的注释. 79 | 80 | 5. 项目的基本用法演示都会放在Demo文件夹中供大家随时查阅. 81 | 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /app/src/main/java/me/soushin/tinmvc/module/demo/TestActivity.java: -------------------------------------------------------------------------------- 1 | package me.soushin.tinmvc.module.demo; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.Nullable; 5 | import android.support.v7.widget.LinearLayoutManager; 6 | import android.support.v7.widget.RecyclerView; 7 | import android.view.MotionEvent; 8 | import android.view.View; 9 | 10 | import com.chad.library.adapter.base.BaseQuickAdapter; 11 | import me.soushin.tinmvc.R; 12 | import me.soushin.tinmvc.base.BaseActivity; 13 | import me.soushin.tinmvc.utils.AppUtils; 14 | import me.soushin.tinmvc.utils.DataUtils; 15 | import com.scwang.smartrefresh.layout.SmartRefreshLayout; 16 | import com.scwang.smartrefresh.layout.api.RefreshLayout; 17 | import com.scwang.smartrefresh.layout.listener.OnRefreshLoadMoreListener; 18 | 19 | import java.util.List; 20 | 21 | import butterknife.BindView; 22 | import me.soushin.tinmvc.base.BaseActivity; 23 | import me.soushin.tinmvc.utils.AppUtils; 24 | import me.soushin.tinmvc.utils.DataUtils; 25 | 26 | /** 27 | * 测试Activity 28 | * BaseAdapter用法演示 29 | * 30 | * @auther SouShin 31 | * @time 2019/1/19 16:29 32 | */ 33 | public class TestActivity extends BaseActivity implements BaseQuickAdapter.OnItemClickListener,OnRefreshLoadMoreListener { 34 | @BindView(R.id.rv_test) 35 | RecyclerView rvTest; 36 | @BindView(R.id.srl_refresh) 37 | SmartRefreshLayout srlRefresh; 38 | private TestAdapter testAdapter; 39 | 40 | @Override 41 | protected int getLayout() { 42 | return R.layout.activity_test; 43 | } 44 | 45 | @Override 46 | protected void initView(@Nullable Bundle savedInstanceState) { 47 | testAdapter = new TestAdapter(); 48 | rvTest.setLayoutManager(new LinearLayoutManager(getThis())); 49 | rvTest.setHasFixedSize(true); 50 | testAdapter.bindToRecyclerView(rvTest); 51 | srlRefresh.autoRefresh(); 52 | } 53 | 54 | @Override 55 | protected void initListener() { 56 | srlRefresh.setOnRefreshLoadMoreListener(this); 57 | testAdapter.setOnItemClickListener(this); 58 | } 59 | 60 | @Override 61 | public void onLoadMore(RefreshLayout refreshLayout) { 62 | requestData(); 63 | } 64 | 65 | @Override 66 | public boolean dispatchTouchEvent(MotionEvent ev) { 67 | // 演示处理EditText显示隐藏输入法 68 | AppUtils.dispatchEditText(getThis(), ev); 69 | return super.dispatchTouchEvent(ev); 70 | } 71 | 72 | @Override 73 | public void onRefresh(RefreshLayout refreshLayout) { 74 | requestData(); 75 | } 76 | 77 | @Override 78 | public void onItemClick(BaseQuickAdapter adapter, View view, int position) { 79 | showToasty(testAdapter.getItem(position)+position); 80 | } 81 | 82 | //模拟请求接口 83 | private void requestData() { 84 | //访问接口之前 85 | if (srlRefresh.isRefreshing()) { 86 | testAdapter.setCurrent(0); 87 | srlRefresh.setEnableLoadMore(false); 88 | } 89 | //访问成功之后获取数据 90 | List datalist = DataUtils.getActiveList(); 91 | testAdapter.setCurrent(testAdapter.getCurrent() + 1)//设置当前页 92 | .setPageNo(5);//设置总页数 93 | if (srlRefresh.isRefreshing()) { 94 | testAdapter.setNewData(srlRefresh, datalist); 95 | } else { 96 | testAdapter.addData(srlRefresh, datalist); 97 | } 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /app/src/main/java/me/soushin/tinmvc/base/BaseAdapter.java: -------------------------------------------------------------------------------- 1 | package me.soushin.tinmvc.base; 2 | 3 | import android.support.annotation.NonNull; 4 | import android.support.annotation.Nullable; 5 | import android.view.ViewGroup; 6 | 7 | import com.chad.library.adapter.base.BaseQuickAdapter; 8 | import com.chad.library.adapter.base.BaseViewHolder; 9 | import me.soushin.tinmvc.R; 10 | import me.soushin.tinmvc.utils.StringUtils; 11 | import com.scwang.smartrefresh.layout.SmartRefreshLayout; 12 | 13 | import java.util.Collection; 14 | import java.util.List; 15 | 16 | /** 17 | * 基于SmartRefresh和BaseQuickAdapter封装的BaseAdapter 18 | * 使用之前先设当前页 current 和总页 pageNo 19 | * 设置adapter请用bindToRecyclerView();这种方式 20 | * 21 | * @author SouShin 22 | * @time 2018/11/13 11:34 23 | */ 24 | public class BaseAdapter extends BaseQuickAdapter { 25 | private int pageNo; 26 | private int current; 27 | 28 | public BaseAdapter(int layoutResId) { 29 | super(layoutResId); 30 | } 31 | 32 | public BaseAdapter(int layoutResId, @Nullable List data) { 33 | super(layoutResId, data); 34 | } 35 | 36 | public BaseAdapter(@Nullable List data) { 37 | super(data); 38 | } 39 | 40 | @Override 41 | protected void convert(K helper, T item) {} 42 | 43 | /** 44 | * 刷新数据 45 | * 使用默认空布局 46 | * 47 | * @param refresh 48 | * @param data 49 | */ 50 | public void setNewData(SmartRefreshLayout refresh, @Nullable List data) { 51 | setNewData(refresh, data, null); 52 | } 53 | 54 | /** 55 | * 刷新数据 56 | * 使用自定义空布局 57 | * 使用之前先设当前页 current 和总页 pageNo 58 | * 59 | * @param refresh 60 | * @param data 61 | */ 62 | public void setNewData(SmartRefreshLayout refresh, @Nullable List data,@Nullable Integer emptyLayout) { 63 | if (refresh==null){ 64 | return; 65 | } 66 | if (getRecyclerView()==null){ 67 | throw new NullPointerException("新手指引:设置adapter请用bindToRecyclerView();这种方式"); 68 | } 69 | // TODO: 2018/11/13 设置空布局 70 | emptyLayout = emptyLayout == null ? R.layout.layout_empty : emptyLayout; 71 | refresh.finishRefresh(); 72 | setNewData(data); 73 | if (StringUtils.isNull(data)) { 74 | refresh.setEnableLoadMore(false); 75 | setEmptyView(emptyLayout, (ViewGroup) getRecyclerView().getParent()); 76 | }else { 77 | refresh.setEnableLoadMore(true); 78 | } 79 | refresh.setNoMoreData(!hasMore()); 80 | } 81 | 82 | /** 83 | * 加载数据 84 | * 使用之前先设当前页 current 和总页 pageNo 85 | * 86 | * @param refresh 87 | * @param newData 88 | */ 89 | public void addData(SmartRefreshLayout refresh, @NonNull Collection newData) { 90 | if (refresh==null){ 91 | return; 92 | } 93 | addData(newData); 94 | if (hasMore()) { 95 | refresh.finishLoadMore(); 96 | } else { 97 | refresh.finishLoadMoreWithNoMoreData(); 98 | } 99 | } 100 | 101 | /** 102 | * 加载失败 103 | * @param refresh 104 | */ 105 | public void loadFail(SmartRefreshLayout refresh) { 106 | if (refresh==null){ 107 | return; 108 | } 109 | if (refresh.isRefreshing()) { 110 | refresh.finishRefresh(0); 111 | } else { 112 | refresh.finishLoadMore(0); 113 | } 114 | refresh.setEnableLoadMore(true); 115 | } 116 | 117 | /** 118 | * true 可以加载更多 false 没有更多了 119 | * 120 | * 先setPageNo&setCurrent再调用setNewData/addData 121 | * @return 122 | */ 123 | public boolean hasMore() { 124 | return current < pageNo; 125 | } 126 | 127 | public BaseAdapter setPageNo(int pageNo) { 128 | this.pageNo = pageNo; 129 | return this; 130 | } 131 | 132 | public int getCurrent() { 133 | return current; 134 | } 135 | 136 | public BaseAdapter setCurrent(int current) { 137 | this.current = current; 138 | return this; 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /app/src/main/java/me/soushin/tinmvc/ActivityLifeCycleCallBackIml.java: -------------------------------------------------------------------------------- 1 | package me.soushin.tinmvc; 2 | 3 | import android.app.Activity; 4 | import android.app.Application; 5 | import android.os.Bundle; 6 | import android.support.v4.app.FragmentActivity; 7 | 8 | import com.blankj.ALog; 9 | import com.gyf.barlibrary.ImmersionBar; 10 | 11 | import java.util.HashMap; 12 | 13 | import me.soushin.tinmvc.base.BaseActivity; 14 | import me.soushin.tinmvc.module.MainActivity; 15 | import me.soushin.tinmvc.module.demo.TestActivity; 16 | import me.soushin.tinmvc.network.HttpClient; 17 | import me.soushin.tinmvc.utils.ActivityUtils; 18 | import me.soushin.tinmvc.utils.StringUtils; 19 | import me.soushin.tinmvc.utils.TitleBarUtils; 20 | 21 | /** 22 | * activity生命周期管理类 23 | * 你想象力有多丰富,这里就有多强大, 24 | * 以前放到BaseActivity的操作都可以放到这里 25 | * 使用:registerActivityLifecycleCallbacks(new ActivityLifeCycleCallBackIml()); 26 | * 27 | * @author SouShin 28 | * @time 2018/12/10 15:38 29 | */ 30 | public class ActivityLifeCycleCallBackIml implements Application.ActivityLifecycleCallbacks { 31 | private TitleBarUtils titleBar; 32 | private HashMap lifecycleCallbacksHashMap=new HashMap<>(); 33 | 34 | @Override 35 | public void onActivityCreated(Activity activity, Bundle savedInstanceState) { 36 | ALog.i("onActivityCreated",activity.getClass().getSimpleName()); 37 | ActivityUtils.addActivity(activity); 38 | if (activity instanceof FragmentActivity){ 39 | FragmentLifecycleCallbacksImpl lifecycleCallbacks=new FragmentLifecycleCallbacksImpl(); 40 | lifecycleCallbacksHashMap.put(activity.getClass().getSimpleName(),lifecycleCallbacks); 41 | ((FragmentActivity) activity).getSupportFragmentManager().registerFragmentLifecycleCallbacks(lifecycleCallbacks,true); 42 | } 43 | if (activity instanceof BaseActivity &&((BaseActivity) activity).useEventBus()){ 44 | //eventbus注册代码 45 | } 46 | } 47 | 48 | @Override 49 | public void onActivityStarted(Activity activity) { 50 | ALog.i("activity生命周期管理类", "onActivityStarted"); 51 | // 这里根据不同的activity显示不同的toolBar 52 | // onActivityCreated()方法回调在activity的setContentView()之前,所以要在onActivityStarted()设置toolbar 53 | titleBar=new TitleBarUtils(); 54 | if (activity instanceof MainActivity) { 55 | ImmersionBar.with(activity).init(); 56 | }else if (activity instanceof TestActivity){ 57 | titleBar.setToolBar(activity,true,"测试"); 58 | } 59 | } 60 | 61 | @Override 62 | public void onActivityResumed(Activity activity) { 63 | ALog.i("activity生命周期管理类", "onActivityResumed"); 64 | } 65 | 66 | @Override 67 | public void onActivityPaused(Activity activity) { 68 | ALog.i("activity生命周期管理类", "onActivityPaused"); 69 | } 70 | 71 | @Override 72 | public void onActivityStopped(Activity activity) { 73 | ALog.i("activity生命周期管理类", "onActivityStopped"); 74 | } 75 | 76 | @Override 77 | public void onActivitySaveInstanceState(Activity activity, Bundle outState) { 78 | ALog.i("activity生命周期管理类", "onActivitySaveInstanceState"); 79 | } 80 | 81 | @Override 82 | public void onActivityDestroyed(Activity activity) { 83 | ALog.i("activity生命周期管理类", "onActivityDestroyed"); 84 | ImmersionBar.with(activity).destroy(); //必须调用该方法,防止内存泄漏 85 | ActivityUtils.removeActivity(activity); 86 | HttpClient.disposeRequest(activity.getLocalClassName()); 87 | if (activity instanceof FragmentActivity){ 88 | FragmentLifecycleCallbacksImpl lifecycleCallbacks=lifecycleCallbacksHashMap.get(activity.getClass().getSimpleName()); 89 | ((FragmentActivity) activity).getSupportFragmentManager().unregisterFragmentLifecycleCallbacks(lifecycleCallbacks); 90 | lifecycleCallbacksHashMap.remove(activity.getClass().getSimpleName()); 91 | } 92 | if (activity instanceof BaseActivity&&((BaseActivity) activity).useEventBus()){ 93 | //eventbus解绑代码 94 | } 95 | if (!StringUtils.isNull(titleBar)){ 96 | titleBar.Unbind(); 97 | titleBar=null; 98 | } 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /app/src/main/java/me/soushin/tinmvc/network/HttpClient.java: -------------------------------------------------------------------------------- 1 | package me.soushin.tinmvc.network; 2 | 3 | import com.blankj.ALog; 4 | import me.soushin.tinmvc.model.SubcribeModel; 5 | import me.soushin.tinmvc.utils.ActivityUtils; 6 | import me.soushin.tinmvc.utils.AppUtils; 7 | import me.soushin.tinmvc.utils.StringUtils; 8 | import com.zhouyou.http.EasyHttp; 9 | import com.zhouyou.http.body.ProgressResponseCallBack; 10 | 11 | import java.util.ArrayList; 12 | import java.util.Iterator; 13 | import java.util.List; 14 | 15 | import io.reactivex.disposables.Disposable; 16 | 17 | /** 18 | * 网络交互 19 | * RxEasyHttp 详情链接(https://github.com/zhou-you/RxEasyHttp) 20 | * @warning 由于Retrofit的限制,传递的参数不能为空! 21 | * @author SouShin 22 | * @time 2018/11/16 17:31 23 | */ 24 | public class HttpClient { 25 | public static List disposList = new ArrayList<>(); 26 | /** 27 | * file上传结果回调 28 | */ 29 | private static ProgressResponseCallBack fileProgressCallBack = new ProgressResponseCallBack() { 30 | @Override 31 | public void onResponseProgress(long bytesWritten, long contentLength, boolean done) { 32 | ALog.e("file上传回调", bytesWritten, contentLength, done); 33 | } 34 | }; 35 | 36 | /** 37 | * 使用示例1 带有加载框的请求 38 | * ResultModel:ResultModel是与后台约定的数据返回类型 Object是需要在回调中返回的对象 39 | * ProgressSubscriber要求传入一个dialog对象 40 | * 注意这个dialog必须实现IProgressDialog接口 41 | * 错误回调 super.onError(e);必须写 因为里面加了订阅关系的处理 42 | * 43 | * @param phone 手机号 44 | * @param path 文件路径 45 | * @param progressCallBack 带有加载框的回调 46 | 47 | public static void getPhoneCode(String phone,String path, ProgressSubscriber progressCallBack) { 48 | EasyHttp.post("get_code") 49 | .params("phone", phone) 50 | .params("file",new File(path),fileProgressCallBack) 51 | .execute(new CallClazzProxy, Object>(Object.class) { 52 | }).subscribe(progressCallBack); 53 | }*/ 54 | 55 | /** 56 | * 使用示例2 普通网络请求 57 | * ResultModel:ResultModel是与后台约定的数据返回类型 Object是需要在回调中返回的对象 58 | * 返回Disposable对象,方便取消网络请求 记得调用addDisposable(disposable) 把订阅添加到集合中 59 | * ActivityLifeCycleCallBackIml中已经做了解除订阅的处理 60 | * 61 | * @param phone 手机号 62 | * @param path 文件路径 63 | * @param simpleCallBack 没有加载框的回调 64 | 65 | public static void getPhoneCode(String phone, String path, SimpleCallBack simpleCallBack) { 66 | Disposable disposable=EasyHttp.post("get_code") 67 | .params("phone", phone) 68 | .params("file",new File(path),fileProgressCallBack) 69 | .execute(new CallBackProxy, Object>(simpleCallBack) { 70 | }); 71 | addDisposable(disposable); 72 | }*/ 73 | 74 | /** 75 | * 使用示例3 请求返回Observable 76 | * ResultModel:ResultModel是与后台约定的数据返回类型 Object是需要在回调中返回的对象 77 | * 返回Observable对象,方便使用Rxjava2进行自己的逻辑操作 78 | * 79 | * @param phone 手机号 80 | * @param path 文件路径 81 | public static Observable getPhoneCode(String phone, String path) { 82 | return EasyHttp.post("get_code") 83 | .params("phone", phone) 84 | .params("file",new File(path),fileProgressCallBack) 85 | .execute(new CallClazzProxy, Object>(Object.class) { 86 | }); 87 | }*/ 88 | 89 | /** 90 | * 添加订阅关系 91 | * 92 | * @param disposable 93 | */ 94 | public static void addDisposable(Disposable disposable) { 95 | disposList.add(new SubcribeModel(ActivityUtils.currentActivity().getLocalClassName()+System.currentTimeMillis(),disposable)); 96 | ALog.e("添加订阅", disposList.size()); 97 | } 98 | 99 | /** 100 | * 在onDestroy里执行 101 | * 取消所有订阅的网络请求 102 | */ 103 | /** 104 | * 在onDestroy里执行 105 | * 取消所有订阅的网络请求 106 | */ 107 | public static synchronized void disposeRequest(String className) { 108 | Iterator it = disposList.iterator(); 109 | while(it.hasNext()){ 110 | SubcribeModel subcribeModel=it.next(); 111 | String key=subcribeModel.getClassName(); 112 | if(!StringUtils.isNull(key)&&key.startsWith(className)){ 113 | EasyHttp.cancelSubscription(subcribeModel.getmDisposable()); 114 | subcribeModel.setClassName(null); 115 | subcribeModel.setmDisposable(null); 116 | it.remove(); 117 | } 118 | } 119 | ALog.e("取消订阅", disposList.size()); 120 | } 121 | 122 | } 123 | -------------------------------------------------------------------------------- /app/src/main/java/me/soushin/tinmvc/base/BaseFragment.java: -------------------------------------------------------------------------------- 1 | package me.soushin.tinmvc.base; 2 | 3 | import android.content.Intent; 4 | import android.content.res.Configuration; 5 | import android.os.Bundle; 6 | import android.support.annotation.Nullable; 7 | import android.support.v4.app.FragmentActivity; 8 | import android.view.LayoutInflater; 9 | import android.view.View; 10 | import android.view.ViewGroup; 11 | 12 | import com.gyf.barlibrary.SimpleImmersionOwner; 13 | import com.gyf.barlibrary.SimpleImmersionProxy; 14 | import com.hjq.toast.ToastUtils; 15 | 16 | import butterknife.ButterKnife; 17 | import butterknife.Unbinder; 18 | import me.yokeyword.fragmentation.SupportFragment; 19 | 20 | /** 21 | * Created by puyanming on 16/5/9. 22 | * 这是Fragment的基类 23 | * 继承SupportFragment 解决fragment重叠问题 24 | * (https://github.com/YoKeyword/Fragmentation) 25 | * 提供onSupportVisible()、懒加载onLazyInitView()等生命周期方法,简化嵌套Fragment的开发过程 26 | */ 27 | public abstract class BaseFragment extends SupportFragment implements SimpleImmersionOwner { 28 | private Unbinder unbinder; 29 | //ImmersionBar代理类 30 | private SimpleImmersionProxy mSimpleImmersionProxy = new SimpleImmersionProxy(this); 31 | 32 | @Nullable 33 | @Override 34 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 35 | return inflater.inflate(initLayout(), container, false); 36 | } 37 | 38 | @Override 39 | public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { 40 | super.onViewCreated(view, savedInstanceState); 41 | unbinder = ButterKnife.bind(this, view); 42 | initView(savedInstanceState); 43 | initListener(); 44 | } 45 | 46 | @Override 47 | public void setUserVisibleHint(boolean isVisibleToUser) { 48 | super.setUserVisibleHint(isVisibleToUser); 49 | mSimpleImmersionProxy.setUserVisibleHint(isVisibleToUser); 50 | } 51 | 52 | @Override 53 | public void onActivityCreated(@Nullable Bundle savedInstanceState) { 54 | super.onActivityCreated(savedInstanceState); 55 | mSimpleImmersionProxy.onActivityCreated(savedInstanceState); 56 | } 57 | 58 | @Override 59 | public void onHiddenChanged(boolean hidden) { 60 | super.onHiddenChanged(hidden); 61 | mSimpleImmersionProxy.onHiddenChanged(hidden); 62 | } 63 | 64 | @Override 65 | public void onConfigurationChanged(Configuration newConfig) { 66 | super.onConfigurationChanged(newConfig); 67 | mSimpleImmersionProxy.onConfigurationChanged(newConfig); 68 | } 69 | 70 | @Override 71 | public void onDestroyView() { 72 | super.onDestroyView(); 73 | } 74 | 75 | @Override 76 | public void onDestroy() { 77 | super.onDestroy(); 78 | mSimpleImmersionProxy.onDestroy(); 79 | unbinder.unbind(); 80 | } 81 | 82 | public abstract int initLayout(); 83 | 84 | //这是加载View的方法 85 | protected abstract void initView(@Nullable Bundle savedInstanceState); 86 | 87 | //这是初始化监听的方法 88 | protected abstract void initListener(); 89 | 90 | @Override 91 | public void onLazyInitView(@Nullable Bundle savedInstanceState) { 92 | super.onLazyInitView(savedInstanceState); 93 | // 只有第一次可见时调用 94 | // ALog.i("懒加载",getClass().getSimpleName()); 95 | } 96 | 97 | @Override 98 | public void onSupportInvisible() { 99 | super.onSupportInvisible(); 100 | // ALog.i("fragment隐藏",getClass().getSimpleName()); 101 | } 102 | 103 | @Override 104 | public void onSupportVisible() { 105 | super.onSupportVisible(); 106 | // ALog.i("fragment可见",getClass().getSimpleName()); 107 | } 108 | 109 | /** 110 | * 获取上下文 111 | * 112 | * @return 113 | */ 114 | protected FragmentActivity getThis() { 115 | return _mActivity; 116 | } 117 | 118 | public boolean useEventBus(){ 119 | return false; 120 | } 121 | 122 | @Override 123 | public void initImmersionBar() {} 124 | 125 | @Override 126 | public boolean immersionBarEnabled() { 127 | // 当为true的时候才可以执行initImmersionBar方法 128 | return false; 129 | } 130 | /** 131 | * 显示toast 132 | * 133 | * @param msg 134 | */ 135 | protected void showToasty(String msg) { 136 | ToastUtils.show(msg); 137 | } 138 | 139 | /** 140 | * 页面跳转 141 | * 142 | * @param clazz 143 | */ 144 | protected void startActivity(Class clazz) { 145 | startActivity(new Intent(getActivity(), clazz)); 146 | } 147 | 148 | /** 149 | * 页面跳转 150 | * 151 | * @param clazz 152 | * @param intent 153 | */ 154 | protected void startActivity(Class clazz, Intent intent) { 155 | intent.setClass(getActivity(), clazz); 156 | startActivity(intent); 157 | } 158 | 159 | } -------------------------------------------------------------------------------- /app/src/main/java/me/soushin/tinmvc/module/MainActivity.java: -------------------------------------------------------------------------------- 1 | package me.soushin.tinmvc.module; 2 | 3 | import android.Manifest; 4 | import android.media.MediaRouter; 5 | import android.os.Bundle; 6 | import android.support.annotation.Nullable; 7 | 8 | import com.blankj.ALog; 9 | import me.soushin.tinmvc.R; 10 | import me.soushin.tinmvc.base.BaseActivity; 11 | import me.soushin.tinmvc.module.demo.ActiviteFragment; 12 | import me.soushin.tinmvc.module.demo.HomeFragment; 13 | import me.soushin.tinmvc.module.demo.MineFragment; 14 | import me.soushin.tinmvc.utils.ActivityUtils; 15 | import me.soushin.tinmvc.utils.AppUtils; 16 | import me.soushin.tinmvc.utils.NetUtils; 17 | import me.soushin.tinmvc.utils.StringUtils; 18 | import com.roughike.bottombar.BottomBar; 19 | import com.roughike.bottombar.OnTabSelectListener; 20 | import com.tbruyelle.rxpermissions2.Permission; 21 | import com.tbruyelle.rxpermissions2.RxPermissions; 22 | import com.zhouyou.http.subsciber.ProgressSubscriber; 23 | 24 | import java.util.ArrayList; 25 | import java.util.List; 26 | 27 | import butterknife.BindView; 28 | import me.yokeyword.fragmentation.SupportFragment; 29 | 30 | 31 | /** 32 | * 主页 33 | * 这里简单演示一下fragmentation 详细的使用请到(https://github.com/YoKeyword/Fragmentation) 34 | * 35 | * @author SouShin 36 | * @time 2018/8/28 17:51 37 | */ 38 | public class MainActivity extends BaseActivity implements OnTabSelectListener { 39 | 40 | @BindView(R.id.bottomBar) 41 | BottomBar bottomBar; 42 | List fragmentList; 43 | 44 | @Override 45 | protected int getLayout() { 46 | return R.layout.activity_main; 47 | } 48 | 49 | @Override 50 | protected void initView(@Nullable Bundle savedInstanceState) { 51 | initPermission(); 52 | fragmentList = new ArrayList<>(); 53 | if (StringUtils.isNull(findFragment(HomeFragment.class))) { 54 | fragmentList.add(new HomeFragment()); 55 | fragmentList.add(new ActiviteFragment()); 56 | fragmentList.add(new MineFragment()); 57 | loadMultipleRootFragment(R.id.fl_container, 0, fragmentList.get(0), 58 | fragmentList.get(1), fragmentList.get(2)); 59 | }else { 60 | fragmentList.add(findFragment(HomeFragment.class)); 61 | fragmentList.add(findFragment(ActiviteFragment.class)); 62 | fragmentList.add(findFragment(MineFragment.class)); 63 | } 64 | } 65 | 66 | private void initPermission() { 67 | //获取权限 68 | new RxPermissions(getThis()) 69 | .requestEachCombined(Manifest.permission.WRITE_EXTERNAL_STORAGE) 70 | .subscribe(new ProgressSubscriber(getThis()) { 71 | @Override 72 | public void onNext(Permission permission) { 73 | super.onNext(permission); 74 | if (permission.granted){ 75 | ALog.e("权限已同意.....", permission.toString()); 76 | }else if (permission.shouldShowRequestPermissionRationale){ 77 | showToasty("权限已被拒绝,这将会导致部分功能不可用!"); 78 | }else { 79 | ALog.e("权限被拒绝.....", permission.toString()); 80 | } 81 | } 82 | }); 83 | } 84 | 85 | @Override 86 | protected void initListener() { 87 | bottomBar.setOnTabSelectListener(this); 88 | } 89 | 90 | @Override 91 | public void onTabSelected(int tabId) { 92 | switch (tabId){ 93 | case R.id.tab_home: 94 | // showToasty("当前:"+bottomBar.getTabAtPosition(0).getTitle()); 95 | showHideFragment(fragmentList.get(0)); 96 | break; 97 | case R.id.tab_activity: 98 | // showToasty("当前:"+bottomBar.getTabAtPosition(1).getTitle()); 99 | showHideFragment(fragmentList.get(1)); 100 | break; 101 | case R.id.tab_mine: 102 | // showToasty("当前:"+bottomBar.getTabAtPosition(2).getTitle()); 103 | showHideFragment(fragmentList.get(2)); 104 | break; 105 | } 106 | } 107 | 108 | @Override 109 | protected void onResume() { 110 | super.onResume(); 111 | inspectNet(); 112 | } 113 | 114 | /** 115 | * 检测网络 116 | */ 117 | private void inspectNet() { 118 | int netMobile = NetUtils.getNetWorkState(getThis()); 119 | switch (netMobile) { 120 | case 1: 121 | ALog.e("网络状况:当前没有网络"); 122 | showToasty("当前网络不可用,请检查网络是否连接"); 123 | break; 124 | case 2: 125 | ALog.i("网络状况:连接移动数据"); 126 | break; 127 | case 3: 128 | ALog.i("网络状况:连接wifi"); 129 | break; 130 | } 131 | } 132 | 133 | @Override 134 | public void onBackPressedSupport() { 135 | // onBackPressed()已停用 请使用onBackPressedSupport代替 136 | if (AppUtils.doubleClickExit()) { 137 | ActivityUtils.AppExit(getThis()); 138 | } 139 | } 140 | 141 | } 142 | 143 | -------------------------------------------------------------------------------- /app/src/main/java/me/soushin/tinmvc/utils/AppUtils.java: -------------------------------------------------------------------------------- 1 | package me.soushin.tinmvc.utils; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.app.Activity; 5 | import android.content.Context; 6 | import android.view.MotionEvent; 7 | import android.view.View; 8 | import android.view.inputmethod.InputMethodManager; 9 | import android.widget.EditText; 10 | import android.widget.TextView; 11 | 12 | import com.hjq.toast.ToastUtils; 13 | 14 | import java.security.MessageDigest; 15 | import java.security.NoSuchAlgorithmException; 16 | 17 | 18 | /** 19 | * 常用工具类 20 | * 1 双击退出 21 | * 2 避免快速重复点击 22 | * 3 生成MD5加密32位字符串 23 | * 4 获取验证码倒计时 24 | * 5 处理EditText显示隐藏输入法 25 | * 26 | * @author SouShin 27 | * @time 2018/11/16 13:54 28 | */ 29 | public class AppUtils { 30 | 31 | /** 32 | * 退出登录 33 | * 34 | * @param context 35 | * @return 36 | */ 37 | private static long mExitTime; 38 | 39 | public static boolean doubleClickExit() { 40 | if ((System.currentTimeMillis() - mExitTime) > 2000) { 41 | mExitTime = System.currentTimeMillis(); 42 | ToastUtils.show("再按一次退出"); 43 | return false; 44 | } 45 | return true; 46 | } 47 | 48 | /** 49 | * 避免快速重复点击 50 | * 51 | * @return 52 | */ 53 | private static long lastClickTime = 0;//上次点击的时间 54 | private static int spaceTime = 1000;//时间间隔 55 | 56 | public static boolean isFastClick() { 57 | long currentTime = System.currentTimeMillis();//当前系统时间 58 | boolean isAllowClick;//是否允许点击 59 | if (currentTime - lastClickTime > spaceTime) { 60 | isAllowClick = true; 61 | } else { 62 | isAllowClick = false; 63 | } 64 | lastClickTime = currentTime; 65 | return isAllowClick; 66 | } 67 | 68 | //--------------------------处理EditText显示隐藏输入法------------------------------- 69 | 70 | /** 71 | * 处理EditText显示隐藏输入法 72 | * 点击EditText显示输入法 点击其他View隐藏输入法 73 | * @param activity 74 | * @param event 75 | */ 76 | public static void dispatchEditText(Activity activity, MotionEvent event) { 77 | View v = activity.getCurrentFocus(); 78 | if (event.getAction() == MotionEvent.ACTION_DOWN && isShouldHideKeyboard(v, event)) { 79 | hideSoftInput(v); 80 | } 81 | } 82 | 83 | /** 84 | * 是否隐藏软键盘 85 | * 86 | * @param v 87 | * @param event 88 | * @return 89 | */ 90 | public static boolean isShouldHideKeyboard(View v, MotionEvent event) { 91 | if (v instanceof EditText) { 92 | int[] l = {0, 0}; 93 | v.getLocationInWindow(l); 94 | int left = l[0], top = l[1], bottom = top + v.getHeight(), right = left + v.getWidth(); 95 | return !(event.getX() > left && event.getX() < right 96 | && event.getY() > top && event.getY() < bottom); 97 | } 98 | return false; 99 | } 100 | 101 | /** 102 | * 隐藏输入法 103 | * 104 | * @param v 105 | */ 106 | public static void hideSoftInput(View v) { 107 | InputMethodManager imm = (InputMethodManager) v.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); 108 | if (imm.isActive()) { 109 | imm.hideSoftInputFromWindow(v.getApplicationWindowToken(), 0); 110 | } 111 | } 112 | 113 | //--------------------------处理EditText显示隐藏输入法------------------------------- 114 | 115 | //--------------------------MD5加密------------------------------- 116 | 117 | /** 118 | * 生成MD5加密32位字符串 119 | * 120 | * @param MStr :需要加密的字符串 121 | * @return 122 | */ 123 | public static String Md5(String MStr) { 124 | try { 125 | final MessageDigest mDigest = MessageDigest.getInstance("MD5"); 126 | mDigest.update(MStr.getBytes()); 127 | return bytesToHexString(mDigest.digest()); 128 | } catch (NoSuchAlgorithmException e) { 129 | return String.valueOf(MStr.hashCode()); 130 | } 131 | } 132 | 133 | /** 134 | * MD5内部算法-------不能修改! 135 | * 136 | * @param bytes 137 | * @return 138 | */ 139 | private static String bytesToHexString(byte[] bytes) { 140 | // http://stackoverflow.com/questions/332079 141 | StringBuilder sb = new StringBuilder(); 142 | for (int i = 0; i < bytes.length; i++) { 143 | String hex = Integer.toHexString(0xFF & bytes[i]); 144 | if (hex.length() == 1) { 145 | sb.append('0'); 146 | } 147 | sb.append(hex); 148 | } 149 | return sb.toString(); 150 | } 151 | //--------------------------MD5加密------------------------------- 152 | 153 | /** 154 | * 倒计时 155 | * 156 | * @param textView 控件 157 | * @param waitTime 倒计时总时长 158 | * @param interval 倒计时的间隔时间 159 | */ 160 | public static void countDown(final TextView textView, long waitTime, long interval) { 161 | textView.setEnabled(false); 162 | android.os.CountDownTimer timer = new android.os.CountDownTimer(waitTime, interval) { 163 | @SuppressLint("DefaultLocale") 164 | @Override 165 | public void onTick(long millisUntilFinished) { 166 | textView.setText(millisUntilFinished / 1000 + "s"); 167 | } 168 | 169 | @Override 170 | public void onFinish() { 171 | textView.setEnabled(true); 172 | textView.setText("重新获取"); 173 | } 174 | }; 175 | timer.start(); 176 | } 177 | 178 | } 179 | -------------------------------------------------------------------------------- /app/src/main/java/me/soushin/tinmvc/network/TokenInterceptor.java: -------------------------------------------------------------------------------- 1 | package me.soushin.tinmvc.network; 2 | 3 | import android.text.TextUtils; 4 | 5 | import com.blankj.ALog; 6 | import me.soushin.tinmvc.model.ResultModel; 7 | import me.soushin.tinmvc.utils.ActivityUtils; 8 | import me.soushin.tinmvc.utils.GsonUtils; 9 | import com.zhouyou.http.interceptor.BaseExpiredInterceptor; 10 | import com.zhouyou.http.utils.HttpLog; 11 | 12 | import java.io.IOException; 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | import java.util.TreeMap; 16 | 17 | import okhttp3.FormBody; 18 | import okhttp3.HttpUrl; 19 | import okhttp3.Request; 20 | import okhttp3.Response; 21 | 22 | /** 23 | * 处理token过期等问题 24 | * 25 | * @author SouShin 26 | * @time 2018/12/10 13:32 27 | */ 28 | public class TokenInterceptor extends BaseExpiredInterceptor { 29 | private ResultModel resultModel; 30 | 31 | @Override 32 | public boolean isResponseExpired(Response response, String bodyString) { 33 | resultModel = GsonUtils.toBean(bodyString, ResultModel.class); 34 | /*if (resultModel != null) { 35 | ALog.e("token拦截isResponseExpired", bodyString); 36 | return resultModel.getData() != null; 37 | }*/ 38 | return false; 39 | } 40 | 41 | /** 42 | * 只有上面配置的过期情况才会执行这里(return true;) 43 | * token 过期处理方案 44 | * 1 跳转登录页 45 | * 2 自动请求登录接口刷新token 并继续该接口的业务 46 | * @param chain 47 | * @param bodyString 48 | * @return 49 | */ 50 | @Override 51 | public Response responseExpired(Chain chain, String bodyString) { 52 | ALog.e("token拦截responseExpired", bodyString); 53 | HttpClient.disposeRequest(ActivityUtils.currentActivity().getLocalClassName()); 54 | // 第一种处理方式:直接跳转登录页 55 | // App.goLogin(); 56 | if (resultModel != null) { 57 | try { 58 | return chain.proceed(chain.request()); 59 | } catch (IOException e) { 60 | e.printStackTrace(); 61 | } 62 | } 63 | // 第二种处理方式: 自动请求登录接口刷新token 并继续该接口的业务 64 | /*try { 65 | refreshToken(); 66 | if (resultModel != null) { 67 | return processAccessTokenError(chain, chain.request()); 68 | } 69 | }catch (Exception e){ 70 | 71 | }*/ 72 | return null; 73 | } 74 | 75 | /** 76 | * 执行登录请求刷新本地token 77 | * 请求成功后一定要把token保存下来 78 | * @throws IOException 79 | */ 80 | public void refreshToken() throws IOException { 81 | 82 | } 83 | 84 | /** 85 | * 重新拼装并执行Request 86 | */ 87 | private Response processAccessTokenError(Chain chain, Request request) throws IOException { 88 | // create a new request and modify it accordingly using the new token 89 | String method = request.method(); 90 | FormBody oldBody = (FormBody) request.body(); 91 | if (oldBody == null) { 92 | if (method.equalsIgnoreCase("GET")) { 93 | oldBody = getRequestParams(request.url().query()); 94 | } else { 95 | return chain.proceed(request); 96 | } 97 | } 98 | FormBody.Builder newBody = new FormBody.Builder(); 99 | for (int i = 0; i < oldBody.size(); i++) { 100 | String name = oldBody.encodedName(i); 101 | String value = oldBody.encodedValue(i); 102 | newBody.add(name, value); 103 | } 104 | 105 | Request newRequest; 106 | if (method.equalsIgnoreCase("GET")) { 107 | String url = packageParams(newBody.build()); 108 | HttpLog.i("uuok.GET.Error.newUrl:" + url); 109 | HttpUrl newHrrpIrl = request.url().newBuilder().query(url).build(); 110 | newRequest = request.newBuilder().url(newHrrpIrl).get().build(); 111 | } else { 112 | newRequest = request.newBuilder().post(newBody.build()).build(); 113 | } 114 | return chain.proceed(newRequest); 115 | } 116 | 117 | /** 118 | * 将GET请求的参数封装成FormBody 119 | */ 120 | private FormBody getRequestParams(String params) { 121 | if (params == null) 122 | return null; 123 | String[] strArr = params.split("&"); 124 | if (strArr == null) { 125 | return null; 126 | } 127 | TreeMap map = new TreeMap<>(); 128 | FormBody.Builder fBulder = new FormBody.Builder(); 129 | for (String s : strArr) { 130 | String[] sArr = s.split("="); 131 | if (sArr.length < 2) 132 | continue; 133 | map.put(sArr[0], sArr[1]); 134 | fBulder.add(sArr[0], sArr[1]); 135 | } 136 | FormBody formBody = fBulder.build(); 137 | return formBody; 138 | } 139 | 140 | /** 141 | * 封装参数 142 | */ 143 | private String packageParams(FormBody oldBody) { 144 | List namesAndValues = new ArrayList<>(); 145 | for (int i = 0; i < oldBody.size(); i++) { 146 | String name = oldBody.encodedName(i); 147 | String value = oldBody.encodedValue(i); 148 | if (!TextUtils.isEmpty(name)) { 149 | namesAndValues.add(name); 150 | namesAndValues.add(value); 151 | } 152 | } 153 | StringBuilder sb = new StringBuilder(); 154 | namesAndValuesToQueryString(sb, namesAndValues); 155 | return sb.toString(); 156 | } 157 | 158 | /** 159 | * 合并GET参数 160 | */ 161 | private void namesAndValuesToQueryString(StringBuilder out, List namesAndValues) { 162 | for (int i = 0, size = namesAndValues.size(); i < size; i += 2) { 163 | String name = namesAndValues.get(i); 164 | String value = namesAndValues.get(i + 1); 165 | if (i > 0) out.append('&'); 166 | out.append(name); 167 | if (value != null) { 168 | out.append('='); 169 | out.append(value); 170 | } 171 | } 172 | } 173 | 174 | } 175 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/java/me/soushin/tinmvc/utils/ActivityUtils.java: -------------------------------------------------------------------------------- 1 | package me.soushin.tinmvc.utils; 2 | 3 | import android.app.Activity; 4 | import android.app.ActivityManager; 5 | import android.content.Context; 6 | import android.content.Intent; 7 | import android.os.Bundle; 8 | 9 | import java.util.Stack; 10 | 11 | /** 12 | * 封装Activity相关工具类 13 | * 14 | * @author SouShin 15 | * @time 2018/10/29 14:45 16 | */ 17 | public class ActivityUtils { 18 | 19 | private static Stack activityStack; 20 | 21 | /** 22 | * 添加Activity 到栈 23 | * 24 | * @param activity 25 | */ 26 | public static void addActivity(Activity activity) { 27 | if (activityStack == null) { 28 | activityStack = new Stack<>(); 29 | } 30 | activityStack.add(activity); 31 | } 32 | 33 | /** 34 | * 获取当前的Activity(堆栈中最后一个压入的) 35 | */ 36 | public static Activity currentActivity() { 37 | return activityStack.lastElement(); 38 | } 39 | 40 | /** 41 | * 结束当前Activity(堆栈中最后一个压入的) 42 | */ 43 | public static void finishActivity() { 44 | Activity activity = activityStack.lastElement(); 45 | if (activity != null) { 46 | activityStack.remove(activity); 47 | activity.finish(); 48 | } 49 | } 50 | 51 | /** 52 | * 移除指定的Activity 53 | * 54 | * @param activity 55 | */ 56 | public static void removeActivity(Activity activity) { 57 | if (activity != null) { 58 | activityStack.remove(activity); 59 | } 60 | } 61 | 62 | /** 63 | * 结束指定的Activity 64 | * 65 | * @param activity 66 | */ 67 | public static void finishActivity(Activity activity) { 68 | if (activity != null) { 69 | activityStack.remove(activity); 70 | activity.finish(); 71 | activity = null; 72 | } 73 | } 74 | 75 | /** 76 | * 结束指定类名的Activity 77 | */ 78 | public static void finishActivity(Class cls) { 79 | for (int i = 0; i < activityStack.size(); i++) { 80 | if (activityStack.get(i).getClass().equals(cls)) { 81 | finishActivity(activityStack.get(i)); 82 | } 83 | } 84 | } 85 | 86 | /** 87 | * 结束所有的Activity 88 | */ 89 | public static void finishAllActivity() { 90 | int size = activityStack.size(); 91 | for (int i = 0; i < size; i++) { 92 | if (null != activityStack.get(i)) { 93 | activityStack.get(i).finish(); 94 | } 95 | } 96 | activityStack.clear(); 97 | } 98 | 99 | public static Stack getActivityStack() { 100 | return activityStack; 101 | } 102 | 103 | /** 104 | * 判断是否存在指定Activity 105 | * 106 | * @return {@code true}: 是
{@code false}: 否 107 | */ 108 | public static boolean isExistActivity(Class clazz) { 109 | return activityStack.contains(clazz); 110 | } 111 | 112 | /** 113 | * 要求最低API为11 114 | * Activity 跳转 115 | * 跳转后Finish之前所有的Activity 116 | * 117 | * @param activity 上下文 118 | * @param goal 目标 119 | */ 120 | public static void goAndFinishAll(Activity activity, Class goal, Bundle bundle) { 121 | Intent intent = new Intent(activity, goal); 122 | intent.putExtras(bundle); 123 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); 124 | activity.startActivity(intent); 125 | activity.finish(); 126 | } 127 | 128 | /** 129 | * 要求最低API为11 130 | * Activity 跳转 131 | * 跳转后Finish之前所有的Activity 132 | * 133 | * @param activity 上下文 134 | * @param goal 目标 135 | */ 136 | public static void goAndFinishAll(Activity activity, Class goal) { 137 | Intent intent = new Intent(activity, goal); 138 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); 139 | activity.startActivity(intent); 140 | activity.finish(); 141 | } 142 | 143 | /** 144 | * Activity 跳转 145 | * 146 | * @param activity 上下文 147 | * @param goal 目标 148 | */ 149 | public static void goAndFinish(Activity activity, Class goal, Bundle bundle) { 150 | Intent intent = new Intent(activity, goal); 151 | intent.putExtras(bundle); 152 | activity.startActivity(intent); 153 | activity.finish(); 154 | } 155 | 156 | /** 157 | * Activity 跳转 158 | * 159 | * @param activity 上下文 160 | * @param goal 目标 161 | */ 162 | public static void goAndFinish(Activity activity, Class goal) { 163 | activity.startActivity(new Intent(activity, goal)); 164 | activity.finish(); 165 | } 166 | 167 | /** 168 | * Activity 跳转 169 | * 170 | * @param activity 上下文 171 | * @param goal 目标 172 | */ 173 | public static void goTo(Activity activity, Class goal) { 174 | Intent intent = new Intent(activity, goal); 175 | activity.startActivity(intent); 176 | } 177 | 178 | /** 179 | * Activity 跳转 180 | * 181 | * @param activity 上下文 182 | * @param goal 目标 183 | */ 184 | public static void goTo(Activity activity, Class goal, Bundle bundle) { 185 | Intent intent = new Intent(activity, goal); 186 | intent.putExtras(bundle); 187 | activity.startActivity(intent); 188 | } 189 | 190 | /** 191 | * activity跳转 192 | * @param activity 上下文 193 | * @param goal 目标 194 | * @param requestCode 请求码 195 | */ 196 | public static void goForResult(Activity activity, Class goal, int requestCode) { 197 | activity.startActivityForResult(new Intent(activity, goal), requestCode); 198 | } 199 | 200 | /** 201 | * activity跳转 202 | * @param activity 上下文 203 | * @param goal 目标 204 | * @param bundle 参数 205 | * @param requestCode 请求码 206 | */ 207 | public static void goForResult(Activity activity, Class goal, Bundle bundle, int requestCode) { 208 | Intent intent = new Intent(activity, goal); 209 | intent.putExtras(bundle); 210 | activity.startActivityForResult(intent, requestCode); 211 | } 212 | 213 | /** 214 | * 退出app 215 | * 216 | * @param context 217 | */ 218 | public static void AppExit(Context context) { 219 | try { 220 | finishAllActivity(); 221 | ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); 222 | activityManager.killBackgroundProcesses(context.getPackageName()); 223 | System.exit(0); 224 | } catch (Exception e) {} 225 | } 226 | 227 | } 228 | -------------------------------------------------------------------------------- /app/src/main/java/me/soushin/tinmvc/utils/SharedUtils.java: -------------------------------------------------------------------------------- 1 | package me.soushin.tinmvc.utils; 2 | 3 | import android.content.Context; 4 | import android.content.SharedPreferences; 5 | import android.support.annotation.NonNull; 6 | 7 | import com.google.gson.JsonArray; 8 | import com.google.gson.JsonElement; 9 | import com.google.gson.JsonObject; 10 | import com.google.gson.JsonParser; 11 | 12 | import java.util.ArrayList; 13 | import java.util.HashMap; 14 | import java.util.List; 15 | import java.util.Map; 16 | import java.util.Set; 17 | 18 | /** 19 | * Created by SouShin on 2018/8/270916. 20 | * 可以存取Boolean/Long/Integer/Float/Object/List/HashMap 21 | */ 22 | public class SharedUtils { 23 | private static String filename = "cookie"; 24 | private static SharedPreferences.Editor editor; 25 | private static SharedPreferences preferences; 26 | 27 | /** 28 | * 需要初始化 29 | * @param context 30 | */ 31 | public static void initShared(Context context){ 32 | preferences = context.getSharedPreferences(filename, context.MODE_PRIVATE); 33 | editor = preferences.edit(); 34 | } 35 | 36 | /** 37 | * 保存数据到SharedPreferences 38 | * 39 | * @param key 键 40 | * @param value 需要保存的数据 41 | * @return 保存结果 42 | */ 43 | public static boolean putData(String key, Object value) { 44 | String type = value.getClass().getSimpleName(); 45 | try { 46 | switch (type) { 47 | case "Boolean": 48 | editor.putBoolean(key, (Boolean) value); 49 | break; 50 | case "Long": 51 | editor.putLong(key, (Long) value); 52 | break; 53 | case "Float": 54 | editor.putFloat(key, (Float) value); 55 | break; 56 | case "String": 57 | editor.putString(key, (String) value); 58 | break; 59 | case "Integer": 60 | editor.putInt(key, (Integer) value); 61 | break; 62 | default: 63 | String json = GsonUtils.toString(value); 64 | editor.putString(key, json); 65 | break; 66 | } 67 | } catch (Exception e) { 68 | return false; 69 | } 70 | return editor.commit(); 71 | } 72 | 73 | /** 74 | * 获取SharedPreferences中保存的数据 75 | * 76 | * @param key 键 77 | * @param defaultValue 获取失败默认值 78 | * @return 从SharedPreferences读取的数据 79 | */ 80 | public static Object getData(String key,@NonNull Object defaultValue) { 81 | Object result; 82 | String type = defaultValue.getClass().getSimpleName(); 83 | try { 84 | switch (type) { 85 | case "Boolean": 86 | result = preferences.getBoolean(key, (Boolean) defaultValue); 87 | break; 88 | case "Long": 89 | result = preferences.getLong(key, (Long) defaultValue); 90 | break; 91 | case "Float": 92 | result = preferences.getFloat(key, (Float) defaultValue); 93 | break; 94 | case "String": 95 | result = preferences.getString(key, (String) defaultValue); 96 | break; 97 | case "Integer": 98 | result = preferences.getInt(key, (Integer) defaultValue); 99 | break; 100 | default: 101 | String json = preferences.getString(key, ""); 102 | if (!json.equals("") && json.length() > 0) { 103 | result = GsonUtils.getGson().fromJson(json, defaultValue.getClass()); 104 | } else { 105 | result = defaultValue; 106 | } 107 | break; 108 | } 109 | } catch (Exception e) { 110 | result = defaultValue; 111 | } 112 | return result; 113 | } 114 | 115 | /** 116 | * 获取字符串数据 117 | * @param key 118 | * @return 119 | */ 120 | public static String getStringData(String key){ 121 | return (String) getData(key,""); 122 | } 123 | 124 | /** 125 | * 获取int型数据 126 | * @param key 127 | * @return 128 | */ 129 | public static int getIntData(String key){ 130 | return (int) getData(key,0); 131 | } 132 | 133 | /** 134 | * 获取布尔型数据 135 | * @param key 136 | * @return 137 | */ 138 | public static boolean getBooleanData(String key){ 139 | return (boolean) getData(key,false); 140 | } 141 | 142 | /** 143 | * 用于保存集合 144 | * 145 | * @param key key 146 | * @param list 集合数据 147 | * @return 保存结果 148 | */ 149 | public static boolean putListData(String key, List list) { 150 | String type = list.get(0).getClass().getSimpleName(); 151 | JsonArray array = new JsonArray(); 152 | try { 153 | switch (type) { 154 | case "Boolean": 155 | for (int i = 0; i < list.size(); i++) { 156 | array.add((Boolean) list.get(i)); 157 | } 158 | break; 159 | case "Long": 160 | for (int i = 0; i < list.size(); i++) { 161 | array.add((Long) list.get(i)); 162 | } 163 | break; 164 | case "Float": 165 | for (int i = 0; i < list.size(); i++) { 166 | array.add((Float) list.get(i)); 167 | } 168 | break; 169 | case "String": 170 | for (int i = 0; i < list.size(); i++) { 171 | array.add((String) list.get(i)); 172 | } 173 | break; 174 | case "Integer": 175 | for (int i = 0; i < list.size(); i++) { 176 | array.add((Integer) list.get(i)); 177 | } 178 | break; 179 | default: 180 | for (int i = 0; i < list.size(); i++) { 181 | JsonElement obj = GsonUtils.getGson().toJsonTree(list.get(i)); 182 | array.add(obj); 183 | } 184 | break; 185 | } 186 | editor.putString(key, array.toString()); 187 | } catch (Exception e) { 188 | e.printStackTrace(); 189 | return false; 190 | } 191 | return editor.commit(); 192 | } 193 | 194 | /** 195 | * 获取保存的List 196 | * 197 | * @param key key 198 | * @return 对应的Lis集合 199 | */ 200 | public static List getListData(String key, Class cls) { 201 | List list = new ArrayList<>(); 202 | String json = preferences.getString(key, ""); 203 | if (!json.equals("") && json.length() > 0) { 204 | JsonArray array = new JsonParser().parse(json).getAsJsonArray(); 205 | for (JsonElement elem : array) { 206 | list.add(GsonUtils.getGson().fromJson(elem, cls)); 207 | } 208 | } 209 | return list; 210 | } 211 | 212 | /** 213 | * 用于保存集合 214 | * 215 | * @param key key 216 | * @param map map数据 217 | * @return 保存结果 218 | */ 219 | public static boolean putHashMapData(String key, Map map) { 220 | boolean result; 221 | try { 222 | String json = GsonUtils.toString(map); 223 | editor.putString(key, json); 224 | result = true; 225 | } catch (Exception e) { 226 | result = false; 227 | e.printStackTrace(); 228 | } 229 | editor.apply(); 230 | return result; 231 | } 232 | 233 | /** 234 | * 用于保存集合 235 | * 236 | * @param key key 237 | * @return HashMap 238 | */ 239 | public static HashMap getHashMapData(String key, Class clsV) { 240 | String json = preferences.getString(key, ""); 241 | HashMap map = new HashMap<>(); 242 | 243 | JsonObject obj = new JsonParser().parse(json).getAsJsonObject(); 244 | Set> entrySet = obj.entrySet(); 245 | for (Map.Entry entry : entrySet) { 246 | String entryKey = entry.getKey(); 247 | JsonObject value = (JsonObject) entry.getValue(); 248 | map.put(entryKey, GsonUtils.getGson().fromJson(value, clsV)); 249 | } 250 | return map; 251 | } 252 | 253 | /** 254 | * 清除缓存的数据 255 | * @return 256 | */ 257 | public static Boolean clearCache() { 258 | try { 259 | editor.clear(); 260 | return editor.commit(); 261 | }catch (Exception e){ 262 | e.printStackTrace(); 263 | return false; 264 | } 265 | } 266 | 267 | } 268 | -------------------------------------------------------------------------------- /config.gradle: -------------------------------------------------------------------------------- 1 | //这里配置了开发项目常用的第三方库以及SDK的版本号 可以按照项目需求进行选择或者版本升级 2 | //config.gradle配置方法: 3 | //在项目的根目录的build.gradle里添加 apply from: "config.gradle"支持 然后就可以直接使用了 4 | //具体使用方法看项目app里的build.gradle文件 5 | //打包混淆所需要的命令都已添加到proguard-rules.pro文件里 6 | 7 | ext { 8 | android = [ 9 | compileSdkVersion: 27, 10 | buildToolsVersion: "27.0.3", 11 | minSdkVersion : 19, 12 | targetSdkVersion : 27, 13 | versionCode : 1, 14 | versionName : "1.0" 15 | ] 16 | 17 | version = [ 18 | androidSupportSdkVersion: "27.1.1", 19 | retrofitSdkVersion : "2.4.0", 20 | glideSdkVersion : "4.7.1", 21 | butterknifeSdkVersion : "8.8.1", 22 | dagger2SdkVersion : "2.15", 23 | espressoSdkVersion : "3.0.2", 24 | alogSdkVersion : "1.9.4", 25 | ] 26 | 27 | 28 | dependencies = [ 29 | //support 30 | "appcompat-v7" : "com.android.support:appcompat-v7:${version["androidSupportSdkVersion"]}", 31 | "design" : "com.android.support:design:${version["androidSupportSdkVersion"]}", 32 | "support-v4" : "com.android.support:support-v4:${version["androidSupportSdkVersion"]}", 33 | "cardview-v7" : "com.android.support:cardview-v7:${version["androidSupportSdkVersion"]}", 34 | "annotations" : "com.android.support:support-annotations:${version["androidSupportSdkVersion"]}", 35 | "recyclerview-v7" : "com.android.support:recyclerview-v7:${version["androidSupportSdkVersion"]}", 36 | 37 | //network 38 | "retrofit" : "com.squareup.retrofit2:retrofit:${version["retrofitSdkVersion"]}", 39 | "retrofit-converter-gson" : "com.squareup.retrofit2:converter-gson:${version["retrofitSdkVersion"]}", 40 | "retrofit-adapter-rxjava" : "com.squareup.retrofit2:adapter-rxjava:${version["retrofitSdkVersion"]}", 41 | "retrofit-adapter-rxjava2" : "com.squareup.retrofit2:adapter-rxjava2:${version["retrofitSdkVersion"]}", 42 | "logging-interceptor" : "com.squareup.okhttp3:logging-interceptor:3.10.0", 43 | "okhttp3" : "com.squareup.okhttp3:okhttp:3.10.0", 44 | "okhttp-urlconnection" : "com.squareup.okhttp:okhttp-urlconnection:2.0.0", 45 | "glide" : "com.github.bumptech.glide:glide:${version["glideSdkVersion"]}", 46 | "glide-compiler" : "com.github.bumptech.glide:compiler:${version["glideSdkVersion"]}", 47 | "glide-loader-okhttp3" : "com.github.bumptech.glide:okhttp3-integration:${version["glideSdkVersion"]}", 48 | "picasso" : "com.squareup.picasso:picasso:2.71828", 49 | "fresco" : "com.facebook.fresco:fresco:1.9.0", 50 | "photodraweeview" : "me.relex:photodraweeview:1.1.3", 51 | 52 | //view 53 | "constraint-layout" : "com.android.support.constraint:constraint-layout:1.1.3", 54 | "autolayout" : "com.zhy:autolayout:1.4.5", 55 | "butterknife" : "com.jakewharton:butterknife:${version["butterknifeSdkVersion"]}", 56 | "butterknife-compiler" : "com.jakewharton:butterknife-compiler:${version["butterknifeSdkVersion"]}", 57 | "pickerview" : "com.contrarywind:Android-PickerView:3.2.5", 58 | "photoview" : "com.github.chrisbanes.photoview:library:1.2.3", 59 | "numberprogressbar" : "com.daimajia.numberprogressbar:library:1.2@aar", 60 | "nineoldandroids" : "com.nineoldandroids:library:2.4.0", 61 | "paginate" : "com.github.markomilos:paginate:0.5.1", 62 | "vlayout" : "com.alibaba.android:vlayout:1.1.0@aar", 63 | "smartrefresh" : "com.scwang.smartrefresh:SmartRefreshLayout:1.0.5.1", 64 | //没有使用特殊Header,可以不加这个 65 | "smartrefresh_header" : "com.scwang.smartrefresh:SmartRefreshHeader:1.0.5.1", 66 | "BaseRecyclerViewAdapterHelper": "com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.42", 67 | //状态栏解决方案 68 | "statusbarutil" : "com.jaeger.statusbarutil:library:1.5.1", 69 | //封装webview 70 | "agentweb" : "com.just.agentweb:agentweb:4.0.2", 71 | "download" : "com.just.agentweb:download:4.0.2", 72 | "filechooser" : "com.just.agentweb:filechooser:4.0.2", 73 | "ToastUtils" : "com.hjq:toast:5.2", 74 | "fragmentation" : "me.yokeyword:fragmentation:1.3.6", 75 | "bottom-bar" : "com.roughike:bottom-bar:2.3.1", 76 | "immersionbar" : "com.gyf.immersionbar:immersionbar:2.3.3-beta15", 77 | "pictureselector" : "com.github.LuckSiege.PictureSelector:picture_library:v2.2.3", 78 | "autosize" : "me.jessyan:autosize:1.1.2", 79 | 80 | //rx2 81 | "rxandroid2" : "io.reactivex.rxjava2:rxandroid:2.0.2", 82 | "rxjava2" : "io.reactivex.rxjava2:rxjava:2.1.12", 83 | "rxlifecycle2" : "com.trello.rxlifecycle2:rxlifecycle:${version["rxlifecycle2SdkVersion"]}", 84 | "rxlifecycle2-android" : "com.trello.rxlifecycle2:rxlifecycle-android:${version["rxlifecycle2SdkVersion"]}", 85 | "rxlifecycle2-components" : "com.trello.rxlifecycle2:rxlifecycle-components:${version["rxlifecycle2SdkVersion"]}", 86 | "rxcache2" : "com.github.VictorAlbertos.RxCache:runtime:1.8.3-2.x", 87 | "rxeasyhttp" : "com.zhouyou:rxeasyhttp:2.1.2", 88 | "rxpermissions" : "com.github.tbruyelle:rxpermissions:0.10.2", 89 | 90 | //tools 91 | "dagger2" : "com.google.dagger:dagger:${version["dagger2SdkVersion"]}", 92 | "dagger2-android" : "com.google.dagger:dagger-android:${version["dagger2SdkVersion"]}", 93 | "dagger2-android-support" : "com.google.dagger:dagger-android-support:${version["dagger2SdkVersion"]}", 94 | "dagger2-compiler" : "com.google.dagger:dagger-compiler:${version["dagger2SdkVersion"]}", 95 | "dagger2-android-processor" : "com.google.dagger:dagger-android-processor:${version["dagger2SdkVersion"]}", 96 | "androideventbus" : "org.simple:androideventbus:1.0.5.1", 97 | "otto" : "com.squareup:otto:1.3.8", 98 | "gson" : "com.google.code.gson:gson:2.8.2", 99 | "javax.annotation" : "javax.annotation:jsr250-api:1.0", 100 | "arouter" : "com.alibaba:arouter-api:1.3.1", 101 | "arouter-compiler" : "com.alibaba:arouter-compiler:1.1.4", 102 | "progressmanager" : "me.jessyan:progressmanager:1.5.0", 103 | "retrofit-url-manager" : "me.jessyan:retrofit-url-manager:1.4.0", 104 | "lifecyclemodel" : "me.jessyan:lifecyclemodel:1.0.1", 105 | "luban" : "top.zibin:Luban:1.1.2", 106 | "eventbus" : "org.greenrobot:eventbus:3.1.1", 107 | "litepal" : "org.litepal.android:core:2.0.0", 108 | "andpermission" : "com.yanzhenjie:permission:2.0.0-rc12", 109 | "umeng-analytics" : "com.umeng.analytics:analytics:6.0.1", 110 | 111 | //test 112 | "junit" : "junit:junit:4.12", 113 | "androidJUnitRunner" : "android.support.test.runner.AndroidJUnitRunner", 114 | "runner" : "com.android.support.test:runner:1.0.2", 115 | "espresso-core" : "com.android.support.test.espresso:espresso-core:${version["espressoSdkVersion"]}", 116 | "espresso-contrib" : "com.android.support.test.espresso:espresso-contrib:${version["espressoSdkVersion"]}", 117 | "espresso-intents" : "com.android.support.test.espresso:espresso-intents:${version["espressoSdkVersion"]}", 118 | "mockito-core" : "org.mockito:mockito-core:1.+", 119 | "timber" : "com.jakewharton.timber:timber:4.7.0", 120 | "alog" : "com.github.932707629:alog:${version["alogSdkVersion"]}", 121 | "canary-debug" : "com.squareup.leakcanary:leakcanary-android:${version["canarySdkVersion"]}", 122 | "canary-release" : "com.squareup.leakcanary:leakcanary-android-no-op:${version["canarySdkVersion"]}", 123 | 124 | ] 125 | 126 | } -------------------------------------------------------------------------------- /app/src/main/java/me/soushin/tinmvc/widget/InitProvider.java: -------------------------------------------------------------------------------- 1 | package me.soushin.tinmvc.widget; 2 | 3 | import android.app.Activity; 4 | import android.app.Application; 5 | import android.app.Dialog; 6 | import android.app.Fragment; 7 | import android.content.ContentProvider; 8 | import android.content.ContentValues; 9 | import android.database.Cursor; 10 | import android.net.Uri; 11 | import android.support.annotation.NonNull; 12 | import android.support.annotation.Nullable; 13 | import android.view.View; 14 | 15 | import com.blankj.ALog; 16 | import com.hjq.toast.ToastUtils; 17 | import com.zhouyou.http.EasyHttp; 18 | import com.zhouyou.http.cache.converter.SerializableDiskConverter; 19 | import com.zhouyou.http.cookie.CookieManger; 20 | 21 | import java.util.ArrayList; 22 | 23 | import me.jessyan.autosize.AutoSizeConfig; 24 | import me.jessyan.autosize.internal.CustomAdapt; 25 | import me.soushin.tinmvc.ActivityLifeCycleCallBackIml; 26 | import me.soushin.tinmvc.App; 27 | import me.soushin.tinmvc.BuildConfig; 28 | import me.soushin.tinmvc.Configure; 29 | import me.soushin.tinmvc.network.TokenInterceptor; 30 | import me.soushin.tinmvc.utils.SharedUtils; 31 | import me.soushin.tinmvc.utils.StringUtils; 32 | 33 | /** 34 | * 通过声明 {@link ContentProvider} 自动完成应用初始化 35 | * @auther SouShin 36 | * @time 2019/4/18 17:04 37 | */ 38 | public class InitProvider extends ContentProvider { 39 | @Override 40 | public boolean onCreate() { 41 | initApp((Application) getContext().getApplicationContext()); 42 | return true; 43 | } 44 | 45 | /** 46 | * 这些初始化操作在主线程会拖延App启动速度 47 | * 建议在子线程进行 48 | */ 49 | private void initApp(Application app) { 50 | initALog(app); 51 | initAutoSize(); 52 | ToastUtils.init(app, new ToastStyle()); 53 | // 注册ActivityLifeCycleCallBackIml必须要在初始化alog之后 因为里面有用到alog 54 | app.registerActivityLifecycleCallbacks(new ActivityLifeCycleCallBackIml()); 55 | SharedUtils.initShared(app); 56 | // initEasyHttp(); 网络框架初始化 57 | } 58 | 59 | // init it in ur application 60 | public void initALog(Application app) { 61 | ALog.init(app) 62 | .setLogSwitch(BuildConfig.DEBUG)// 设置 log 总开关,包括输出到控制台和文件,默认开 63 | .setConsoleSwitch(BuildConfig.DEBUG)// 设置是否输出到控制台开关,默认开 64 | .setGlobalTag(null)// 设置 log 全局标签,默认为空 65 | // 当全局标签不为空时,我们输出的 log 全部为该 tag, 66 | // 为空时,如果传入的 tag 为空那就显示类名,否则显示 tag 67 | .setLogHeadSwitch(true)// 设置 log 头信息开关,默认为开 68 | .setLog2FileSwitch(false)// 打印 log 时是否存到文件的开关,默认关 69 | .setDir("")// 当自定义路径为空时,写入应用的 /cache/log/ 目录中 70 | .setFilePrefix("")// 当文件前缀为空时,默认为 "alog",即写入文件为 "alog-MM-dd.txt" 71 | .setBorderSwitch(false)// 输出日志是否带边框开关,默认开 72 | .setSingleTagSwitch(true)// 一条日志仅输出一条,默认开,为美化 AS 3.1 的 Logcat 73 | .setConsoleFilter(ALog.V)// log 的控制台过滤器,和 logcat 过滤器同理,默认 Verbose 74 | .setFileFilter(ALog.V)// log 文件过滤器,和 logcat 过滤器同理,默认 Verbose 75 | .setStackDeep(1)// log 栈深度,默认为 1 76 | .setStackOffset(0)// 设置栈偏移,比如二次封装的话就需要设置,默认为 0 77 | .setSaveDays(1)// 设置日志可保留天数,默认为 -1 表示无限时长 78 | // 新增 ArrayList 格式化器,默认已支持 Array, Throwable, Bundle, Intent 的格式化输出 79 | .addFormatter(new ALog.IFormatter() { 80 | @Override 81 | public String format(ArrayList list) { 82 | return "ALog Formatter ArrayList { " + list.toString() + " }"; 83 | } 84 | }); 85 | } 86 | 87 | /** 88 | * ================================================ 89 | * https://github.com/JessYanCoding/AndroidAutoSize 90 | * AndroidAutoSize框架核心原理来自于 今日头条官方适配方案 91 | * 此方案不光可以适配 {@link Activity}, 这个 {@link Activity} 下的所有 {@link Fragment}、{@link Dialog}、{@link View} 都会自动适配 92 | *

93 | * Activity 是以屏幕宽度为基准进行适配的, 并且使用的是在 AndroidManifest 中填写的全局设计图尺寸 360 * 640 94 | * 不懂什么叫基准的话, 请看 {@link AutoSizeConfig#isBaseOnWidth}) 的注释, AndroidAutoSize 默认全局以屏幕宽度为基准进行适配 95 | * 如果想更改为全局以屏幕高度为基准进行适配, 请在 {@link App} 中按注释中更改, 为什么强调全局? 96 | * 因为 AndroidAutoSize 允许每个 {@link Activity} 可以自定义适配参数, 自定义适配参数通过实现 {@link CustomAdapt} 97 | * 如果不自定义适配参数就会使用全局的适配参数, 全局适配参数在 {@link App} 中按注释设置 98 | *

99 | * ================================================ 100 | */ 101 | private void initAutoSize() { 102 | //当 App 中出现多进程, 并且您需要适配所有的进程, 就需要在 App 初始化时调用 initCompatMultiProcess() 103 | // AutoSize.initCompatMultiProcess(this); 104 | 105 | AutoSizeConfig.getInstance() 106 | //是否让框架支持自定义 Fragment 的适配参数, 由于这个需求是比较少见的, 所以须要使用者手动开启 107 | //如果没有这个需求建议不开启 108 | // .setCustomFragment(false) 109 | //是否屏蔽系统字体大小对 AndroidAutoSize 的影响, 如果为 true, App 内的字体的大小将不会跟随系统设置中字体大小的改变 110 | //如果为 false, 则会跟随系统设置中字体大小的改变, 默认为 false 111 | .setExcludeFontScale(true) 112 | //屏幕适配监听器 113 | // .setOnAdaptListener(new onAdaptListener() { 114 | // @Override 115 | // public void onAdaptBefore(Object target, Activity activity) { 116 | // //使用以下代码, 可支持 Android 的分屏或缩放模式, 但前提是在分屏或缩放模式下当用户改变您 App 的窗口大小时 117 | // //系统会重绘当前的页面, 经测试在某些机型, 某些情况下系统不会重绘当前页面, ScreenUtils.getScreenSize(activity) 的参数一定要不要传 Application!!! 118 | // AutoSizeConfig.getInstance().setScreenWidth(ScreenUtils.getScreenSize(activity)[0]); 119 | // AutoSizeConfig.getInstance().setScreenHeight(ScreenUtils.getScreenSize(activity)[1]); 120 | // LogUtils.d(String.format(Locale.ENGLISH, "%s onAdaptBefore!", target.getClass().getName())); 121 | // } 122 | // 123 | // @Override 124 | // public void onAdaptAfter(Object target, Activity activity) { 125 | // LogUtils.d(String.format(Locale.ENGLISH, "%s onAdaptAfter!", target.getClass().getName())); 126 | // } 127 | // }) 128 | //是否打印 AutoSize 的内部日志, 默认为 true, 如果您不想 AutoSize 打印日志, 则请设置为 false 129 | // .setLog(false) 130 | //是否使用设备的实际尺寸做适配, 默认为 false, 如果设置为 false, 在以屏幕高度为基准进行适配时 131 | //AutoSize 会将屏幕总高度减去状态栏高度来做适配 132 | //设置为 true 则使用设备的实际屏幕高度, 不会减去状态栏高度 133 | // .setUseDeviceSize(true) 134 | //是否全局按照宽度进行等比例适配, 默认为 true, 如果设置为 false, AutoSize 会全局按照高度进行适配 135 | .setBaseOnWidth(true) 136 | //设置屏幕适配逻辑策略类, 一般不用设置, 使用框架默认的就好 137 | // .setAutoAdaptStrategy(new AutoAdaptStrategy()) 138 | ; 139 | 140 | // 给外部的三方库 {@link Activity} 自定义适配参数, 因为三方库的 {@link Activity} 并不能通过实现 141 | // {@link CustomAdapt} 接口的方式来提供自定义适配参数 (因为远程依赖改不了源码) 142 | // 所以使用 {@link ExternalAdaptManager } 来替代实现接口的方式, 来提供自定义适配参数 143 | // {@link ExternalAdaptManager} 是一个管理外部三方库的适配信息和状态的管理类, 详细介绍请看 {@link ExternalAdaptManager} 的类注释 144 | 145 | // AutoSizeConfig.getInstance().getExternalAdaptManager() 146 | 147 | //加入的 Activity 将会放弃屏幕适配, 一般用于三方库的 Activity, 详情请看方法注释 148 | //如果不想放弃三方库页面的适配, 请用 addExternalAdaptInfoOfActivity 方法, 建议对三方库页面进行适配, 让自己的 App 更完美一点 149 | // .addCancelAdaptOfActivity(DefaultErrorActivity.class) 150 | 151 | //为指定的 Activity 提供自定义适配参数, AndroidAutoSize 将会按照提供的适配参数进行适配, 详情请看方法注释 152 | //一般用于三方库的 Activity, 因为三方库的设计图尺寸可能和项目自身的设计图尺寸不一致, 所以要想完美适配三方库的页面 153 | //就需要提供三方库的设计图尺寸, 以及适配的方向 (以宽为基准还是高为基准?) 154 | //三方库页面的设计图尺寸可能无法获知, 所以如果想让三方库的适配效果达到最好, 只有靠不断的尝试 155 | //由于 AndroidAutoSize 可以让布局在所有设备上都等比例缩放, 所以只要您在一个设备上测试出了一个最完美的设计图尺寸 156 | //那这个三方库页面在其他设备上也会呈现出同样的适配效果, 等比例缩放, 所以也就完成了三方库页面的屏幕适配 157 | //即使在不改三方库源码的情况下也可以完美适配三方库的页面, 这就是 AndroidAutoSize 的优势 158 | //但前提是三方库页面的布局使用的是 dp 和 sp, 如果布局全部使用的 px, 那 AndroidAutoSize 也将无能为力 159 | //经过测试 DefaultErrorActivity(第三方库的Activity) 的设计图宽度在 380dp - 400dp 显示效果都是比较舒服的 160 | // .addExternalAdaptInfoOfActivity(DefaultErrorActivity.class, new ExternalAdaptInfo(true, 400)); 161 | } 162 | 163 | /** 164 | * 网络框架初始化 165 | * 这里加了判空,初始化之前先设置baseurl 166 | * 没有设置会报异常NullPointerException 167 | */ 168 | private void initEasyHttp(Application app) { 169 | if (StringUtils.isNull(Configure.BASE_URL)) { 170 | throw new NullPointerException("新手指引:请在Configure.class中设置BASE_URL"); 171 | } 172 | EasyHttp.init(app); 173 | EasyHttp.getInstance() 174 | .debug("NetWork--INFO",BuildConfig.DEBUG) 175 | .setReadTimeOut(20 * 1000) 176 | .setWriteTimeOut(20 * 1000) 177 | .setConnectTimeout(20 * 1000) 178 | .setRetryCount(3)//默认网络不好自动重试3次 179 | .setBaseUrl(Configure.BASE_URL) 180 | .setCacheDiskConverter(new SerializableDiskConverter())//默认缓存使用序列化转化 181 | .setCacheMaxSize(50 * 1024 * 1024)//设置缓存大小为50M 182 | .setCacheVersion(1)//缓存版本为1 183 | .addInterceptor(new TokenInterceptor()) 184 | .setCookieStore(new CookieManger(app)) 185 | .setCertificates();//信任所有证书 186 | } 187 | 188 | 189 | @Nullable 190 | @Override 191 | public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) { 192 | return null; 193 | } 194 | 195 | @Nullable 196 | @Override 197 | public String getType(@NonNull Uri uri) { 198 | return null; 199 | } 200 | 201 | @Nullable 202 | @Override 203 | public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) { 204 | return null; 205 | } 206 | 207 | @Override 208 | public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) { 209 | return 0; 210 | } 211 | 212 | @Override 213 | public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) { 214 | return 0; 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | --------------------------------------------------------------------------------