Commit a9c69d4b by konghaorui

init repository

parents

Too many changes to show.

To preserve performance only 1000 of 1000+ files are displayed.

*.iml
.gradle
/local.properties
/.idea
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
*/build
build
/captures
.externalNativeBuild
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
# Default value: -Xmx10248m -XX:MaxPermSize=256m
#org.gradle.jvmargs=-Xmx4096m -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
#org.gradle.parallel=true
# org.gradle.parallel=true
#org.gradle.parallel=true
android.useDeprecatedNdk=true
android.enableAapt2=false
# pack:test|prod|pre test测试 prod正式
#pack=pre
#pack=auto_test
#pack=test
pack=prod
#予发布环境
#http://prem.yidianling.com/
#server php服务器api域名
serverurl.test=https://testapp2.yidianling.com/v3/
serverurl.auto_test=https://auto_testapp2.yidianling.com/v3/
serverurl.prod=https://app2.yidianling.com/v3/
serverurl.pre=https://preapp.yidianling.com/v3/
#行为数据埋点域名
actionurl.test=https://testapi.ydl.com/api/data/bigdata/
actionurl.auto_test=https://auto_testapi.ydl.com/api/data/bigdata/
actionurl.prod=https://api.ydl.com/api/data/bigdata/
#新h5
weburl.test=https://testwebapp.ydl.com/
weburl.auto_test=https://auto_testwebapp.ydl.com/
weburl.prod=https://webapp.ydl.com/
#ydlh5
ydlh5url.test=https://testh5.ydl.com/
ydlh5url.auto_test=https://auto_testh5.ydl.com/
#ydlh5url.test=http://192.168.204.5:3006/
ydlh5url.prod=https://h5.ydl.com/
#h5
h5url.test=https://testh2.yidianling.com/
h5url.auto_test=https://auto_testh2.yidianling.com/
h5url.prod=https://h2.yidianling.com/
h5url.pre=https://preh.yidianling.com/
#java服务器api域名(投放系统)
javaurl.test = http://testdelivery.yidianling.com/
javaurl.auto_test = http://auto_testdelivery.yidianling.com/
javaurl.prod = https://delivery.yidianling.com/
#m站H5地址
mh5url.test = https://testnewm.ydl.com/
mh5url.auto_test = https://auto_testnewm.ydl.com/
mh5url.prod = https://m.ydl.com/
#java服务器api域名(优惠券)
javacouponurl.test = https://testapi.ydl.com/
javacouponurl.auto_test = https://auto_testapi.ydl.com/
javacouponurl.prod = https://api.ydl.com/
#java服务器api域名(h5耗时统计,域名问题后续服务端会统一)
javatempurl.test = https://47.97.49.44:8082/api/
javatempurl.prod = https://dc.ydl.com/api/
#java服务器api统一域名
javaapi.test = https://testapi.ydl.com/api/
javaapi.auto_test = https://auto_testapi.ydl.com/api/
javaapi.prod = https://api.ydl.com/api/
#android.enableBuildCache=true
#org.gradle.caching=true
#org.gradle.daemon=true
#org.gradle.jvmargs=-Xmx4096M
#systemProp.http.proxyHost=192.168.98.150
#systemProp.http.proxyPort=8123
org.gradle.jvmargs=-Xmx1024M
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
kapt {
arguments {
arg("AROUTER_MODULE_NAME", project.getName())
}
}
android {
compileSdkVersion 28
compileOptions {
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility JavaVersion.VERSION_1_8
}
defaultConfig {
applicationId "com.ydl.component"
minSdkVersion 17
targetSdkVersion 28
versionCode 1
versionName "1.0"
multiDexEnabled true
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
//统一维度
flavorDimensions "versionCode"
javaCompileOptions {
annotationProcessorOptions {
includeCompileClasspath true
arguments = [AROUTER_MODULE_NAME: project.getName(), AROUTER_GENERATE_DOC: "enable"]
}
}
}
compileOptions {
sourceCompatibility = '1.8'
targetCompatibility = '1.8'
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
debug {
versionNameSuffix "-debug"
signingConfig null
}
}
buildToolsVersion = '28.0.3'
//配置签名文件
signingConfigs {
ydl {
storeFile file("keystore.jks")
storePassword "123456"
keyAlias "ydl"
keyPassword "123456"
}
xlzx {
storeFile file("keystore.jks")
storePassword "123456"
keyAlias "ydl"
keyPassword "123456"
}
}
//多维度
//flavorDimensions "APP", "SERVER"
productFlavors {
ydl {
applicationId rootProject.ext.ydl_app["applicationId"]
versionName rootProject.ext.ydl_app["versionName"]
versionCode rootProject.ext.ydl_app["versionCode"]
//应用名
resValue "string", "app_name", rootProject.ext.ydl_app["appName"]
manifestPlaceholders = [
JPUSH_PKGNAME : applicationId,
JPUSH_APPKEY : "e4b0d0bb7ef01053c93e25ba", //JPush上注册的包名对应的appkey.
JPUSH_CHANNEL : "developer-default", //暂时填写默认值即可.
XIAOMI_APPKEY : "MI-5241743243980",//小米平台注册的appkey
XIAOMI_APPID : "MI-2882303761517432980",//小米平台注册的appid
HUAWEI_APPID : "10444675",//华为平台注册的appid
OPPO_APPKEY : "OP-afk71f35VogGw0w0wKsookksc", // OPPO平台注册的appkey
OPPO_APPID : "OP-3245516", // OPPO平台注册的appid
OPPO_APPSECRET: "OP-691184F044acA7a6851F578451f67616",//OPPO平台注册的appsecret
MEIZU_APPKEY : "MZ-00d268d7228748479036f202d45b4ef2",//魅族平台注册的appkey
MEIZU_APPID : "MZ-120344",//魅族平台注册的appid
qqappid : "1105070461",
APPLICATIONID : applicationId
]
//签名文件
signingConfig signingConfigs.ydl
}
xlzx {
applicationId rootProject.ext.xlzx_app["applicationId"]
versionName rootProject.ext.xlzx_app["versionName"]
versionCode rootProject.ext.xlzx_app["versionCode"]
//应用名
resValue "string", "app_name", rootProject.ext.xlzx_app["appName"]
manifestPlaceholders = [
JPUSH_PKGNAME : applicationId,
JPUSH_APPKEY : "e4b0d0bb7ef01053c93e25ba", //JPush上注册的包名对应的appkey.
JPUSH_CHANNEL : "developer-default", //暂时填写默认值即可.
XIAOMI_APPKEY : "MI-5241743243980",//小米平台注册的appkey
XIAOMI_APPID : "MI-2882303761517432980",//小米平台注册的appid
HUAWEI_APPID : "10444675",//华为平台注册的appid
OPPO_APPKEY : "OP-afk71f35VogGw0w0wKsookksc", // OPPO平台注册的appkey
OPPO_APPID : "OP-3245516", // OPPO平台注册的appid
OPPO_APPSECRET: "OP-691184F044acA7a6851F578451f67616",//OPPO平台注册的appsecret
MEIZU_APPKEY : "MZ-00d268d7228748479036f202d45b4ef2",//魅族平台注册的appkey
MEIZU_APPID : "MZ-120344",//魅族平台注册的appid
qqappid : "1105070461",
APPLICATIONID : applicationId
]
//签名文件
signingConfig signingConfigs.xlzx
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'com.android.support:appcompat-v7:28.0.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
kapt rootProject.ext.dependencies["dagger2-compiler"]
implementation project(':ydl-platform')
implementation project(':m-other')
implementation project(':m-user')
implementation rootProject.ext.dependencies["retrofit-url-manager"]
}
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
package com.ydl.component
import android.support.test.InstrumentationRegistry
import android.support.test.runner.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
val appContext = InstrumentationRegistry.getTargetContext()
assertEquals("com.ydl.component", appContext.packageName)
}
}
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" package="com.ydl.component">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<application
android:name="com.yidianling.ydlcommon.core.base.BaseApplication"
android:allowBackup="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
tools:replace="android:allowBackup, android:icon, android:label"
android:theme="@style/AppTheme"
tools:ignore="GoogleAppIndexingWarning">
<activity android:name=".MainActivity"
android:theme="@style/NoTitleTheme"
>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<meta-data
android:name="com.ydl.component.base.DemoGlobalConfig"
android:value="ModuleConfig"/>
</application>
</manifest>
\ No newline at end of file
package com.ydl.component
import com.alibaba.android.arouter.launcher.ARouter
import com.ydl.component.mvp.DemoContract
import com.ydl.component.mvp.DemoPresenter
import com.yidianling.ydlcommon.core.mvp.lce.BaseLceActivity
import kotlinx.android.synthetic.main.activity_main.*
/**
* Created by haorui on 2019-09-01 .
* Des:
*/
class MainActivity : BaseLceActivity<DemoContract.View, DemoContract.Presenter>(), DemoContract.View {
override fun setData(data: String) {
tv_content.text = data
}
override fun createPresenter(): DemoContract.Presenter {
return DemoPresenter()
}
override fun layoutResId(): Int {
return R.layout.activity_main
}
override fun initDataAndEvent() {
loadData()
tv_user.setOnClickListener {
loadData()
}
tv_home.setOnClickListener {
mPresenter?.loadHome()
}
bt_to_other.setOnClickListener {
ARouter.getInstance().build("/other/MainActivity").navigation()
}
}
override fun loadData() {
mPresenter?.loadUsers()
}
}
package com.ydl.component.api
import com.google.gson.JsonArray
import com.google.gson.JsonObject
import com.yidianling.ydlcommon.core.base.config.YDL_DOMAIN
import com.yidianling.ydlcommon.core.base.config.YDL_DOMAIN_JAVA
import io.reactivex.Observable
import retrofit2.http.GET
import retrofit2.http.Headers
import retrofit2.http.Query
/**
* Created by haorui on 2019-09-01 .
* Des:
*/
interface DemoService {
@Headers(HEADER_API_VERSION, YDL_DOMAIN+DOMAIN_GITHUB)
@GET("users")
fun getUsers(@Query("since") lastIdQueried: Int, @Query("per_page") perPage: Int): Observable<JsonArray>
@Headers( YDL_DOMAIN+ YDL_DOMAIN_JAVA)
@GET("home/index")
fun getHomeData(): Observable<JsonObject>
@GET("home/index")
fun getHome1Data(): Observable<JsonObject>
companion object {
const val HEADER_API_VERSION = "Accept: application/vnd.github.v3+json"
const val DOMAIN_GITHUB = "github"
}
}
package com.ydl.component.base;
import android.app.Application;
import android.content.Context;
import com.yidianling.ydlcommon.core.base.delegate.IAppLifecycles;
import org.jetbrains.annotations.NotNull;
/**
* Created by haorui on 2019-09-02.
* Des:
*/
public class DemoAppLifecycles implements IAppLifecycles {
@Override
public void attachBaseContext(@NotNull Context base) {
}
@Override
public void onCreate(@NotNull Application application) {
}
@Override
public void onTerminate(@NotNull Application application) {
}
}
package com.ydl.component.base;
import android.content.Context;
import com.ydl.component.BuildConfig;
import com.yidianling.ydlcommon.core.base.config.IConfigModule;
import com.yidianling.ydlcommon.core.base.delegate.IAppLifecycles;
import com.yidianling.ydlcommon.core.base.config.GlobalConfig;
import org.jetbrains.annotations.NotNull;
import java.util.List;
/**
* Created by haorui on 2019-09-01 .
* Des:
*/
public final class DemoGlobalConfig implements IConfigModule {
String APP_DOMAIN = "https://api.github.com/";
@Override
public void injectAppLifecycle(@NotNull Context context, @NotNull List<IAppLifecycles> lifecycles) {
lifecycles.add(new DemoAppLifecycles());
}
@Override
public void applyOptions(@NotNull Context context, @NotNull GlobalConfig.Builder builder) {
builder.setDebug(BuildConfig.DEBUG);
builder.addUrl("github", APP_DOMAIN);
}
}
package com.ydl.component.mvp
import com.google.gson.JsonArray
import com.google.gson.JsonObject
import com.yidianling.ydlcommon.core.mvp.base.IModel
import com.yidianling.ydlcommon.core.mvp.base.IPresenter
import com.yidianling.ydlcommon.core.mvp.lce.ILceView
import io.reactivex.Observable
/**
* Created by haorui on 2019-08-12.
* Des:
*/
interface DemoContract {
interface View : ILceView {
fun setData(data: String)
}
interface Presenter : IPresenter<View> {
fun loadUsers()
fun loadHome()
}
interface Model : IModel{
fun getUser(): Observable<JsonArray>
fun getHome(): Observable<JsonObject>
}
}
\ No newline at end of file
package com.ydl.component.mvp
import android.arch.lifecycle.Lifecycle
import android.arch.lifecycle.OnLifecycleEvent
import com.google.gson.JsonArray
import com.google.gson.JsonObject
import com.ydl.component.api.DemoService
import com.yidianling.ydlcommon.core.mvp.base.BaseModel
import com.ydl.ydlnet.YDLHttpUtils
import io.reactivex.Observable
/**
* Created by haorui on 2019-09-01 .
* Des:
*/
class DemoModel : BaseModel(), DemoContract.Model {
override fun getHome(): Observable<JsonObject> {
return YDLHttpUtils.obtainApi(DemoService::class.java)
.getHomeData()
}
override fun getUser(): Observable<JsonArray> {
return YDLHttpUtils.obtainApi(DemoService::class.java)
.getUsers(1, 10)
//使用接口缓存
.compose(YDLHttpUtils.transformCache("users"))
}
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
internal fun onPause() {
}
}
package com.ydl.component.mvp
import com.google.gson.JsonArray
import com.google.gson.JsonObject
import com.yidianling.ydlcommon.core.mvp.base.BasePresenter
import com.yidianling.ydlcommon.core.utils.RxLifecycleUtils
import com.ydl.ydlnet.builder.interceptor.YDLTransformer
import com.ydl.ydlnet.client.observer.CommonObserver
/**
* Created by haorui on 2019-09-01 .
* Des:
*/
class DemoPresenter : BasePresenter<DemoContract.Model, DemoContract.View>(),
DemoContract.Presenter {
override fun loadHome() = mModel!!.getHome()
.compose(YDLTransformer.switchSchedulers(mView))
.compose(RxLifecycleUtils.bindToLifecycle(mView!!))//使用 Rxlifecycle,使 Disposable 和 Activity 一起销毁
.subscribe(object : CommonObserver<JsonObject>() {
override fun onError(errorMsg: String) {
}
override fun onSuccess(users: JsonObject) {
var cateTitle = users.getAsJsonObject("data").getAsJsonArray("askCategoryData").get(0).asJsonObject.get("cateTitle").asString;
mView?.setData(cateTitle)
}
})
override fun loadUsers() = mModel!!.getUser()
.compose(YDLTransformer.switchSchedulers(mView))
.compose(RxLifecycleUtils.bindToLifecycle(mView!!))//使用 Rxlifecycle,使 Disposable 和 Activity 一起销毁
.subscribe(object : CommonObserver<JsonArray>() {
override fun onError(errorMsg: String) {
}
override fun onSuccess(users: JsonArray) {
var userName = users.get(0).asJsonObject.get("login").asString
mView?.setData(userName)
}
})
override fun createModel(): DemoContract.Model {
return DemoModel()
}
override fun destroy() {
super.destroy()
}
}
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportHeight="108"
android:viewportWidth="108">
<path
android:fillType="evenOdd"
android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
android:strokeColor="#00000000"
android:strokeWidth="1">
<aapt:attr name="android:fillColor">
<gradient
android:endX="78.5885"
android:endY="90.9159"
android:startX="48.7653"
android:startY="61.0927"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
android:strokeColor="#00000000"
android:strokeWidth="1" />
</vector>
<?xml version="1.0" encoding="utf-8"?>
<vector
android:height="108dp"
android:width="108dp"
android:viewportHeight="108"
android:viewportWidth="108"
xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#008577"
android:pathData="M0,0h108v108h-108z"/>
<path android:fillColor="#00000000" android:pathData="M9,0L9,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,0L19,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M29,0L29,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M39,0L39,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M49,0L49,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M59,0L59,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M69,0L69,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M79,0L79,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M89,0L89,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M99,0L99,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,9L108,9"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,19L108,19"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,29L108,29"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,39L108,39"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,49L108,49"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,59L108,59"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,69L108,69"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,79L108,79"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,89L108,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,99L108,99"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,29L89,29"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,39L89,39"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,49L89,49"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,59L89,59"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,69L89,69"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,79L89,79"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M29,19L29,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M39,19L39,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M49,19L49,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M59,19L59,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M69,19L69,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M79,19L79,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
</vector>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<LinearLayout
android:orientation="vertical"
android:id="@+id/lce_content_view"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:paddingLeft="30dp"
android:textSize="14dp"
android:gravity="center_vertical"
android:textColor="@color/white"
android:text="Multiple Base Url Request Test"
android:background="@color/google_blue"
android:layout_width="match_parent"
android:layout_height="50dp"/>
<TextView
android:layout_margin="30dp"
android:text="CONTENT"
android:id="@+id/tv_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<LinearLayout
android:layout_marginLeft="30dp"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="80dp"
>
<TextView
android:layout_marginRight="40dp"
android:background="@android:color/darker_gray"
android:padding="10dp"
android:id="@+id/tv_user"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="User Request!"
/>
<TextView
android:background="@android:color/holo_green_dark"
android:padding="10dp"
android:id="@+id/tv_home"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Home Request!"
/>
</LinearLayout>
<TextView
android:paddingLeft="30dp"
android:textSize="14dp"
android:gravity="center_vertical"
android:textColor="@color/white"
android:text="Component Module Test"
android:background="@color/google_blue"
android:layout_width="match_parent"
android:layout_height="50dp"/>
<Button
android:id="@+id/bt_to_other"
android:layout_marginTop="10dp"
android:layout_marginLeft="30dp"
android:text="Jump to Other"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
<com.yidianling.ydlcommon.core.mvp.lce.view.YDLStateView
android:id="@+id/lce_state_view"
android:layout_width="match_parent"
android:layout_height="300dp"/>
</LinearLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#008577</color>
<color name="colorPrimaryDark">#00574B</color>
<color name="colorAccent">#D81B60</color>
</resources>
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
</resources>
package com.ydl.component
import org.junit.Test
import org.junit.Assert.*
class ExampleUnitTest {
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}
apply from: "config.gradle"
buildscript {
ext.kotlin_version = '1.3.21'
ext {
kotlin_version = '1.3.21'
support_version = '26.1.0'
minSdkVersion = 17
targetSdkVersion = 28
compileSdkVersion = 28
buildToolsVersion = '28.0.3'
arouter_api = '1.4.1'
arouter_compiler = '1.2.2'
ydlrouter_version = '1.2.3'
constrait_support_version = '1.0.2'
}
repositories {
google()
jcenter()
maven { url "https://oss.sonatype.org/content/repositories/snapshots/" }
maven {
url 'https://dl.bintray.com/zouyuhan/maven'
}
}
dependencies {
classpath 'com.android.tools.build:gradle:3.0.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.jakewharton:butterknife-gradle-plugin:8.4.0'
classpath 'com.meituan.android.walle:plugin:1.1.5'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
classpath 'com.meituan.robust:gradle-plugin:0.4.87'
classpath 'com.meituan.robust:auto-patch-plugin:0.4.87'
//bugly 符号表
classpath 'com.tencent.bugly:symtabfileuploader:2.2.1'
//微信资源混淆
classpath 'com.tencent.mm:AndResGuard-gradle-plugin:1.2.16'
}
}
allprojects {
repositories {
google()
jcenter()
maven { url 'http://developer.huawei.com/repo/' }
maven { url "https://jitpack.io" }
maven {
url 'https://maven.google.com'
}
maven {
url 'https://dl.bintray.com/zouyuhan/maven'
}
//壹点灵android maven私服 开发版
maven {
url 'http://nexus.yidianling.com/repository/AndroidRepository/'
credentials {
username "admin"
password "fjoi#1+#@"
}
}
maven {
url 'http://nexus.yidianling.com/repository/AndroidReleases/'
credentials {
username "admin"
password "fjoi#1+#@"
}
}
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
subprojects {
project.configurations.all {
//防止support版本冲突,统一设置第三方support版本
resolutionStrategy.eachDependency { details ->
if (details.requested.group == 'com.android.support'
&& !details.requested.name.contains('multidex')) {
details.useVersion "$support_version"
}
}
}
}
This diff is collapsed. Click to expand it.
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx1536m
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official
#Thu Aug 08 14:35:29 CST 2019
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
#!/usr/bin/env sh
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
apply plugin: 'com.android.library'
android {
compileSdkVersion 28
defaultConfig {
minSdkVersion 14
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
javaCompileOptions {
annotationProcessorOptions {
arguments = [AROUTER_MODULE_NAME: project.getName()]
}
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
sourceSets {
main {
manifest.srcFile 'src/main/AndroidManifest.xml'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:28.0.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
api 'com.alibaba:arouter-api:1.4.1'
annotationProcessor 'com.alibaba:arouter-compiler:1.2.2'
}
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.ydl.other.api" >
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
</manifest>
package com.ydl.other;
/**
* Created by haorui on 2019-09-01 .
* Des:
*/
public class Other {
String name;
int id;
}
if (isApplicaiton.toBoolean()) {
apply plugin: 'com.android.application'
} else {
apply plugin: 'com.android.library'
}
apply from: "../pins.gradle"
android {
compileSdkVersion 28
defaultConfig {
if (isApplicaiton.toBoolean()) {
applicationId "com.ydl.other"
}
minSdkVersion 14
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
javaCompileOptions {
annotationProcessorOptions {
arguments = [AROUTER_MODULE_NAME: project.getName()]
}
}
flavorDimensions "versionCode"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
publishNonDefault true
productFlavors {
ydl {}
xlzx {}
}
sourceSets {
main {
if (isApplicaiton.toBoolean()) {
manifest.srcFile 'src/main/module/AndroidManifest.xml'
} else {
manifest.srcFile 'src/main/AndroidManifest.xml'
}
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:28.0.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
annotationProcessor 'com.alibaba:arouter-compiler:1.2.2'
if (isApplicaiton.toBoolean()) {
implementation project(':m-user')
}
implementation project(":m-user-api")
implementation project(":ydl-platform")
}
isApplicaiton = false
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.ydl.other">
<application>
<activity android:name=".MainActivity"></activity>
</application>
</manifest>
\ No newline at end of file
package com.ydl.other;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Toast;
import com.alibaba.android.arouter.facade.annotation.Autowired;
import com.alibaba.android.arouter.facade.annotation.Route;
import com.alibaba.android.arouter.launcher.ARouter;
import com.ydl.user.UserService;
/**
* Created by haorui on 2019-09-01 .
* Des:
*/
@Route(path = "/other/MainActivity")
public class MainActivity extends AppCompatActivity {
@Autowired//(name = "/user/UserService")
UserService mUserService;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ARouter.getInstance().inject(this);
setContentView(R.layout.other_activity_main);
//mUserService=ARouter.getInstance().navigation(UserService.class);
findViewById(R.id.btHello).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, "Hello:" + mUserService.getUser().getName(), Toast.LENGTH_SHORT).show();
}
});
}
}
package com.ydl.other;
/**
* Created by haorui on 2019-09-01 .
* Des:
*/
public class Other {
String name;
int id;
}
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.ydl.other">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/btHello"
android:text="Hello world"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
\ No newline at end of file
<resources>
<string name="app_name">other</string>
</resources>
package com.ydl;
import com.ydl.other.Other;
/**
* Created by haorui on 2019-09-02.
* Des:
*/
public class Test {
public static void main(){
new Other();
}
}
apply plugin: 'com.android.library'
android {
compileSdkVersion 28
defaultConfig {
minSdkVersion 14
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
javaCompileOptions {
annotationProcessorOptions {
arguments = [AROUTER_MODULE_NAME: project.getName()]
}
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
sourceSets {
main {
manifest.srcFile 'src/main/AndroidManifest.xml'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:28.0.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
api 'com.alibaba:arouter-api:1.4.1'
annotationProcessor 'com.alibaba:arouter-compiler:1.2.2'
}
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.ydl.user.api" >
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
</manifest>
package com.ydl.user;
/**
* Created by haorui on 2019-09-01 .
* Des:
*/
public class UserInfo {
String name;
public UserInfo(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
package com.ydl.user;
import com.alibaba.android.arouter.facade.template.IProvider;
/**
* Created by haorui on 2019-09-01 .
* Des:
*/
public interface UserService extends IProvider {
UserInfo getUser();
}
apply plugin: 'com.android.library'
android {
compileSdkVersion 28
defaultConfig {
minSdkVersion 14
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
javaCompileOptions {
annotationProcessorOptions {
arguments = [AROUTER_MODULE_NAME: project.getName()]
}
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:28.0.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
annotationProcessor 'com.alibaba:arouter-compiler:1.2.2'
implementation project(":m-user-api")
implementation project(":ydl-platform")
}
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.ydl.user" />
package com.ydl.user;
/**
* Created by haorui on 2019-09-01 .
* Des:
*/
public class UserInfo {
String name;
public UserInfo(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
package com.ydl.user;
import com.alibaba.android.arouter.facade.template.IProvider;
/**
* Created by haorui on 2019-09-01 .
* Des:
*/
public interface UserService extends IProvider {
UserInfo getUser();
}
package com.ydl.user;
import android.content.Context;
import com.alibaba.android.arouter.facade.annotation.Route;
/**
* Created by haorui on 2019-09-01 .
* Des:
*/
@Route(path = "/user/UserService")
public class UserServiceImpl implements UserService {
public UserServiceImpl() {
}
@Override
public UserInfo getUser() {
return new UserInfo("from user");
}
@Override
public void init(Context context) {
}
}
<resources>
<string name="app_name">user</string>
</resources>
/*
* pins 模块文件夹设置
*/
ext {
pins = [
"xlzx",
"ydl"
]
}
import com.android.manifmerger.ManifestMerger2
import com.android.manifmerger.MergingReport
import com.android.manifmerger.XmlDocument
import com.android.utils.ILogger
buildscript {
repositories {
jcenter()
google()
}
dependencies {
classpath "com.android.tools.build:manifest-merger:26.1.1"
}
}
def logger = new ILogger() {
@Override
void error(Throwable t, String msgFormat, Object... args) {
}
@Override
void warning(String msgFormat, Object... args) {
}
@Override
void info(String msgFormat, Object... args) {
}
@Override
void verbose(String msgFormat, Object... args) {
}
}
/**
* 合并Manifes文件
*/
def mergeManifest = { ->
File mainManifestFile = new File("$projectDir/src/main/AndroidManifest.xml")
ManifestMerger2.MergeType mergeType = ManifestMerger2.MergeType.APPLICATION
XmlDocument.Type documentType = XmlDocument.Type.MAIN
ManifestMerger2.Invoker invoker = new ManifestMerger2.Invoker(mainManifestFile, logger, mergeType, documentType)
pins.each { pinName ->
File microManifestFile = new File("$projectDir/src/$pinName/AndroidManifest.xml")
if (microManifestFile.exists()) {
//合并 AndroidManifest.xml
invoker.addLibraryManifest(microManifestFile)
} else {
//没有 AndroidManifest.xml 不需要合并
}
}
def mergingReport = invoker.merge()
def moduleAndroidManifest = mergingReport.getMergedDocument(MergingReport.MergedManifestKind.MERGED)
new File("$buildDir").mkdirs()
def file = new File("$buildDir/AndroidManifest.xml")
file.createNewFile()
file.write(moduleAndroidManifest)
}
/**
* 创建pins文件夹
*/
task createPinsDirectory {
pins.each { pinName ->
if (file("$projectDir/src/${pinName}").exists()) {
//目录已经存在
} else {
//创建目录
String packageDir = rootProject.ext.android.applicationId.replace(".", "/")
// 创建java目录
new File("$projectDir/src/${pinName}/java/" + packageDir).mkdirs()
// 创建资源文件目录
new File("$projectDir/src/${pinName}/res").mkdirs()
}
}
}
task mergeManifestTask {
mergeManifest()
}
preBuild.doFirst {
mergeManifest()
}
preBuild.dependsOn(createPinsDirectory)
\ No newline at end of file
def includeWithApi(String moduleName) {
//先正常加载这个模块
include(moduleName)
//找到这个模块的路径
String originDir = project(moduleName).projectDir
//这个是新的路径
String targetDir = "${originDir}-api"
//原模块的名字
String originName=project(moduleName).name;
//新模块的名字
def sdkName = "${originName}-api"
//这个是公共模块的位置,预先放了一个 新建的api.gradle 文件进去
String apiGradle = project(":ydl-platform").projectDir
// 每次编译删除之前的文件
deleteDir(targetDir)
//复制.api文件到新的路径
copy() {
from originDir
into targetDir
exclude '**/build/'
exclude '**/res/'
include '**/*.api'
}
//直接复制公共模块的AndroidManifest文件到新的路径,作为该模块的文件
copy() {
from "${apiGradle}/template/AndroidManifest.xml"
into "${targetDir}/src/main/"
}
//复制 gradle文件到新的路径,作为该模块的gradle
copy() {
from "${apiGradle}/template/template.gradle"
into "${targetDir}/"
}
//删除空文件夹
deleteEmptyDir(new File(targetDir))
//为AndroidManifest新建路径,路径就是在原来的包下面新建一个api包,作为AndroidManifest里面的包名
String packagePath = "${targetDir}/src/main/java/com/ydl/${originName.replaceAll("m-","")}/api";
//修改AndroidManifest文件包路径
fileReader("${targetDir}/src/main/AndroidManifest.xml", "template","${originName.replaceAll("m-","")}.api");
new File(packagePath).mkdirs()
//重命名一下gradle
def build = new File(targetDir + "/template.gradle")
if (build.exists()) {
build.renameTo(new File(targetDir + "/build.gradle"))
}
// 重命名.api文件,生成正常的.java文件
renameApiFiles(targetDir, '.api', '.java')
//正常加载新的模块
include ":$sdkName"
}
private void deleteEmptyDir(File dir) {
if (dir.isDirectory()) {
File[] fs = dir.listFiles();
if (fs != null && fs.length > 0) {
for (int i = 0; i < fs.length; i++) {
File tmpFile = fs[i];
if (tmpFile.isDirectory()) {
deleteEmptyDir(tmpFile);
}
if (tmpFile.isDirectory() && tmpFile.listFiles().length <= 0) {
tmpFile.delete();
}
}
}
if (dir.isDirectory() && dir.listFiles().length == 0) {
dir.delete();
}
}
}
private void deleteDir(String targetDir) {
FileTree targetFiles = fileTree(targetDir)
targetFiles.exclude "*.iml"
targetFiles.each { File file ->
file.delete()
}
}
/**
* rename api files(java, kotlin...)
*/
private def renameApiFiles(root_dir, String suffix, String replace) {
FileTree files = fileTree(root_dir).include("**/*$suffix")
files.each {
File file ->
file.renameTo(new File(file.absolutePath.replace(suffix, replace)))
}
}
//替换AndroidManifest里面的字段
def fileReader(path, name,sdkName) {
def readerString = "";
def hasReplace = false
file(path).withReader('UTF-8') { reader ->
reader.eachLine {
if (it.find(name)) {
it = it.replace(name, sdkName)
hasReplace = true
}
readerString <<= it
readerString << '\n'
}
if (hasReplace) {
file(path).withWriter('UTF-8') {
within ->
within.append(readerString)
}
}
return readerString
}
}
include ':app', ':ydl-net', ':ydl-utils', ':ydl-platform'
includeWithApi ":m-user"
includeWithApi ":m-other"
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
android {
compileSdkVersion 28
defaultConfig {
minSdkVersion 17
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility = '1.8'
targetCompatibility = '1.8'
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'com.android.support:appcompat-v7:28.0.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
api 'com.squareup.retrofit2:retrofit:2.6.0'
api 'com.squareup.retrofit2:converter-gson:2.6.0'
api 'com.squareup.retrofit2:adapter-rxjava2:2.6.0'
api "io.reactivex.rxjava2:rxjava:2.2.10"
api 'io.reactivex.rxjava2:rxandroid:2.1.1'
api 'com.squareup.retrofit2:converter-scalars:2.4.0'
api 'com.squareup.okhttp3:logging-interceptor:3.12.2'
api rootProject.ext.dependencies["gson"]
}
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
package com.ydl.ydlnet;
import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getTargetContext();
assertEquals("com.ydl.ydlnet2.test", appContext.getPackageName());
}
}
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.ydl.ydlnet"/>
package com.ydl.ydlnet
import android.annotation.SuppressLint
import android.content.Context
import com.google.gson.reflect.TypeToken
import com.ydl.ydlnet.builder.config.OkHttpConfig
import com.ydl.ydlnet.builder.cookie.CookieJarImpl
import com.ydl.ydlnet.builder.cookie.store.CookieStore
import com.ydl.ydlnet.builder.factory.ApiFactory
import com.ydl.ydlnet.cache.RxCache
import com.ydl.ydlnet.cache.data.CacheResult
import com.ydl.ydlnet.cache.stategy.CacheStrategy
import com.ydl.ydlnet.client.download.DownloadHelper
import com.ydl.ydlnet.client.upload.UploadHelper
import io.reactivex.Observable
import io.reactivex.ObservableTransformer
import okhttp3.Cookie
import okhttp3.HttpUrl
import okhttp3.ResponseBody
/**
* Created by haorui on 2019-09-02 .
* Des: YDL 网络请求工具类
*/
class YDLHttpUtils {
/**
* 必须在全局Application先调用,获取context上下文,否则缓存无法使用
*
* @param app Application
*/
fun init(app: Context): YDLHttpUtils {
context = app
return this
}
fun config(): ApiFactory {
checkInitialize()
return ApiFactory.getInstance()
}
companion object {
@SuppressLint("StaticFieldLeak")
private var instance: YDLHttpUtils? = null
@SuppressLint("StaticFieldLeak")
private var context: Context? = null
fun getInstance(): YDLHttpUtils {
if (instance == null) {
synchronized(YDLHttpUtils::class.java) {
if (instance == null) {
instance = YDLHttpUtils()
}
}
}
return instance!!
}
/**
* 获取全局上下文
*/
fun getContext(): Context? {
checkInitialize()
return context
}
/**
* 检测是否调用初始化方法
*/
private fun checkInitialize() {
if (context == null) {
throw ExceptionInInitializerError("请先在全局Application中调用 YDLHttpUtils.getInstance().init(this) 初始化!")
}
}
/**
* 使用全局参数创建请求
*
* @param cls Class
* @param <K> K
* @return 返回
</K> */
fun <K> obtainApi(cls: Class<K>): K {
return ApiFactory.getInstance().createApi(cls)
}
/**
* 下载文件
*
* @param fileUrl 地址
* @return ResponseBody
*/
fun downloadFile(fileUrl: String): Observable<ResponseBody> {
return DownloadHelper.downloadFile(fileUrl)
}
/**
* 上传单张图片
*
* @param uploadUrl 地址
* @param filePath 文件路径
* @return ResponseBody
*/
fun uploadImg(uploadUrl: String, filePath: String): Observable<ResponseBody> {
return UploadHelper.uploadImage(uploadUrl, filePath)
}
/**
* 上传多张图片
*
* @param uploadUrl 地址
* @param filePaths 文件路径
* @return ResponseBody
*/
fun uploadImages(uploadUrl: String, filePaths: List<String>): Observable<ResponseBody> {
return UploadHelper.uploadImages(uploadUrl, filePaths)
}
/**
* 上传多张图片
*
* @param uploadUrl 地址
* @param fileName 后台接收文件流的参数名
* @param paramsMap 参数
* @param filePaths 文件路径
* @return ResponseBody
*/
fun uploadImagesWithParams(
uploadUrl: String,
fileName: String,
paramsMap: Map<String, Any>,
filePaths: List<String>
): Observable<ResponseBody> {
return UploadHelper.uploadFilesWithParams(uploadUrl, fileName, paramsMap, filePaths)
}
/**
* 获取全局的CookieJarImpl实例
*/
private val cookieJar: CookieJarImpl
get() = OkHttpConfig.getInstance().okHttpClient.cookieJar() as CookieJarImpl
/**
* 获取全局的CookieStore实例
*/
private val cookieStore: CookieStore
get() = cookieJar.cookieStore
/**
* 获取所有cookie
*/
val allCookie: List<Cookie>
get() {
val cookieStore = cookieStore
return cookieStore.allCookie
}
/**
* 获取某个url所对应的全部cookie
*/
fun getCookieByUrl(url: String): List<Cookie> {
val cookieStore = cookieStore
val httpUrl = HttpUrl.parse(url)
return cookieStore.getCookie(httpUrl)
}
/**
* 移除全部cookie
*/
fun removeAllCookie() {
val cookieStore = cookieStore
cookieStore.removeAllCookie()
}
/**
* 移除某个url下的全部cookie
*/
fun removeCookieByUrl(url: String) {
val httpUrl = HttpUrl.parse(url)
val cookieStore = cookieStore
cookieStore.removeCookie(httpUrl)
}
/**
* 需要缓存数据时使用此方法,strategy缓存规则默认优先网络
*/
inline fun <reified T> transformCache(key: String): ObservableTransformer<T, T> {
return ObservableTransformer {
it.compose<CacheResult<T>>(RxCache.getDefault().transformObservable(key, object : TypeToken<T>() {}.type, CacheStrategy.firstRemote()))
.map { response ->
response.data
}
}
}
}
}
/*
* Copyright 2016 jeasonlzy(廖子尧)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.ydl.ydlnet.builder.cookie;
import com.ydl.ydlnet.builder.cookie.store.CookieStore;
import okhttp3.Cookie;
import okhttp3.CookieJar;
import okhttp3.HttpUrl;
import java.util.List;
/**
* ================================================
* 作 者:jeasonlzy(廖子尧)Github地址:https://github.com/jeasonlzy
* 版 本:1.0
* 创建日期:2016/1/12
* 描 述:CookieJar的实现类,默认管理了用户自己维护的cookie
* 修订历史:
* ================================================
*/
public class CookieJarImpl implements CookieJar {
private CookieStore cookieStore;
public CookieJarImpl(CookieStore cookieStore) {
if (cookieStore == null) {
throw new IllegalArgumentException("cookieStore can not be null!");
}
this.cookieStore = cookieStore;
}
@Override
public synchronized void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
cookieStore.saveCookie(url, cookies);
}
@Override
public synchronized List<Cookie> loadForRequest(HttpUrl url) {
return cookieStore.loadCookie(url);
}
public CookieStore getCookieStore() {
return cookieStore;
}
}
/*
* Copyright 2016 jeasonlzy(廖子尧)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.ydl.ydlnet.builder.cookie;
import android.content.ContentValues;
import android.database.Cursor;
import okhttp3.Cookie;
import java.io.*;
import java.util.Locale;
/**
* ================================================
* 作 者:jeasonlzy(廖子尧)Github地址:https://github.com/jeasonlzy
* 版 本:1.0
* 创建日期:16/9/11
* 描 述:
* 修订历史:
* ================================================
*/
public class SerializableCookie implements Serializable {
private static final long serialVersionUID = 6374381323722046732L;
public static final String HOST = "host";
public static final String NAME = "name";
public static final String DOMAIN = "domain";
public static final String COOKIE = "cookie";
public String host;
public String name;
public String domain;
private transient Cookie cookie;
private transient Cookie clientCookie;
public SerializableCookie(String host, Cookie cookie) {
this.cookie = cookie;
this.host = host;
this.name = cookie.name();
this.domain = cookie.domain();
}
public Cookie getCookie() {
Cookie bestCookie = cookie;
if (clientCookie != null) {
bestCookie = clientCookie;
}
return bestCookie;
}
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject();
out.writeObject(cookie.name());
out.writeObject(cookie.value());
out.writeLong(cookie.expiresAt());
out.writeObject(cookie.domain());
out.writeObject(cookie.path());
out.writeBoolean(cookie.secure());
out.writeBoolean(cookie.httpOnly());
out.writeBoolean(cookie.hostOnly());
out.writeBoolean(cookie.persistent());
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
String name = (String) in.readObject();
String value = (String) in.readObject();
long expiresAt = in.readLong();
String domain = (String) in.readObject();
String path = (String) in.readObject();
boolean secure = in.readBoolean();
boolean httpOnly = in.readBoolean();
boolean hostOnly = in.readBoolean();
boolean persistent = in.readBoolean();
Cookie.Builder builder = new Cookie.Builder();
builder = builder.name(name);
builder = builder.value(value);
builder = builder.expiresAt(expiresAt);
builder = hostOnly ? builder.hostOnlyDomain(domain) : builder.domain(domain);
builder = builder.path(path);
builder = secure ? builder.secure() : builder;
builder = httpOnly ? builder.httpOnly() : builder;
clientCookie = builder.build();
}
public static SerializableCookie parseCursorToBean(Cursor cursor) {
String host = cursor.getString(cursor.getColumnIndex(HOST));
byte[] cookieBytes = cursor.getBlob(cursor.getColumnIndex(COOKIE));
Cookie cookie = bytesToCookie(cookieBytes);
return new SerializableCookie(host, cookie);
}
public static ContentValues getContentValues(SerializableCookie serializableCookie) {
ContentValues values = new ContentValues();
values.put(SerializableCookie.HOST, serializableCookie.host);
values.put(SerializableCookie.NAME, serializableCookie.name);
values.put(SerializableCookie.DOMAIN, serializableCookie.domain);
values.put(SerializableCookie.COOKIE, cookieToBytes(serializableCookie.host, serializableCookie.getCookie()));
return values;
}
/**
* cookies 序列化成 string
*
* @param cookie 要序列化
* @return 序列化之后的string
*/
public static String encodeCookie(String host, Cookie cookie) {
if (cookie == null) return null;
byte[] cookieBytes = cookieToBytes(host, cookie);
return byteArrayToHexString(cookieBytes);
}
public static byte[] cookieToBytes(String host, Cookie cookie) {
SerializableCookie serializableCookie = new SerializableCookie(host, cookie);
ByteArrayOutputStream os = new ByteArrayOutputStream();
try {
ObjectOutputStream outputStream = new ObjectOutputStream(os);
outputStream.writeObject(serializableCookie);
} catch (IOException e) {
e.printStackTrace();
return null;
}
return os.toByteArray();
}
/**
* 将字符串反序列化成cookies
*
* @param cookieString cookies string
* @return cookie object
*/
public static Cookie decodeCookie(String cookieString) {
byte[] bytes = hexStringToByteArray(cookieString);
return bytesToCookie(bytes);
}
public static Cookie bytesToCookie(byte[] bytes) {
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
Cookie cookie = null;
try {
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
cookie = ((SerializableCookie) objectInputStream.readObject()).getCookie();
} catch (Exception e) {
e.printStackTrace();
}
return cookie;
}
/**
* 二进制数组转十六进制字符串
*
* @param bytes byte array to be converted
* @return string containing hex values
*/
private static String byteArrayToHexString(byte[] bytes) {
StringBuilder sb = new StringBuilder(bytes.length * 2);
for (byte element : bytes) {
int v = element & 0xff;
if (v < 16) {
sb.append('0');
}
sb.append(Integer.toHexString(v));
}
return sb.toString().toUpperCase(Locale.US);
}
/**
* 十六进制字符串转二进制数组
*
* @param hexString string of hex-encoded values
* @return decoded byte array
*/
private static byte[] hexStringToByteArray(String hexString) {
int len = hexString.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4) + Character.digit(hexString.charAt(i + 1), 16));
}
return data;
}
/**
* host, name, domain 标识一个cookie是否唯一
*/
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
SerializableCookie that = (SerializableCookie) o;
if (host != null ? !host.equals(that.host) : that.host != null) return false;
if (name != null ? !name.equals(that.name) : that.name != null) return false;
return domain != null ? domain.equals(that.domain) : that.domain == null;
}
@Override
public int hashCode() {
int result = host != null ? host.hashCode() : 0;
result = 31 * result + (name != null ? name.hashCode() : 0);
result = 31 * result + (domain != null ? domain.hashCode() : 0);
return result;
}
}
/*
* Copyright 2016 jeasonlzy(廖子尧)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.ydl.ydlnet.builder.cookie.store;
import okhttp3.Cookie;
import okhttp3.HttpUrl;
import java.util.List;
/**
* ================================================
* 作 者:jeasonlzy(廖子尧)Github地址:https://github.com/jeasonlzy
* 版 本:1.0
* 创建日期:2016/1/14
* 描 述:CookieStore 的公共接口
* 修订历史:
* ================================================
*/
public interface CookieStore {
/** 保存url对应所有cookie */
void saveCookie(HttpUrl url, List<Cookie> cookie);
/** 保存url对应所有cookie */
void saveCookie(HttpUrl url, Cookie cookie);
/** 加载url所有的cookie */
List<Cookie> loadCookie(HttpUrl url);
/** 获取当前所有保存的cookie */
List<Cookie> getAllCookie();
/** 获取当前url对应的所有的cookie */
List<Cookie> getCookie(HttpUrl url);
/** 根据url和cookie移除对应的cookie */
boolean removeCookie(HttpUrl url, Cookie cookie);
/** 根据url移除所有的cookie */
boolean removeCookie(HttpUrl url);
/** 移除所有的cookie */
boolean removeAllCookie();
}
/*
* Copyright 2016 jeasonlzy(廖子尧)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.ydl.ydlnet.builder.cookie.store;
import okhttp3.Cookie;
import okhttp3.HttpUrl;
import java.util.*;
/**
* ================================================
* 作 者:jeasonlzy(廖子尧)Github地址:https://github.com/jeasonlzy
* 版 本:1.0
* 创建日期:2016/1/14
* 描 述:Cookie 的内存管理
* 修订历史:
* ================================================
*/
public class MemoryCookieStore implements CookieStore {
private final Map<String, List<Cookie>> memoryCookies = new HashMap<>();
@Override
public synchronized void saveCookie(HttpUrl url, List<Cookie> cookies) {
List<Cookie> oldCookies = memoryCookies.get(url.host());
List<Cookie> needRemove = new ArrayList<>();
for (Cookie newCookie : cookies) {
for (Cookie oldCookie : oldCookies) {
if (newCookie.name().equals(oldCookie.name())) {
needRemove.add(oldCookie);
}
}
}
oldCookies.removeAll(needRemove);
oldCookies.addAll(cookies);
}
@Override
public synchronized void saveCookie(HttpUrl url, Cookie cookie) {
List<Cookie> cookies = memoryCookies.get(url.host());
List<Cookie> needRemove = new ArrayList<>();
for (Cookie item : cookies) {
if (cookie.name().equals(item.name())) {
needRemove.add(item);
}
}
cookies.removeAll(needRemove);
cookies.add(cookie);
}
@Override
public synchronized List<Cookie> loadCookie(HttpUrl url) {
List<Cookie> cookies = memoryCookies.get(url.host());
if (cookies == null) {
cookies = new ArrayList<>();
memoryCookies.put(url.host(), cookies);
}
return cookies;
}
@Override
public synchronized List<Cookie> getAllCookie() {
List<Cookie> cookies = new ArrayList<>();
Set<String> httpUrls = memoryCookies.keySet();
for (String url : httpUrls) {
cookies.addAll(memoryCookies.get(url));
}
return cookies;
}
@Override
public List<Cookie> getCookie(HttpUrl url) {
List<Cookie> cookies = new ArrayList<>();
List<Cookie> urlCookies = memoryCookies.get(url.host());
if (urlCookies != null) cookies.addAll(urlCookies);
return cookies;
}
@Override
public synchronized boolean removeCookie(HttpUrl url, Cookie cookie) {
List<Cookie> cookies = memoryCookies.get(url.host());
return (cookie != null) && cookies.remove(cookie);
}
@Override
public synchronized boolean removeCookie(HttpUrl url) {
return memoryCookies.remove(url.host()) != null;
}
@Override
public synchronized boolean removeAllCookie() {
memoryCookies.clear();
return true;
}
}
/*
* Copyright 2016 jeasonlzy(廖子尧)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.ydl.ydlnet.builder.cookie.store;
import android.content.Context;
import android.content.SharedPreferences;
import android.text.TextUtils;
import com.ydl.ydlnet.builder.cookie.SerializableCookie;
import okhttp3.Cookie;
import okhttp3.HttpUrl;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* ================================================
* 作 者:jeasonlzy(廖子尧)Github地址:https://github.com/jeasonlzy
* 版 本:1.0
* 创建日期:2016/1/14
* 描 述:使用 SharedPreferences 持久化存储 cookie
* 修订历史:
* ================================================
*/
public class SPCookieStore implements CookieStore {
private static final String COOKIE_PREFS = "rx_http_utils_cookie"; //cookie使用prefs保存
private static final String COOKIE_NAME_PREFIX = "cookie_"; //cookie持久化的统一前缀
/**
* 数据结构如下
* Url.host -> cookieToken1,cookieToken2,cookieToken3
* cookie_cookieToken1 -> cookie1
* cookie_cookieToken2 -> cookie2
* cookie_cookieToken3 -> cookie3
*/
private final Map<String, ConcurrentHashMap<String, Cookie>> cookies;
private final SharedPreferences cookiePrefs;
public SPCookieStore(Context context) {
cookiePrefs = context.getSharedPreferences(COOKIE_PREFS, Context.MODE_PRIVATE);
cookies = new HashMap<>();
//将持久化的cookies缓存到内存中,数据结构为 Map<Url.host, Map<CookieToken, Cookie>>
Map<String, ?> prefsMap = cookiePrefs.getAll();
for (Map.Entry<String, ?> entry : prefsMap.entrySet()) {
if ((entry.getValue()) != null && !entry.getKey().startsWith(COOKIE_NAME_PREFIX)) {
//获取url对应的所有cookie的key,用","分割
String[] cookieNames = TextUtils.split((String) entry.getValue(), ",");
for (String name : cookieNames) {
//根据对应cookie的Key,从xml中获取cookie的真实值
String encodedCookie = cookiePrefs.getString(COOKIE_NAME_PREFIX + name, null);
if (encodedCookie != null) {
Cookie decodedCookie = SerializableCookie.decodeCookie(encodedCookie);
if (decodedCookie != null) {
if (!cookies.containsKey(entry.getKey())) {
cookies.put(entry.getKey(), new ConcurrentHashMap<String, Cookie>());
}
cookies.get(entry.getKey()).put(name, decodedCookie);
}
}
}
}
}
}
private String getCookieToken(Cookie cookie) {
return cookie.name() + "@" + cookie.domain();
}
/** 当前cookie是否过期 */
private static boolean isCookieExpired(Cookie cookie) {
return cookie.expiresAt() < System.currentTimeMillis();
}
/** 将url的所有Cookie保存在本地 */
@Override
public synchronized void saveCookie(HttpUrl url, List<Cookie> urlCookies) {
for (Cookie cookie : urlCookies) {
saveCookie(url, cookie);
}
}
@Override
public synchronized void saveCookie(HttpUrl url, Cookie cookie) {
if (!cookies.containsKey(url.host())) {
cookies.put(url.host(), new ConcurrentHashMap<String, Cookie>());
}
//当前cookie是否过期
if (isCookieExpired(cookie)) {
removeCookie(url, cookie);
} else {
saveCookie(url, cookie, getCookieToken(cookie));
}
}
/** 保存cookie,并将cookies持久化到本地 */
private void saveCookie(HttpUrl url, Cookie cookie, String cookieToken) {
//内存缓存
cookies.get(url.host()).put(cookieToken, cookie);
//文件缓存
SharedPreferences.Editor prefsWriter = cookiePrefs.edit();
prefsWriter.putString(url.host(), TextUtils.join(",", cookies.get(url.host()).keySet()));
prefsWriter.putString(COOKIE_NAME_PREFIX + cookieToken, SerializableCookie.encodeCookie(url.host(), cookie));
prefsWriter.apply();
}
/** 根据当前url获取所有需要的cookie,只返回没有过期的cookie */
@Override
public synchronized List<Cookie> loadCookie(HttpUrl url) {
List<Cookie> ret = new ArrayList<>();
if (!cookies.containsKey(url.host())) return ret;
Collection<Cookie> urlCookies = cookies.get(url.host()).values();
for (Cookie cookie : urlCookies) {
if (isCookieExpired(cookie)) {
removeCookie(url, cookie);
} else {
ret.add(cookie);
}
}
return ret;
}
/** 根据url移除当前的cookie */
@Override
public synchronized boolean removeCookie(HttpUrl url, Cookie cookie) {
if (!cookies.containsKey(url.host())) return false;
String cookieToken = getCookieToken(cookie);
if (!cookies.get(url.host()).containsKey(cookieToken)) return false;
//内存移除
cookies.get(url.host()).remove(cookieToken);
//文件移除
SharedPreferences.Editor prefsWriter = cookiePrefs.edit();
if (cookiePrefs.contains(COOKIE_NAME_PREFIX + cookieToken)) {
prefsWriter.remove(COOKIE_NAME_PREFIX + cookieToken);
}
prefsWriter.putString(url.host(), TextUtils.join(",", cookies.get(url.host()).keySet()));
prefsWriter.apply();
return true;
}
@Override
public synchronized boolean removeCookie(HttpUrl url) {
if (!cookies.containsKey(url.host())) return false;
//内存移除
ConcurrentHashMap<String, Cookie> urlCookie = cookies.remove(url.host());
//文件移除
Set<String> cookieTokens = urlCookie.keySet();
SharedPreferences.Editor prefsWriter = cookiePrefs.edit();
for (String cookieToken : cookieTokens) {
if (cookiePrefs.contains(COOKIE_NAME_PREFIX + cookieToken)) {
prefsWriter.remove(COOKIE_NAME_PREFIX + cookieToken);
}
}
prefsWriter.remove(url.host());
prefsWriter.apply();
return true;
}
@Override
public synchronized boolean removeAllCookie() {
//内存移除
cookies.clear();
//文件移除
SharedPreferences.Editor prefsWriter = cookiePrefs.edit();
prefsWriter.clear();
prefsWriter.apply();
return true;
}
/** 获取所有的cookie */
@Override
public synchronized List<Cookie> getAllCookie() {
List<Cookie> ret = new ArrayList<>();
for (String key : cookies.keySet()) {
ret.addAll(cookies.get(key).values());
}
return ret;
}
@Override
public synchronized List<Cookie> getCookie(HttpUrl url) {
List<Cookie> ret = new ArrayList<>();
Map<String, Cookie> mapCookie = cookies.get(url.host());
if (mapCookie != null) ret.addAll(mapCookie.values());
return ret;
}
}
package com.ydl.ydlnet.builder.factory;
import android.support.annotation.Nullable;
import com.ydl.ydlnet.builder.manage.HttpUrlManager;
import com.ydl.ydlnet.builder.retrofit.RetrofitBuilder;
import com.ydl.ydlnet.cache.utils.LruCache;
import io.reactivex.Observable;
import io.reactivex.Single;
import okhttp3.OkHttpClient;
import retrofit2.CallAdapter;
import retrofit2.Converter;
import retrofit2.Retrofit;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
/**
* Created by haorui on 2019-09-02 .
* Des: API工厂类
*/
public class ApiFactory {
private volatile static ApiFactory instance;
/**
* 缓存retrofit Api Service对象
*/
private static LruCache<String, Object> retrofitServiceCache;
private CallAdapter.Factory[] callAdapterFactory;
private Converter.Factory[] converterFactory;
private OkHttpClient okHttpClient;
private Retrofit retrofit;
public static ApiFactory getInstance() {
if (instance == null) {
synchronized (ApiFactory.class) {
if (instance == null) {
instance = new ApiFactory();
}
}
}
return instance;
}
private ApiFactory() {
retrofitServiceCache = new LruCache<>(15);
}
/**
* 清空所有api缓存(用于切换环境时候使用)
*/
public void clearAllApi() {
retrofitServiceCache.clear();
}
public ApiFactory setCallAdapterFactory(CallAdapter.Factory... callAdapterFactory) {
this.callAdapterFactory = callAdapterFactory;
return this;
}
public ApiFactory setConverterFactory(Converter.Factory... converterFactory) {
this.converterFactory = converterFactory;
return this;
}
public ApiFactory setOkClient(OkHttpClient okHttpClient) {
this.okHttpClient = okHttpClient;
return this;
}
public ApiFactory setBaseUrl(String baseUrl) {
HttpUrlManager.Companion.getInstance().setUrl(baseUrl);
return this;
}
public ApiFactory addUrl(String domainName, String url) {
HttpUrlManager.Companion.getInstance().addUrl(domainName,url);
return this;
}
public ApiFactory setMultipleUrlMap(HashMap<String, String> multipleUrlMap) {
HttpUrlManager.Companion.getInstance().setMultipleUrl(multipleUrlMap);
return this;
}
public boolean isMultipleUrl() {
return HttpUrlManager.Companion.getInstance().getMDomainNameHub().size()>1;
}
public <A> A createApi(Class<A> apiClass) {
return createWrapperService(apiClass);
}
public <A> A createApi(Class<A> apiClass,OkHttpClient client) {
return getRetrofitService(apiClass,client);
}
/**
* 根据 https://zhuanlan.zhihu.com/p/40097338 对 Retrofit 进行的优化
*
* @param serviceClass ApiService class
* @param <T> ApiService class
* @return ApiService
*/
private <T> T createWrapperService(Class<T> serviceClass) {
// 二次代理
return (T) Proxy.newProxyInstance(serviceClass.getClassLoader(),
new Class<?>[]{serviceClass}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, @Nullable Object[] args)
throws Throwable {
// 此处在调用 serviceClass 中的方法时触发
if (method.getReturnType() == Observable.class) {
// 如果方法返回值是 Observable 的话,则包一层再返回,
// 只包一层 defer 由外部去控制耗时方法以及网络请求所处线程,
// 如此对原项目的影响为 0,且更可控。
return Observable.defer(() -> {
final T service = getRetrofitService(serviceClass);
// 执行真正的 Retrofit 动态代理的方法
return ((Observable) getRetrofitMethod(service, method)
.invoke(service, args));
});
} else if (method.getReturnType() == Single.class) {
// 如果方法返回值是 Single 的话,则包一层再返回。
return Single.defer(() -> {
final T service = getRetrofitService(serviceClass);
// 执行真正的 Retrofit 动态代理的方法
return ((Single) getRetrofitMethod(service, method)
.invoke(service, args));
});
}
// 返回值不是 Observable 或 Single 的话不处理。
final T service = getRetrofitService(serviceClass);
return getRetrofitMethod(service, method).invoke(service, args);
}
});
}
private <T> T getRetrofitService(Class<T> serviceClass) {
String apiKey = getApiKey(serviceClass);
T retrofitService = (T) retrofitServiceCache.get(apiKey);
if (retrofitService == null) {
if(retrofit==null) {
String defaultBaseUrl = HttpUrlManager.Companion.getInstance().getUrl();
retrofit = new RetrofitBuilder()
.setBaseUrl(defaultBaseUrl)
.setCallAdapterFactory(callAdapterFactory)
.setConverterFactory(converterFactory)
.setOkHttpClient(okHttpClient)
.build();
}
retrofitService = retrofit.create(serviceClass);
retrofitServiceCache.put(apiKey,retrofitService);
}
return getRetrofitService(serviceClass,okHttpClient);
}
/**
* 自定义 okHttpClient
* @param serviceClass
* @param okHttpClient
* @param <T>
* @return
*/
private <T> T getRetrofitService(Class<T> serviceClass,OkHttpClient okHttpClient) {
String apiKey = getApiKey(serviceClass);
T retrofitService = (T) retrofitServiceCache.get(apiKey);
if (retrofitService == null) {
String defaultBaseUrl = HttpUrlManager.Companion.getInstance().getUrl();
Retrofit retrofit = new RetrofitBuilder()
.setBaseUrl(defaultBaseUrl)
.setCallAdapterFactory(callAdapterFactory)
.setConverterFactory(converterFactory)
.setOkHttpClient(okHttpClient)
.build();
retrofitService = retrofit.create(serviceClass);
retrofitServiceCache.put(apiKey,retrofitService);
}
return retrofitService;
}
private static <A> String getApiKey(Class<A> apiClass) {
return String.format("_%s", apiClass);
}
private <T> Method getRetrofitMethod(T service, Method method) throws NoSuchMethodException {
return service.getClass().getMethod(method.getName(), method.getParameterTypes());
}
}
package com.ydl.ydlnet.builder.gson;
import com.google.gson.*;
import java.lang.reflect.Type;
/**
* Created by haorui on 2019-09-02 .
* Des:定义为double类型,如果后台返回""或者null,则返回0.00
*/
public class DoubleDefault0Adapter implements JsonSerializer<Double>, JsonDeserializer<Double> {
@Override
public Double deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
try {
if (json.getAsString().equals("") || json.getAsString().equals("null")) {
return 0.00;
}
} catch (Exception ignore) {
}
try {
return json.getAsDouble();
} catch (NumberFormatException e) {
throw new JsonSyntaxException(e);
}
}
@Override
public JsonElement serialize(Double src, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive(src);
}
}
\ No newline at end of file
package com.ydl.ydlnet.builder.gson;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
/**
* Created by haorui on 2019-09-02 .
* Des:
*/
public class GsonAdapter {
public static Gson buildGson() {
Gson gson = new GsonBuilder()
.registerTypeAdapter(Integer.class, new IntegerDefault0Adapter())
.registerTypeAdapter(int.class, new IntegerDefault0Adapter())
.registerTypeAdapter(Double.class, new DoubleDefault0Adapter())
.registerTypeAdapter(double.class, new DoubleDefault0Adapter())
.registerTypeAdapter(Long.class, new LongDefault0Adapter())
.registerTypeAdapter(long.class, new LongDefault0Adapter())
.create();
return gson;
}
}
package com.ydl.ydlnet.builder.gson;
import com.google.gson.*;
import java.lang.reflect.Type;
/**
* Created by haorui on 2019-09-02 .
* Des: 定义为int类型,如果后台返回""或者null,则返回0
*/
public class IntegerDefault0Adapter implements JsonSerializer<Integer>, JsonDeserializer<Integer> {
@Override
public Integer deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
try {
if (json.getAsString().equals("") || json.getAsString().equals("null")) {
return 0;
}
} catch (Exception ignore) {
}
try {
return json.getAsInt();
} catch (NumberFormatException e) {
throw new JsonSyntaxException(e);
}
}
@Override
public JsonElement serialize(Integer src, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive(src);
}
}
\ No newline at end of file
package com.ydl.ydlnet.builder.gson;
import com.google.gson.*;
import java.lang.reflect.Type;
/**
* Created by haorui on 2019-09-02 .
* Des: 定义为long类型,如果后台返回""或者null,则返回0
*/
public class LongDefault0Adapter implements JsonSerializer<Long>, JsonDeserializer<Long> {
@Override
public Long deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
try {
if (json.getAsString().equals("") || json.getAsString().equals("null")) {
return 0l;
}
} catch (Exception ignore) {
}
try {
return json.getAsLong();
} catch (NumberFormatException e) {
throw new JsonSyntaxException(e);
}
}
@Override
public JsonElement serialize(Long src, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive(src);
}
}
\ No newline at end of file
package com.ydl.ydlnet.builder.http;
import android.annotation.SuppressLint;
import android.util.Log;
import javax.net.ssl.*;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
/**
* Created by haorui on 2019-09-02 .
* Des: 证书工具类
*/
public class SSLUtils {
public static class SSLParams {
public SSLSocketFactory sSLSocketFactory;
public X509TrustManager trustManager;
}
public static SSLParams getSslSocketFactory() {
return getSslSocketFactoryBase(null, null, null);
}
/**
* https单向认证
* 可以额外配置信任服务端的证书策略,否则默认是按CA证书去验证的,若不是CA可信任的证书,则无法通过验证
*/
public static SSLParams getSslSocketFactory(X509TrustManager trustManager) {
return getSslSocketFactoryBase(trustManager, null, null);
}
/**
* https单向认证
* 用含有服务端公钥的证书校验服务端证书
*/
public static SSLParams getSslSocketFactory(InputStream... certificates) {
return getSslSocketFactoryBase(null, null, null, certificates);
}
/**
* https双向认证
* bksFile 和 password -> 客户端使用bks证书校验服务端证书
* certificates -> 用含有服务端公钥的证书校验服务端证书
*/
public static SSLParams getSslSocketFactory(InputStream bksFile, String password, InputStream... certificates) {
return getSslSocketFactoryBase(null, bksFile, password, certificates);
}
/**
* https双向认证
* bksFile 和 password -> 客户端使用bks证书校验服务端证书
* X509TrustManager -> 如果需要自己校验,那么可以自己实现相关校验,如果不需要自己校验,那么传null即可
*/
public static SSLParams getSslSocketFactory(InputStream bksFile, String password, X509TrustManager trustManager) {
return getSslSocketFactoryBase(trustManager, bksFile, password);
}
private static SSLParams getSslSocketFactoryBase(X509TrustManager trustManager, InputStream bksFile, String password, InputStream... certificates) {
SSLParams sslParams = new SSLParams();
try {
KeyManager[] keyManagers = prepareKeyManager(bksFile, password);
TrustManager[] trustManagers = prepareTrustManager(certificates);
X509TrustManager manager;
if (trustManager != null) {
//优先使用用户自定义的TrustManager
manager = trustManager;
} else if (trustManagers != null) {
//然后使用默认的TrustManager
manager = chooseTrustManager(trustManagers);
} else {
//否则使用不安全的TrustManager
manager = UnSafeTrustManager;
}
// 创建TLS类型的SSLContext对象, that uses our TrustManager
SSLContext sslContext = SSLContext.getInstance("TLS");
// 用上面得到的trustManagers初始化SSLContext,这样sslContext就会信任keyStore中的证书
// 第一个参数是授权的密钥管理器,用来授权验证,比如授权自签名的证书验证。第二个是被授权的证书管理器,用来验证服务器端的证书
sslContext.init(keyManagers, new TrustManager[]{manager}, null);
// 通过sslContext获取SSLSocketFactory对象
sslParams.sSLSocketFactory = sslContext.getSocketFactory();
sslParams.trustManager = manager;
return sslParams;
} catch (NoSuchAlgorithmException e) {
throw new AssertionError(e);
} catch (KeyManagementException e) {
throw new AssertionError(e);
}
}
private static KeyManager[] prepareKeyManager(InputStream bksFile, String password) {
try {
if (bksFile == null || password == null) return null;
KeyStore clientKeyStore = KeyStore.getInstance("BKS");
clientKeyStore.load(bksFile, password.toCharArray());
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(clientKeyStore, password.toCharArray());
return kmf.getKeyManagers();
} catch (Exception e) {
Log.e("ssl", e.getMessage());
}
return null;
}
private static TrustManager[] prepareTrustManager(InputStream... certificates) {
if (certificates == null || certificates.length <= 0) return null;
try {
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
// 创建一个默认类型的KeyStore,存储我们信任的证书
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null);
int index = 0;
for (InputStream certStream : certificates) {
String certificateAlias = Integer.toString(index++);
// 证书工厂根据证书文件的流生成证书 cert
Certificate cert = certificateFactory.generateCertificate(certStream);
// 将 cert 作为可信证书放入到keyStore中
keyStore.setCertificateEntry(certificateAlias, cert);
try {
if (certStream != null) certStream.close();
} catch (IOException e) {
Log.e("ssl", e.getMessage());
}
}
//我们创建一个默认类型的TrustManagerFactory
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
//用我们之前的keyStore实例初始化TrustManagerFactory,这样tmf就会信任keyStore中的证书
tmf.init(keyStore);
//通过tmf获取TrustManager数组,TrustManager也会信任keyStore中的证书
return tmf.getTrustManagers();
} catch (Exception e) {
Log.e("ssl", e.getMessage());
}
return null;
}
private static X509TrustManager chooseTrustManager(TrustManager[] trustManagers) {
for (TrustManager trustManager : trustManagers) {
if (trustManager instanceof X509TrustManager) {
return (X509TrustManager) trustManager;
}
}
return null;
}
/**
* 为了解决客户端不信任服务器数字证书的问题,网络上大部分的解决方案都是让客户端不对证书做任何检查,
* 这是一种有很大安全漏洞的办法
*/
public static X509TrustManager UnSafeTrustManager = new X509TrustManager() {
@SuppressLint("TrustAllX509TrustManager")
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
@SuppressLint("TrustAllX509TrustManager")
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new java.security.cert.X509Certificate[]{};
}
};
/**
* 此类是用于主机名验证的基接口。 在握手期间,如果 URL 的主机名和服务器的标识主机名不匹配,
* 则验证机制可以回调此接口的实现程序来确定是否应该允许此连接。策略可以是基于证书的或依赖于其他验证方案。
* 当验证 URL 主机名使用的默认规则失败时使用这些回调。如果主机名是可接受的,则返回 true
*/
public static HostnameVerifier UnSafeHostnameVerifier = new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
};
}
package com.ydl.ydlnet.builder.interceptor;
import okhttp3.Headers;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
import java.io.IOException;
import java.util.Map;
/**
* Created by haorui on 2019-09-02 .
* Des: 请求拦截器 统一添加请求头使用
*/
public abstract class HeaderInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Map<String, String> headers = buildHeaders();
if (headers == null || headers.isEmpty()) {
return chain.proceed(request);
} else {
Response response = chain.proceed(request.newBuilder()
.headers(buildHeaders(request, headers))
.build());
return response;
}
}
private Headers buildHeaders(Request request, Map<String, String> headerMap) {
Headers headers = request.headers();
if (headers != null) {
Headers.Builder builder = headers.newBuilder();
for (String key : headerMap.keySet()) {
builder.add(key, headerMap.get(key));
}
return builder.build();
} else {
return headers;
}
}
public abstract Map<String, String> buildHeaders();
}
package com.ydl.ydlnet.builder.interceptor;
import okhttp3.CacheControl;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import static com.ydl.ydlnet.utils.YDLNetUtils.isNetworkConnected;
/**
* Created by haorui on 2019-09-02 .
* Des: 网络缓存 参考 https://www.jianshu.com/p/cf59500990c7
*/
public class NetCacheInterceptor implements Interceptor {
/**
* 默认缓存60秒
*/
private int cacheTime;
public NetCacheInterceptor(int cacheTime) {
this.cacheTime = cacheTime;
}
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
boolean connected = isNetworkConnected();
if (connected) {
//如果有网络,缓存60s
Response response = chain.proceed(request);
CacheControl.Builder builder = new CacheControl.Builder()
.maxAge(cacheTime, TimeUnit.SECONDS);
return response.newBuilder()
.header("Cache-Control", builder.build().toString())
.removeHeader("Pragma")
.build();
}
//如果没有网络,不做处理,直接返回
return chain.proceed(request);
}
}
package com.ydl.ydlnet.builder.interceptor;
import okhttp3.CacheControl;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
import java.io.IOException;
import static com.ydl.ydlnet.utils.YDLNetUtils.isNetworkConnected;
/**
* Created by haorui on 2019-09-02 .
* Des: 网络缓存 参考 https://www.jianshu.com/p/cf59500990c7
*/
public class NoNetCacheInterceptor implements Interceptor {
/**
* 无网络缓存时间3600秒
*/
private int noNetCacheTime;
public NoNetCacheInterceptor(int noNetCacheTime) {
this.noNetCacheTime = noNetCacheTime;
}
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
boolean connected = isNetworkConnected();
//如果没有网络,则启用 FORCE_CACHE
if (!connected) {
request = request.newBuilder()
.cacheControl(CacheControl.FORCE_CACHE)
.build();
Response response = chain.proceed(request);
//没网的时候如果也没缓存的话就走网络
if (response.code() == 504) {
request = request.newBuilder()
.cacheControl(CacheControl.FORCE_NETWORK)
.build();
return chain.proceed(request);
}
return response.newBuilder()
.header("Cache-Control", "public, only-if-cached, max-stale="+noNetCacheTime)
.removeHeader("Pragma")
.build();
}
//有网络的时候,这个拦截器不做处理,直接返回
return chain.proceed(request);
}
}
package com.ydl.ydlnet.builder.interceptor;
import com.ydl.ydlnet.client.interfaces.ILoadingView;
import io.reactivex.Observable;
import io.reactivex.ObservableSource;
import io.reactivex.ObservableTransformer;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.annotations.NonNull;
import io.reactivex.disposables.Disposable;
import io.reactivex.functions.Action;
import io.reactivex.functions.Consumer;
import io.reactivex.schedulers.Schedulers;
/**
* Created by haorui on 2019-09-02 .
* Des: 线程调度辅助类
*/
public class YDLTransformer {
/**
* 无参数
*
* @param <T> 泛型
* @return 返回Observable
*/
public static <T> ObservableTransformer<T, T> switchSchedulers() {
return switchSchedulers(null);
}
/**
* 带参数 显示loading对话框
*
* @param loadingView loading
* @param <T> 泛型
* @return 返回Observable
*/
public static <T> ObservableTransformer<T, T> switchSchedulers(final ILoadingView loadingView) {
return new ObservableTransformer<T, T>() {
@Override
public ObservableSource<T> apply(@NonNull Observable<T> upstream) {
return upstream
.subscribeOn(Schedulers.io())
.unsubscribeOn(Schedulers.io())
.doOnSubscribe(new Consumer<Disposable>() {
@Override
public void accept(@NonNull Disposable disposable) throws Exception {
if (loadingView != null) {
loadingView.showLoadingView();
}
}
})
.subscribeOn(AndroidSchedulers.mainThread())
.observeOn(AndroidSchedulers.mainThread())
.doFinally(new Action() {
@Override
public void run() throws Exception {
if (loadingView != null) {
loadingView.hideLoadingView();
}
}
});
}
};
}
}
package com.ydl.ydlnet.builder.interceptor.log;
import android.support.annotation.Nullable;
import com.ydl.ydlnet.utils.CharacterHandler;
import com.ydl.ydlnet.utils.NetLogUtils;
import com.ydl.ydlnet.utils.UrlEncoderUtils;
import com.ydl.ydlnet.utils.ZipHelper;
import okhttp3.*;
import okio.Buffer;
import okio.BufferedSource;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.nio.charset.Charset;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* Created by haorui on 2019-09-01 .
* Des: 解析框架中的网络请求和响应结果并打印
*/
public class RequestLogInterceptor implements Interceptor {
private DefaultFormatPrinter mPrinter = new DefaultFormatPrinter();
public RequestLogInterceptor() {
}
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
//打印请求信息
if (request.body() != null && isParseable(request.body().contentType())) {
mPrinter.printJsonRequest(request, parseParams(request));
} else {
mPrinter.printFileRequest(request);
}
long t1 =System.nanoTime() ;
Response originalResponse;
try {
originalResponse = chain.proceed(request);
} catch (Exception e) {
NetLogUtils.debugInfo("Http Error: " + e);
throw e;
}
long t2 = System.nanoTime() ;
ResponseBody responseBody = originalResponse.body();
//打印响应结果
String bodyString = null;
if (responseBody != null && isParseable(responseBody.contentType())) {
bodyString = printResult(request, originalResponse);
}
final List<String> segmentList = request.url().encodedPathSegments();
final String header = originalResponse.headers().toString();
final int code = originalResponse.code();
final boolean isSuccessful = originalResponse.isSuccessful();
final String message = originalResponse.message();
final String url = originalResponse.request().url().toString();
if (responseBody != null && isParseable(responseBody.contentType())) {
mPrinter.printJsonResponse(TimeUnit.NANOSECONDS.toMillis(t2 - t1), isSuccessful,
code, header, responseBody.contentType(), bodyString, segmentList, message, url);
} else {
mPrinter.printFileResponse(TimeUnit.NANOSECONDS.toMillis(t2 - t1),
isSuccessful, code, header, segmentList, message, url);
}
return originalResponse;
}
/**
* 打印响应结果
*
* @param request {@link Request}
* @param response {@link Response}
* @return 解析后的响应结果
* @throws IOException
*/
@Nullable
private String printResult(Request request, Response response) throws IOException {
try {
//读取服务器返回的结果
ResponseBody responseBody = response.newBuilder().build().body();
BufferedSource source = responseBody.source();
source.request(Long.MAX_VALUE); // Buffer the entire body.
Buffer buffer = source.buffer();
//获取content的压缩类型
String encoding = response
.headers()
.get("Content-Encoding");
Buffer clone = buffer.clone();
//解析response content
return parseContent(responseBody, encoding, clone);
} catch (IOException e) {
e.printStackTrace();
return "{\"error\": \"" + e.getMessage() + "\"}";
}
}
/**
* 解析服务器响应的内容
*
* @param responseBody {@link ResponseBody}
* @param encoding 编码类型
* @param clone 克隆后的服务器响应内容
* @return 解析后的响应结果
*/
private String parseContent(ResponseBody responseBody, String encoding, Buffer clone) {
Charset charset = Charset.forName("UTF-8");
MediaType contentType = responseBody.contentType();
if (contentType != null) {
charset = contentType.charset(charset);
}
if (encoding != null && encoding.equalsIgnoreCase("gzip")) {//content 使用 gzip 压缩
return ZipHelper.decompressForGzip(clone.readByteArray(), convertCharset(charset));//解压
} else if (encoding != null && encoding.equalsIgnoreCase("zlib")) {//content 使用 zlib 压缩
return ZipHelper.decompressToStringForZlib(clone.readByteArray(), convertCharset(charset));//解压
} else {//content 没有被压缩, 或者使用其他未知压缩方式
return clone.readString(charset);
}
}
/**
* 解析请求服务器的请求参数
*
* @param request {@link Request}
* @return 解析后的请求信息
* @throws UnsupportedEncodingException
*/
public static String parseParams(Request request) throws UnsupportedEncodingException {
try {
RequestBody body = request.newBuilder().build().body();
if (body == null) return "";
Buffer requestbuffer = new Buffer();
body.writeTo(requestbuffer);
Charset charset = Charset.forName("UTF-8");
MediaType contentType = body.contentType();
if (contentType != null) {
charset = contentType.charset(charset);
}
String json = requestbuffer.readString(charset);
if (UrlEncoderUtils.hasUrlEncoded(json)) {
json = URLDecoder.decode(json, convertCharset(charset));
}
return CharacterHandler.jsonFormat(json);
} catch (IOException e) {
e.printStackTrace();
return "{\"error\": \"" + e.getMessage() + "\"}";
}
}
/**
* 是否可以解析
*
* @param mediaType {@link MediaType}
* @return {@code true} 为可以解析
*/
public static boolean isParseable(MediaType mediaType) {
if (mediaType == null || mediaType.type() == null) return false;
return isText(mediaType) || isPlain(mediaType)
|| isJson(mediaType) || isForm(mediaType)
|| isHtml(mediaType) || isXml(mediaType);
}
public static boolean isText(MediaType mediaType) {
if (mediaType == null || mediaType.type() == null) return false;
return mediaType.type().equals("text");
}
public static boolean isPlain(MediaType mediaType) {
if (mediaType == null || mediaType.subtype() == null) return false;
return mediaType.subtype().toLowerCase().contains("plain");
}
public static boolean isJson(MediaType mediaType) {
if (mediaType == null || mediaType.subtype() == null) return false;
return mediaType.subtype().toLowerCase().contains("json");
}
public static boolean isXml(MediaType mediaType) {
if (mediaType == null || mediaType.subtype() == null) return false;
return mediaType.subtype().toLowerCase().contains("xml");
}
public static boolean isHtml(MediaType mediaType) {
if (mediaType == null || mediaType.subtype() == null) return false;
return mediaType.subtype().toLowerCase().contains("html");
}
public static boolean isForm(MediaType mediaType) {
if (mediaType == null || mediaType.subtype() == null) return false;
return mediaType.subtype().toLowerCase().contains("x-www-form-urlencoded");
}
public static String convertCharset(Charset charset) {
String s = charset.toString();
int i = s.indexOf("[");
if (i == -1)
return s;
return s.substring(i + 1, s.length() - 1);
}
}
package com.ydl.ydlnet.builder.manage
import com.ydl.ydlnet.YDLHttpUtils
import com.ydl.ydlnet.builder.factory.ApiFactory
import okhttp3.HttpUrl
import okhttp3.Interceptor
import okhttp3.Request
import java.util.*
/**
* Created by haorui on 2019-09-02 .
* Des: 多域名管理类
*/
class HttpUrlManager private constructor() {
open val mRedirectInterceptor: Interceptor
val DOMAIN = "Domain-Name"
open var mDomainNameHub: MutableMap<String, String>? = null
/**
* 获取全局唯一的baseUrl
*
* @return url
*/
val url: String?
get() = getUrlByKey(DEFAULT_URL_KEY)
init {
mDomainNameHub = HashMap()
this.mRedirectInterceptor = Interceptor { chain -> chain.proceed(processRequest(chain.request())) }
}
/**
* 一次性传入urlMap
*
* @param urlMap map
* @return HttpUrlManager
*/
fun setMultipleUrl(urlMap: MutableMap<String, String>): HttpUrlManager {
if (urlMap.isNotEmpty()){
this.mDomainNameHub?.putAll(urlMap)
}
return this
}
/**
* 向map中添加url
*
* @param urlKey key
* @param urlValue value
* @return HttpUrlManager
*/
fun addUrl(urlKey: String, urlValue: String): HttpUrlManager {
mDomainNameHub!![urlKey] = urlValue
return this
}
/**
* 从map中删除某个url
*
* @param urlKey 需要删除的urlKey
* @return HttpUrlManager
*/
fun removeUrlByKey(urlKey: String): HttpUrlManager {
mDomainNameHub!!.remove(urlKey)
return this
}
/**
* 针对单个baseUrl切换的时候清空老baseUrl的所有信息
*
* @param urlValue url
* @return HttpUrlManager
*/
fun setUrl(urlValue: String): HttpUrlManager {
mDomainNameHub!![DEFAULT_URL_KEY] = urlValue
return this
}
/**
* 根据key
*
* @param urlKey 获取对应的url
* @return url
*/
fun getUrlByKey(urlKey: String): String? {
return mDomainNameHub!![urlKey]
}
/**
* 清空设置的url相关的所以信息
* 相当于重置url
* 动态切换生产测试环境时候调用
*
* @return HttpUrlManager
*/
fun clear(): HttpUrlManager {
mDomainNameHub!!.clear()
ApiFactory.getInstance().clearAllApi()
YDLHttpUtils.removeAllCookie()
return this
}
companion object {
@Volatile
private var instance: HttpUrlManager? = null
var DEFAULT_URL_KEY = "http_default_url_key"
fun getInstance(): HttpUrlManager? {
if (instance == null) {
synchronized(HttpUrlManager::class.java) {
if (instance == null) {
instance = HttpUrlManager()
}
}
}
return instance
}
}
//========================================
/**
*解析Request的Header
*/
private fun processRequest(request: Request): Request {
var newBuilder = request.newBuilder()
var domainName = obtainDomainNameFromHeaders(request)
var httpUrl: HttpUrl? = null
if (domainName.isNotEmpty()) {
httpUrl = fetchDomain(domainName)?.let { checkUrl(it) }
newBuilder.removeHeader(DOMAIN)
}
if (null != httpUrl) {
val newUrl = parseUrl(httpUrl, request.url())
return newBuilder.url(newUrl).build()
}
return newBuilder.build()
}
/**
* 解析请求的Header
*/
private fun obtainDomainNameFromHeaders(request: Request): String {
val headers = request.headers(DOMAIN)
if (headers == null || headers.size == 0)
return ""
if (headers.size > 1)
throw IllegalArgumentException("Only one Domain-Name in the headers")
return request.header(DOMAIN)!!
}
/**
*获得Header对应的HttpUrl
*/
fun fetchDomain(domainName: String): String? {
return mDomainNameHub?.get(domainName)
}
fun parseUrl(domainUrl: HttpUrl, url: HttpUrl): HttpUrl {
return checkUrl(url.toString().replace(this.url?:url.host(),domainUrl.toString()))
// return url.newBuilder()
// .scheme(domainUrl.scheme())
// .host(domainUrl.host())
// .port(domainUrl.port())
// .build()
}
/**
* 包装url类型String->HttpUrl
*/
fun checkUrl(url: String): HttpUrl {
val parseUrl = HttpUrl.parse(url)
return parseUrl!!
}
}
package com.ydl.ydlnet.builder.retrofit;
import com.ydl.ydlnet.builder.factory.ApiFactory;
import com.ydl.ydlnet.builder.gson.GsonAdapter;
import com.ydl.ydlnet.builder.http.SSLUtils;
import com.ydl.ydlnet.builder.manage.HttpUrlManager;
import okhttp3.OkHttpClient;
import retrofit2.CallAdapter;
import retrofit2.Converter;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
import retrofit2.converter.gson.GsonConverterFactory;
import retrofit2.converter.scalars.ScalarsConverterFactory;
import java.util.concurrent.TimeUnit;
/**
* Created by haorui on 2019-09-02 .
* Des: RetrofitBuilder
*/
public class RetrofitBuilder {
private String baseUrl;
private CallAdapter.Factory[] callAdapterFactory;
private Converter.Factory[] converterFactory;
private OkHttpClient okHttpClient;
public RetrofitBuilder setBaseUrl(String baseUrl) {
this.baseUrl = baseUrl;
return this;
}
public RetrofitBuilder setCallAdapterFactory(CallAdapter.Factory... callAdapterFactory) {
this.callAdapterFactory = callAdapterFactory;
return this;
}
public RetrofitBuilder setConverterFactory(Converter.Factory... converterFactory) {
this.converterFactory = converterFactory;
return this;
}
public RetrofitBuilder setOkHttpClient(OkHttpClient okHttpClient) {
this.okHttpClient = okHttpClient;
return this;
}
public Retrofit build() {
Retrofit.Builder builder = new Retrofit.Builder();
builder.baseUrl(baseUrl);
if (callAdapterFactory == null || callAdapterFactory.length <= 0) {
builder.addCallAdapterFactory(RxJava2CallAdapterFactory.create());
} else {
for (CallAdapter.Factory factory : callAdapterFactory) {
builder.addCallAdapterFactory(factory);
}
}
if (converterFactory == null || converterFactory.length <= 0) {
builder.addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create(GsonAdapter.buildGson()));
} else {
for (Converter.Factory factory : converterFactory) {
builder.addConverterFactory(factory);
}
}
if (okHttpClient == null) {
builder.client(createOkHttpClient());
} else {
builder.client(okHttpClient);
}
return builder.build();
}
private OkHttpClient createOkHttpClient() {
OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.readTimeout(10, TimeUnit.SECONDS);
builder.writeTimeout(10, TimeUnit.SECONDS);
builder.connectTimeout(10, TimeUnit.SECONDS);
SSLUtils.SSLParams sslParams = SSLUtils.getSslSocketFactory();
builder.sslSocketFactory(sslParams.sSLSocketFactory, sslParams.trustManager);
if(ApiFactory.getInstance().isMultipleUrl()) {
builder.addInterceptor(HttpUrlManager.Companion.getInstance().getMRedirectInterceptor());
}
return builder.build();
}
}
package com.ydl.ydlnet.cache;
import com.ydl.ydlnet.cache.data.CacheResult;
import com.ydl.ydlnet.cache.data.ResultFrom;
import java.io.IOException;
import java.lang.reflect.Type;
/**
* 缓存核心
* 作者: 赵成柱 on 2016/9/9
*/
class CacheCore {
private LruMemoryCache memory;
private LruDiskCache disk;
CacheCore(LruMemoryCache memory, LruDiskCache disk) {
this.memory = memory;
this.disk = disk;
}
/**
* 读取
*/
<T> CacheResult<T> load(String key, Type type) {
if (memory != null) {
CacheHolder<T> result = memory.load(key);
if (result != null) {
return new CacheResult<>(ResultFrom.Memory, key, result.data, result.timestamp);
}
}
if (disk != null) {
CacheHolder<T> result = disk.load(key, type);
if (result != null) {
return new CacheResult<>(ResultFrom.Disk, key, result.data, result.timestamp);
}
}
return null;
}
/**
* 保存
*/
<T> boolean save(String key, T value, CacheTarget target) {
if (value == null) { //如果要保存的值为空,则删除
boolean memoryRemove = true;
if (memory != null) {
memoryRemove = memory.remove(key);
}
boolean diskRemove = true;
if (disk != null) {
diskRemove = disk.remove(key);
}
return memoryRemove && diskRemove;
}
boolean save = false;
if (target.supportMemory() && memory != null) {
save = memory.save(key, value);
}
if (target.supportDisk() && disk != null) {
return disk.save(key, value);
}
return save;
}
/**
* 是否包含
*/
boolean containsKey(String key) {
return memory != null && memory.containsKey(key) || disk != null && disk.containsKey(key);
}
/**
* 删除缓存
*/
boolean remove(String key) {
boolean isRemove = true;
if (memory != null) {
isRemove = memory.remove(key);
}
if (disk != null) {
isRemove = isRemove && disk.remove(key);
}
return isRemove;
}
/**
* 清空缓存
*/
void clear() throws IOException {
if (memory != null) {
memory.clear();
}
if (disk != null) {
disk.clear();
}
}
}
package com.ydl.ydlnet.cache;
class CacheHolder<T> {
public CacheHolder(T data, long timestamp) {
this.data = data;
this.timestamp = timestamp;
}
public T data;
public long timestamp;
}
package com.ydl.ydlnet.cache;
/**
* 缓存目标
* 作者: 赵成柱 on 2016/9/9
*/
public enum CacheTarget {
Memory,
Disk,
MemoryAndDisk;
public boolean supportMemory() {
return this==Memory || this== MemoryAndDisk;
}
public boolean supportDisk() {
return this==Disk || this== MemoryAndDisk;
}
}
package com.ydl.ydlnet.cache;
import com.ydl.ydlnet.cache.diskconverter.IDiskConverter;
import com.ydl.ydlnet.cache.utils.DiskLruCache;
import com.ydl.ydlnet.cache.utils.RxCacheLogUtils;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Type;
/**
* Created by Z.Chu on 2016/9/10.
*/
class LruDiskCache {
private IDiskConverter mDiskConverter;
private DiskLruCache mDiskLruCache;
LruDiskCache(IDiskConverter diskConverter, File diskDir, int appVersion, long diskMaxSize) {
this.mDiskConverter = diskConverter;
try {
mDiskLruCache = DiskLruCache.open(diskDir, appVersion, 2, diskMaxSize);
} catch (IOException e) {
RxCacheLogUtils.log(e);
}
}
<T> CacheHolder<T> load(String key, Type type) {
if (mDiskLruCache == null) {
return null;
}
try {
DiskLruCache.Snapshot snapshot = mDiskLruCache.get(key);
if (snapshot != null) {
InputStream source = snapshot.getInputStream(0);
T value = mDiskConverter.load(source, type);
long timestamp = 0;
String string = snapshot.getString(1);
if (string != null) {
timestamp = Long.parseLong(string);
}
snapshot.close();
return new CacheHolder<>(value, timestamp);
}
} catch (IOException e) {
RxCacheLogUtils.log(e);
}
return null;
}
<T> boolean save(String key, T value) {
if (mDiskLruCache == null) {
return false;
}
//如果要保存的值为空,则删除
if (value == null) {
return remove(key);
}
DiskLruCache.Editor edit = null;
try {
edit = mDiskLruCache.edit(key);
OutputStream sink = edit.newOutputStream(0);
mDiskConverter.writer(sink, value);
long l = System.currentTimeMillis();
edit.set(1, String.valueOf(l));
edit.commit();
RxCacheLogUtils.log("save: value="+value+" , status="+true);
return true;
} catch (IOException e) {
RxCacheLogUtils.log(e);
if (edit != null) {
try {
edit.abort();
} catch (IOException e1) {
RxCacheLogUtils.log(e1);
}
}
RxCacheLogUtils.log("save: value="+value+" , status="+false);
}
return false;
}
boolean containsKey(String key) {
try {
return mDiskLruCache.get(key) != null;
} catch (IOException e) {
RxCacheLogUtils.log(e);
}
return false;
}
/**
* 删除缓存
*/
final boolean remove(String key) {
try {
return mDiskLruCache.remove(key);
} catch (IOException e) {
RxCacheLogUtils.log(e);
}
return false;
}
void clear() throws IOException {
deleteContents(mDiskLruCache.getDirectory());
}
private static void deleteContents(File dir) throws IOException {
File[] files = dir.listFiles();
if (files == null) {
throw new IOException("not a readable directory: " + dir);
}
for (File file : files) {
if (file.isDirectory()) {
deleteContents(file);
}
if (!file.delete()) {
throw new IOException("failed to delete file: " + file);
}
}
}
}
package com.ydl.ydlnet.cache;
import android.graphics.Bitmap;
import android.support.v4.util.LruCache;
import com.ydl.ydlnet.cache.utils.RxCacheLogUtils;
import com.ydl.ydlnet.cache.utils.MemorySizeOf;
import com.ydl.ydlnet.cache.utils.Occupy;
import java.util.HashMap;
/**
* Created by Chu on 2016/9/10.
*/
class LruMemoryCache {
private LruCache<String, Object> mCache;
private HashMap<String, Integer> memorySizeMap;//储存初次加入缓存的size,规避对象在内存中大小变化造成的测量出错
private HashMap<String, Long> timestampMap;
private Occupy occupy;
public LruMemoryCache(final int cacheSize) {
memorySizeMap = new HashMap<>();
timestampMap = new HashMap<>();
byte to = 0;
byte t4 = 4;
occupy = new Occupy(to, to, t4);
mCache = new LruCache<String, Object>(cacheSize) {
@Override
protected int sizeOf(String key, Object value) {
Integer integer = memorySizeMap.get(key);
if (integer == null) {
return 0;
}
return integer;
}
};
}
public <T> CacheHolder<T> load(String key) {
T value = (T) mCache.get(key);
if (value != null) {
return new CacheHolder<>(value, timestampMap.get(key));
}
return null;
}
public <T> boolean save(String key, T value) {
if (null != value) {
memorySizeMap.put(key, (int) countSize(value));
mCache.put(key, value);
timestampMap.put(key, System.currentTimeMillis());
}
return true;
}
public boolean containsKey(String key) {
return memorySizeMap.containsKey(key);
}
/**
* 删除缓存
*
* @param key
*/
public final boolean remove(String key) {
Object remove = mCache.remove(key);
if (remove != null) {
memorySizeMap.remove(key);
timestampMap.remove(key);
return true;
}
return false;
}
public void clear() {
mCache.evictAll();
memorySizeMap.clear();
}
private long countSize(Object value) {
if (value == null) {
return 0;
}
// 更优良的内存大小算法
long size;
if (value instanceof Bitmap) {
RxCacheLogUtils.debug("Bitmap");
size = MemorySizeOf.sizeOf((Bitmap) value);
} else {
size = occupy.occupyof(value);
}
RxCacheLogUtils.debug("size=" + size + " value=" + value);
return size;
}
}
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment