├── .gitignore ├── .idea ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── gradle.xml ├── inspectionProfiles │ ├── Project_Default.xml │ └── profiles_settings.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml └── vcs.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── example │ │ └── wsj │ │ └── splashdemo │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── example │ │ │ └── wsj │ │ │ └── splashdemo │ │ │ ├── ColumnApplication.java │ │ │ ├── UserCenter.java │ │ │ ├── activities │ │ │ ├── LoginActivity.java │ │ │ ├── MainActivity.java │ │ │ ├── SplashActivity.java │ │ │ └── WebActivity.java │ │ │ ├── entity │ │ │ ├── Attachment.java │ │ │ ├── Common.java │ │ │ ├── Constants.java │ │ │ ├── Splash.java │ │ │ └── User.java │ │ │ ├── http │ │ │ ├── ApiStores.java │ │ │ ├── HttpClient.java │ │ │ ├── HttpsUtils.java │ │ │ ├── callback │ │ │ │ └── CommonCallback.java │ │ │ └── interceptor │ │ │ │ ├── AddQueryParameterInterceptor.java │ │ │ │ ├── CacheInterceptor.java │ │ │ │ └── LoggingInterceptor.java │ │ │ ├── service │ │ │ └── SplashDownLoadService.java │ │ │ └── utils │ │ │ ├── DownLoadUtils.java │ │ │ ├── NetWorkUtils.java │ │ │ └── SerializableUtils.java │ └── res │ │ ├── drawable │ │ └── btn_splash_shape.xml │ │ ├── layout │ │ ├── activity_login.xml │ │ ├── activity_main.xml │ │ ├── activity_splash.xml │ │ └── activity_web.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 │ │ ├── gank10.jpg │ │ ├── gank15.jpg │ │ ├── gank8.jpg │ │ ├── ic_launcher.png │ │ ├── ic_launcher_round.png │ │ ├── icon_splash.png │ │ └── splash.jpg │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── example │ └── wsj │ └── splashdemo │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── 效果图.gif /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the ART/Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | out/ 15 | 16 | # Gradle files 17 | .gradle/ 18 | build/ 19 | 20 | # Local configuration file (sdk path, etc) 21 | local.properties 22 | 23 | # Proguard folder generated by Eclipse 24 | proguard/ 25 | 26 | # Log Files 27 | *.log 28 | 29 | # Android Studio Navigation editor temp files 30 | .navigation/ 31 | 32 | # Android Studio captures folder 33 | captures/ 34 | 35 | # Intellij 36 | *.iml 37 | .idea/ 38 | 39 | # Keystore files 40 | *.jks 41 | *~ 42 | .DS_Store 43 | app/assets/ 44 | app/libs/ 45 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 13 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 46 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SplashActivityDemo 2 | App必备功能——闪屏页面实现,详情见: http://www.jianshu.com/p/b38ec0bfee7d 以授权 张鸿洋 公众号首发 3 | 4 | 该 Demo 实现了 App 中常见的闪屏业务需求,其中功能包括: 5 | 6 | 1. 创建一个冷启动后的闪屏页面(Splash 页面) 7 | 2. 这个页面默认 3s 倒计时,点击倒计时按钮可以跳转并结束倒计时 8 | 3. 点击图片如果有外链,则跳转应用的 web 页面用来作为活动页面 9 | 4. 根据后台返回数据判断是否需要下载新的文件到本地,以及清空原有数据(活动结束去掉 Splash 展示) 10 | 11 | 实现效果: 12 | 13 | ![效果图](https://github.com/ImportEffort/SplashActivityDemo/blob/master/%E6%95%88%E6%9E%9C%E5%9B%BE.gif?raw=true) 14 | 15 | 具体内容请参照博客 16 | > http://www.jianshu.com/p/b38ec0bfee7d 17 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | # built application files 2 | *.apk 3 | *.ap_ 4 | 5 | #OSX 6 | *.DS_Store 7 | #Gradle files 8 | build/ 9 | .gradle/ 10 | */build/ 11 | 12 | # files for the dex VM 13 | *.dex 14 | 15 | # Java class files 16 | *.class 17 | 18 | #Logfile 19 | *.log 20 | 21 | # generated files 22 | bin/ 23 | gen/ 24 | 25 | # Local configuration file (sdk path, etc) 26 | local.properties 27 | 28 | # Eclipse project files 29 | .classpath 30 | .project 31 | 32 | # Proguard folder generated by Eclipse 33 | proguard/ 34 | 35 | # Intellij project files 36 | *.iml 37 | *.ipr 38 | *.iws 39 | .idea/ 40 | .gradle/ 41 | app/.gradle/ 42 | .idea/libraries 43 | .idea/workspace.xml 44 | .idea/vcs.xml 45 | .idea/scopes/scope_setting.xml 46 | .idea/moudles.xml 47 | .idea/misc.xml 48 | .idea/inspectionProfiles/Project_Default.xml 49 | .idea/inspectionProfiles/profiles_setting.xml 50 | .idea/encodings.xml 51 | .idea/.name 52 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 25 5 | buildToolsVersion "26.0.0" 6 | defaultConfig { 7 | applicationId "com.example.wsj.splashdemo" 8 | minSdkVersion 15 9 | targetSdkVersion 25 10 | versionCode 1 11 | versionName "1.0" 12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile fileTree(dir: 'libs', include: ['*.jar']) 24 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 25 | exclude group: 'com.android.support', module: 'support-annotations' 26 | }) 27 | 28 | 29 | compile 'com.android.support:appcompat-v7:25.3.1' 30 | compile 'com.android.support.constraint:constraint-layout:1.0.2' 31 | compile 'com.jakewharton:butterknife:8.5.1' 32 | compile 'com.jakewharton:butterknife-compiler:8.5.1' 33 | compile 'com.github.bumptech.glide:glide:3.7.0' 34 | compile 'com.squareup.retrofit2:retrofit:2.3.0' 35 | compile 'com.squareup.retrofit2:converter-gson:2.2.0' 36 | compile 'com.squareup.okhttp3:okhttp:3.8.0' 37 | compile 'pub.devrel:easypermissions:0.2.1' 38 | compile 'io.reactivex.rxjava2:rxjava:2.1.0' 39 | compile 'io.reactivex.rxjava2:rxandroid:2.0.1' 40 | compile 'com.squareup.retrofit2:adapter-rxjava2:2.2.0' 41 | testCompile 'junit:junit:4.12' 42 | } 43 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/goldenalpha/Library/Android/sdk/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | #-keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | #-renamesourcefileattribute SourceFile 26 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/example/wsj/splashdemo/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.example.wsj.splashdemo; 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 org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumentation test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.example.wsj.splashdemo", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 18 | 19 | 20 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/wsj/splashdemo/ColumnApplication.java: -------------------------------------------------------------------------------- 1 | package com.example.wsj.splashdemo; 2 | 3 | import android.app.Application; 4 | import android.content.Context; 5 | 6 | /** 7 | * Created by wangshijia on 2017/6/21 下午12:25. 8 | * Copyright (c) 2017. alpha, Inc. All rights reserved. 9 | */ 10 | 11 | public class ColumnApplication extends Application { 12 | private static Context context; 13 | 14 | public static Context getContext() { 15 | return context; 16 | } 17 | 18 | @Override 19 | public void onCreate() { 20 | super.onCreate(); 21 | context = this; 22 | UserCenter.initInstance(this); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/wsj/splashdemo/UserCenter.java: -------------------------------------------------------------------------------- 1 | package com.example.wsj.splashdemo; 2 | 3 | import android.content.Context; 4 | import android.content.SharedPreferences; 5 | 6 | import com.example.wsj.splashdemo.entity.User; 7 | 8 | /** 9 | * Created by wangshijia on 2014/6/18 上午10:12. 10 | * Copyright (c) 2017. alpha, Inc. All rights reserved. 11 | */ 12 | public class UserCenter { 13 | private static String TAG = "UserCenter"; 14 | 15 | private Context mContext; 16 | 17 | private SharedPreferences preferences; 18 | 19 | // ***单例的用户中心 20 | private static UserCenter mUserCenter; 21 | 22 | private UserCenter() { 23 | } 24 | 25 | static UserCenter initInstance(Context context) { 26 | if (mUserCenter == null) { 27 | mUserCenter = new UserCenter(); 28 | mUserCenter.mContext = context; 29 | mUserCenter.preferences = context.getSharedPreferences(TAG, Context.MODE_PRIVATE); 30 | 31 | } 32 | return mUserCenter; 33 | } 34 | 35 | public static UserCenter getInstance() { 36 | return mUserCenter; 37 | } 38 | 39 | // *****Token相关 40 | private static String KEY_TOKEN = "tokens"; 41 | 42 | private String token; 43 | 44 | public String getToken() { 45 | if (token == null) { 46 | token = getLocalToken(); 47 | } 48 | return token; 49 | } 50 | 51 | public void setToken(String token) { 52 | this.token = token; 53 | saveLocalToken(token); 54 | } 55 | 56 | private void saveLocalToken(String token) { 57 | SharedPreferences sharedPreferences = mContext.getSharedPreferences( 58 | TAG, Context.MODE_PRIVATE); 59 | sharedPreferences.edit().putString(KEY_TOKEN, token).apply(); 60 | } 61 | 62 | private String getLocalToken() { 63 | SharedPreferences sharedPreferences = mContext.getSharedPreferences( 64 | TAG, Context.MODE_PRIVATE); 65 | return sharedPreferences.getString(KEY_TOKEN, null); 66 | } 67 | 68 | // CurrentUser 相关 69 | private User mCurrentUser = null; 70 | 71 | public User getCurrentUser() { 72 | if (mCurrentUser == null) { 73 | mCurrentUser = getCurrentUserFromLocal(); 74 | if (mCurrentUser == null) { 75 | throw new IllegalStateException("CurrentUser has not been initialized"); 76 | } 77 | 78 | } 79 | return mCurrentUser; 80 | } 81 | 82 | public void setCurrentUser(User user) { 83 | if (mCurrentUser == null) 84 | mCurrentUser = new User(); 85 | mCurrentUser.uid = user.uid;//uid 86 | saveCurrentLocalUser(mCurrentUser); 87 | } 88 | 89 | private void saveCurrentLocalUser(User user) { 90 | preferences.edit() 91 | .putLong("uid", user.uid) 92 | .apply(); 93 | } 94 | 95 | private User getCurrentUserFromLocal() { 96 | User user = new User(); 97 | user.uid = preferences.getLong("uid", 0); 98 | return user; 99 | 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/wsj/splashdemo/activities/LoginActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.wsj.splashdemo.activities; 2 | 3 | import android.support.v7.app.AppCompatActivity; 4 | import android.os.Bundle; 5 | 6 | import com.example.wsj.splashdemo.R; 7 | 8 | public class LoginActivity extends AppCompatActivity { 9 | 10 | @Override 11 | protected void onCreate(Bundle savedInstanceState) { 12 | super.onCreate(savedInstanceState); 13 | setContentView(R.layout.activity_login); 14 | setTitle("登录界面"); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/wsj/splashdemo/activities/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.wsj.splashdemo.activities; 2 | 3 | import android.support.v7.app.AppCompatActivity; 4 | import android.os.Bundle; 5 | 6 | import com.example.wsj.splashdemo.R; 7 | 8 | public class MainActivity extends AppCompatActivity { 9 | 10 | @Override 11 | protected void onCreate(Bundle savedInstanceState) { 12 | super.onCreate(savedInstanceState); 13 | setContentView(R.layout.activity_main); 14 | setTitle("产品首页"); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/wsj/splashdemo/activities/SplashActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.wsj.splashdemo.activities; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.os.CountDownTimer; 6 | import android.support.v7.app.AppCompatActivity; 7 | import android.text.TextUtils; 8 | import android.util.Log; 9 | import android.view.View; 10 | import android.widget.Button; 11 | import android.widget.ImageView; 12 | 13 | import com.bumptech.glide.Glide; 14 | import com.example.wsj.splashdemo.R; 15 | import com.example.wsj.splashdemo.UserCenter; 16 | import com.example.wsj.splashdemo.entity.Constants; 17 | import com.example.wsj.splashdemo.entity.Splash; 18 | import com.example.wsj.splashdemo.service.SplashDownLoadService; 19 | import com.example.wsj.splashdemo.utils.SerializableUtils; 20 | 21 | import java.io.File; 22 | import java.io.IOException; 23 | 24 | import butterknife.BindView; 25 | import butterknife.ButterKnife; 26 | import butterknife.OnClick; 27 | 28 | public class SplashActivity extends AppCompatActivity { 29 | 30 | private Splash mSplash; 31 | @BindView(R.id.sp_bg) 32 | ImageView mSpBgImage; 33 | @BindView(R.id.sp_jump_btn) 34 | Button mSpJumpBtn; 35 | //由于CountDownTimer有一定的延迟,所以这里设置3400 36 | private CountDownTimer countDownTimer = new CountDownTimer(3400, 1000) { 37 | @Override 38 | public void onTick(long millisUntilFinished) { 39 | mSpJumpBtn.setText("跳过(" + millisUntilFinished / 1000 + "s)"); 40 | } 41 | 42 | @Override 43 | public void onFinish() { 44 | mSpJumpBtn.setText("跳过(" + 0 + "s)"); 45 | gotoLoginOrMainActivity(); 46 | } 47 | }; 48 | 49 | @Override 50 | protected void onCreate(Bundle savedInstanceState) { 51 | super.onCreate(savedInstanceState); 52 | setContentView(R.layout.activity_splash); 53 | ButterKnife.bind(this); 54 | showAndDownSplash(); 55 | } 56 | 57 | 58 | @OnClick({R.id.sp_bg, R.id.sp_jump_btn}) 59 | public void onViewClicked(View view) { 60 | switch (view.getId()) { 61 | case R.id.sp_bg: 62 | gotoWebActivity(); 63 | break; 64 | case R.id.sp_jump_btn: 65 | gotoLoginOrMainActivity(); 66 | break; 67 | } 68 | } 69 | 70 | private void gotoWebActivity() { 71 | 72 | if (mSplash != null && mSplash.click_url != null) { 73 | Intent intent = new Intent(this, WebActivity.class); 74 | intent.putExtra("url", mSplash.click_url); 75 | intent.putExtra("title", mSplash.title); 76 | intent.putExtra("fromSplash", true); 77 | startActivity(intent); 78 | finish(); 79 | } 80 | } 81 | 82 | private void showAndDownSplash() { 83 | showSplash(); 84 | startImageDownLoad(); 85 | } 86 | 87 | private void showSplash() { 88 | mSplash = getLocalSplash(); 89 | if (mSplash != null && !TextUtils.isEmpty(mSplash.savePath)) { 90 | Log.d("SplashDemo","SplashActivity 获取本地序列化成功" + mSplash); 91 | Glide.with(this).load(mSplash.savePath).dontAnimate().into(mSpBgImage); 92 | startClock(); 93 | } else { 94 | mSpJumpBtn.setVisibility(View.INVISIBLE); 95 | mSpJumpBtn.postDelayed(new Runnable() { 96 | @Override 97 | public void run() { 98 | gotoLoginOrMainActivity(); 99 | } 100 | }, 1000); 101 | } 102 | } 103 | 104 | private void startImageDownLoad() { 105 | SplashDownLoadService.startDownLoadSplashImage(this, Constants.DOWNLOAD_SPLASH); 106 | } 107 | 108 | private Splash getLocalSplash() { 109 | Splash splash = null; 110 | try { 111 | Log.d("存储路径",Constants.SPLASH_PATH);//修改为存储到内存卡中,不需要动态申请权限 112 | // /data/user/0/com.example.wsj.splashdemo/files/alpha/splash 113 | File serializableFile = SerializableUtils.getSerializableFile(Constants.SPLASH_PATH, 114 | Constants.SPLASH_FILE_NAME); 115 | splash = (Splash) SerializableUtils.readObject(serializableFile); 116 | } catch (IOException e) { 117 | Log.d("SplashDemo","SplashActivity 获取本地序列化闪屏失败" + e.getMessage()); 118 | } 119 | return splash; 120 | } 121 | 122 | 123 | private void startClock() { 124 | mSpJumpBtn.setVisibility(View.VISIBLE); 125 | countDownTimer.start(); 126 | } 127 | 128 | private void gotoLoginOrMainActivity() { 129 | countDownTimer.cancel(); 130 | if (UserCenter.getInstance().getToken() == null) { 131 | gotoLoginActivity(); 132 | } else { 133 | gotoMainActivity(); 134 | } 135 | } 136 | 137 | private void gotoMainActivity() { 138 | Intent intent = new Intent(this, MainActivity.class); 139 | startActivity(intent); 140 | finish(); 141 | } 142 | 143 | private void gotoLoginActivity() { 144 | Intent intent = new Intent(this, LoginActivity.class); 145 | startActivity(intent); 146 | finish(); 147 | } 148 | 149 | @Override 150 | public void finish() { 151 | super.finish(); 152 | overridePendingTransition(0,0); 153 | } 154 | 155 | @Override 156 | protected void onDestroy() { 157 | super.onDestroy(); 158 | if (countDownTimer != null) 159 | countDownTimer.cancel(); 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/wsj/splashdemo/activities/WebActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.wsj.splashdemo.activities; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | import android.support.v7.app.AppCompatActivity; 7 | import android.webkit.WebView; 8 | import android.webkit.WebViewClient; 9 | 10 | import com.example.wsj.splashdemo.R; 11 | import com.example.wsj.splashdemo.UserCenter; 12 | 13 | public class WebActivity extends AppCompatActivity { 14 | 15 | private WebView mWebView; 16 | private boolean mFromSplash; 17 | 18 | @SuppressLint("SetJavaScriptEnabled") 19 | @Override 20 | protected void onCreate(Bundle savedInstanceState) { 21 | super.onCreate(savedInstanceState); 22 | setContentView(R.layout.activity_web); 23 | mWebView = (WebView) findViewById(R.id.web); 24 | mFromSplash = getIntent().getBooleanExtra("fromSplash", false); 25 | String url = getIntent().getStringExtra("url"); 26 | String title = getIntent().getStringExtra("title"); 27 | if (title != null && title.length() > 10) { 28 | title = title.substring(0, 10) + "..."; 29 | } 30 | setTitle(title); 31 | mWebView.loadUrl(url); 32 | mWebView.getSettings().setJavaScriptEnabled(true); 33 | mWebView.setWebViewClient(new WebViewClient(){ 34 | @Override 35 | public boolean shouldOverrideUrlLoading(WebView view, String url) { 36 | view.loadUrl(url); 37 | return true; 38 | } 39 | }); 40 | } 41 | 42 | 43 | @Override 44 | public void onBackPressed() { 45 | if (mWebView.canGoBack()) { 46 | mWebView.goBack(); 47 | } else if (mFromSplash) { 48 | gotoLoginOrMainActivity(); 49 | } else { 50 | super.onBackPressed(); 51 | } 52 | } 53 | 54 | private void gotoLoginOrMainActivity() { 55 | if (UserCenter.getInstance().getToken() == null) { 56 | gotoLoginActivity(); 57 | } else { 58 | gotoMainActivity(); 59 | } 60 | } 61 | 62 | private void gotoMainActivity() { 63 | Intent intent = new Intent(this, MainActivity.class); 64 | startActivity(intent); 65 | finish(); 66 | } 67 | 68 | private void gotoLoginActivity() { 69 | Intent intent = new Intent(this, LoginActivity.class); 70 | startActivity(intent); 71 | finish(); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/wsj/splashdemo/entity/Attachment.java: -------------------------------------------------------------------------------- 1 | package com.example.wsj.splashdemo.entity; 2 | 3 | /** 4 | * Created by wangshijia on 2017/2/3 下午2:33. 5 | * Copyright (c) 2017. alpha, Inc. All rights reserved. 6 | */ 7 | 8 | public class Attachment { 9 | public Splash flashScreen; 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/wsj/splashdemo/entity/Common.java: -------------------------------------------------------------------------------- 1 | package com.example.wsj.splashdemo.entity; 2 | 3 | /** 4 | * Created by wangshijia on 2017/6/9 下午5:17. 5 | * Copyright (c) 2017. alpha, Inc. All rights reserved. 6 | */ 7 | 8 | public class Common { 9 | // 返回码,0成功 10 | public String message; 11 | // 12 | public String debug; 13 | // 返回状态 200代表成功 14 | public int status; 15 | // 具体内容 16 | public Attachment attachment; 17 | 18 | public boolean isValid() { 19 | return status == 200; 20 | 21 | } 22 | 23 | public boolean isServiceBlock() { 24 | return status == 500; 25 | 26 | } 27 | 28 | public boolean isNeedOut() { 29 | return status == 1000; 30 | 31 | } 32 | 33 | @Override 34 | public String toString() { 35 | return "Common{" + 36 | "message='" + message + '\'' + 37 | ", debug='" + debug + '\'' + 38 | ", status=" + status + 39 | ", attachment=" + attachment + 40 | '}'; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/wsj/splashdemo/entity/Constants.java: -------------------------------------------------------------------------------- 1 | package com.example.wsj.splashdemo.entity; 2 | 3 | import com.example.wsj.splashdemo.ColumnApplication; 4 | 5 | /** 6 | * Created by wangshijia on 2017/2/8 下午2:52. 7 | * Copyright (c) 2017. alpha, Inc. All rights reserved. 8 | */ 9 | 10 | public interface Constants { 11 | 12 | String API_DEBUG_SERVER_URL = "http://beta.goldenalpha.com.cn/"; 13 | 14 | String EXTRA_KEY_EXIT = "extra_key_exit"; 15 | 16 | String DOWNLOAD_SPLASH = "download_splash"; 17 | String EXTRA_DOWNLOAD = "extra_download"; 18 | 19 | //动态闪屏序列化地址 20 | String SPLASH_PATH = ColumnApplication.getContext().getFilesDir().getAbsolutePath() + "/alpha/splash"; 21 | 22 | String SPLASH_FILE_NAME = "splash.srr"; 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/wsj/splashdemo/entity/Splash.java: -------------------------------------------------------------------------------- 1 | package com.example.wsj.splashdemo.entity; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * Created by wangshijia on 2017/6/9 下午12:00. 7 | * Copyright (c) 2017. alpha, Inc. All rights reseved. 8 | */ 9 | 10 | public class Splash implements Serializable { 11 | 12 | private static final long serialVersionUID = 7382351359868556980L;//这里需要写死 序列化Id 13 | public int id; 14 | public String burl;//大图 url 15 | public String surl;//小图url 16 | public int type;//图片类型 Android 1 IOS 2 17 | public String click_url; // 点击跳转 URl 18 | public String savePath;//图片的存储地址 19 | public String title;//图片的存储地址 20 | 21 | public Splash(String burl, String surl, String click_url, String savePath) { 22 | this.burl = burl; 23 | this.surl = surl; 24 | this.click_url = click_url; 25 | this.savePath = savePath; 26 | } 27 | 28 | @Override 29 | public String toString() { 30 | return "Splash{" + 31 | "id=" + id + 32 | ", burl='" + burl + '\'' + 33 | ", surl='" + surl + '\'' + 34 | ", type=" + type + 35 | ", click_url='" + click_url + '\'' + 36 | ", savePath='" + savePath + '\'' + 37 | '}'; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/wsj/splashdemo/entity/User.java: -------------------------------------------------------------------------------- 1 | package com.example.wsj.splashdemo.entity; 2 | 3 | 4 | /** 5 | * Created by wangshijia on 2016/12/5 下午2:20. 6 | * Copyright (c) 2016. alpha, Inc. All rights reserved. 7 | * 基金工厂正在使用的User 8 | */ 9 | 10 | public class User { 11 | public long uid; 12 | 13 | public User() { 14 | } 15 | 16 | public User(long uid) { 17 | this.uid = uid; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/wsj/splashdemo/http/ApiStores.java: -------------------------------------------------------------------------------- 1 | package com.example.wsj.splashdemo.http; 2 | 3 | 4 | import com.example.wsj.splashdemo.entity.Common; 5 | 6 | import io.reactivex.Observable; 7 | import retrofit2.http.GET; 8 | import retrofit2.http.Query; 9 | 10 | /** 11 | * Created by wangshijia on 2017/2/3 下午1:54. 12 | * Copyright (c) 2017. alpha, Inc. All rights reserved. 13 | */ 14 | 15 | public interface ApiStores { 16 | @GET("fundworks/media/getFlashScreen") 17 | Observable getSplashImage(@Query("type") int type); 18 | 19 | } 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/wsj/splashdemo/http/HttpClient.java: -------------------------------------------------------------------------------- 1 | package com.example.wsj.splashdemo.http; 2 | 3 | 4 | import android.content.Context; 5 | import android.support.annotation.NonNull; 6 | 7 | import com.example.wsj.splashdemo.ColumnApplication; 8 | import com.example.wsj.splashdemo.http.interceptor.CacheInterceptor; 9 | import com.example.wsj.splashdemo.http.interceptor.LoggingInterceptor; 10 | 11 | import java.io.File; 12 | import java.util.concurrent.TimeUnit; 13 | 14 | import okhttp3.Cache; 15 | import okhttp3.OkHttpClient; 16 | import retrofit2.Retrofit; 17 | import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; 18 | import retrofit2.converter.gson.GsonConverterFactory; 19 | 20 | import static com.example.wsj.splashdemo.entity.Constants.API_DEBUG_SERVER_URL; 21 | 22 | 23 | /** 24 | * Created by wangshijia on 2017/2/4 下午3:33. 25 | * Copyright (c) 2017. alpha, Inc. All rights reserved. 26 | *

27 | * eg: 全局 单利网络请求Client 请求方法 调用静态方法getInstance().***(); 其中*** 为ApiStore中定义的请求方法 28 | */ 29 | 30 | public class HttpClient { 31 | 32 | private static final int CONNECT_TIME_OUT = 3000; 33 | private static final int READ_TIME_OUT = 5000; 34 | private static final int WRITE_TIME_OUT = 5000; 35 | 36 | private static Retrofit retrofit = null; 37 | 38 | private Context mContext; 39 | 40 | public HttpClient(Context context) { 41 | mContext = context; 42 | } 43 | 44 | public static ApiStores getInstance() { 45 | Retrofit retrofit = getClient(); 46 | return retrofit.create(ApiStores.class); 47 | } 48 | 49 | 50 | private static Retrofit getClient() { 51 | if (retrofit == null) { 52 | OkHttpClient.Builder builder = new OkHttpClient.Builder(); 53 | // 缓存设置 54 | setCacheConfig(builder); 55 | // 公共参数 56 | // builder.addInterceptor(new AddQueryParameterInterceptor()); 57 | // https设置 58 | setHttpsConfig(builder); 59 | //设置超时和重连 60 | setTimeOutConfig(builder); 61 | //错误重连 62 | builder.retryOnConnectionFailure(true); 63 | //Log信息拦截器,debug模式下打印log 64 | String BASE_URL = setLogConfig(builder); 65 | OkHttpClient okHttpClient = builder.build(); 66 | retrofit = new Retrofit.Builder() 67 | .baseUrl(BASE_URL) 68 | .addConverterFactory(GsonConverterFactory.create()) 69 | .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) 70 | .client(okHttpClient) 71 | .build(); 72 | } 73 | 74 | return retrofit; 75 | } 76 | 77 | private static void setHttpsConfig(OkHttpClient.Builder builder) { 78 | HttpsUtils.SSLParams sslParams = HttpsUtils.getSslSocketFactory(null, null, null); 79 | builder.sslSocketFactory(sslParams.sSLSocketFactory, sslParams.trustManager); 80 | } 81 | 82 | @NonNull 83 | private static String setLogConfig(OkHttpClient.Builder builder) { 84 | String BASE_URL; 85 | BASE_URL = API_DEBUG_SERVER_URL; 86 | LoggingInterceptor interceptor = new LoggingInterceptor(); 87 | interceptor.setLevel(LoggingInterceptor.Level.BODY); 88 | builder.addInterceptor(interceptor); 89 | return BASE_URL; 90 | } 91 | 92 | private static void setCacheConfig(OkHttpClient.Builder builder) { 93 | File cacheFile = new File(ColumnApplication.getContext().getExternalCacheDir(), "ColumnCache"); 94 | Cache cache = new Cache(cacheFile, 1024 * 1024 * 50); 95 | builder.cache(cache).addInterceptor(new CacheInterceptor()); 96 | } 97 | 98 | private static void setTimeOutConfig(OkHttpClient.Builder builder) { 99 | builder.connectTimeout(CONNECT_TIME_OUT, TimeUnit.MILLISECONDS); 100 | builder.readTimeout(READ_TIME_OUT, TimeUnit.MILLISECONDS); 101 | builder.writeTimeout(WRITE_TIME_OUT, TimeUnit.MILLISECONDS); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/wsj/splashdemo/http/HttpsUtils.java: -------------------------------------------------------------------------------- 1 | package com.example.wsj.splashdemo.http; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import java.security.KeyManagementException; 6 | import java.security.KeyStore; 7 | import java.security.KeyStoreException; 8 | import java.security.NoSuchAlgorithmException; 9 | import java.security.UnrecoverableKeyException; 10 | import java.security.cert.CertificateException; 11 | import java.security.cert.CertificateFactory; 12 | import java.security.cert.X509Certificate; 13 | 14 | import javax.net.ssl.HostnameVerifier; 15 | import javax.net.ssl.KeyManager; 16 | import javax.net.ssl.KeyManagerFactory; 17 | import javax.net.ssl.SSLContext; 18 | import javax.net.ssl.SSLSession; 19 | import javax.net.ssl.SSLSocketFactory; 20 | import javax.net.ssl.TrustManager; 21 | import javax.net.ssl.TrustManagerFactory; 22 | import javax.net.ssl.X509TrustManager; 23 | 24 | /** 25 | * Created by wangshijia on 15/12/14 上午10:45. 26 | * Copyright (c) 2017. alpha, Inc. All rights reserved. 27 | * ex: 鸿洋关于Https的封装 默认采用无证书验证 28 | */ 29 | public class HttpsUtils { 30 | 31 | public static class SSLParams { 32 | public SSLSocketFactory sSLSocketFactory; 33 | public X509TrustManager trustManager; 34 | } 35 | 36 | public static SSLParams getSslSocketFactory(InputStream[] certificates, InputStream bksFile, String password) { 37 | SSLParams sslParams = new SSLParams(); 38 | try { 39 | TrustManager[] trustManagers = prepareTrustManager(certificates); 40 | KeyManager[] keyManagers = prepareKeyManager(bksFile, password); 41 | SSLContext sslContext = SSLContext.getInstance("TLS"); 42 | X509TrustManager trustManager = null; 43 | if (trustManagers != null) { 44 | trustManager = new MyTrustManager(chooseTrustManager(trustManagers)); 45 | } else { 46 | trustManager = new UnSafeTrustManager(); 47 | } 48 | sslContext.init(keyManagers, new TrustManager[]{trustManager}, null); 49 | sslParams.sSLSocketFactory = sslContext.getSocketFactory(); 50 | sslParams.trustManager = trustManager; 51 | return sslParams; 52 | } catch (NoSuchAlgorithmException e) { 53 | throw new AssertionError(e); 54 | } catch (KeyManagementException e) { 55 | throw new AssertionError(e); 56 | } catch (KeyStoreException e) { 57 | throw new AssertionError(e); 58 | } 59 | } 60 | 61 | private class UnSafeHostnameVerifier implements HostnameVerifier { 62 | @Override 63 | public boolean verify(String hostname, SSLSession session) { 64 | return true; 65 | } 66 | } 67 | 68 | private static class UnSafeTrustManager implements X509TrustManager { 69 | @Override 70 | public void checkClientTrusted(X509Certificate[] chain, String authType) 71 | throws CertificateException { 72 | } 73 | 74 | @Override 75 | public void checkServerTrusted(X509Certificate[] chain, String authType) 76 | throws CertificateException { 77 | } 78 | 79 | @Override 80 | public X509Certificate[] getAcceptedIssuers() { 81 | return new X509Certificate[]{}; 82 | } 83 | } 84 | 85 | private static TrustManager[] prepareTrustManager(InputStream... certificates) { 86 | if (certificates == null || certificates.length <= 0) return null; 87 | try { 88 | 89 | CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); 90 | KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); 91 | keyStore.load(null); 92 | int index = 0; 93 | for (InputStream certificate : certificates) { 94 | String certificateAlias = Integer.toString(index++); 95 | keyStore.setCertificateEntry(certificateAlias, certificateFactory.generateCertificate(certificate)); 96 | try { 97 | if (certificate != null) 98 | certificate.close(); 99 | } catch (IOException e) { 100 | } 101 | } 102 | TrustManagerFactory trustManagerFactory = null; 103 | 104 | trustManagerFactory = TrustManagerFactory. 105 | getInstance(TrustManagerFactory.getDefaultAlgorithm()); 106 | trustManagerFactory.init(keyStore); 107 | 108 | TrustManager[] trustManagers = trustManagerFactory.getTrustManagers(); 109 | 110 | return trustManagers; 111 | } catch (NoSuchAlgorithmException e) { 112 | e.printStackTrace(); 113 | } catch (CertificateException e) { 114 | e.printStackTrace(); 115 | } catch (KeyStoreException e) { 116 | e.printStackTrace(); 117 | } catch (Exception e) { 118 | e.printStackTrace(); 119 | } 120 | return null; 121 | 122 | } 123 | 124 | private static KeyManager[] prepareKeyManager(InputStream bksFile, String password) { 125 | try { 126 | if (bksFile == null || password == null) return null; 127 | 128 | KeyStore clientKeyStore = KeyStore.getInstance("BKS"); 129 | clientKeyStore.load(bksFile, password.toCharArray()); 130 | KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); 131 | keyManagerFactory.init(clientKeyStore, password.toCharArray()); 132 | return keyManagerFactory.getKeyManagers(); 133 | 134 | } catch (KeyStoreException e) { 135 | e.printStackTrace(); 136 | } catch (NoSuchAlgorithmException e) { 137 | e.printStackTrace(); 138 | } catch (UnrecoverableKeyException e) { 139 | e.printStackTrace(); 140 | } catch (CertificateException e) { 141 | e.printStackTrace(); 142 | } catch (IOException e) { 143 | e.printStackTrace(); 144 | } catch (Exception e) { 145 | e.printStackTrace(); 146 | } 147 | return null; 148 | } 149 | 150 | private static X509TrustManager chooseTrustManager(TrustManager[] trustManagers) { 151 | for (TrustManager trustManager : trustManagers) { 152 | if (trustManager instanceof X509TrustManager) { 153 | return (X509TrustManager) trustManager; 154 | } 155 | } 156 | return null; 157 | } 158 | 159 | 160 | private static class MyTrustManager implements X509TrustManager { 161 | private X509TrustManager defaultTrustManager; 162 | private X509TrustManager localTrustManager; 163 | 164 | public MyTrustManager(X509TrustManager localTrustManager) throws NoSuchAlgorithmException, KeyStoreException { 165 | TrustManagerFactory var4 = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); 166 | var4.init((KeyStore) null); 167 | defaultTrustManager = chooseTrustManager(var4.getTrustManagers()); 168 | this.localTrustManager = localTrustManager; 169 | } 170 | 171 | 172 | @Override 173 | public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { 174 | 175 | } 176 | 177 | @Override 178 | public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { 179 | try { 180 | defaultTrustManager.checkServerTrusted(chain, authType); 181 | } catch (CertificateException ce) { 182 | localTrustManager.checkServerTrusted(chain, authType); 183 | } 184 | } 185 | 186 | 187 | @Override 188 | public X509Certificate[] getAcceptedIssuers() { 189 | return new X509Certificate[0]; 190 | } 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/wsj/splashdemo/http/callback/CommonCallback.java: -------------------------------------------------------------------------------- 1 | package com.example.wsj.splashdemo.http.callback; 2 | 3 | 4 | import android.content.Context; 5 | import android.content.Intent; 6 | 7 | import com.example.wsj.splashdemo.ColumnApplication; 8 | import com.example.wsj.splashdemo.UserCenter; 9 | import com.example.wsj.splashdemo.activities.LoginActivity; 10 | import com.example.wsj.splashdemo.entity.Attachment; 11 | import com.example.wsj.splashdemo.entity.Common; 12 | 13 | import java.net.ConnectException; 14 | import java.net.SocketTimeoutException; 15 | 16 | import retrofit2.Call; 17 | import retrofit2.Callback; 18 | import retrofit2.Response; 19 | 20 | import static com.example.wsj.splashdemo.entity.Constants.EXTRA_KEY_EXIT; 21 | 22 | /** 23 | * Created by wangshijia on 2017/2/3 下午2:37. 24 | * Copyright (c) 2017. alpha, Inc. All rights reserved. 25 | */ 26 | 27 | public abstract class CommonCallback implements Callback { 28 | 29 | @Override 30 | public void onResponse(Call call, Response response) { 31 | if (response.raw().code() == 200) { 32 | T body = response.body(); 33 | if (body != null) { 34 | if (body.isValid()) { 35 | if (body.attachment != null) { 36 | onSuccess(body.attachment); 37 | } else { 38 | onError("Attachment 对象为空"); 39 | } 40 | } else { 41 | if (body.isNeedOut()) {//token过期重新登录 42 | gotoLogInActivity(); 43 | 44 | } else if (body.isServiceBlock()) { 45 | } else { 46 | onError(body.message); 47 | } 48 | } 49 | } else { 50 | onError(response.message()); 51 | } 52 | } else { 53 | onFailure(call, new RuntimeException("response error,detail = " + response.raw().toString())); 54 | } 55 | } 56 | 57 | private void gotoLogInActivity() { 58 | Context context = ColumnApplication.getContext(); 59 | UserCenter.getInstance().setToken(null);//清楚token 60 | Intent intent = new Intent(context, LoginActivity.class); 61 | intent.putExtra(EXTRA_KEY_EXIT, true); 62 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 63 | context.startActivity(intent); 64 | } 65 | 66 | @Override 67 | public void onFailure(Call call, Throwable t) { 68 | if (t instanceof SocketTimeoutException) { 69 | return; 70 | } else if (t instanceof ConnectException) { 71 | onError(t.getMessage()); 72 | } else if (t instanceof RuntimeException) { 73 | onError(t.getMessage()); 74 | } else { 75 | onError(t.getMessage()); 76 | } 77 | call.cancel(); 78 | } 79 | 80 | public abstract void onSuccess(Attachment attachment); 81 | 82 | public void onError(String message) { 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/wsj/splashdemo/http/interceptor/AddQueryParameterInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.example.wsj.splashdemo.http.interceptor; 2 | 3 | import android.text.TextUtils; 4 | 5 | import com.example.wsj.splashdemo.UserCenter; 6 | 7 | import java.io.IOException; 8 | 9 | import okhttp3.HttpUrl; 10 | import okhttp3.Interceptor; 11 | import okhttp3.Request; 12 | import okhttp3.Response; 13 | 14 | /** 15 | * Created by wangshijia on 2017/2/3 下午3:54. 16 | * Copyright (c) 2017. alpha, Inc. All rights reserved. 17 | */ 18 | 19 | public class AddQueryParameterInterceptor implements Interceptor { 20 | @Override 21 | public Response intercept(Chain chain) throws IOException { 22 | Request originalRequest = chain.request(); 23 | Request request; 24 | //String method = originalRequest.method(); 25 | // Headers headers = originalRequest.headers(); 26 | String token = UserCenter.getInstance().getToken(); 27 | String uid = String.valueOf(UserCenter.getInstance().getCurrentUser().uid); 28 | HttpUrl modifiedUrl = originalRequest.url().newBuilder() 29 | .addQueryParameter("token", TextUtils.isEmpty(UserCenter.getInstance().getToken()) ? "" : token) 30 | .addQueryParameter("uid", UserCenter.getInstance().getCurrentUser().uid == 0 ? "" : uid) 31 | .build(); 32 | request = originalRequest.newBuilder().url(modifiedUrl).build(); 33 | 34 | return chain.proceed(request); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/wsj/splashdemo/http/interceptor/CacheInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.example.wsj.splashdemo.http.interceptor; 2 | 3 | import android.text.TextUtils; 4 | 5 | import com.example.wsj.splashdemo.ColumnApplication; 6 | import com.example.wsj.splashdemo.utils.NetWorkUtils; 7 | 8 | import java.io.IOException; 9 | 10 | import okhttp3.CacheControl; 11 | import okhttp3.Interceptor; 12 | import okhttp3.Request; 13 | import okhttp3.Response; 14 | 15 | /** 16 | * Created by wangshijia on 2017/2/3 下午3:55. 17 | * Copyright (c) 2017. alpha, Inc. All rights reserved. 18 | */ 19 | //该缓存方案是有网的时候从服务器取没网的时候就从本地取,如果并未从服务器取过,那么就 20 | public class CacheInterceptor implements Interceptor { 21 | @Override 22 | public Response intercept(Chain chain) throws IOException { 23 | Request request = chain.request(); 24 | if (!NetWorkUtils.isNetWorkAvailable(ColumnApplication.getContext())) {//如果网络不可用 25 | request = request.newBuilder() 26 | .cacheControl(CacheControl.FORCE_CACHE)//强制从缓存取 对应的强制从网络取为CacheControl.FORCE_NETWORK(); 27 | .build(); 28 | } 29 | 30 | Response response = chain.proceed(request); 31 | 32 | if (NetWorkUtils.isNetWorkAvailable(ColumnApplication.getContext()) ) { 33 | int maxAge = 0; 34 | // 有网络时 设置缓存超时时间0个小时 35 | // String cacheControl = request.cacheControl().toString(); 36 | // 如果单个请求不同请在请求中写上Cache-control头则按照对应的配置进行本地缓存时间配置 37 | String cacheControl = request.cacheControl().toString(); 38 | if (TextUtils.isEmpty(cacheControl)) { 39 | response.newBuilder() 40 | .header("Cache-Control", "public, max-age=" + maxAge) 41 | .removeHeader("Pragma")// 清除头信息,因为服务器如果不支持,会返回一些干扰信息,不清除下面无法生效 42 | .build(); 43 | } else { 44 | response.newBuilder() 45 | .header("Cache-Control", cacheControl) 46 | .removeHeader("Pragma")// 清除头信息,因为服务器如果不支持,会返回一些干扰信息,不清除下面无法生效 47 | .build(); 48 | } 49 | } else { 50 | // 无网络时,设置超时为4周 51 | int maxStale = 60 * 60 * 24 * 28; 52 | response.newBuilder() 53 | .header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale) 54 | .removeHeader("Pragma") 55 | .build(); 56 | } 57 | return response; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/wsj/splashdemo/http/interceptor/LoggingInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.example.wsj.splashdemo.http.interceptor; 2 | 3 | import java.io.EOFException; 4 | import java.io.IOException; 5 | import java.nio.charset.Charset; 6 | import java.nio.charset.UnsupportedCharsetException; 7 | import java.util.concurrent.TimeUnit; 8 | 9 | import okhttp3.Headers; 10 | import okhttp3.Interceptor; 11 | import okhttp3.MediaType; 12 | import okhttp3.MultipartBody; 13 | import okhttp3.Protocol; 14 | import okhttp3.Request; 15 | import okhttp3.RequestBody; 16 | import okhttp3.Response; 17 | import okhttp3.ResponseBody; 18 | import okhttp3.internal.http.HttpHeaders; 19 | import okhttp3.internal.platform.Platform; 20 | import okio.Buffer; 21 | import okio.BufferedSource; 22 | 23 | import static okhttp3.internal.platform.Platform.INFO; 24 | 25 | 26 | /** 27 | * Created by wangshijia on 2017/2/4 下午4:02. 28 | * Copyright (c) 2017. alpha, Inc. All rights reserved. 29 | */ 30 | 31 | public class LoggingInterceptor implements Interceptor { 32 | private static final Charset UTF8 = Charset.forName("UTF-8"); 33 | 34 | public enum Level { 35 | /** * No logs. */ 36 | NONE, 37 | /** * Logs request and response lines. *

* Example: *

{@code * --> POST /greeting HTTP/1.1 (3-byte body) * * <-- HTTP/1.1 200 OK (22ms, 6-byte body) * }
*/ 38 | BASIC, 39 | /** * Logs request and response lines and their respective headers. *

* Example: *

{@code * --> POST /greeting HTTP/1.1 * Host: example.com * Content-Type: plain/text * Content-Length: 3 * --> END POST * * <-- HTTP/1.1 200 OK (22ms) * Content-Type: plain/text * Content-Length: 6 * <-- END HTTP * }
*/ 40 | HEADERS, 41 | /** * Logs request and response lines and their respective headers and bodies (if present). *

* Example: *

{@code * --> POST /greeting HTTP/1.1 * Host: example.com * Content-Type: plain/text * Content-Length: 3 * * Hi? * --> END GET * * <-- HTTP/1.1 200 OK (22ms) * Content-Type: plain/text * Content-Length: 6 * * Hello! * <-- END HTTP * }
*/ 42 | BODY 43 | } 44 | 45 | public interface Logger { 46 | void log(String message); 47 | 48 | /** * A {@link Logger} defaults output appropriate for the current platform. */ 49 | 50 | Logger DEFAULT = new Logger() { 51 | @Override public void log(String message) { 52 | Platform.get().log(INFO, message, null); 53 | } 54 | }; 55 | } 56 | 57 | public LoggingInterceptor() { 58 | this(Logger.DEFAULT); 59 | } 60 | 61 | public LoggingInterceptor(Logger logger) { 62 | this.logger = logger; 63 | } 64 | 65 | private final Logger logger; 66 | 67 | private volatile Level level = Level.BODY; 68 | 69 | /** * Change the level at which this interceptor logs. */ 70 | public LoggingInterceptor setLevel(Level level) { 71 | if (level == null) 72 | throw new NullPointerException("level == null. Use Level.NONE instead."); 73 | this.level = level; 74 | return this; 75 | } 76 | 77 | @Override 78 | public Response intercept(Chain chain) throws IOException { 79 | Level level = this.level; 80 | 81 | Request request = chain.request(); 82 | if (level == Level.NONE) { 83 | return chain.proceed(request); 84 | } 85 | 86 | boolean logBody = level == Level.BODY; 87 | boolean logHeaders = logBody || level == Level.HEADERS; 88 | 89 | RequestBody requestBody = request.body(); 90 | boolean hasRequestBody = requestBody != null; 91 | 92 | String requestStartMessage = request.method() + ' ' + request.url(); 93 | if (!logHeaders && hasRequestBody) { 94 | requestStartMessage += " (" + requestBody.contentLength() + "-byte body)"; 95 | } 96 | logger.log("----------------request start----------------"); 97 | logger.log(requestStartMessage); 98 | 99 | if (logHeaders) { 100 | if (hasRequestBody) { 101 | // Request body headers are only present when installed as a network interceptor. 102 | // Forcethem to be included (when available) so there values are known. 103 | if (requestBody.contentType() != null) { 104 | logger.log("Content-Type: " + requestBody.contentType()); 105 | } 106 | if (requestBody.contentLength() != -1) { 107 | logger.log("Content-Length: " + requestBody.contentLength()); 108 | } 109 | } 110 | 111 | Headers headers = request.headers(); 112 | for (int i = 0, count = headers.size(); i < count; i++) { 113 | String name = headers.name(i); 114 | // Skip headers from the request body as they are explicitly logged above. 115 | if (skipHeader(name)) { 116 | logger.log(name + ": " + headers.value(i)); 117 | } 118 | } 119 | 120 | if (!logBody || !hasRequestBody) { 121 | } else if (bodyEncoded(request.headers())) { 122 | } else if (request.body() instanceof MultipartBody) { 123 | logger.log("content: " + "too many bytes, ignored"); 124 | } else { 125 | Buffer buffer = new Buffer(); 126 | requestBody.writeTo(buffer); 127 | 128 | Charset charset = UTF8; 129 | MediaType contentType = requestBody.contentType(); 130 | if (contentType != null) { 131 | contentType.charset(UTF8); 132 | } 133 | logger.log("content: " + buffer.readString(charset)); 134 | } 135 | logger.log("----------------request end------------------"); 136 | } 137 | 138 | long startNs = System.nanoTime(); 139 | Response response; 140 | try { 141 | response = chain.proceed(request); 142 | logger.log("----------------response start---------------"); 143 | } catch (Exception e) { 144 | logger.log("HTTP FAILED: " + e); 145 | logger.log("----------------response end-----------------"); 146 | throw e; 147 | } 148 | long tookMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs); 149 | ResponseBody responseBody = response.body(); 150 | long contentLength = responseBody.contentLength(); 151 | logger.log(protocol(response.protocol()) + " " + response.request().url()); 152 | logger.log("info: " + "code:" + response.code() + " " + "result:" + response.message() + 153 | " times:" + tookMs + "ms"); 154 | 155 | if (logHeaders) { 156 | 157 | if (!logBody || !HttpHeaders.hasBody(response)) { 158 | } else if (bodyEncoded(response.headers())) { 159 | } else { 160 | BufferedSource source = responseBody.source(); 161 | source.request(Long.MAX_VALUE); // Buffer the entire body. 162 | Buffer buffer = source.buffer(); 163 | 164 | Charset charset = UTF8; 165 | MediaType contentType = responseBody.contentType(); 166 | if (contentType != null) { 167 | try { 168 | charset = contentType.charset(UTF8); 169 | } catch (UnsupportedCharsetException e) { 170 | logger.log(""); 171 | logger.log("Couldn't decode the response body; charset is likely " + 172 | "malformed."); 173 | logger.log("----------------response end-----------------"); 174 | return response; 175 | } 176 | } 177 | 178 | if (!isPlaintext(buffer)) { 179 | logger.log(""); 180 | logger.log("----------------response end-----------------"); 181 | return response; 182 | } 183 | 184 | if (contentLength != 0) { 185 | logger.log(""); 186 | logger.log("content: " + buffer.clone().readString(charset)); 187 | } 188 | } 189 | } 190 | logger.log("----------------response end-----------------"); 191 | return response; 192 | } 193 | 194 | private boolean skipHeader(String name) { 195 | return !"Content-Type".equalsIgnoreCase(name) && !"Content-Length".equalsIgnoreCase 196 | (name) && !"Host".equalsIgnoreCase(name) && !"Accept-Encoding" 197 | .equalsIgnoreCase(name) && !"User-Agent".equalsIgnoreCase(name) && 198 | !"Connection".equalsIgnoreCase(name); 199 | } 200 | 201 | private boolean bodyEncoded(Headers headers) { 202 | String contentEncoding = headers.get("Content-Encoding"); 203 | return contentEncoding != null && !contentEncoding.equalsIgnoreCase("identity"); 204 | } 205 | 206 | private static String protocol(Protocol protocol) { 207 | return protocol == Protocol.HTTP_1_0 ? "HTTP/1.0" : "HTTP/1.1"; 208 | } 209 | 210 | /** * Returns true if the body in question probably contains human readable text. Uses a small * sample * of code points to detect unicode control characters commonly used in binary file signatures. */ 211 | private static boolean isPlaintext(Buffer buffer) { 212 | try { 213 | Buffer prefix = new Buffer(); 214 | long byteCount = buffer.size() < 64 ? buffer.size() : 64; 215 | buffer.copyTo(prefix, 0, byteCount); 216 | for (int i = 0; i < 16; i++) { 217 | if (prefix.exhausted()) { 218 | break; 219 | } 220 | int codePoint = prefix.readUtf8CodePoint(); 221 | if (Character.isISOControl(codePoint) && !Character.isWhitespace(codePoint)) { 222 | return false; 223 | } 224 | } 225 | return true; 226 | } catch (EOFException e) { 227 | return false; // Truncated UTF-8 sequence. 228 | } 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/wsj/splashdemo/service/SplashDownLoadService.java: -------------------------------------------------------------------------------- 1 | package com.example.wsj.splashdemo.service; 2 | 3 | import android.app.IntentService; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.support.annotation.NonNull; 7 | import android.support.annotation.Nullable; 8 | import android.text.TextUtils; 9 | import android.util.Log; 10 | 11 | import com.example.wsj.splashdemo.entity.Common; 12 | import com.example.wsj.splashdemo.entity.Constants; 13 | import com.example.wsj.splashdemo.entity.Splash; 14 | import com.example.wsj.splashdemo.http.HttpClient; 15 | import com.example.wsj.splashdemo.utils.DownLoadUtils; 16 | import com.example.wsj.splashdemo.utils.SerializableUtils; 17 | 18 | import java.io.File; 19 | import java.io.IOException; 20 | import java.util.ArrayList; 21 | 22 | import io.reactivex.android.schedulers.AndroidSchedulers; 23 | import io.reactivex.functions.Consumer; 24 | import io.reactivex.schedulers.Schedulers; 25 | 26 | import static com.example.wsj.splashdemo.utils.SerializableUtils.readObject; 27 | 28 | public class SplashDownLoadService extends IntentService { 29 | 30 | private Splash mScreen; 31 | public static final int TYPE_ANDROID = 1; 32 | private static final String SPLASH_FILE_NAME = "splash.srr"; 33 | public SplashDownLoadService() { 34 | super("SplashDownLoad"); 35 | } 36 | 37 | public static void startDownLoadSplashImage(Context context, String action) { 38 | Intent intent = new Intent(context, SplashDownLoadService.class); 39 | intent.putExtra(Constants.EXTRA_DOWNLOAD, action); 40 | context.startService(intent); 41 | } 42 | 43 | @Override 44 | protected void onHandleIntent(@Nullable Intent intent) { 45 | if (intent != null) { 46 | String action = intent.getStringExtra(Constants.EXTRA_DOWNLOAD); 47 | if (action.equals(Constants.DOWNLOAD_SPLASH)) { 48 | loadSplashNetDate(); 49 | } 50 | } 51 | } 52 | 53 | private void loadSplashNetDate() { 54 | HttpClient.getInstance() 55 | .getSplashImage(TYPE_ANDROID) 56 | .subscribeOn(Schedulers.io()) 57 | .observeOn(AndroidSchedulers.mainThread()) 58 | .subscribe(new Consumer() { 59 | @Override 60 | public void accept(@NonNull Common common) throws Exception { 61 | if (common.isValid() && common.attachment != null) { 62 | mScreen = common.attachment.flashScreen; 63 | Splash splashLocal = getSplashLocal(); 64 | if (mScreen != null) { 65 | if (splashLocal == null) { 66 | Log.d("SplashDemo","splashLocal 为空导致下载"); 67 | startDownLoadSplash(Constants.SPLASH_PATH, mScreen.burl); 68 | } else if (isNeedDownLoad(splashLocal.savePath, mScreen.burl)) { 69 | Log.d("SplashDemo","isNeedDownLoad 导致下载"); 70 | startDownLoadSplash(Constants.SPLASH_PATH, mScreen.burl); 71 | } 72 | } else { 73 | if (splashLocal != null) { 74 | File splashFile = SerializableUtils.getSerializableFile(Constants.SPLASH_PATH, Constants.SPLASH_FILE_NAME); 75 | if (splashFile.exists()) { 76 | splashFile.delete(); 77 | Log.d("SplashDemo","mScreen为空删除本地文件"); 78 | } 79 | } 80 | } 81 | } 82 | } 83 | }, new Consumer() { 84 | @Override 85 | public void accept(@NonNull Throwable throwable) throws Exception { 86 | 87 | } 88 | }); 89 | } 90 | 91 | private Splash getSplashLocal() { 92 | Splash splash = null; 93 | try { 94 | File splashFile = SerializableUtils.getSerializableFile(Constants.SPLASH_PATH, SPLASH_FILE_NAME); 95 | splash = (Splash) readObject(splashFile); 96 | } catch (IOException e) { 97 | e.printStackTrace(); 98 | } 99 | return splash; 100 | } 101 | 102 | /** 103 | * @param path 本地存储的图片绝对路径 104 | * @param url 网络获取url 105 | * @return 比较储存的 图片名称的哈希值与 网络获取的哈希值是否相同 106 | */ 107 | private boolean isNeedDownLoad(String path, String url) { 108 | if (TextUtils.isEmpty(path)) { 109 | Log.d("SplashDemo","本地url " + TextUtils.isEmpty(path)); 110 | Log.d("SplashDemo","本地url " + TextUtils.isEmpty(url)); 111 | return true; 112 | } 113 | File file = new File(path); 114 | if (!file.exists()) { 115 | Log.d("SplashDemo","本地file " + file.exists()); 116 | return true; 117 | } 118 | if (getImageName(path).hashCode() != getImageName(url).hashCode()) { 119 | Log.d("SplashDemo","path hashcode " + getImageName(path) + " " + getImageName(path).hashCode()); 120 | Log.d("SplashDemo","url hashcode " + getImageName(url) + " " + getImageName(url).hashCode()); 121 | return true; 122 | } 123 | return false; 124 | } 125 | 126 | 127 | private String getImageName(String url) { 128 | if (TextUtils.isEmpty(url)) { 129 | return ""; 130 | } 131 | String[] split = url.split("/"); 132 | String nameWith_ = split[split.length - 1]; 133 | String[] split1 = nameWith_.split("\\."); 134 | return split1[0]; 135 | } 136 | 137 | private void startDownLoadSplash(String splashPath, String burl) { 138 | DownLoadUtils.downLoad(splashPath, new DownLoadUtils.DownLoadInterFace() { 139 | @Override 140 | public void afterDownLoad(ArrayList savePaths) { 141 | if (savePaths.size() == 1) { 142 | Log.d("SplashDemo","闪屏页面下载完成" + savePaths); 143 | if (mScreen != null) { 144 | mScreen.savePath = savePaths.get(0); 145 | } 146 | SerializableUtils.writeObject(mScreen, Constants.SPLASH_PATH + "/" + SPLASH_FILE_NAME); 147 | } else { 148 | Log.d("SplashDemo","闪屏页面下载失败" + savePaths); 149 | } 150 | } 151 | }, burl); 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/wsj/splashdemo/utils/DownLoadUtils.java: -------------------------------------------------------------------------------- 1 | package com.example.wsj.splashdemo.utils; 2 | 3 | import android.os.AsyncTask; 4 | import android.os.Environment; 5 | import android.text.TextUtils; 6 | 7 | import java.io.File; 8 | import java.io.FileOutputStream; 9 | import java.io.IOException; 10 | import java.io.InputStream; 11 | import java.net.HttpURLConnection; 12 | import java.net.URL; 13 | import java.util.ArrayList; 14 | 15 | /** 16 | * Created by wangshijia on 2017/6/12 下午3:29. 17 | * Copyright (c) 2017. alpha, Inc. All rights reserved. 18 | */ 19 | 20 | public class DownLoadUtils { 21 | 22 | public interface DownLoadInterFace { 23 | void afterDownLoad(ArrayList savePaths); 24 | } 25 | 26 | public static void downLoad(String savePath, DownLoadInterFace downLoadInterFace, String... download) { 27 | new DownLoadTask(savePath, downLoadInterFace).execute(download); 28 | } 29 | 30 | private static class DownLoadTask extends AsyncTask> { 31 | private String mSavePath; 32 | private DownLoadInterFace mDownLoadInterFace; 33 | 34 | private DownLoadTask(String savePath, DownLoadInterFace downLoadTask) { 35 | this.mSavePath = savePath; 36 | this.mDownLoadInterFace = downLoadTask; 37 | } 38 | 39 | @Override 40 | protected ArrayList doInBackground(String... params) { 41 | ArrayList names = new ArrayList<>(); 42 | for (String url : params) { 43 | if (!TextUtils.isEmpty(url)) { 44 | if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { 45 | // 获得存储卡的路径 46 | FileOutputStream fos = null; 47 | InputStream is = null; 48 | try { 49 | URL downUrl = new URL(url); 50 | // 创建连接 51 | HttpURLConnection conn = (HttpURLConnection) downUrl.openConnection(); 52 | conn.connect(); 53 | // 创建输入流 54 | is = conn.getInputStream(); 55 | File file = new File(mSavePath); 56 | // 判断文件目录是否存在 57 | if (!file.exists()) { 58 | file.mkdirs(); 59 | } 60 | 61 | String[] split = url.split("/"); 62 | String fileName = split[split.length - 1]; 63 | File mApkFile = new File(mSavePath, fileName); 64 | names.add(mApkFile.getAbsolutePath()); 65 | fos = new FileOutputStream(mApkFile, false); 66 | int count = 0; 67 | // 缓存 68 | byte buf[] = new byte[1024]; 69 | while (true) { 70 | int read = is.read(buf); 71 | if (read == -1) { 72 | break; 73 | } 74 | fos.write(buf, 0, read); 75 | count += read; 76 | publishProgress(count); 77 | } 78 | fos.flush(); 79 | 80 | } catch (Exception e) { 81 | e.printStackTrace(); 82 | } finally { 83 | try { 84 | if (is != null) { 85 | is.close(); 86 | } 87 | if (fos != null) { 88 | fos.close(); 89 | } 90 | } catch (IOException e1) { 91 | e1.printStackTrace(); 92 | } 93 | } 94 | } 95 | } 96 | } 97 | return names; 98 | } 99 | 100 | @Override 101 | protected void onPostExecute(ArrayList strings) { 102 | super.onPostExecute(strings); 103 | if (mDownLoadInterFace != null) { 104 | mDownLoadInterFace.afterDownLoad(strings); 105 | } 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/wsj/splashdemo/utils/NetWorkUtils.java: -------------------------------------------------------------------------------- 1 | package com.example.wsj.splashdemo.utils; 2 | 3 | import android.content.Context; 4 | import android.net.ConnectivityManager; 5 | import android.net.NetworkInfo; 6 | 7 | /** 8 | * Created by wangshijia on 2017/2/3 下午4:00. 9 | * Copyright (c) 2017. alpha, Inc. All rights reserved. 10 | */ 11 | 12 | public class NetWorkUtils { 13 | 14 | public static boolean isNetWorkAvailable(Context context) { 15 | ConnectivityManager connectivity = (ConnectivityManager) context 16 | .getSystemService(Context.CONNECTIVITY_SERVICE); 17 | NetworkInfo info = connectivity.getActiveNetworkInfo(); 18 | return !(info == null || info.getState() != NetworkInfo.State.CONNECTED); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/wsj/splashdemo/utils/SerializableUtils.java: -------------------------------------------------------------------------------- 1 | package com.example.wsj.splashdemo.utils; 2 | 3 | import android.util.Log; 4 | 5 | import java.io.EOFException; 6 | import java.io.File; 7 | import java.io.FileInputStream; 8 | import java.io.FileOutputStream; 9 | import java.io.IOException; 10 | import java.io.ObjectInputStream; 11 | import java.io.ObjectOutputStream; 12 | import java.io.Serializable; 13 | 14 | /** 15 | * Created by wangshijia on 2017/6/12 下午1:18. 16 | * Copyright (c) 2017. alpha, Inc. All rights reserved. 17 | */ 18 | 19 | public class SerializableUtils { 20 | 21 | public static Object readObject(File file) { 22 | ObjectInputStream in = null; 23 | T t = null; 24 | try { 25 | in = new ObjectInputStream(new FileInputStream(file)); 26 | t = (T) in.readObject(); 27 | } catch (EOFException e) { 28 | // ... this is fine 29 | } catch (IOException | ClassNotFoundException e) { 30 | e.printStackTrace(); 31 | } finally { 32 | try { 33 | if (in != null) in.close(); 34 | } catch (IOException e) { 35 | e.printStackTrace(); 36 | } 37 | } 38 | return t; 39 | } 40 | 41 | public static boolean writeObject(T t, String fileName) { 42 | ObjectOutputStream out = null; 43 | try { 44 | out = new ObjectOutputStream(new FileOutputStream(fileName)); 45 | out.writeObject(t); 46 | Log.d("SplashDemo","序列化成功 " + t.toString()); 47 | return true; 48 | } catch (IOException e) { 49 | e.printStackTrace(); 50 | Log.d("SplashDemo","序列化失败 " + e.getMessage()); 51 | return false; 52 | } finally { 53 | try { 54 | if (out != null) out.close(); 55 | } catch (IOException e) { 56 | e.printStackTrace(); 57 | } 58 | } 59 | } 60 | 61 | public static File getSerializableFile(String rootPath, String fileName) throws IOException { 62 | File file = new File(rootPath); 63 | if (!file.exists()) file.mkdirs(); 64 | File serializable = new File(file, fileName); 65 | if (!serializable.exists()) serializable.createNewFile(); 66 | return serializable; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/btn_splash_shape.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_login.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_splash.xml: -------------------------------------------------------------------------------- 1 | 4 | 10 | 11 |