first commit
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
package com.abbidot.baselibrary
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ExampleInstrumentedTest {
|
||||
@Test
|
||||
fun useAppContext() {
|
||||
// Context of the app under test.
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
assertEquals("com.abbidot.baselibrary.test", appContext.packageName)
|
||||
}
|
||||
}
|
||||
4
baselibrary/src/main/AndroidManifest.xml
Normal file
4
baselibrary/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
</manifest>
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.abbidot.baselibrary.constant
|
||||
|
||||
import androidx.annotation.IntDef
|
||||
|
||||
/**
|
||||
*Created by .yzq on 2021/11/8/008.
|
||||
* @link
|
||||
* @description:设备连接状态
|
||||
*/
|
||||
@IntDef(
|
||||
ConState.DISCONNECTED,
|
||||
ConState.CONNECTED,
|
||||
ConState.CONNECTING,
|
||||
ConState.CONNECTING_MAC,
|
||||
ConState.CONNECTION_FAIL,
|
||||
ConState.BLUETOOTH_ON,
|
||||
ConState.BLUETOOTH_OFF,
|
||||
ConState.DEVICE_NOT_FOUND
|
||||
)
|
||||
@Retention(AnnotationRetention.SOURCE)
|
||||
annotation class ConState {
|
||||
companion object {
|
||||
//连接断开
|
||||
const val DISCONNECTED = 0
|
||||
|
||||
//已连接
|
||||
const val CONNECTED = 1
|
||||
|
||||
//连接中
|
||||
const val CONNECTING = 2
|
||||
|
||||
//连接指定Mac设备
|
||||
const val CONNECTING_MAC = 3
|
||||
|
||||
//没有找到设备
|
||||
const val DEVICE_NOT_FOUND = 4
|
||||
|
||||
//未连接,连接失败
|
||||
const val CONNECTION_FAIL = 5
|
||||
|
||||
//打开蓝牙
|
||||
const val BLUETOOTH_ON = 6
|
||||
const val BLUETOOTH_OFF = 7
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.abbidot.baselibrary.constant
|
||||
|
||||
import androidx.annotation.IntDef
|
||||
|
||||
/**
|
||||
*Created by .yzq on 2021/11/9/009.
|
||||
* @link
|
||||
* @description:设备类型
|
||||
*/
|
||||
@IntDef(DeviceType.TYPE_ONE, DeviceType.TYPE_TWO)
|
||||
@Retention(AnnotationRetention.SOURCE)
|
||||
annotation class DeviceType {
|
||||
companion object {
|
||||
const val TYPE_ONE = 1
|
||||
const val TYPE_TWO = 2
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package com.abbidot.baselibrary.constant
|
||||
|
||||
import androidx.annotation.StringDef
|
||||
|
||||
/**
|
||||
* EventBus发送消息名
|
||||
*/
|
||||
@StringDef(
|
||||
EventName.RefreshHomeList,
|
||||
EventName.Test,
|
||||
EventName.FinishActivity,
|
||||
EventName.UpdateUserInfo,
|
||||
EventName.UpdateData,
|
||||
EventName.LogOut,
|
||||
EventName.PayBack,
|
||||
EventName.HistoryDataSnapshot,
|
||||
EventName.ActiveGoal,
|
||||
EventName.UpdateReportInterval,
|
||||
EventName.DeviceReceiveData,
|
||||
EventName.WXLoginResult,
|
||||
EventName.ChangePassword,
|
||||
EventName.ConnectDeviceState,
|
||||
EventName.RefreshFences,
|
||||
EventName.DeleteFences,
|
||||
EventName.RefreshPet,
|
||||
EventName.DeletePet,
|
||||
EventName.RefreshWiFiZone,
|
||||
EventName.RefreshDevice,
|
||||
EventName.DeleteWiFiZone,
|
||||
EventName.DeleteFamily,
|
||||
EventName.RefreshFamily,
|
||||
EventName.DeleteDevice,
|
||||
EventName.GPSSwitchState,
|
||||
EventName.RefreshPackage,
|
||||
EventName.LiveAutoEnd,
|
||||
EventName.LiveOpenTimeOut,
|
||||
EventName.SetGoal,
|
||||
EventName.RefreshMessage,
|
||||
EventName.BluetoothSwitch,
|
||||
EventName.BleReport,
|
||||
EventName.BleReportData,
|
||||
EventName.LogReport,
|
||||
EventName.ActionConDeviceState
|
||||
)
|
||||
@Retention(AnnotationRetention.SOURCE)
|
||||
annotation class EventName {
|
||||
companion object {
|
||||
const val RefreshHomeList = "refreshHomeList"
|
||||
const val Test = "test"
|
||||
const val FinishActivity = "finishActivity"
|
||||
const val UpdateUserInfo = "updateUserInfo"
|
||||
const val UpdateData = "updateData"
|
||||
const val RefreshFences = "refreshFences"
|
||||
const val RefreshMessage = "refreshMessage"
|
||||
const val DeleteFences = "deleteFences"
|
||||
const val RefreshDevice = "refreshDevice"
|
||||
const val DeleteDevice = "deleteDevice"
|
||||
const val DeletePet = "deletePet"
|
||||
const val RefreshPet = "refreshPet"
|
||||
const val RefreshPackage = "refreshPackage"
|
||||
const val SetGoal = "setGoal"
|
||||
const val DeleteFamily = "deleteFamily"
|
||||
const val RefreshFamily = "refreshFamily"
|
||||
const val RefreshWiFiZone = "refreshWiFiZone"
|
||||
const val DeleteWiFiZone = "deleteWiFiZone"
|
||||
const val LogOut = "logOut"
|
||||
|
||||
//支付成功返回
|
||||
const val PayBack = "payBack"
|
||||
const val HistoryDataSnapshot = "historyDataSnapshot"
|
||||
const val ActiveGoal = "activeGoal"
|
||||
const val UpdateReportInterval = "updateReportInterval"
|
||||
|
||||
//已找到设备,正在去连接或已经连接上的设备状态
|
||||
const val ConnectDeviceState = "connectDeviceState"
|
||||
|
||||
//去搜索查找连接设备,这种操作设备状态
|
||||
const val ActionConDeviceState = "actionConDeviceState"
|
||||
|
||||
//蓝牙开关切换
|
||||
const val BluetoothSwitch = "bluetoothSwitch"
|
||||
|
||||
//gps开关状态
|
||||
const val GPSSwitchState = "gpsSwitchState"
|
||||
|
||||
//直播自动结束
|
||||
const val LiveAutoEnd = "liveAutoEnd"
|
||||
const val LiveOpenTimeOut = "liveOpenTimeOut"
|
||||
|
||||
//接收到设备返回的数据
|
||||
const val DeviceReceiveData = "deviceReceiveData"
|
||||
|
||||
//微信登录成功返回
|
||||
const val WXLoginResult = "wxLoginResult"
|
||||
|
||||
//修改密码成功返回
|
||||
const val ChangePassword = "changePassword"
|
||||
|
||||
//蓝牙上报数据
|
||||
const val BleReport = "bleReport"
|
||||
|
||||
//蓝牙上报的数据
|
||||
const val BleReportData = "bleReportData"
|
||||
|
||||
//日志上报数据
|
||||
const val LogReport = "logReport"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
package com.abbidot.baselibrary.constant
|
||||
|
||||
import androidx.annotation.StringDef
|
||||
|
||||
/**
|
||||
* 存储SharedPreferences的key,枚举常量
|
||||
*/
|
||||
@StringDef(
|
||||
MMKVKey.UserId,
|
||||
MMKVKey.Email,
|
||||
MMKVKey.UserName,
|
||||
MMKVKey.HeadUrl,
|
||||
MMKVKey.Phone,
|
||||
MMKVKey.Location,
|
||||
MMKVKey.Gender,
|
||||
MMKVKey.BirthdayDate,
|
||||
MMKVKey.PetSelectPosition,
|
||||
MMKVKey.Token,
|
||||
MMKVKey.LineSelectPosition,
|
||||
MMKVKey.isBindDevice,
|
||||
MMKVKey.isBindPet,
|
||||
MMKVKey.DebugIp,
|
||||
MMKVKey.FirstOpen,
|
||||
MMKVKey.FirstDeviceId,
|
||||
MMKVKey.FirstDeviceOutId,
|
||||
MMKVKey.FirstDeviceMac,
|
||||
MMKVKey.HeightUnit,
|
||||
MMKVKey.WeightUnit,
|
||||
MMKVKey.MeasureUnit,
|
||||
MMKVKey.ActivityGoal,
|
||||
MMKVKey.CountryCode,
|
||||
MMKVKey.MapShowDefaultLat,
|
||||
MMKVKey.MapShowDefaultLon,
|
||||
MMKVKey.MealType,
|
||||
MMKVKey.isExistNewInvite,
|
||||
MMKVKey.OnlyGoogleMap,
|
||||
MMKVKey.MapType,
|
||||
MMKVKey.ShowFence,
|
||||
MMKVKey.Shared
|
||||
)
|
||||
@Retention(AnnotationRetention.SOURCE)
|
||||
annotation class MMKVKey {
|
||||
companion object {
|
||||
// const val SPName = "abbidot_sp"
|
||||
const val UserId = "userId"
|
||||
const val Token = "token"
|
||||
const val UserName = "username"
|
||||
const val Email = "email"
|
||||
const val HeadUrl = "imgurl"
|
||||
const val isBindDevice = "isBindDevice"
|
||||
const val isBindPet = "isBindPet"
|
||||
const val isExistNewInvite = "isExistNewInvite"
|
||||
const val Phone = "phone"
|
||||
const val ActivityGoal = "activityGoal"
|
||||
const val Location = "location"
|
||||
const val Gender = "gender"
|
||||
const val BirthdayDate = "birthdayDate"
|
||||
const val CountryCode = "countryCode"
|
||||
|
||||
//首次绑定流程
|
||||
const val FirstDeviceId = "firstDeviceId"
|
||||
const val FirstDeviceOutId = "firstDeviceOutId"
|
||||
const val FirstDeviceMac = "firstDeviceMac"
|
||||
const val HeightUnit = "heightUnit"
|
||||
const val WeightUnit = "weightUnit"
|
||||
|
||||
//1:Imperial 2:Metric
|
||||
const val MeasureUnit = "measureUnit"
|
||||
|
||||
//保存首页弹窗选中的宠物下标
|
||||
const val PetSelectPosition = "petSelectPosition"
|
||||
|
||||
//保存历史记录展示线还是点的类型
|
||||
const val LineSelectPosition = "lineSelectPosition"
|
||||
|
||||
//国内外服务器端口切换
|
||||
const val DebugIp = "debugIp"
|
||||
|
||||
//只使用谷歌地图
|
||||
const val OnlyGoogleMap = "onlyGoogleMap"
|
||||
const val MapShowDefaultLat = "mapDefaultLat"
|
||||
const val MapShowDefaultLon = "mapDefaultLon"
|
||||
|
||||
//地图模式类型
|
||||
const val MapType = "mapType"
|
||||
|
||||
//map页是否显示围栏
|
||||
const val ShowFence = "isShowFence"
|
||||
|
||||
//是首次打开APP
|
||||
const val FirstOpen = "firstOpen"
|
||||
|
||||
//套餐类型
|
||||
const val MealType = "mealType"
|
||||
|
||||
//是否分享的
|
||||
const val Shared = "shared"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.abbidot.baselibrary.constant
|
||||
|
||||
import androidx.annotation.IntDef
|
||||
|
||||
/**
|
||||
*Created by .yzq on 2022/3/4/004.
|
||||
* @link
|
||||
* @description:
|
||||
*/
|
||||
@IntDef(ResultCode.ResultCode_1, ResultCode.ResultCode_2, ResultCode.ResultCode_3,
|
||||
ResultCode.ResultCode_4, ResultCode.ResultCode_5)
|
||||
@Retention(AnnotationRetention.SOURCE)
|
||||
annotation class ResultCode() {
|
||||
companion object {
|
||||
const val ResultCode_1 = 1
|
||||
const val ResultCode_2 = 2
|
||||
const val ResultCode_3 = 3
|
||||
const val ResultCode_4 = 4
|
||||
const val ResultCode_5 = 5
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package com.abbidot.baselibrary.eventbus
|
||||
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.Observer
|
||||
import com.abbidot.baselibrary.constant.EventName
|
||||
import com.abbidot.baselibrary.eventbus.core.EmptyMessage
|
||||
import com.abbidot.baselibrary.eventbus.core.EventLiveData
|
||||
|
||||
object XEventBus {
|
||||
|
||||
private val channels = HashMap<String, EventLiveData<*>>()
|
||||
|
||||
private fun <T> with(@EventName eventName: String): EventLiveData<T> {
|
||||
synchronized(channels) {
|
||||
if (!channels.containsKey(eventName)) {
|
||||
channels[eventName] = EventLiveData<T>()
|
||||
}
|
||||
return (channels[eventName] as EventLiveData<T>)
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> post(@EventName eventName: String, message: T) {
|
||||
val eventLiveData = with<T>(eventName)
|
||||
eventLiveData.postValue(message!!)
|
||||
}
|
||||
|
||||
fun <T> observe(owner: LifecycleOwner,
|
||||
@EventName eventName: String,
|
||||
sticky: Boolean = false,
|
||||
observer: Observer<T>) {
|
||||
with<T>(eventName).observe(owner, sticky, observer)
|
||||
}
|
||||
|
||||
fun post(@EventName eventName: String) {
|
||||
val eventLiveData = with<EmptyMessage>(eventName)
|
||||
eventLiveData.postValue(EmptyMessage)
|
||||
}
|
||||
|
||||
/**
|
||||
* 空Message调用
|
||||
*/
|
||||
fun observe(owner: LifecycleOwner,
|
||||
@EventName eventName: String,
|
||||
sticky: Boolean = false,
|
||||
observer: () -> Unit) {
|
||||
with<EmptyMessage>(eventName).observe(owner, sticky) {
|
||||
observer()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package com.abbidot.baselibrary.eventbus.core
|
||||
|
||||
object EmptyMessage {
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.abbidot.baselibrary.eventbus.core
|
||||
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.Observer
|
||||
|
||||
|
||||
class EventLiveData<T> : MutableLiveData<T>() {
|
||||
|
||||
fun observe(owner: LifecycleOwner, sticky: Boolean, observer: Observer<in T>) {
|
||||
observe(owner, wrapObserver(sticky, observer))
|
||||
}
|
||||
|
||||
private fun wrapObserver(sticky: Boolean, observer: Observer<in T>): Observer<T> {
|
||||
return EventObserverWrapper(this, sticky, observer)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.abbidot.baselibrary.eventbus.core
|
||||
|
||||
import androidx.lifecycle.Observer
|
||||
import com.abbidot.baselibrary.util.reflect.ReflectHelper
|
||||
|
||||
class EventObserverWrapper<T>(liveData: EventLiveData<T>,
|
||||
sticky: Boolean,
|
||||
private val observerDelegate: Observer<in T>) : Observer<T> {
|
||||
|
||||
private var preventNextEvent = false
|
||||
|
||||
companion object {
|
||||
private const val START_VERSION = -1
|
||||
}
|
||||
|
||||
init {
|
||||
if (!sticky) {
|
||||
val version = ReflectHelper.of(liveData).getField("mVersion") as? Int ?: START_VERSION
|
||||
preventNextEvent = version > START_VERSION
|
||||
}
|
||||
}
|
||||
|
||||
override fun onChanged(t: T) {
|
||||
if (preventNextEvent) {
|
||||
preventNextEvent = false
|
||||
return
|
||||
}
|
||||
observerDelegate.onChanged(t)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,495 @@
|
||||
/*
|
||||
* Copyright (C) 2017 The Android Open Source Project
|
||||
*
|
||||
* 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.abbidot.baselibrary.eventbus.core;
|
||||
|
||||
import androidx.annotation.MainThread;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.arch.core.executor.ArchTaskExecutor;
|
||||
import androidx.arch.core.internal.SafeIterableMap;
|
||||
import androidx.lifecycle.Lifecycle;
|
||||
import androidx.lifecycle.LifecycleEventObserver;
|
||||
import androidx.lifecycle.LifecycleOwner;
|
||||
import androidx.lifecycle.Observer;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
import static androidx.lifecycle.Lifecycle.State.DESTROYED;
|
||||
import static androidx.lifecycle.Lifecycle.State.STARTED;
|
||||
|
||||
/**
|
||||
* LiveData is a data holder class that can be observed within a given lifecycle.
|
||||
* This means that an {@link Observer} can be added in a pair with a {@link LifecycleOwner}, and
|
||||
* this observer will be notified about modifications of the wrapped data only if the paired
|
||||
* LifecycleOwner is in active state. LifecycleOwner is considered as active, if its state is
|
||||
* {@link Lifecycle.State#STARTED} or {@link Lifecycle.State#RESUMED}. An observer added via
|
||||
* {@link #observeForever(Observer)} is considered as always active and thus will be always notified
|
||||
* about modifications. For those observers, you should manually call
|
||||
* {@link #removeObserver(Observer)}.
|
||||
*
|
||||
* <p> An observer added with a Lifecycle will be automatically removed if the corresponding
|
||||
* Lifecycle moves to {@link Lifecycle.State#DESTROYED} state. This is especially useful for
|
||||
* activities and fragments where they can safely observe LiveData and not worry about leaks:
|
||||
* they will be instantly unsubscribed when they are destroyed.
|
||||
*
|
||||
* <p>
|
||||
* In addition, LiveData has {@link LiveData#onActive()} and {@link LiveData#onInactive()} methods
|
||||
* to get notified when number of active {@link Observer}s change between 0 and 1.
|
||||
* This allows LiveData to release any heavy resources when it does not have any Observers that
|
||||
* are actively observing.
|
||||
* <p>
|
||||
* This class is designed to hold individual data fields of {@link ViewModel},
|
||||
* but can also be used for sharing data between different modules in your application
|
||||
* in a decoupled fashion.
|
||||
*
|
||||
* @param <T> The type of data held by this instance
|
||||
* @see ViewModel
|
||||
*/
|
||||
public abstract class LiveData<T> {
|
||||
@SuppressWarnings("WeakerAccess") /* synthetic access */ final Object mDataLock = new Object();
|
||||
static final int START_VERSION = -1;
|
||||
@SuppressWarnings(
|
||||
"WeakerAccess") /* synthetic access */ static final Object NOT_SET = new Object();
|
||||
|
||||
private SafeIterableMap<Observer<? super T>, ObserverWrapper> mObservers =
|
||||
new SafeIterableMap<>();
|
||||
|
||||
// how many observers are in active state
|
||||
@SuppressWarnings("WeakerAccess") /* synthetic access */ int mActiveCount = 0;
|
||||
// to handle active/inactive reentry, we guard with this boolean
|
||||
private boolean mChangingActiveState;
|
||||
private volatile Object mData;
|
||||
// when setData is called, we set the pending data and actual data swap happens on the main
|
||||
// thread
|
||||
@SuppressWarnings("WeakerAccess") /* synthetic access */ volatile Object mPendingData = NOT_SET;
|
||||
private int mVersion;
|
||||
|
||||
private boolean mDispatchingValue;
|
||||
@SuppressWarnings("FieldCanBeLocal")
|
||||
private boolean mDispatchInvalidated;
|
||||
private final Runnable mPostValueRunnable = new Runnable() {
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void run() {
|
||||
Object newValue;
|
||||
synchronized (mDataLock) {
|
||||
newValue = mPendingData;
|
||||
mPendingData = NOT_SET;
|
||||
}
|
||||
setValue((T) newValue);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a LiveData initialized with the given {@code value}.
|
||||
*
|
||||
* @param value initial value
|
||||
*/
|
||||
public LiveData(T value) {
|
||||
mData = value;
|
||||
mVersion = START_VERSION + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a LiveData with no value assigned to it.
|
||||
*/
|
||||
public LiveData() {
|
||||
mData = NOT_SET;
|
||||
mVersion = START_VERSION;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void considerNotify(ObserverWrapper observer) {
|
||||
/* 修改源码,实现后台收消息功能
|
||||
if (!observer.mActive) {
|
||||
return;
|
||||
}
|
||||
// Check latest state b4 dispatch. Maybe it changed state but we didn't get the event yet.
|
||||
//
|
||||
// we still first check observer.active to keep it as the entrance for events. So even if
|
||||
// the observer moved to an active state, if we've not received that event, we better not
|
||||
// notify for a more predictable notification order.
|
||||
if (!observer.shouldBeActive()) {
|
||||
observer.activeStateChanged(false);
|
||||
return;
|
||||
}
|
||||
*/
|
||||
|
||||
if (observer.mLastVersion >= mVersion) {
|
||||
return;
|
||||
}
|
||||
observer.mLastVersion = mVersion;
|
||||
observer.mObserver.onChanged((T) mData);
|
||||
}
|
||||
|
||||
@SuppressWarnings("WeakerAccess") /* synthetic access */
|
||||
void dispatchingValue(@Nullable ObserverWrapper initiator) {
|
||||
if (mDispatchingValue) {
|
||||
mDispatchInvalidated = true;
|
||||
return;
|
||||
}
|
||||
mDispatchingValue = true;
|
||||
do {
|
||||
mDispatchInvalidated = false;
|
||||
if (initiator != null) {
|
||||
considerNotify(initiator);
|
||||
initiator = null;
|
||||
} else {
|
||||
for (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>> iterator =
|
||||
mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
|
||||
considerNotify(iterator.next().getValue());
|
||||
if (mDispatchInvalidated) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (mDispatchInvalidated);
|
||||
mDispatchingValue = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given observer to the observers list within the lifespan of the given
|
||||
* owner. The events are dispatched on the main thread. If LiveData already has data
|
||||
* set, it will be delivered to the observer.
|
||||
* <p>
|
||||
* The observer will only receive events if the owner is in {@link Lifecycle.State#STARTED}
|
||||
* or {@link Lifecycle.State#RESUMED} state (active).
|
||||
* <p>
|
||||
* If the owner moves to the {@link Lifecycle.State#DESTROYED} state, the observer will
|
||||
* automatically be removed.
|
||||
* <p>
|
||||
* When data changes while the {@code owner} is not active, it will not receive any updates.
|
||||
* If it becomes active again, it will receive the last available data automatically.
|
||||
* <p>
|
||||
* LiveData keeps a strong reference to the observer and the owner as long as the
|
||||
* given LifecycleOwner is not destroyed. When it is destroyed, LiveData removes references to
|
||||
* the observer & the owner.
|
||||
* <p>
|
||||
* If the given owner is already in {@link Lifecycle.State#DESTROYED} state, LiveData
|
||||
* ignores the call.
|
||||
* <p>
|
||||
* If the given owner, observer tuple is already in the list, the call is ignored.
|
||||
* If the observer is already in the list with another owner, LiveData throws an
|
||||
* {@link IllegalArgumentException}.
|
||||
*
|
||||
* @param owner The LifecycleOwner which controls the observer
|
||||
* @param observer The observer that will receive the events
|
||||
*/
|
||||
@MainThread
|
||||
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
|
||||
assertMainThread("observe");
|
||||
if (owner.getLifecycle().getCurrentState() == DESTROYED) {
|
||||
// ignore
|
||||
return;
|
||||
}
|
||||
LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
|
||||
ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
|
||||
if (existing != null && !existing.isAttachedTo(owner)) {
|
||||
throw new IllegalArgumentException("Cannot add the same observer" + " with different "
|
||||
+ "lifecycles");
|
||||
}
|
||||
if (existing != null) {
|
||||
return;
|
||||
}
|
||||
owner.getLifecycle().addObserver(wrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given observer to the observers list. This call is similar to
|
||||
* {@link LiveData#observe(LifecycleOwner, Observer)} with a LifecycleOwner, which
|
||||
* is always active. This means that the given observer will receive all events and will never
|
||||
* be automatically removed. You should manually call {@link #removeObserver(Observer)} to stop
|
||||
* observing this LiveData.
|
||||
* While LiveData has one of such observers, it will be considered
|
||||
* as active.
|
||||
* <p>
|
||||
* If the observer was already added with an owner to this LiveData, LiveData throws an
|
||||
* {@link IllegalArgumentException}.
|
||||
*
|
||||
* @param observer The observer that will receive the events
|
||||
*/
|
||||
@MainThread
|
||||
public void observeForever(@NonNull Observer<? super T> observer) {
|
||||
assertMainThread("observeForever");
|
||||
AlwaysActiveObserver wrapper = new AlwaysActiveObserver(observer);
|
||||
ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
|
||||
if (existing instanceof LiveData.LifecycleBoundObserver) {
|
||||
throw new IllegalArgumentException("Cannot add the same observer" + " with different "
|
||||
+ "lifecycles");
|
||||
}
|
||||
if (existing != null) {
|
||||
return;
|
||||
}
|
||||
wrapper.activeStateChanged(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the given observer from the observers list.
|
||||
*
|
||||
* @param observer The Observer to receive events.
|
||||
*/
|
||||
@MainThread
|
||||
public void removeObserver(@NonNull final Observer<? super T> observer) {
|
||||
assertMainThread("removeObserver");
|
||||
ObserverWrapper removed = mObservers.remove(observer);
|
||||
if (removed == null) {
|
||||
return;
|
||||
}
|
||||
removed.detachObserver();
|
||||
removed.activeStateChanged(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all observers that are tied to the given {@link LifecycleOwner}.
|
||||
*
|
||||
* @param owner The {@code LifecycleOwner} scope for the observers to be removed.
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
@MainThread
|
||||
public void removeObservers(@NonNull final LifecycleOwner owner) {
|
||||
assertMainThread("removeObservers");
|
||||
for (Map.Entry<Observer<? super T>, ObserverWrapper> entry : mObservers) {
|
||||
if (entry.getValue().isAttachedTo(owner)) {
|
||||
removeObserver(entry.getKey());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Posts a task to a main thread to set the given value. So if you have a following code
|
||||
* executed in the main thread:
|
||||
* <pre class="prettyprint">
|
||||
* liveData.postValue("a");
|
||||
* liveData.setValue("b");
|
||||
* </pre>
|
||||
* The value "b" would be set at first and later the main thread would override it with
|
||||
* the value "a".
|
||||
* <p>
|
||||
* If you called this method multiple times before a main thread executed a posted task, only
|
||||
* the last value would be dispatched.
|
||||
*
|
||||
* @param value The new value
|
||||
*/
|
||||
protected void postValue(T value) {
|
||||
boolean postTask;
|
||||
synchronized (mDataLock) {
|
||||
postTask = mPendingData == NOT_SET;
|
||||
mPendingData = value;
|
||||
}
|
||||
if (!postTask) {
|
||||
return;
|
||||
}
|
||||
ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value. If there are active observers, the value will be dispatched to them.
|
||||
* <p>
|
||||
* This method must be called from the main thread. If you need set a value from a background
|
||||
* thread, you can use {@link #postValue(Object)}
|
||||
*
|
||||
* @param value The new value
|
||||
*/
|
||||
@MainThread
|
||||
protected void setValue(T value) {
|
||||
assertMainThread("setValue");
|
||||
mVersion++;
|
||||
mData = value;
|
||||
dispatchingValue(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current value.
|
||||
* Note that calling this method on a background thread does not guarantee that the latest
|
||||
* value set will be received.
|
||||
*
|
||||
* @return the current value
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@Nullable
|
||||
public T getValue() {
|
||||
Object data = mData;
|
||||
if (data != NOT_SET) {
|
||||
return (T) data;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
int getVersion() {
|
||||
return mVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the number of active observers change from 0 to 1.
|
||||
* <p>
|
||||
* This callback can be used to know that this LiveData is being used thus should be kept
|
||||
* up to date.
|
||||
*/
|
||||
protected void onActive() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the number of active observers change from 1 to 0.
|
||||
* <p>
|
||||
* This does not mean that there are no observers left, there may still be observers but their
|
||||
* lifecycle states aren't {@link Lifecycle.State#STARTED} or {@link Lifecycle.State#RESUMED}
|
||||
* (like an Activity in the back stack).
|
||||
* <p>
|
||||
* You can check if there are observers via {@link #hasObservers()}.
|
||||
*/
|
||||
protected void onInactive() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this LiveData has observers.
|
||||
*
|
||||
* @return true if this LiveData has observers
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public boolean hasObservers() {
|
||||
return mObservers.size() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this LiveData has active observers.
|
||||
*
|
||||
* @return true if this LiveData has active observers
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public boolean hasActiveObservers() {
|
||||
return mActiveCount > 0;
|
||||
}
|
||||
|
||||
@MainThread
|
||||
void changeActiveCounter(int change) {
|
||||
int previousActiveCount = mActiveCount;
|
||||
mActiveCount += change;
|
||||
if (mChangingActiveState) {
|
||||
return;
|
||||
}
|
||||
mChangingActiveState = true;
|
||||
try {
|
||||
while (previousActiveCount != mActiveCount) {
|
||||
boolean needToCallActive = previousActiveCount == 0 && mActiveCount > 0;
|
||||
boolean needToCallInactive = previousActiveCount > 0 && mActiveCount == 0;
|
||||
previousActiveCount = mActiveCount;
|
||||
if (needToCallActive) {
|
||||
onActive();
|
||||
} else if (needToCallInactive) {
|
||||
onInactive();
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
mChangingActiveState = false;
|
||||
}
|
||||
}
|
||||
|
||||
class LifecycleBoundObserver extends ObserverWrapper implements LifecycleEventObserver {
|
||||
@NonNull
|
||||
final LifecycleOwner mOwner;
|
||||
|
||||
LifecycleBoundObserver(@NonNull LifecycleOwner owner, Observer<? super T> observer) {
|
||||
super(observer);
|
||||
mOwner = owner;
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean shouldBeActive() {
|
||||
return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event) {
|
||||
Lifecycle.State currentState = mOwner.getLifecycle().getCurrentState();
|
||||
if (currentState == DESTROYED) {
|
||||
removeObserver(mObserver);
|
||||
return;
|
||||
}
|
||||
Lifecycle.State prevState = null;
|
||||
while (prevState != currentState) {
|
||||
prevState = currentState;
|
||||
activeStateChanged(shouldBeActive());
|
||||
currentState = mOwner.getLifecycle().getCurrentState();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isAttachedTo(LifecycleOwner owner) {
|
||||
return mOwner == owner;
|
||||
}
|
||||
|
||||
@Override
|
||||
void detachObserver() {
|
||||
mOwner.getLifecycle().removeObserver(this);
|
||||
}
|
||||
}
|
||||
|
||||
private abstract class ObserverWrapper {
|
||||
final Observer<? super T> mObserver;
|
||||
boolean mActive;
|
||||
int mLastVersion = START_VERSION;
|
||||
|
||||
ObserverWrapper(Observer<? super T> observer) {
|
||||
mObserver = observer;
|
||||
}
|
||||
|
||||
abstract boolean shouldBeActive();
|
||||
|
||||
boolean isAttachedTo(LifecycleOwner owner) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void detachObserver() {
|
||||
}
|
||||
|
||||
void activeStateChanged(boolean newActive) {
|
||||
if (newActive == mActive) {
|
||||
return;
|
||||
}
|
||||
// immediately set active state, so we'd never dispatch anything to inactive
|
||||
// owner
|
||||
mActive = newActive;
|
||||
changeActiveCounter(mActive ? 1 : -1);
|
||||
if (mActive) {
|
||||
dispatchingValue(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class AlwaysActiveObserver extends ObserverWrapper {
|
||||
|
||||
AlwaysActiveObserver(Observer<? super T> observer) {
|
||||
super(observer);
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean shouldBeActive() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
static void assertMainThread(String methodName) {
|
||||
if (!ArchTaskExecutor.getInstance().isMainThread()) {
|
||||
throw new IllegalStateException("Cannot invoke " + methodName + " on a background" +
|
||||
" thread");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (C) 2017 The Android Open Source Project
|
||||
*
|
||||
* 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.abbidot.baselibrary.eventbus.core;
|
||||
|
||||
/**
|
||||
* {@link LiveData} which publicly exposes {@link #setValue(T)} and {@link #postValue(T)} method.
|
||||
*
|
||||
* @param <T> The type of data hold by this instance
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public class MutableLiveData<T> extends LiveData<T> {
|
||||
|
||||
/**
|
||||
* Creates a MutableLiveData initialized with the given {@code value}.
|
||||
*
|
||||
* @param value initial value
|
||||
*/
|
||||
public MutableLiveData(T value) {
|
||||
super(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a MutableLiveData with no value assigned to it.
|
||||
*/
|
||||
public MutableLiveData() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postValue(T value) {
|
||||
super.postValue(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(T value) {
|
||||
super.setValue(value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
/*
|
||||
* Tencent is pleased to support the open source community by making QMUI_Android available.
|
||||
*
|
||||
* Copyright (C) 2017-2018 THL A29 Limited, a Tencent company. All rights reserved.
|
||||
*
|
||||
* Licensed under the MIT License (the "License"); you may not use this file except in
|
||||
* compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://opensource.org/licenses/MIT
|
||||
*
|
||||
* 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.abbidot.baselibrary.list
|
||||
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
|
||||
/**
|
||||
* @author cginechen
|
||||
* @date 2016-10-19
|
||||
* @description 配合DiffUtil使用取消notifyDataSetChanged()方法
|
||||
*/
|
||||
abstract class BaseRecyclerAdapter<T>(ctx: Context, list: MutableList<T>?) :
|
||||
RecyclerView.Adapter<RecyclerViewHolder>() {
|
||||
|
||||
private var mData: MutableList<T> = mutableListOf()
|
||||
val mContext: Context
|
||||
private val mInflater: LayoutInflater
|
||||
private var mClickListener: OnItemClickListener? = null
|
||||
private var mLongClickListener: OnItemLongClickListener? = null
|
||||
|
||||
//是否显示没有数据的添加按钮
|
||||
var isShowNoDataAddButton = true
|
||||
|
||||
// 是否显示空布局,默认显示
|
||||
private var showEmptyView = true
|
||||
|
||||
//重试事件,整个view点击
|
||||
private var mAgainClickListener: OnAgainClickListener? = null
|
||||
|
||||
/**
|
||||
* viewType--分别为item以及空view
|
||||
*/
|
||||
private val mViewTypeEmpty = 0
|
||||
private val mViewTypeItem = 1
|
||||
|
||||
init {
|
||||
list?.apply {
|
||||
mData.addAll(this.toList())
|
||||
}
|
||||
mContext = ctx
|
||||
mInflater = LayoutInflater.from(ctx)
|
||||
}
|
||||
|
||||
open fun setData(list: MutableList<T>?, isNeedNotifyData: Boolean = false) {
|
||||
clearData()
|
||||
if (null == list) {
|
||||
if (mData.size == 0) {
|
||||
showEmptyView(true)
|
||||
} else {
|
||||
showEmptyView(false)
|
||||
}
|
||||
}
|
||||
list?.apply {
|
||||
mData.addAll(list)
|
||||
//出现没有数据就显示空布局
|
||||
if (mData.size == 0) {
|
||||
showEmptyView(true)
|
||||
} else {
|
||||
showEmptyView(false)
|
||||
}
|
||||
if (isNeedNotifyData) {
|
||||
notifyItemRangeInserted(0, list.size)
|
||||
// notifyItemRangeChanged(0, list.size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun clearData() {
|
||||
notifyItemRangeRemoved(0, mData.size)
|
||||
mData.clear()
|
||||
}
|
||||
|
||||
fun getData() = mData
|
||||
|
||||
fun remove(oldList: MutableList<T>, pos: Int): MutableList<T> {
|
||||
val newList: MutableList<T> = oldList.toMutableList()
|
||||
newList.removeAt(pos)
|
||||
return newList
|
||||
}
|
||||
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerViewHolder {
|
||||
val holder: RecyclerViewHolder
|
||||
//空布局
|
||||
if (getEmptyLayoutId(viewType) != 0 && viewType == mViewTypeEmpty) {
|
||||
holder = RecyclerViewHolder(
|
||||
mContext, mInflater.inflate(getEmptyLayoutId(viewType), parent, false)
|
||||
)
|
||||
if (null != mAgainClickListener) {
|
||||
holder.itemView.setOnClickListener {
|
||||
mAgainClickListener!!.onAgainListener()
|
||||
}
|
||||
}
|
||||
mEmptyViewHolder = holder
|
||||
} else {
|
||||
holder = RecyclerViewHolder(
|
||||
mContext, mInflater.inflate(getItemLayoutId(viewType), parent, false)
|
||||
)
|
||||
if (null != mClickListener) {
|
||||
holder.itemView.setOnClickListener {
|
||||
mClickListener!!.onItemClick(holder.itemView, holder.layoutPosition)
|
||||
}
|
||||
}
|
||||
if (null != mLongClickListener) {
|
||||
holder.itemView.setOnLongClickListener {
|
||||
mLongClickListener!!.onItemLongClick(holder.itemView, holder.layoutPosition)
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
return holder
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: RecyclerViewHolder, position: Int) {
|
||||
if (mData.size > 0) bindData(holder, position, mData[position])
|
||||
else noData(holder)
|
||||
}
|
||||
|
||||
private var mEmptyViewHolder: RecyclerViewHolder? = null
|
||||
|
||||
override fun onBindViewHolder(
|
||||
holder: RecyclerViewHolder, position: Int, payloads: MutableList<Any>
|
||||
) {
|
||||
if (payloads.isEmpty()) {
|
||||
onBindViewHolder(holder, position)
|
||||
} else {
|
||||
partBindData(holder, position, payloads[0] as T)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否是空布局
|
||||
*/
|
||||
private fun isEmptyPosition(position: Int): Boolean {
|
||||
val count = mData.size
|
||||
return position == 0 && showEmptyView && count == 0
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置空布局显示。默认显示
|
||||
*/
|
||||
private fun showEmptyView(isShow: Boolean) {
|
||||
if (isShow != showEmptyView) {
|
||||
showEmptyView = isShow
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
fun getItem(pos: Int) = mData[pos]
|
||||
|
||||
//只有空布局默认才有一个item
|
||||
override fun getItemCount() =
|
||||
if (mData.size == 0 && getEmptyLayoutId(mViewTypeEmpty) != 0) 1 else mData.size
|
||||
|
||||
override fun getItemViewType(position: Int) =
|
||||
if (isEmptyPosition(position)) mViewTypeEmpty else mViewTypeItem
|
||||
|
||||
fun add(pos: Int, item: T) {
|
||||
mData.add(pos, item)
|
||||
notifyItemInserted(pos)
|
||||
}
|
||||
|
||||
fun prepend(items: List<T>) {
|
||||
mData.addAll(0, items)
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
fun append(items: List<T>) {
|
||||
mData.addAll(items)
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
fun delete(pos: Int) {
|
||||
mData.removeAt(pos)
|
||||
notifyItemRemoved(pos)
|
||||
}
|
||||
|
||||
fun showNoDataAddButton(show: Boolean) {
|
||||
isShowNoDataAddButton = show
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置显示空布局 等于0不显示空布局
|
||||
*/
|
||||
abstract fun getEmptyLayoutId(viewType: Int): Int
|
||||
abstract fun getItemLayoutId(viewType: Int): Int
|
||||
abstract fun bindData(holder: RecyclerViewHolder?, position: Int, item: T)
|
||||
|
||||
//没有数据,空布局的情况
|
||||
open fun noData(holder: RecyclerViewHolder?) {
|
||||
|
||||
}
|
||||
|
||||
//局部刷新数据
|
||||
open fun partBindData(holder: RecyclerViewHolder, position: Int, item: T) {
|
||||
bindData(holder, position, mData[position])
|
||||
}
|
||||
|
||||
fun setOnItemClickListener(listener: OnItemClickListener?) {
|
||||
mClickListener = listener
|
||||
}
|
||||
|
||||
fun setOnItemLongClickListener(listener: OnItemLongClickListener?) {
|
||||
mLongClickListener = listener
|
||||
}
|
||||
|
||||
fun setOnAgainClickListener(listener: OnAgainClickListener?) {
|
||||
mAgainClickListener = listener
|
||||
}
|
||||
|
||||
interface OnItemClickListener {
|
||||
fun onItemClick(itemView: View?, pos: Int)
|
||||
}
|
||||
|
||||
interface OnItemLongClickListener {
|
||||
fun onItemLongClick(itemView: View?, pos: Int)
|
||||
}
|
||||
|
||||
/**
|
||||
* 点击屏幕重试 接口
|
||||
*/
|
||||
interface OnAgainClickListener {
|
||||
/**
|
||||
* 点击屏幕重试
|
||||
*/
|
||||
fun onAgainListener()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Tencent is pleased to support the open source community by making QMUI_Android available.
|
||||
*
|
||||
* Copyright (C) 2017-2018 THL A29 Limited, a Tencent company. All rights reserved.
|
||||
*
|
||||
* Licensed under the MIT License (the "License"); you may not use this file except in
|
||||
* compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://opensource.org/licenses/MIT
|
||||
*
|
||||
* 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.abbidot.baselibrary.list
|
||||
|
||||
import android.content.Context
|
||||
import android.util.SparseArray
|
||||
import android.view.View
|
||||
import android.widget.Button
|
||||
import android.widget.EditText
|
||||
import android.widget.ImageButton
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
|
||||
/**
|
||||
* @author cginechen
|
||||
* @date 2016-10-19
|
||||
*/
|
||||
class RecyclerViewHolder(ctx: Context?, itemView: View?) : RecyclerView.ViewHolder(itemView!!) {
|
||||
|
||||
private val mViews: SparseArray<View?> = SparseArray()
|
||||
|
||||
private fun <T : View> findViewById(viewId: Int): T {
|
||||
var view = mViews[viewId]
|
||||
if (view == null) {
|
||||
view = itemView.findViewById(viewId)
|
||||
mViews.put(viewId, view)
|
||||
}
|
||||
return view as T
|
||||
}
|
||||
|
||||
fun getView(viewId: Int): View {
|
||||
return findViewById(viewId)
|
||||
}
|
||||
|
||||
fun getTextView(viewId: Int): TextView {
|
||||
return getView(viewId) as TextView
|
||||
}
|
||||
|
||||
fun getButton(viewId: Int): Button {
|
||||
return getView(viewId) as Button
|
||||
}
|
||||
|
||||
fun getImageView(viewId: Int): ImageView {
|
||||
return getView(viewId) as ImageView
|
||||
}
|
||||
|
||||
fun getImageButton(viewId: Int): ImageButton {
|
||||
return getView(viewId) as ImageButton
|
||||
}
|
||||
|
||||
fun getEditText(viewId: Int): EditText {
|
||||
return getView(viewId) as EditText
|
||||
}
|
||||
|
||||
fun setText(viewId: Int, value: String?): RecyclerViewHolder {
|
||||
val view = findViewById<TextView>(viewId)
|
||||
view.text = value
|
||||
return this
|
||||
}
|
||||
|
||||
fun setBackground(viewId: Int, resId: Int): RecyclerViewHolder {
|
||||
val view = findViewById<View>(viewId)
|
||||
view.setBackgroundResource(resId)
|
||||
return this
|
||||
}
|
||||
|
||||
fun setClickListener(viewId: Int, listener: View.OnClickListener?): RecyclerViewHolder {
|
||||
val view = findViewById<View>(viewId)
|
||||
view.setOnClickListener(listener)
|
||||
return this
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
package com.abbidot.baselibrary.network.base
|
||||
|
||||
import com.abbidot.baselibrary.network.exception.ErrorCode
|
||||
import com.abbidot.baselibrary.network.exception.NetworkErrorCodeException
|
||||
import com.abbidot.baselibrary.network.exception.NetworkException
|
||||
import com.abbidot.baselibrary.network.interceptor.CommonRequestInterceptor
|
||||
import com.abbidot.baselibrary.network.interceptor.CommonResponseInterceptor
|
||||
import com.abbidot.baselibrary.network.interceptor.DoubleDefault0Adapter
|
||||
import com.abbidot.baselibrary.network.interceptor.StringNullAdapter
|
||||
import com.abbidot.baselibrary.util.AppUtils
|
||||
import com.abbidot.baselibrary.util.LogUtil
|
||||
import com.google.gson.GsonBuilder
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import retrofit2.HttpException
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.converter.gson.GsonConverterFactory
|
||||
import java.lang.reflect.ParameterizedType
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
/**
|
||||
* 网络请求封装
|
||||
*/
|
||||
abstract class BaseNetworkApi<I>(private val baseUrl: String) : IService<I> {
|
||||
|
||||
protected val service: I by lazy {
|
||||
getRetrofit().create(getServiceClass())
|
||||
}
|
||||
|
||||
protected open fun getRetrofit(): Retrofit {
|
||||
//添加gson解析null拦截器
|
||||
val gson = GsonBuilder().registerTypeAdapter(String::class.java, StringNullAdapter())
|
||||
.registerTypeAdapter(Double::class.java, DoubleDefault0Adapter()).create()
|
||||
|
||||
return Retrofit.Builder().baseUrl(baseUrl).client(getOkHttpClient())
|
||||
.addConverterFactory(GsonConverterFactory.create(gson)).build()
|
||||
}
|
||||
|
||||
private fun getServiceClass(): Class<I> {
|
||||
val genType = javaClass.genericSuperclass as ParameterizedType
|
||||
return genType.actualTypeArguments[0] as Class<I>
|
||||
}
|
||||
|
||||
private fun getOkHttpClient(): OkHttpClient {
|
||||
val okHttpClient = getCustomOkHttpClient()
|
||||
if (null != okHttpClient) {
|
||||
return okHttpClient
|
||||
}
|
||||
return defaultOkHttpClient
|
||||
}
|
||||
|
||||
protected open fun getCustomOkHttpClient(): OkHttpClient? {
|
||||
return null
|
||||
}
|
||||
|
||||
protected open fun getCustomInterceptor(): Interceptor? {
|
||||
return null
|
||||
}
|
||||
|
||||
protected suspend fun <T> getResult(block: suspend () -> BaseResponse<T>): Result<T> {
|
||||
for (i in 1..RETRY_COUNT) {
|
||||
try {
|
||||
val response = block()
|
||||
//处理”response.code != OK“情况
|
||||
if (response.code != ErrorCode.OK) {
|
||||
|
||||
//处理信用卡支付返回结果
|
||||
if (response.code == ErrorCode.ERROR) {
|
||||
val message = response.message
|
||||
if (message == "支付失败") {
|
||||
return Result.success("${ErrorCode.CREDIT_CARD_PAY_FAIL}" as T)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
throw NetworkErrorCodeException.of(
|
||||
response.code, "exception code is ${response.code},response code not 200"
|
||||
)
|
||||
}
|
||||
//适用于只管请求成功(T=String),不需要处理结果数据
|
||||
val data = response.data
|
||||
if (null == data || data == "") {
|
||||
|
||||
//处理信用卡支付返回结果
|
||||
if (response.code == ErrorCode.OK) {
|
||||
val message = response.message
|
||||
if (message == "支付成功") {
|
||||
return Result.success("${ErrorCode.CREDIT_CARD_PAY_SUCCESS}" as T)
|
||||
}
|
||||
}
|
||||
|
||||
//throw NetworkException.of(ErrorCode.VALUE_IS_NULL, "response data is null")
|
||||
return Result.success("${response.code}" as T)
|
||||
}
|
||||
return Result.success(response.data)
|
||||
} catch (throwable: Throwable) {
|
||||
LogUtil.e("BaseNetworkApi-->Throwable", throwable.message.toString())
|
||||
if (throwable is NetworkException) {
|
||||
return Result.failure(throwable)
|
||||
} else if (throwable is NetworkErrorCodeException) {
|
||||
return Result.failure(throwable)
|
||||
}
|
||||
if ((throwable is HttpException && throwable.code() == ErrorCode.UNAUTHORIZED)) {
|
||||
// 这里刷新token,然后重试
|
||||
}
|
||||
}
|
||||
}
|
||||
return Result.failure(NetworkException.of(ErrorCode.VALUE_IS_NULL, "unknown"))
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TAG = "BaseNetworkApi"
|
||||
|
||||
//失败重试请求次数
|
||||
private const val RETRY_COUNT = 1
|
||||
private val defaultOkHttpClient by lazy {
|
||||
val builder = OkHttpClient.Builder().callTimeout(10L, TimeUnit.SECONDS)
|
||||
.connectTimeout(10L, TimeUnit.SECONDS).readTimeout(10L, TimeUnit.SECONDS)
|
||||
.writeTimeout(10L, TimeUnit.SECONDS).retryOnConnectionFailure(true)
|
||||
//添加拦截器
|
||||
builder.addInterceptor(CommonRequestInterceptor())
|
||||
builder.addInterceptor(CommonResponseInterceptor())
|
||||
if (AppUtils.isDebug()) {
|
||||
val loggingInterceptor = HttpLoggingInterceptor()
|
||||
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY)
|
||||
builder.addInterceptor(loggingInterceptor)
|
||||
}
|
||||
|
||||
builder.build()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.abbidot.baselibrary.network.base
|
||||
|
||||
/**
|
||||
* 网络数据返回基类
|
||||
*/
|
||||
data class BaseResponse<T>(
|
||||
var code: Int = 0,
|
||||
val message: String? = null,
|
||||
val redirect: String? = null,
|
||||
val data: T? = null
|
||||
)
|
||||
@@ -0,0 +1,4 @@
|
||||
package com.abbidot.baselibrary.network.base
|
||||
|
||||
interface IService<I> {
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package com.abbidot.baselibrary.network.exception
|
||||
|
||||
import androidx.annotation.IntDef
|
||||
|
||||
@IntDef(
|
||||
ErrorCode.OK,
|
||||
ErrorCode.UNAUTHORIZED,
|
||||
ErrorCode.CUSTOM_FIRST,
|
||||
ErrorCode.VALUE_IS_NULL,
|
||||
ErrorCode.DEVICE_RENEWAL,
|
||||
ErrorCode.TOKEN_OVERDUE,
|
||||
ErrorCode.USER_PASSWORD_ERROR,
|
||||
ErrorCode.VERIFY_CODE_ERROR,
|
||||
ErrorCode.VERIFY_CODE_EXPIRE,
|
||||
ErrorCode.REGISTER_FAIL,
|
||||
ErrorCode.USER_NO_EXIST,
|
||||
ErrorCode.USER_HAS_EXIST,
|
||||
ErrorCode.EMAIL_FORMAT_ERROR,
|
||||
ErrorCode.NO_SHARE_MYSELF,
|
||||
ErrorCode.DEVICE_HAS_BOUND,
|
||||
ErrorCode.DEVICE_HAS_BIND,
|
||||
ErrorCode.DEVICE_NOT_EXIST
|
||||
)
|
||||
@Retention(AnnotationRetention.SOURCE)
|
||||
annotation class ErrorCode {
|
||||
companion object {
|
||||
const val OK = 200
|
||||
const val ERROR = 500
|
||||
const val UNAUTHORIZED = 401
|
||||
const val CUSTOM_FIRST = 600
|
||||
const val VALUE_IS_NULL = CUSTOM_FIRST + 1
|
||||
|
||||
//505-注册成功 507-登录成功
|
||||
//设备已过期,请续费
|
||||
const val DEVICE_RENEWAL = 501
|
||||
|
||||
//TOKEN失效或者过期
|
||||
const val TOKEN_OVERDUE = 502
|
||||
|
||||
//503-用户名或密码错误
|
||||
const val USER_PASSWORD_ERROR = 503
|
||||
|
||||
//504-验证码错误
|
||||
const val VERIFY_CODE_ERROR = 504
|
||||
|
||||
//511-验证码已过期
|
||||
const val VERIFY_CODE_EXPIRE = 511
|
||||
|
||||
//506-注册失败
|
||||
const val REGISTER_FAIL = 506
|
||||
|
||||
//508-用户不存在
|
||||
const val USER_NO_EXIST = 508
|
||||
|
||||
//509-用户已存在
|
||||
const val USER_HAS_EXIST = 509
|
||||
|
||||
// 510-邮箱格式不对
|
||||
const val EMAIL_FORMAT_ERROR = 510
|
||||
|
||||
//不能分享给自己
|
||||
const val NO_SHARE_MYSELF = 512
|
||||
|
||||
//513 当前用户已经绑定该设备
|
||||
const val DEVICE_HAS_BOUND = 513
|
||||
|
||||
//514 设备已经存在,已绑定
|
||||
const val DEVICE_HAS_BIND = 514
|
||||
|
||||
//515 设备未存在
|
||||
const val DEVICE_NOT_EXIST = 515
|
||||
|
||||
//信用卡支付成功
|
||||
const val CREDIT_CARD_PAY_SUCCESS = 1
|
||||
|
||||
//信用卡支付失败
|
||||
const val CREDIT_CARD_PAY_FAIL = 2
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.abbidot.baselibrary.network.exception
|
||||
|
||||
/**
|
||||
* 网络错误码返回
|
||||
*/
|
||||
class NetworkErrorCodeException private constructor(val code: Int, message: String) :
|
||||
RuntimeException(message) {
|
||||
|
||||
override fun toString(): String {
|
||||
return "$code"
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun of(code: Int, message: String) = NetworkErrorCodeException(code, message)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.abbidot.baselibrary.network.exception
|
||||
|
||||
class NetworkException private constructor(val code: Int, message: String) : RuntimeException(message) {
|
||||
|
||||
override fun toString(): String {
|
||||
return "exception code is $code msg is $message"
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun of(code: Int, message: String) = NetworkException(code, message)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package com.abbidot.baselibrary.network.interceptor
|
||||
|
||||
import android.os.Build
|
||||
import com.abbidot.baselibrary.constant.MMKVKey
|
||||
import com.abbidot.baselibrary.util.LogUtil
|
||||
import com.abbidot.baselibrary.util.MMKVUtil
|
||||
import com.abbidot.baselibrary.util.Utils
|
||||
import okhttp3.FormBody
|
||||
import okhttp3.HttpUrl
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Response
|
||||
|
||||
|
||||
/**
|
||||
* 开始请求的拦截器
|
||||
* 拦截器获取GET/POST请求参数并添加公共请求头
|
||||
*/
|
||||
class CommonRequestInterceptor : Interceptor {
|
||||
|
||||
//https://blog.csdn.net/u014619545/article/details/98634994
|
||||
// companion object {
|
||||
// //根据headers来动态切换BaseUrl
|
||||
// const val HEADERS_NAME = "urlName"
|
||||
// const val HEADERS_value = "WXBaseUrl"
|
||||
//
|
||||
// //微信BaseUrl
|
||||
// const val WX_BASE_URL = "https://api.weixin.qq.com/sns/"
|
||||
// }
|
||||
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
//获取原始的chain
|
||||
val request = chain.request()
|
||||
//获取chain的创建者builder
|
||||
val builder = request.newBuilder()
|
||||
// 手机型品牌
|
||||
builder.addHeader("brand", Build.BRAND)
|
||||
//手机型号
|
||||
builder.addHeader("model", Build.MODEL)
|
||||
|
||||
//请求参数的key+value添加list中
|
||||
val paramList = mutableListOf<String>()
|
||||
// GET方法
|
||||
if (request.method == "GET") {
|
||||
val urlBuilder: HttpUrl.Builder = request.url.newBuilder()
|
||||
val httpUrl = urlBuilder.build()
|
||||
// 打印全部get参数
|
||||
val paramKeys: Set<String> = httpUrl.queryParameterNames
|
||||
for (key in paramKeys) {
|
||||
val value = httpUrl.queryParameter(key)
|
||||
LogUtil.e("${request.method}请求参数", "$key,$value")
|
||||
paramList.add("$key$value")
|
||||
}
|
||||
|
||||
//动态更改BaseUrl
|
||||
//获取当前的BaseUrl
|
||||
// val oldBaseUrl = request.url
|
||||
// //获取头信息的集合
|
||||
// val urlNameList: List<String> = request.headers(HEADERS_NAME)
|
||||
// if (urlNameList.isNotEmpty()) {
|
||||
// val urlName = urlNameList[0]
|
||||
// //删除原有配置中的值,就是namesAndValues集合里的值
|
||||
// builder.removeHeader(HEADERS_NAME)
|
||||
// LogUtil.e("HeaderName$urlName")
|
||||
// }
|
||||
} else if (request.method == "POST") {
|
||||
if (request.body is FormBody) {
|
||||
val formBody = request.body as FormBody
|
||||
// 打印全部post参数
|
||||
for (i in 0 until formBody.size) {
|
||||
val key = formBody.encodedName(i)
|
||||
val value = formBody.encodedValue(i)
|
||||
LogUtil.e("${request.method}请求参数", "${key},${value}")
|
||||
paramList.add("$key$value")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val sign = if (paramList.size > 0) {
|
||||
//按key的字母排序
|
||||
paramList.sort()
|
||||
Utils.get32Md5Value(Utils.SECRET_KEY + paramList.joinToString(""))
|
||||
} else {
|
||||
Utils.get32Md5Value(Utils.SECRET_KEY)
|
||||
}
|
||||
|
||||
val token = MMKVUtil.getString(MMKVKey.Token)
|
||||
builder.addHeader("token", token)
|
||||
builder.addHeader("sign", sign)
|
||||
|
||||
return chain.proceed(builder.build())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.abbidot.baselibrary.network.interceptor
|
||||
|
||||
import com.abbidot.baselibrary.network.base.BaseNetworkApi
|
||||
import com.abbidot.baselibrary.util.LogUtil
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Response
|
||||
|
||||
/**
|
||||
* 请求返回后的拦截器
|
||||
*/
|
||||
class CommonResponseInterceptor : Interceptor {
|
||||
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
val startTime = System.currentTimeMillis()
|
||||
val request = chain.request()
|
||||
val response = chain.proceed(request)
|
||||
LogUtil.d(BaseNetworkApi.TAG,
|
||||
"url=${request.url}, requestTime=${System.currentTimeMillis() - startTime}ms")
|
||||
return response
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.abbidot.baselibrary.network.interceptor
|
||||
|
||||
import android.text.TextUtils
|
||||
import com.google.gson.TypeAdapter
|
||||
import com.google.gson.stream.JsonReader
|
||||
import com.google.gson.stream.JsonToken
|
||||
import com.google.gson.stream.JsonWriter
|
||||
|
||||
/**
|
||||
*Created by .yzq on 2024/9/10/010.
|
||||
* @link
|
||||
* @description:处理gson解析服务器Double返回null或""
|
||||
*/
|
||||
class DoubleDefault0Adapter : TypeAdapter<Double>() {
|
||||
override fun write(writer: JsonWriter?, value: Double?) {
|
||||
if (value == null) {
|
||||
writer!!.nullValue()
|
||||
return
|
||||
}
|
||||
writer!!.value(value)
|
||||
}
|
||||
|
||||
override fun read(reader: JsonReader): Double {
|
||||
if (reader.peek() === JsonToken.NULL) {
|
||||
reader.nextNull()
|
||||
//原先是返回Null,这里改为返回空字符串
|
||||
return 0.0
|
||||
}
|
||||
val jsonStr = reader.nextString()
|
||||
return if (jsonStr == "null" || TextUtils.isEmpty(jsonStr)) {
|
||||
0.0
|
||||
} else {
|
||||
jsonStr.toDouble()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.abbidot.baselibrary.network.interceptor
|
||||
|
||||
import com.google.gson.TypeAdapter
|
||||
import com.google.gson.stream.JsonReader
|
||||
import com.google.gson.stream.JsonToken
|
||||
import com.google.gson.stream.JsonWriter
|
||||
|
||||
/**
|
||||
*Created by .yzq on 2022/2/18/018.
|
||||
* @link
|
||||
* @description:处理gson解析服务器返回null,变成""
|
||||
*/
|
||||
class StringNullAdapter : TypeAdapter<String>() {
|
||||
|
||||
override fun write(writer: JsonWriter?, value: String?) {
|
||||
if (value == null) {
|
||||
writer!!.nullValue()
|
||||
return
|
||||
}
|
||||
writer!!.value(value)
|
||||
}
|
||||
|
||||
override fun read(reader: JsonReader): String {
|
||||
if (reader.peek() === JsonToken.NULL) {
|
||||
reader.nextNull()
|
||||
//原先是返回Null,这里改为返回空字符串
|
||||
return ""
|
||||
}
|
||||
val jsonStr = reader.nextString()
|
||||
return if (jsonStr == "null") {
|
||||
""
|
||||
} else {
|
||||
jsonStr
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
package com.abbidot.baselibrary.util
|
||||
|
||||
import android.content.Context
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.res.Configuration
|
||||
import android.content.res.Resources
|
||||
import com.abbidot.baselibrary.constant.MMKVKey
|
||||
import java.util.Locale
|
||||
|
||||
|
||||
/**
|
||||
*Created by .yzq on 2021/11/6/006.
|
||||
* @link
|
||||
* @description: app属性相关工具类
|
||||
*/
|
||||
class AppUtils {
|
||||
|
||||
companion object {
|
||||
|
||||
private var isDebug: Boolean? = null
|
||||
|
||||
/**
|
||||
* 屏幕密度,系统源码注释不推荐使用
|
||||
*/
|
||||
private val DENSITY = Resources.getSystem().displayMetrics.density
|
||||
private val SCALED_DENSITY = Resources.getSystem().displayMetrics.scaledDensity
|
||||
|
||||
fun isDebug(): Boolean {
|
||||
return if (isDebug == null) false else isDebug!!
|
||||
}
|
||||
|
||||
fun syncIsDebug(context: Context) {
|
||||
if (isDebug == null) {
|
||||
isDebug =
|
||||
context.applicationInfo != null && context.applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE != 0
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根据包名判断app是否安装
|
||||
*/
|
||||
fun isAppInstalled(context: Context, packageName: String): Boolean {
|
||||
val pm = context.packageManager
|
||||
return try {
|
||||
pm.getPackageInfo(packageName, PackageManager.GET_ACTIVITIES)
|
||||
true
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 把以 dp 为单位的值,转化为以 px 为单位的值
|
||||
*
|
||||
* @param dpValue 以 dp 为单位的值
|
||||
* @return px value
|
||||
*/
|
||||
fun dpToPx(dpValue: Float) = dpValue * DENSITY + 0.5f
|
||||
|
||||
/**
|
||||
* 把以 dp 为单位的值,转化为以 px 为单位的值
|
||||
*
|
||||
* @param dpValue 以 dp 为单位的值
|
||||
* @return px value
|
||||
*/
|
||||
fun dpToPx(dpValue: Int) = (dpValue * DENSITY + 0.5f).toInt()
|
||||
|
||||
/**
|
||||
* 单位转换: sp -> px
|
||||
*
|
||||
* @param sp
|
||||
* @return
|
||||
*/
|
||||
fun spToPx(sp: Int) = (SCALED_DENSITY * sp + 0.5).toInt()
|
||||
|
||||
/**
|
||||
* 单位转换: sp -> px
|
||||
*
|
||||
* @param sp
|
||||
* @return
|
||||
*/
|
||||
fun spToPx(sp: Float) = SCALED_DENSITY * sp + 0.5f
|
||||
|
||||
|
||||
const val SWITCH_MAP_TYPE = "Map"
|
||||
|
||||
// const val SWITCH_BASE_URL_TYPE = "BaseUrl"
|
||||
private const val SWITCH_PAGE_TYPE = "PageUrl"
|
||||
|
||||
/**
|
||||
* 是否是国内
|
||||
*/
|
||||
fun isChina(type: String = SWITCH_PAGE_TYPE): Boolean {
|
||||
return false
|
||||
if (isDebug()) {
|
||||
if (type == SWITCH_MAP_TYPE && MMKVUtil.getBoolean(MMKVKey.OnlyGoogleMap, false)) {
|
||||
//只启用谷歌地图
|
||||
return false
|
||||
}
|
||||
return MMKVUtil.getBoolean(MMKVKey.DebugIp, true)
|
||||
}
|
||||
val locale = Locale.getDefault()
|
||||
val lang = locale.language + "-" + locale.country
|
||||
if (lang.contains("CN") || lang.contains("zh")) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否是深色模式
|
||||
*/
|
||||
fun isDarkThemeEnabled(context: Context): Boolean {
|
||||
val nightModeFlags =
|
||||
context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
|
||||
return nightModeFlags == Configuration.UI_MODE_NIGHT_YES
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package com.abbidot.baselibrary.util
|
||||
|
||||
import android.util.Log
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
*Created by .yzq on 2021/11/3/003.
|
||||
* @link
|
||||
* @description: 日志相关工具类
|
||||
*/
|
||||
class LogUtil {
|
||||
|
||||
companion object {
|
||||
|
||||
private const val TAG = "AbbiDot"
|
||||
|
||||
private fun isLoggable(): Boolean {
|
||||
return AppUtils.isDebug()
|
||||
}
|
||||
|
||||
fun i(tag: String, fmt: String, vararg args: Any) {
|
||||
if (isLoggable()) {
|
||||
Log.i(tag, format(fmt, *args))
|
||||
}
|
||||
}
|
||||
|
||||
fun i(msg: String) {
|
||||
i(TAG, msg)
|
||||
}
|
||||
|
||||
fun d(tag: String, fmt: String, vararg args: Any) {
|
||||
if (isLoggable()) {
|
||||
Log.d(tag, format(fmt, *args))
|
||||
}
|
||||
}
|
||||
|
||||
fun d(msg: String) {
|
||||
d(TAG, msg)
|
||||
}
|
||||
|
||||
fun w(tag: String, fmt: String, vararg args: Any) {
|
||||
if (isLoggable()) {
|
||||
Log.w(tag, format(fmt, *args))
|
||||
}
|
||||
}
|
||||
|
||||
fun w(msg: String) {
|
||||
w(TAG, msg)
|
||||
}
|
||||
|
||||
fun e(tag: String, fmt: String, vararg args: Any) {
|
||||
if (isLoggable()) {
|
||||
Log.e(tag, format(fmt, *args))
|
||||
}
|
||||
}
|
||||
|
||||
fun e(msg: String) {
|
||||
e(TAG, msg)
|
||||
}
|
||||
|
||||
fun e(tag: String, t: Throwable, fmt: String, vararg args: Any) {
|
||||
if (isLoggable()) {
|
||||
Log.e(tag, format(fmt, *args), t)
|
||||
}
|
||||
}
|
||||
|
||||
private fun format(fmt: String, vararg args: Any): String {
|
||||
return if (args.isEmpty()) {
|
||||
fmt
|
||||
} else {
|
||||
String.format(Locale.getDefault(), fmt, *args)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package com.abbidot.baselibrary.util
|
||||
|
||||
import android.app.Application
|
||||
import android.os.Parcelable
|
||||
import com.abbidot.baselibrary.constant.MMKVKey
|
||||
import com.tencent.mmkv.MMKV
|
||||
|
||||
/**
|
||||
*Created by .yzq on 2021/11/6/006.
|
||||
* @link
|
||||
* @description:本类为MMKV的封装类,防止代码入侵
|
||||
*/
|
||||
object MMKVUtil {
|
||||
fun init(application: Application) {
|
||||
MMKV.initialize(application)
|
||||
}
|
||||
|
||||
fun putBoolean(@MMKVKey key: String, value: Boolean) {
|
||||
MMKV.defaultMMKV().encode(key, value)
|
||||
}
|
||||
|
||||
fun getBoolean(@MMKVKey key: String, defaultValue: Boolean = false): Boolean {
|
||||
return MMKV.defaultMMKV().decodeBool(key, defaultValue)
|
||||
}
|
||||
|
||||
fun putString(@MMKVKey key: String, value: String) {
|
||||
MMKV.defaultMMKV().encode(key, value)
|
||||
}
|
||||
|
||||
fun getString(@MMKVKey key: String, defaultValue: String = ""): String {
|
||||
return MMKV.defaultMMKV().decodeString(key, defaultValue)!!
|
||||
}
|
||||
|
||||
fun putInt(@MMKVKey key: String, value: Int) {
|
||||
MMKV.defaultMMKV().encode(key, value)
|
||||
}
|
||||
|
||||
fun getInt(@MMKVKey key: String, defaultValue: Int = 0): Int {
|
||||
return MMKV.defaultMMKV().decodeInt(key, defaultValue)
|
||||
}
|
||||
|
||||
fun putFloat(@MMKVKey key: String, value: Float) {
|
||||
MMKV.defaultMMKV().encode(key, value)
|
||||
}
|
||||
|
||||
fun getFloat(@MMKVKey key: String, defaultValue: Float = 0F): Float {
|
||||
return MMKV.defaultMMKV().decodeFloat(key, defaultValue)
|
||||
}
|
||||
|
||||
fun putLong(@MMKVKey key: String, value: Long) {
|
||||
MMKV.defaultMMKV().encode(key, value)
|
||||
}
|
||||
|
||||
fun getLong(@MMKVKey key: String, defaultValue: Long = 0L): Long {
|
||||
return MMKV.defaultMMKV().decodeLong(key, defaultValue)
|
||||
}
|
||||
|
||||
fun putDouble(@MMKVKey key: String, value: Double) {
|
||||
MMKV.defaultMMKV().encode(key, value)
|
||||
}
|
||||
|
||||
fun getDouble(@MMKVKey key: String, defaultValue: Double = 0.0): Double {
|
||||
return MMKV.defaultMMKV().decodeDouble(key, defaultValue)
|
||||
}
|
||||
|
||||
fun putByteArray(@MMKVKey key: String, value: ByteArray) {
|
||||
MMKV.defaultMMKV().encode(key, value)
|
||||
}
|
||||
|
||||
fun getByteArray(@MMKVKey key: String, defaultValue: ByteArray = ByteArray(0)): ByteArray {
|
||||
return MMKV.defaultMMKV().decodeBytes(key, defaultValue)!!
|
||||
}
|
||||
|
||||
fun putStringSet(@MMKVKey key: String, value: Set<String>) {
|
||||
MMKV.defaultMMKV().encode(key, value)
|
||||
}
|
||||
|
||||
fun getStringSet(@MMKVKey key: String,
|
||||
defaultValue: Set<String> = mutableSetOf()): Set<String> {
|
||||
return MMKV.defaultMMKV().decodeStringSet(key, defaultValue)!!
|
||||
}
|
||||
|
||||
fun putParcelable(@MMKVKey key: String, value: Parcelable) {
|
||||
MMKV.defaultMMKV().encode(key, value)
|
||||
}
|
||||
|
||||
//清空所有sp
|
||||
fun clearAll() {
|
||||
//正式测试标识不清空
|
||||
val debugIp = getBoolean(MMKVKey.DebugIp, true)
|
||||
MMKV.defaultMMKV().clearAll()
|
||||
putBoolean(MMKVKey.DebugIp, debugIp)
|
||||
}
|
||||
|
||||
inline fun <reified T : Parcelable> getParcelable(@MMKVKey key: String): T? {
|
||||
return MMKV.defaultMMKV().decodeParcelable(key, T::class.java)
|
||||
}
|
||||
}
|
||||
378
baselibrary/src/main/java/com/abbidot/baselibrary/util/Utils.kt
Normal file
378
baselibrary/src/main/java/com/abbidot/baselibrary/util/Utils.kt
Normal file
@@ -0,0 +1,378 @@
|
||||
package com.abbidot.baselibrary.util
|
||||
|
||||
import android.os.Build
|
||||
import android.text.TextUtils
|
||||
import java.security.MessageDigest
|
||||
import java.security.NoSuchAlgorithmException
|
||||
import java.text.SimpleDateFormat
|
||||
import java.time.LocalDate
|
||||
import java.time.Period
|
||||
import java.util.Calendar
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
import java.util.regex.Pattern
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.atan2
|
||||
import kotlin.math.cos
|
||||
import kotlin.math.roundToInt
|
||||
import kotlin.math.sin
|
||||
|
||||
|
||||
/**
|
||||
*Created by .yzq on 2022/1/19/019.
|
||||
* @link
|
||||
* @description:工具类
|
||||
*/
|
||||
class Utils {
|
||||
|
||||
companion object {
|
||||
|
||||
//HTTP请求加密key
|
||||
var SECRET_KEY = ""
|
||||
const val DATE_FORMAT_PATTERN_CN = "yyyy-MM-dd"
|
||||
const val DATE_FORMAT_PATTERN_CN2 = "yyyy-MM-dd HH:mm:ss"
|
||||
const val DATE_FORMAT_PATTERN_CN3 = "yyyy/MM/dd HH:mm:ss"
|
||||
const val DATE_FORMAT_PATTERN_EN1 = "MMM d, yyyy | HH:mm"
|
||||
const val DATE_FORMAT_PATTERN_EN2 = "d MMM yyyy hh:mm a"
|
||||
const val DATE_FORMAT_PATTERN_EN3 = "MMM d, yyyy"
|
||||
const val DATE_FORMAT_PATTERN_EN4 = "MMM d yyyy"
|
||||
const val DATE_FORMAT_PATTERN_EN5 = "d/M"
|
||||
const val DATE_FORMAT_PATTERN_EN6 = "dd/MM/yyyy HH:mm"
|
||||
const val DATE_FORMAT_PATTERN_EN7 = "dd/MM/yyyy"
|
||||
const val DATE_FORMAT_PATTERN_EN8 = "hh:mm a"
|
||||
const val DATE_FORMAT_PATTERN_EN9 = "MMM d, yyyy hh:mm a"
|
||||
const val DATE_FORMAT_PATTERN_EN10 = "dd/MM/yyyy HH:mm:ss"
|
||||
|
||||
//返回星期几 简写
|
||||
const val WEEK_FORMAT_PATTERN = "E"
|
||||
|
||||
/**
|
||||
* 时间戳转指定格式时间
|
||||
*
|
||||
* @param time 13位时间戳
|
||||
* @return 时间
|
||||
*/
|
||||
fun formatTime(time: Long, format: String): String {
|
||||
val sdf = SimpleDateFormat(format, Locale.getDefault())
|
||||
return if (time < 1000000000000) {
|
||||
sdf.format(Date(time * 1000))
|
||||
} else {
|
||||
sdf.format(Date(time))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前时间
|
||||
* @param format 返回时间格式
|
||||
*/
|
||||
fun getCurrentTime(format: String): String {
|
||||
val date = Calendar.getInstance().time
|
||||
val sdf = SimpleDateFormat(format, Locale.getDefault())
|
||||
return sdf.format(date)
|
||||
}
|
||||
|
||||
/**
|
||||
* 把字符串转成日期
|
||||
* @param parseFormat 字符串是什么格式的日期就传什么格式
|
||||
* @param resultFormat 转换后的格式
|
||||
*/
|
||||
fun stringToDate(
|
||||
dateString: String,
|
||||
parseFormat: String = DATE_FORMAT_PATTERN_CN2,
|
||||
resultFormat: String = DATE_FORMAT_PATTERN_CN2
|
||||
): String {
|
||||
return try {
|
||||
formatTime(stringToTimestamp(dateString, parseFormat), resultFormat)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
LogUtil.e("解析失败,$e")
|
||||
""
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 把字符串转成时间戳,返回13位时间戳
|
||||
* @param parseFormat 字符串是什么格式的日期就传什么格式
|
||||
*/
|
||||
fun stringToTimestamp(
|
||||
dateString: String, parseFormat: String = DATE_FORMAT_PATTERN_CN2
|
||||
): Long {
|
||||
return try {
|
||||
val formatter = SimpleDateFormat(parseFormat, Locale.getDefault())
|
||||
val date = formatter.parse(dateString)
|
||||
date?.time ?: 0
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
LogUtil.e("解析失败,$e")
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取这个时间戳的,前几天13位时间戳
|
||||
*/
|
||||
fun getBeforeHowTimestamp(cTimestamp: Long, before: Long): Long {
|
||||
return cTimestamp - (before * 24 * 60 * 60 * 1000L)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取这个时间戳的,后几天13位时间戳
|
||||
*/
|
||||
fun getAfterHowTimestamp(cTimestamp: Long, after: Long): Long {
|
||||
return cTimestamp + (after * 24 * 60 * 60 * 1000L)
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算2个日期相册多少年,多少月
|
||||
*/
|
||||
fun differYear(startDate: String, endDate: String): Array<Int?> {
|
||||
val result = arrayOfNulls<Int>(2)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val period = Period.between(LocalDate.parse(startDate), LocalDate.parse(endDate))
|
||||
result[0] = period.years
|
||||
result[1] = period.months
|
||||
} else {
|
||||
val dfs = SimpleDateFormat(DATE_FORMAT_PATTERN_CN, Locale.getDefault())
|
||||
val sDate = dfs.parse(startDate)
|
||||
val eDate = dfs.parse(endDate)
|
||||
// 得到两者的毫秒数
|
||||
// val between = (eDate.time - sDate.time)
|
||||
val year = eDate.year - sDate.year
|
||||
val month = eDate.month - sDate.month
|
||||
//2021-8-1,2122-1-1
|
||||
if (month < 0) {
|
||||
result[0] = year - 1
|
||||
//取绝对值
|
||||
result[1] = 12 - abs(month)
|
||||
} else {
|
||||
result[0] = year
|
||||
result[1] = month
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* 秒转换为 时分秒
|
||||
*/
|
||||
fun timeConversion(time: Int): String {
|
||||
var hour = 0
|
||||
var minutes = 0
|
||||
var second = 0
|
||||
val temp = time % 3600
|
||||
if (time > 3600) {
|
||||
hour = time / 3600
|
||||
if (temp != 0) {
|
||||
if (temp > 60) {
|
||||
minutes = temp / 60
|
||||
if (temp % 60 != 0) {
|
||||
second = temp % 60
|
||||
}
|
||||
} else {
|
||||
second = temp
|
||||
}
|
||||
}
|
||||
} else {
|
||||
minutes = time / 60
|
||||
if (time % 60 != 0) {
|
||||
second = time % 60
|
||||
}
|
||||
}
|
||||
return "${fill2Digits(hour)}:${fill2Digits(minutes)}:${fill2Digits(second)}"
|
||||
}
|
||||
|
||||
/**
|
||||
* 把秒转换为 day hour min
|
||||
*/
|
||||
fun getSecondToDayHourMin(second: Long): Array<String> {
|
||||
val day = second / (24 * 60 * 60)
|
||||
val hour = (second % (24 * 60 * 60)) / (60 * 60)
|
||||
val min = ((second % (24 * 60 * 60)) % (60 * 60)) / 60
|
||||
return arrayOf(
|
||||
fill2Digits(day.toInt()), fill2Digits(hour.toInt()), fill2Digits(min.toInt())
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 把秒转换为 year month day
|
||||
*/
|
||||
fun getDayToYearMonthDay(second: Long): Array<String> {
|
||||
if (second <= 0) return arrayOf("0", "0", "0")
|
||||
val year = second / 365
|
||||
val month = (second % 365) / 30
|
||||
val day = (second % 365) % 30
|
||||
return arrayOf(
|
||||
fill2Digits(year.toInt()), fill2Digits(month.toInt()), fill2Digits(day.toInt())
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param d 需要处理的数字
|
||||
* @param num 保留位数(只能是1或者2)
|
||||
* @return 保留num位小数后的字符串
|
||||
*/
|
||||
fun formatDecimal(d: Double, num: Int = 2): String {
|
||||
return if (num == 2) {
|
||||
String.format(Locale("en", "US"), "%.2f", d)
|
||||
} else {
|
||||
String.format(Locale("en", "US"), "%.1f", d)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param f 需要处理的数字
|
||||
* @param num 保留位数(只能是1或者2)
|
||||
* @return 保留num位小数后的字符串
|
||||
*/
|
||||
fun formatDecimal(f: Float, num: Int): String {
|
||||
return if (num == 2) {
|
||||
String.format(Locale("en", "US"), "%.2f", f)
|
||||
} else {
|
||||
String.format(Locale("en", "US"), "%.1f", f)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 四舍五入取整
|
||||
*/
|
||||
fun roundOffToInt(f: Double): Int {
|
||||
return f.roundToInt()
|
||||
}
|
||||
|
||||
const val KM = 0
|
||||
const val KCAL = 1
|
||||
|
||||
/**
|
||||
* 处理转换为千 不足千不转换
|
||||
*
|
||||
* @param d 需要处理的数字 double类型才会精准四舍五入
|
||||
* @param num 保留位数(只能是1或者2)
|
||||
* @param unitType 单位类型 0里程m,km 1卡路里 cal kcal
|
||||
* @return
|
||||
*/
|
||||
fun convertThousand(d: Double, num: Int, unitType: Int): String {
|
||||
return if (d < 1000) {
|
||||
var unit = " m"
|
||||
when (unitType) {
|
||||
KM -> unit = " m"
|
||||
KCAL -> unit = " Cal"
|
||||
}
|
||||
formatDecimal(d, num) + unit
|
||||
} else {
|
||||
var unit = " Km"
|
||||
when (unitType) {
|
||||
KM -> unit = " Km"
|
||||
KCAL -> unit = " KCal"
|
||||
}
|
||||
formatDecimal(d / 1000, num) + unit
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 个位数补0转两位数
|
||||
*/
|
||||
fun fill2Digits(value: Int): String {
|
||||
var str = value.toString()
|
||||
if (value < 10) {
|
||||
str = "0$value"
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
/**
|
||||
* md5加密、32位小写加密
|
||||
*/
|
||||
fun get32Md5Value(sSecret: String): String {
|
||||
if (TextUtils.isEmpty(sSecret)) {
|
||||
return ""
|
||||
}
|
||||
try {
|
||||
val bmd5 = MessageDigest.getInstance("MD5")
|
||||
bmd5.update(sSecret.toByteArray())
|
||||
var i: Int
|
||||
val buf = StringBuffer()
|
||||
val b = bmd5.digest()
|
||||
for (offset in b.indices) {
|
||||
i = b[offset].toInt()
|
||||
if (i < 0) i += 256
|
||||
if (i < 16) buf.append("0")
|
||||
buf.append(Integer.toHexString(i))
|
||||
}
|
||||
return buf.toString()
|
||||
} catch (e: NoSuchAlgorithmException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断邮箱email格式是否正确
|
||||
*/
|
||||
fun isValidEmail(email: String): Boolean {
|
||||
val str =
|
||||
"^([a-zA-Z0-9_\\-.+]+)@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.)|(([a-zA-Z0-9\\-]+\\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(]?)$"
|
||||
return isRegularExpression(email, str)
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否符合该正则表达式
|
||||
*/
|
||||
fun isRegularExpression(value: String, regex: String): Boolean {
|
||||
val p = Pattern.compile(regex)
|
||||
val m = p.matcher(value)
|
||||
return m.matches()
|
||||
}
|
||||
|
||||
/**
|
||||
*求圆弧上某个点的坐标
|
||||
* @angle 角度是以圆的右方开始计算角度,顺时针增加
|
||||
*/
|
||||
fun getPointOnCircleArc(
|
||||
centerX: Float, centerY: Float, radius: Float, angle: Double
|
||||
): FloatArray {
|
||||
val angleInRadians = Math.toRadians(angle)
|
||||
val x = (centerX + radius * cos(angleInRadians)).toFloat()
|
||||
val y = (centerY + radius * sin(angleInRadians)).toFloat()
|
||||
return floatArrayOf(x, y)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取圆弧上某个点和圆心角度
|
||||
*/
|
||||
fun getAngleFromCenter(x: Float, y: Float, centerX: Float, centerY: Float): Double {
|
||||
val angle = atan2((y - centerY).toDouble(), (x - centerX).toDouble())
|
||||
// 转换为角度
|
||||
return Math.toDegrees(angle)
|
||||
}
|
||||
|
||||
/**
|
||||
*根据信号区间转换为信号级别
|
||||
* @param numLevels 级别个数
|
||||
*/
|
||||
fun calculateSignalLevel(
|
||||
rssi: Int, numLevels: Int, minRssi: Int = -100, maxRssi: Int = -55
|
||||
): Int {
|
||||
return if (rssi <= minRssi) {
|
||||
0
|
||||
} else if (rssi >= maxRssi) {
|
||||
numLevels - 1
|
||||
} else {
|
||||
val inputRange: Float = (maxRssi - minRssi).toFloat()
|
||||
val outputRange = (numLevels - 1).toFloat()
|
||||
((rssi - minRssi).toFloat() * outputRange / inputRange).toInt()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据电量计算级别
|
||||
*/
|
||||
fun calculatePowerLevel(power: Int): Int {
|
||||
return if (power in 10..24) 1
|
||||
else if (power in 25..49) 2
|
||||
else if (power in 50..74) 3
|
||||
else if (power >= 75) 4
|
||||
else 0
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.abbidot.baselibrary.util.reflect;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
public final class ReflectArgument<T> {
|
||||
private final Class<T> clazz;
|
||||
private final T value;
|
||||
|
||||
private ReflectArgument(@NonNull Class<T> clazz, @Nullable T value) {
|
||||
this.clazz = clazz;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public static <T> ReflectArgument<T> of(@NonNull String className, @Nullable T value) {
|
||||
Class<T> clazz = ReflectHelper.getClassForName(className);
|
||||
return new ReflectArgument<>(clazz, value);
|
||||
}
|
||||
|
||||
public static <T> ReflectArgument<T> of(@NonNull Class<T> clazz, @Nullable T value) {
|
||||
return new ReflectArgument<>(clazz, value);
|
||||
}
|
||||
|
||||
public static <T> ReflectArgument<T> of(@NonNull T value) {
|
||||
Class<T> clazz = (Class<T>) value.getClass();
|
||||
return new ReflectArgument<>(clazz, value);
|
||||
}
|
||||
|
||||
static Class[] getClasses(@NonNull ReflectArgument[] args) {
|
||||
final int size = args.length;
|
||||
Class[] result = new Class[size];
|
||||
for (int i = 0; i < size; i++) {
|
||||
result[i] = args[i].clazz;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static Object[] getValues(@NonNull ReflectArgument[] args) {
|
||||
final int size = args.length;
|
||||
Object[] result = new Object[size];
|
||||
for (int i = 0; i < size; i++) {
|
||||
result[i] = args[i].value;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
package com.abbidot.baselibrary.util.reflect;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
|
||||
import com.abbidot.baselibrary.util.LogUtil;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* 反射调用帮助类
|
||||
*/
|
||||
public final class ReflectHelper {
|
||||
private static final String TAG = "ReflectHelper";
|
||||
|
||||
private static final Object NOT_FOUND = new Object();
|
||||
|
||||
private static final Map<String, Object> CACHE = new ConcurrentHashMap<>();
|
||||
|
||||
private Object inst;
|
||||
private Class clazz;
|
||||
|
||||
private ReflectHelper(Class clazz) {
|
||||
this.clazz = clazz;
|
||||
}
|
||||
|
||||
public ReflectHelper setInstance(Object o) {
|
||||
this.inst = o;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Object getField(@NonNull String fieldName) {
|
||||
Field field = getClassField(this.clazz, fieldName);
|
||||
if (field != null) {
|
||||
try {
|
||||
return field.get(this.inst);
|
||||
} catch (Exception e) {
|
||||
LogUtil.Companion.e(TAG, "getField error=%s", e);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean setField(String fieldName, Object value) {
|
||||
Field field = getClassField(this.clazz, fieldName);
|
||||
if (field != null) {
|
||||
try {
|
||||
field.set(this.inst, value);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
LogUtil.Companion.e(TAG, "setField error=%s", e);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行反射方法,并返回执行状态和函数返回值
|
||||
*
|
||||
* @param outResult 方法返回值
|
||||
* @param methodName 方法名称
|
||||
* @param args 参数列表,包含类型和值
|
||||
* @return 是否调用成功
|
||||
*/
|
||||
public boolean invoke(Object[] outResult, String methodName, ReflectArgument... args) {
|
||||
Method method = getClassMethod(this.clazz, methodName, ReflectArgument.getClasses(args));
|
||||
if (method != null) {
|
||||
try {
|
||||
Object result = method.invoke(this.inst, ReflectArgument.getValues(args));
|
||||
if (outResult != null && outResult.length > 0) {
|
||||
outResult[0] = result;
|
||||
}
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
LogUtil.Companion.e(TAG, "invoke error=%s", e);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行反射方法
|
||||
*
|
||||
* @param methodName 方法名称
|
||||
* @param args 参数列表,包含类型和值
|
||||
* @return 函数返回值
|
||||
*/
|
||||
public Object invoke(String methodName, ReflectArgument... args) {
|
||||
Object[] result = new Object[1];
|
||||
if (invoke(result, methodName, args)) {
|
||||
return result[0];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 新建对象实例
|
||||
*
|
||||
* @param args 构造方法参数
|
||||
* @return 新实例
|
||||
*/
|
||||
public Object newInstance(ReflectArgument... args) {
|
||||
Constructor constructor = getClassConstructor(this.clazz, ReflectArgument.getClasses(args));
|
||||
if (constructor != null) {
|
||||
try {
|
||||
return constructor.newInstance(ReflectArgument.getValues(args));
|
||||
} catch (Exception e) {
|
||||
LogUtil.Companion.e(TAG, "newInstance error=%s", e);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Field getClassField(Class<?> target, String fieldName) {
|
||||
if (target == null || target == Object.class) {
|
||||
return null;
|
||||
}
|
||||
String key = target.getName() + '#' + fieldName;
|
||||
Object value = CACHE.get(key);
|
||||
if (value == NOT_FOUND) {
|
||||
LogUtil.Companion.d(TAG, "getClassField NOT_FOUND %s", key);
|
||||
return null;
|
||||
}
|
||||
Field field = (Field) value;
|
||||
if (field == null) {
|
||||
try {
|
||||
field = target.getDeclaredField(fieldName);
|
||||
field.setAccessible(true);
|
||||
} catch (Exception e) {
|
||||
//中间过程不记录
|
||||
}
|
||||
if (field == null) {
|
||||
// 递归查找父类
|
||||
field = getClassField(target.getSuperclass(), fieldName);
|
||||
}
|
||||
putCache(key, field);
|
||||
}
|
||||
return field;
|
||||
}
|
||||
|
||||
private static Constructor getClassConstructor(Class<?> target, Class[] paramTypes) {
|
||||
if (target == null) {
|
||||
return null;
|
||||
}
|
||||
String key = target.getName() + "#<init>(" + Arrays.toString(paramTypes) + ')';
|
||||
Object value = CACHE.get(key);
|
||||
if (value == NOT_FOUND) {
|
||||
LogUtil.Companion.d(TAG, "getClassConstructor NOT_FOUND %s", key);
|
||||
return null;
|
||||
}
|
||||
Constructor constructor = (Constructor) value;
|
||||
if (constructor == null) {
|
||||
try {
|
||||
constructor = target.getDeclaredConstructor(paramTypes);
|
||||
constructor.setAccessible(true);
|
||||
} catch (Exception e) {
|
||||
LogUtil.Companion.e(TAG, "getClassConstructor NOT_FOUND %s error=%s", key, e);
|
||||
}
|
||||
putCache(key, constructor);
|
||||
}
|
||||
return constructor;
|
||||
}
|
||||
|
||||
private static Method getClassMethod(Class<?> target, String methodName, Class[] paramTypes) {
|
||||
if (target == null || target == Object.class) {
|
||||
return null;
|
||||
}
|
||||
String key = target.getName() + '#' + methodName + '(' + Arrays.toString(paramTypes) + ')';
|
||||
Object value = CACHE.get(key);
|
||||
if (value == NOT_FOUND) {
|
||||
LogUtil.Companion.d(TAG, "getClassMethod NOT_FOUND %s", key);
|
||||
return null;
|
||||
}
|
||||
Method method = (Method) value;
|
||||
if (method == null) {
|
||||
try {
|
||||
method = target.getDeclaredMethod(methodName, paramTypes);
|
||||
method.setAccessible(true);
|
||||
} catch (Exception e) {
|
||||
//中间过程不记录
|
||||
}
|
||||
if (method == null) {
|
||||
// 递归查找父类
|
||||
method = getClassMethod(target.getSuperclass(), methodName, paramTypes);
|
||||
}
|
||||
putCache(key, method);
|
||||
}
|
||||
return method;
|
||||
}
|
||||
|
||||
static Class getClassForName(String className) {
|
||||
Object value = CACHE.get(className);
|
||||
if (value == NOT_FOUND) {
|
||||
LogUtil.Companion.d(TAG, "getClassForName NOT_FOUND %s", className);
|
||||
return null;
|
||||
}
|
||||
|
||||
Class clazz = (Class) value;
|
||||
if (clazz == null) {
|
||||
try {
|
||||
clazz = Class.forName(className);
|
||||
} catch (Exception e) {
|
||||
LogUtil.Companion.e(TAG, "getClassForName NOT_FOUND %s error=%s", className, e);
|
||||
}
|
||||
putCache(className, clazz);
|
||||
}
|
||||
return clazz;
|
||||
}
|
||||
|
||||
public static ReflectHelper of(Class clazz) {
|
||||
return new ReflectHelper(clazz);
|
||||
}
|
||||
|
||||
public static ReflectHelper of(String className) {
|
||||
return new ReflectHelper(getClassForName(className));
|
||||
}
|
||||
|
||||
public static ReflectHelper of(Object inst) {
|
||||
return new ReflectHelper(inst != null ? inst.getClass() : null).setInstance(inst);
|
||||
}
|
||||
|
||||
public static int getFieldCount(@NonNull Class<? extends Annotation> clazz) {
|
||||
return clazz.getFields().length;
|
||||
}
|
||||
|
||||
private static void putCache(String key, Object value) {
|
||||
if (value == null || value == NOT_FOUND) {
|
||||
LogUtil.Companion.e(TAG, "NOT_FOUND %s", key);
|
||||
value = NOT_FOUND;
|
||||
}
|
||||
CACHE.put(key, value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,604 @@
|
||||
package com.abbidot.baselibrary.util.rsa;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
public class Base64 {
|
||||
/**
|
||||
* Chunk size per RFC 2045 section 6.8.
|
||||
*
|
||||
* <p>The {@value} character limit does not count the trailing CRLF, but counts
|
||||
* all other characters, including any equal signs.</p>
|
||||
*
|
||||
* @see <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045 section 6.8</a>
|
||||
*/
|
||||
static final int CHUNK_SIZE = 76;
|
||||
|
||||
/**
|
||||
* Chunk separator per RFC 2045 section 2.1.
|
||||
*
|
||||
* @see <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045 section 2.1</a>
|
||||
*/
|
||||
static final byte[] CHUNK_SEPARATOR = "\r\n".getBytes();
|
||||
|
||||
/**
|
||||
* The base length.
|
||||
*/
|
||||
static final int BASELENGTH = 255;
|
||||
|
||||
/**
|
||||
* Lookup length.
|
||||
*/
|
||||
static final int LOOKUPLENGTH = 64;
|
||||
|
||||
/**
|
||||
* Used to calculate the number of bits in a byte.
|
||||
*/
|
||||
static final int EIGHTBIT = 8;
|
||||
|
||||
/**
|
||||
* Used when encoding something which has fewer than 24 bits.
|
||||
*/
|
||||
static final int SIXTEENBIT = 16;
|
||||
|
||||
/**
|
||||
* Used to determine how many bits data contains.
|
||||
*/
|
||||
static final int TWENTYFOURBITGROUP = 24;
|
||||
|
||||
/**
|
||||
* Used to get the number of Quadruples.
|
||||
*/
|
||||
static final int FOURBYTE = 4;
|
||||
|
||||
/**
|
||||
* Used to test the sign of a byte.
|
||||
*/
|
||||
static final int SIGN = -128;
|
||||
|
||||
/**
|
||||
* Byte used to pad output.
|
||||
*/
|
||||
static final byte PAD = (byte) '=';
|
||||
|
||||
// Create arrays to hold the base64 characters and a
|
||||
// lookup for base64 chars
|
||||
private static byte[] base64Alphabet = new byte[BASELENGTH];
|
||||
private static byte[] lookUpBase64Alphabet = new byte[LOOKUPLENGTH];
|
||||
|
||||
// Populating the lookup and character arrays
|
||||
static {
|
||||
for (int i = 0; i < BASELENGTH; i++) {
|
||||
base64Alphabet[i] = (byte) -1;
|
||||
}
|
||||
for (int i = 'Z'; i >= 'A'; i--) {
|
||||
base64Alphabet[i] = (byte) (i - 'A');
|
||||
}
|
||||
for (int i = 'z'; i >= 'a'; i--) {
|
||||
base64Alphabet[i] = (byte) (i - 'a' + 26);
|
||||
}
|
||||
for (int i = '9'; i >= '0'; i--) {
|
||||
base64Alphabet[i] = (byte) (i - '0' + 52);
|
||||
}
|
||||
|
||||
base64Alphabet['+'] = 62;
|
||||
base64Alphabet['/'] = 63;
|
||||
|
||||
for (int i = 0; i <= 25; i++) {
|
||||
lookUpBase64Alphabet[i] = (byte) ('A' + i);
|
||||
}
|
||||
|
||||
for (int i = 26, j = 0; i <= 51; i++, j++) {
|
||||
lookUpBase64Alphabet[i] = (byte) ('a' + j);
|
||||
}
|
||||
|
||||
for (int i = 52, j = 0; i <= 61; i++, j++) {
|
||||
lookUpBase64Alphabet[i] = (byte) ('0' + j);
|
||||
}
|
||||
|
||||
lookUpBase64Alphabet[62] = (byte) '+';
|
||||
lookUpBase64Alphabet[63] = (byte) '/';
|
||||
}
|
||||
|
||||
private static boolean isBase64(byte octect) {
|
||||
if (octect == PAD) {
|
||||
return true;
|
||||
} else if (base64Alphabet[octect] == -1) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests a given byte array to see if it contains
|
||||
* only valid characters within the Base64 alphabet.
|
||||
*
|
||||
* @param arrayOctect byte array to test
|
||||
* @return true if all bytes are valid characters in the Base64
|
||||
* alphabet or if the byte array is empty; false, otherwise
|
||||
*/
|
||||
public static boolean isArrayByteBase64(byte[] arrayOctect) {
|
||||
|
||||
arrayOctect = discardWhitespace(arrayOctect);
|
||||
|
||||
int length = arrayOctect.length;
|
||||
if (length == 0) {
|
||||
// shouldn't a 0 length array be valid base64 data?
|
||||
// return false;
|
||||
return true;
|
||||
}
|
||||
for (int i = 0; i < length; i++) {
|
||||
if (!isBase64(arrayOctect[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes binary data using the base64 algorithm but
|
||||
* does not chunk the output.
|
||||
*
|
||||
* @param binaryData binary data to encode
|
||||
* @return Base64 characters
|
||||
*/
|
||||
public static byte[] encodeBase64(byte[] binaryData) {
|
||||
return encodeBase64(binaryData, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes binary data using the base64 algorithm and chunks
|
||||
* the encoded output into 76 character blocks
|
||||
*
|
||||
* @param binaryData binary data to encode
|
||||
* @return Base64 characters chunked in 76 character blocks
|
||||
*/
|
||||
public static byte[] encodeBase64Chunked(byte[] binaryData) {
|
||||
return encodeBase64(binaryData, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes a byte[] containing containing
|
||||
* characters in the Base64 alphabet.
|
||||
*
|
||||
* @param pArray A byte array containing Base64 character data
|
||||
* @return a byte array containing binary data
|
||||
*/
|
||||
public static byte[] decode(byte[] pArray) {
|
||||
return decodeBase64(pArray);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes binary data using the base64 algorithm, optionally
|
||||
* chunking the output into 76 character blocks.
|
||||
*
|
||||
* @param binaryData Array containing binary data to encode.
|
||||
* @param isChunked if isChunked is true this encoder will chunk
|
||||
* the base64 output into 76 character blocks
|
||||
* @return Base64-encoded data.
|
||||
*/
|
||||
public static byte[] encodeBase64(byte[] binaryData, boolean isChunked) {
|
||||
int lengthDataBits = binaryData.length * EIGHTBIT;
|
||||
int fewerThan24bits = lengthDataBits % TWENTYFOURBITGROUP;
|
||||
int numberTriplets = lengthDataBits / TWENTYFOURBITGROUP;
|
||||
byte[] encodedData = null;
|
||||
int encodedDataLength = 0;
|
||||
int nbrChunks = 0;
|
||||
|
||||
if (fewerThan24bits != 0) {
|
||||
//data not divisible by 24 bit
|
||||
encodedDataLength = (numberTriplets + 1) * 4;
|
||||
} else {
|
||||
// 16 or 8 bit
|
||||
encodedDataLength = numberTriplets * 4;
|
||||
}
|
||||
|
||||
// If the output is to be "chunked" into 76 character sections,
|
||||
// for compliance with RFC 2045 MIME, then it is important to
|
||||
// allow for extra length to account for the separator(s)
|
||||
if (isChunked) {
|
||||
|
||||
nbrChunks =
|
||||
(CHUNK_SEPARATOR.length == 0 ? 0 : (int) Math.ceil((float) encodedDataLength / CHUNK_SIZE));
|
||||
encodedDataLength += nbrChunks * CHUNK_SEPARATOR.length;
|
||||
}
|
||||
|
||||
encodedData = new byte[encodedDataLength];
|
||||
|
||||
byte k = 0, l = 0, b1 = 0, b2 = 0, b3 = 0;
|
||||
|
||||
int encodedIndex = 0;
|
||||
int dataIndex = 0;
|
||||
int i;
|
||||
int nextSeparatorIndex = CHUNK_SIZE;
|
||||
int chunksSoFar = 0;
|
||||
|
||||
//log.debug("number of triplets = " + numberTriplets);
|
||||
for (i = 0; i < numberTriplets; i++) {
|
||||
dataIndex = i * 3;
|
||||
b1 = binaryData[dataIndex];
|
||||
b2 = binaryData[dataIndex + 1];
|
||||
b3 = binaryData[dataIndex + 2];
|
||||
|
||||
//log.debug("b1= " + b1 +", b2= " + b2 + ", b3= " + b3);
|
||||
|
||||
l = (byte) (b2 & 0x0f);
|
||||
k = (byte) (b1 & 0x03);
|
||||
|
||||
byte val1 =
|
||||
((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0);
|
||||
byte val2 =
|
||||
((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0);
|
||||
byte val3 =
|
||||
((b3 & SIGN) == 0) ? (byte) (b3 >> 6) : (byte) ((b3) >> 6 ^ 0xfc);
|
||||
|
||||
encodedData[encodedIndex] = lookUpBase64Alphabet[val1];
|
||||
//log.debug( "val2 = " + val2 );
|
||||
//log.debug( "k4 = " + (k<<4) );
|
||||
//log.debug( "vak = " + (val2 | (k<<4)) );
|
||||
encodedData[encodedIndex + 1] =
|
||||
lookUpBase64Alphabet[val2 | (k << 4)];
|
||||
encodedData[encodedIndex + 2] =
|
||||
lookUpBase64Alphabet[(l << 2) | val3];
|
||||
encodedData[encodedIndex + 3] = lookUpBase64Alphabet[b3 & 0x3f];
|
||||
|
||||
encodedIndex += 4;
|
||||
|
||||
// If we are chunking, let's put a chunk separator down.
|
||||
if (isChunked) {
|
||||
// this assumes that CHUNK_SIZE % 4 == 0
|
||||
if (encodedIndex == nextSeparatorIndex) {
|
||||
System.arraycopy(
|
||||
CHUNK_SEPARATOR,
|
||||
0,
|
||||
encodedData,
|
||||
encodedIndex,
|
||||
CHUNK_SEPARATOR.length);
|
||||
chunksSoFar++;
|
||||
nextSeparatorIndex =
|
||||
(CHUNK_SIZE * (chunksSoFar + 1)) +
|
||||
(chunksSoFar * CHUNK_SEPARATOR.length);
|
||||
encodedIndex += CHUNK_SEPARATOR.length;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// form integral number of 6-bit groups
|
||||
dataIndex = i * 3;
|
||||
|
||||
if (fewerThan24bits == EIGHTBIT) {
|
||||
b1 = binaryData[dataIndex];
|
||||
k = (byte) (b1 & 0x03);
|
||||
//log.debug("b1=" + b1);
|
||||
//log.debug("b1<<2 = " + (b1>>2) );
|
||||
byte val1 =
|
||||
((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0);
|
||||
encodedData[encodedIndex] = lookUpBase64Alphabet[val1];
|
||||
encodedData[encodedIndex + 1] = lookUpBase64Alphabet[k << 4];
|
||||
encodedData[encodedIndex + 2] = PAD;
|
||||
encodedData[encodedIndex + 3] = PAD;
|
||||
} else if (fewerThan24bits == SIXTEENBIT) {
|
||||
|
||||
b1 = binaryData[dataIndex];
|
||||
b2 = binaryData[dataIndex + 1];
|
||||
l = (byte) (b2 & 0x0f);
|
||||
k = (byte) (b1 & 0x03);
|
||||
|
||||
byte val1 =
|
||||
((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0);
|
||||
byte val2 =
|
||||
((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0);
|
||||
|
||||
encodedData[encodedIndex] = lookUpBase64Alphabet[val1];
|
||||
encodedData[encodedIndex + 1] =
|
||||
lookUpBase64Alphabet[val2 | (k << 4)];
|
||||
encodedData[encodedIndex + 2] = lookUpBase64Alphabet[l << 2];
|
||||
encodedData[encodedIndex + 3] = PAD;
|
||||
}
|
||||
|
||||
if (isChunked) {
|
||||
// we also add a separator to the end of the final chunk.
|
||||
if (chunksSoFar < nbrChunks) {
|
||||
System.arraycopy(
|
||||
CHUNK_SEPARATOR,
|
||||
0,
|
||||
encodedData,
|
||||
encodedDataLength - CHUNK_SEPARATOR.length,
|
||||
CHUNK_SEPARATOR.length);
|
||||
}
|
||||
}
|
||||
|
||||
return encodedData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes Base64 data into octects
|
||||
*
|
||||
* @param base64Data Byte array containing Base64 data
|
||||
* @return Array containing decoded data.
|
||||
*/
|
||||
public static byte[] decodeBase64(byte[] base64Data) {
|
||||
// RFC 2045 requires that we discard ALL non-Base64 characters
|
||||
base64Data = discardNonBase64(base64Data);
|
||||
|
||||
// handle the edge case, so we don't have to worry about it later
|
||||
if (base64Data.length == 0) {
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
int numberQuadruple = base64Data.length / FOURBYTE;
|
||||
byte[] decodedData = null;
|
||||
byte b1 = 0, b2 = 0, b3 = 0, b4 = 0, marker0 = 0, marker1 = 0;
|
||||
|
||||
// Throw away anything not in base64Data
|
||||
|
||||
int encodedIndex = 0;
|
||||
int dataIndex = 0;
|
||||
{
|
||||
// this sizes the output array properly - rlw
|
||||
int lastData = base64Data.length;
|
||||
// ignore the '=' padding
|
||||
while (base64Data[lastData - 1] == PAD) {
|
||||
if (--lastData == 0) {
|
||||
return new byte[0];
|
||||
}
|
||||
}
|
||||
decodedData = new byte[lastData - numberQuadruple];
|
||||
}
|
||||
|
||||
for (int i = 0; i < numberQuadruple; i++) {
|
||||
dataIndex = i * 4;
|
||||
marker0 = base64Data[dataIndex + 2];
|
||||
marker1 = base64Data[dataIndex + 3];
|
||||
|
||||
b1 = base64Alphabet[base64Data[dataIndex]];
|
||||
b2 = base64Alphabet[base64Data[dataIndex + 1]];
|
||||
|
||||
if (marker0 != PAD && marker1 != PAD) {
|
||||
//No PAD e.g 3cQl
|
||||
b3 = base64Alphabet[marker0];
|
||||
b4 = base64Alphabet[marker1];
|
||||
|
||||
decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4);
|
||||
decodedData[encodedIndex + 1] =
|
||||
(byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
|
||||
decodedData[encodedIndex + 2] = (byte) (b3 << 6 | b4);
|
||||
} else if (marker0 == PAD) {
|
||||
//Two PAD e.g. 3c[Pad][Pad]
|
||||
decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4);
|
||||
} else if (marker1 == PAD) {
|
||||
//One PAD e.g. 3cQ[Pad]
|
||||
b3 = base64Alphabet[marker0];
|
||||
|
||||
decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4);
|
||||
decodedData[encodedIndex + 1] =
|
||||
(byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
|
||||
}
|
||||
encodedIndex += 3;
|
||||
}
|
||||
return decodedData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Discards any whitespace from a base-64 encoded block.
|
||||
*
|
||||
* @param data The base-64 encoded data to discard the whitespace
|
||||
* from.
|
||||
* @return The data, less whitespace (see RFC 2045).
|
||||
*/
|
||||
static byte[] discardWhitespace(byte[] data) {
|
||||
byte[] groomedData = new byte[data.length];
|
||||
int bytesCopied = 0;
|
||||
|
||||
for (byte datum : data) {
|
||||
switch (datum) {
|
||||
case (byte) ' ':
|
||||
case (byte) '\n':
|
||||
case (byte) '\r':
|
||||
case (byte) '\t':
|
||||
break;
|
||||
default:
|
||||
groomedData[bytesCopied++] = datum;
|
||||
}
|
||||
}
|
||||
|
||||
byte[] packedData = new byte[bytesCopied];
|
||||
|
||||
System.arraycopy(groomedData, 0, packedData, 0, bytesCopied);
|
||||
|
||||
return packedData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Discards any characters outside of the base64 alphabet, per
|
||||
* the requirements on page 25 of RFC 2045 - "Any characters
|
||||
* outside of the base64 alphabet are to be ignored in base64
|
||||
* encoded data."
|
||||
*
|
||||
* @param data The base-64 encoded data to groom
|
||||
* @return The data, less non-base64 characters (see RFC 2045).
|
||||
*/
|
||||
static byte[] discardNonBase64(byte[] data) {
|
||||
byte[] groomedData = new byte[data.length];
|
||||
int bytesCopied = 0;
|
||||
|
||||
for (byte datum : data) {
|
||||
if (isBase64(datum)) {
|
||||
groomedData[bytesCopied++] = datum;
|
||||
}
|
||||
}
|
||||
|
||||
byte[] packedData = new byte[bytesCopied];
|
||||
|
||||
System.arraycopy(groomedData, 0, packedData, 0, bytesCopied);
|
||||
|
||||
return packedData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes a byte[] containing binary data, into a byte[] containing
|
||||
* characters in the Base64 alphabet.
|
||||
*
|
||||
* @param pArray a byte array containing binary data
|
||||
* @return A byte array containing only Base64 character data
|
||||
*/
|
||||
public static byte[] encode(byte[] pArray) {
|
||||
return encodeBase64(pArray, false);
|
||||
}
|
||||
|
||||
public static String encode(String str) throws UnsupportedEncodingException
|
||||
{
|
||||
String baseStr = new String(encode(str.getBytes("UTF-8")));
|
||||
String tempStr = Digest.digest(str).toUpperCase();
|
||||
String result = tempStr+baseStr;
|
||||
return new String(encode(result.getBytes("UTF-8")));
|
||||
}
|
||||
|
||||
public static String decode(String cryptoStr) throws
|
||||
UnsupportedEncodingException {
|
||||
if(cryptoStr.length()<40)
|
||||
return "";
|
||||
try
|
||||
{
|
||||
String tempStr = new String(decode(cryptoStr.getBytes("UTF-8")));
|
||||
String result = tempStr.substring(40, tempStr.length());
|
||||
return new String(decode(result.getBytes("UTF-8")));
|
||||
}
|
||||
catch(ArrayIndexOutOfBoundsException ex)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes Base64 data into octects
|
||||
*
|
||||
* @param encoded string containing Base64 data
|
||||
* @return Array containind decoded data.
|
||||
*/
|
||||
public static byte[] decode2(String encoded) {
|
||||
|
||||
if (encoded == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
char[] base64Data = encoded.toCharArray();
|
||||
// remove white spaces
|
||||
int len = removeWhiteSpace(base64Data);
|
||||
|
||||
if (len % FOURBYTE != 0) {
|
||||
return null;//should be divisible by four
|
||||
}
|
||||
|
||||
int numberQuadruple = (len / FOURBYTE);
|
||||
|
||||
if (numberQuadruple == 0) {
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
byte[] decodedData;
|
||||
byte b1 = 0, b2 = 0, b3 = 0, b4 = 0;
|
||||
char d1 = 0, d2 = 0, d3 = 0, d4 = 0;
|
||||
|
||||
int i = 0;
|
||||
int encodedIndex = 0;
|
||||
int dataIndex = 0;
|
||||
decodedData = new byte[(numberQuadruple) * 3];
|
||||
|
||||
for (; i < numberQuadruple - 1; i++) {
|
||||
|
||||
if (!isData((d1 = base64Data[dataIndex++])) || !isData((d2 = base64Data[dataIndex++]))
|
||||
|| !isData((d3 = base64Data[dataIndex++]))
|
||||
|| !isData((d4 = base64Data[dataIndex++]))) {
|
||||
return null;
|
||||
}//if found "no data" just return null
|
||||
|
||||
b1 = base64Alphabet[d1];
|
||||
b2 = base64Alphabet[d2];
|
||||
b3 = base64Alphabet[d3];
|
||||
b4 = base64Alphabet[d4];
|
||||
|
||||
decodedData[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4);
|
||||
decodedData[encodedIndex++] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
|
||||
decodedData[encodedIndex++] = (byte) (b3 << 6 | b4);
|
||||
}
|
||||
|
||||
if (!isData((d1 = base64Data[dataIndex++])) || !isData((d2 = base64Data[dataIndex++]))) {
|
||||
return null;//if found "no data" just return null
|
||||
}
|
||||
|
||||
b1 = base64Alphabet[d1];
|
||||
b2 = base64Alphabet[d2];
|
||||
|
||||
d3 = base64Data[dataIndex++];
|
||||
d4 = base64Data[dataIndex++];
|
||||
if (!isData((d3)) || !isData((d4))) {//Check if they are PAD characters
|
||||
if (isPad(d3) && isPad(d4)) {
|
||||
if ((b2 & 0xf) != 0)//last 4 bits should be zero
|
||||
{
|
||||
return null;
|
||||
}
|
||||
byte[] tmp = new byte[i * 3 + 1];
|
||||
System.arraycopy(decodedData, 0, tmp, 0, i * 3);
|
||||
tmp[encodedIndex] = (byte) (b1 << 2 | b2 >> 4);
|
||||
return tmp;
|
||||
} else if (!isPad(d3) && isPad(d4)) {
|
||||
b3 = base64Alphabet[d3];
|
||||
if ((b3 & 0x3) != 0)//last 2 bits should be zero
|
||||
{
|
||||
return null;
|
||||
}
|
||||
byte[] tmp = new byte[i * 3 + 2];
|
||||
System.arraycopy(decodedData, 0, tmp, 0, i * 3);
|
||||
tmp[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4);
|
||||
tmp[encodedIndex] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
|
||||
return tmp;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else { //No PAD e.g 3cQl
|
||||
b3 = base64Alphabet[d3];
|
||||
b4 = base64Alphabet[d4];
|
||||
decodedData[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4);
|
||||
decodedData[encodedIndex++] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
|
||||
decodedData[encodedIndex++] = (byte) (b3 << 6 | b4);
|
||||
|
||||
}
|
||||
|
||||
return decodedData;
|
||||
}
|
||||
|
||||
private static boolean isWhiteSpace(char octect) {
|
||||
return (octect == 0x20 || octect == 0xd || octect == 0xa || octect == 0x9);
|
||||
}
|
||||
|
||||
private static boolean isData(char octect) {
|
||||
return (octect < BASELENGTH && base64Alphabet[octect] != -1);
|
||||
}
|
||||
|
||||
private static boolean isPad(char octect) {
|
||||
return (octect == PAD);
|
||||
}
|
||||
|
||||
/**
|
||||
* remove WhiteSpace from MIME containing encoded Base64 data.
|
||||
*
|
||||
* @param data the byte array of base64 data (with WS)
|
||||
* @return the new length
|
||||
*/
|
||||
private static int removeWhiteSpace(char[] data) {
|
||||
if (data == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// count characters that's not whitespace
|
||||
int newSize = 0;
|
||||
int len = data.length;
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (!isWhiteSpace(data[i])) {
|
||||
data[newSize++] = data[i];
|
||||
}
|
||||
}
|
||||
return newSize;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
package com.abbidot.baselibrary.util.rsa;
|
||||
|
||||
import com.abbidot.baselibrary.util.LogUtil;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Arrays;
|
||||
|
||||
|
||||
public class Digest {
|
||||
private static final String ENCODE = "UTF-8";
|
||||
|
||||
private String signMD5(String aValue, String encoding) {
|
||||
try {
|
||||
byte[] input = aValue.getBytes(encoding);
|
||||
MessageDigest md = MessageDigest.getInstance("MD5");
|
||||
return toHex(md.digest(input));
|
||||
} catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
|
||||
LogUtil.Companion.e("signMD5 Exception" + e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private String hmacSign(String aValue) {
|
||||
try {
|
||||
byte[] input = aValue.getBytes();
|
||||
MessageDigest md = MessageDigest.getInstance("MD5");
|
||||
return toHex(md.digest(input));
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
LogUtil.Companion.e(e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private String hmacSign(String aValue, String aKey) {
|
||||
return hmacSign(aValue, aKey, ENCODE);
|
||||
}
|
||||
|
||||
public static String hmacSign(String aValue, String aKey, String encoding) {
|
||||
byte[] k_ipad = new byte[64];
|
||||
byte[] k_opad = new byte[64];
|
||||
byte[] keyb;
|
||||
byte[] value;
|
||||
try {
|
||||
keyb = aKey.getBytes(encoding);
|
||||
value = aValue.getBytes(encoding);
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
keyb = aKey.getBytes();
|
||||
value = aValue.getBytes();
|
||||
}
|
||||
Arrays.fill(k_ipad, keyb.length, 64, (byte) 54);
|
||||
Arrays.fill(k_opad, keyb.length, 64, (byte) 92);
|
||||
for (int i = 0; i < keyb.length; i++) {
|
||||
k_ipad[i] = (byte) (keyb[i] ^ 0x36);
|
||||
k_opad[i] = (byte) (keyb[i] ^ 0x5c);
|
||||
}
|
||||
|
||||
MessageDigest md;
|
||||
try {
|
||||
md = MessageDigest.getInstance("MD5");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
LogUtil.Companion.e("hmacSign Exception:" + e.getMessage());
|
||||
return null;
|
||||
}
|
||||
md.update(k_ipad);
|
||||
md.update(value);
|
||||
byte[] dg = md.digest();
|
||||
md.reset();
|
||||
md.update(k_opad);
|
||||
md.update(dg, 0, 16);
|
||||
dg = md.digest();
|
||||
return toHex(dg);
|
||||
}
|
||||
|
||||
private String hmacSHASign(String aValue, String aKey, String encoding) {
|
||||
byte[] k_ipad = new byte[64];
|
||||
byte[] k_opad = new byte[64];
|
||||
byte[] keyb;
|
||||
byte[] value;
|
||||
try {
|
||||
keyb = aKey.getBytes(encoding);
|
||||
value = aValue.getBytes(encoding);
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
keyb = aKey.getBytes();
|
||||
value = aValue.getBytes();
|
||||
}
|
||||
Arrays.fill(k_ipad, keyb.length, 64, (byte) 54);
|
||||
Arrays.fill(k_opad, keyb.length, 64, (byte) 92);
|
||||
for (int i = 0; i < keyb.length; i++) {
|
||||
k_ipad[i] = (byte) (keyb[i] ^ 0x36);
|
||||
k_opad[i] = (byte) (keyb[i] ^ 0x5c);
|
||||
}
|
||||
|
||||
MessageDigest md;
|
||||
try {
|
||||
md = MessageDigest.getInstance("SHA");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
LogUtil.Companion.e("hmacSHASign Exception:" + e.getMessage());
|
||||
return null;
|
||||
}
|
||||
md.update(k_ipad);
|
||||
md.update(value);
|
||||
byte[] dg = md.digest();
|
||||
md.reset();
|
||||
md.update(k_opad);
|
||||
md.update(dg, 0, 20);
|
||||
dg = md.digest();
|
||||
return toHex(dg);
|
||||
}
|
||||
|
||||
public static String digest(String aValue) {
|
||||
return digest(aValue, ENCODE);
|
||||
}
|
||||
|
||||
public static String digest(String aValue, String encoding) {
|
||||
aValue = aValue.trim();
|
||||
byte[] value;
|
||||
try {
|
||||
value = aValue.getBytes(encoding);
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
value = aValue.getBytes();
|
||||
}
|
||||
MessageDigest md;
|
||||
try {
|
||||
md = MessageDigest.getInstance("SHA");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
LogUtil.Companion.e("digest Exception:" + e.getMessage());
|
||||
return null;
|
||||
}
|
||||
return toHex(md.digest(value));
|
||||
}
|
||||
|
||||
|
||||
private String digest(String aValue, String alg, String encoding) {
|
||||
aValue = aValue.trim();
|
||||
byte[] value;
|
||||
try {
|
||||
value = aValue.getBytes(encoding);
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
value = aValue.getBytes();
|
||||
}
|
||||
MessageDigest md;
|
||||
try {
|
||||
md = MessageDigest.getInstance(alg);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
LogUtil.Companion.e("digest Exception:" + e.getMessage());
|
||||
return null;
|
||||
}
|
||||
return toHex(md.digest(value));
|
||||
}
|
||||
|
||||
private String udpSign(String aValue) {
|
||||
try {
|
||||
byte[] input = aValue.getBytes(StandardCharsets.UTF_8);
|
||||
MessageDigest md = MessageDigest.getInstance("SHA1");
|
||||
return new String(Base64.encode(md.digest(input)), ENCODE);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static String toHex(byte[] input) {
|
||||
if (input == null)
|
||||
return null;
|
||||
StringBuilder output = new StringBuilder(input.length * 2);
|
||||
for (byte b : input) {
|
||||
int current = b & 0xff;
|
||||
if (current < 16)
|
||||
output.append("0");
|
||||
output.append(Integer.toString(current, 16));
|
||||
}
|
||||
return output.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
package com.abbidot.baselibrary.util.rsa;
|
||||
|
||||
/*
|
||||
--------------------------------------------**********--------------------------------------------
|
||||
|
||||
该算法于1977年由美国麻省理工学院MIT(Massachusetts Institute of Technology)的Ronal Rivest,Adi Shamir和Len
|
||||
Adleman三位年轻教授提出,并以三人的姓氏Rivest,Shamir和Adlernan命名为RSA算法,是一个支持变长密钥的公共密钥算法,需要加密的文件快的长度也是可变的!
|
||||
|
||||
所谓RSA加密算法,是世界上第一个非对称加密算法,也是数论的第一个实际应用。它的算法如下:
|
||||
|
||||
1.找两个非常大的质数p和q(通常p和q都有155十进制位或都有512十进制位)并计算n=pq,k=(p-1)(q-1)。
|
||||
|
||||
2.将明文编码成整数M,保证M不小于0但是小于n。
|
||||
|
||||
3.任取一个整数e,保证e和k互质,而且e不小于0但是小于k。加密钥匙(称作公钥)是(e, n)。
|
||||
|
||||
4.找到一个整数d,使得ed除以k的余数是1(只要e和n满足上面条件,d肯定存在)。解密钥匙(称作密钥)是(d, n)。
|
||||
|
||||
加密过程: 加密后的编码C等于M的e次方除以n所得的余数。
|
||||
|
||||
解密过程: 解密后的编码N等于C的d次方除以n所得的余数。
|
||||
|
||||
只要e、d和n满足上面给定的条件。M等于N。
|
||||
|
||||
--------------------------------------------**********--------------------------------------------
|
||||
*/
|
||||
|
||||
import com.abbidot.baselibrary.util.LogUtil;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.Key;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.Signature;
|
||||
import java.security.interfaces.RSAPublicKey;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
|
||||
|
||||
/**
|
||||
* <a href="https://github.com/wustrive2008/aes-rsa-java">源码链接</a>
|
||||
*/
|
||||
public class RSA {
|
||||
|
||||
private static final String CHAR_ENCODING = "UTF-8";
|
||||
private static final String AES_ALGORITHM = "AES/CBC/PKCS5Padding";
|
||||
private static final String RSA_ALGORITHM = "RSA/ECB/PKCS1Padding";
|
||||
|
||||
/**
|
||||
* 生成密钥对
|
||||
*/
|
||||
private Map<String, String> generateKeyPair() throws Exception {
|
||||
// 指定key的大小
|
||||
int keySize = 2048;
|
||||
/** RSA算法要求有一个可信任的随机数源 */
|
||||
SecureRandom sr = new SecureRandom();
|
||||
/** 为RSA算法创建一个KeyPairGenerator对象 */
|
||||
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
|
||||
/** 利用上面的随机数据源初始化这个KeyPairGenerator对象 */
|
||||
kpg.initialize(keySize, sr);
|
||||
/** 生成密匙对 */
|
||||
KeyPair kp = kpg.generateKeyPair();
|
||||
/** 得到公钥 */
|
||||
Key publicKey = kp.getPublic();
|
||||
byte[] publicKeyBytes = publicKey.getEncoded();
|
||||
String pub = new String(Base64.encodeBase64(publicKeyBytes), CHAR_ENCODING);
|
||||
/** 得到私钥 */
|
||||
Key privateKey = kp.getPrivate();
|
||||
byte[] privateKeyBytes = privateKey.getEncoded();
|
||||
String pri = new String(Base64.encodeBase64(privateKeyBytes), CHAR_ENCODING);
|
||||
|
||||
Map<String, String> map = new HashMap<>();
|
||||
map.put("publicKey", pub);
|
||||
map.put("privateKey", pri);
|
||||
RSAPublicKey rsp = (RSAPublicKey) kp.getPublic();
|
||||
BigInteger bint = rsp.getModulus();
|
||||
byte[] b = bint.toByteArray();
|
||||
byte[] deBase64Value = Base64.encodeBase64(b);
|
||||
String retValue = new String(deBase64Value);
|
||||
map.put("modulus", retValue);
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* 加密方法 source: 源数据
|
||||
*/
|
||||
public static String encrypt(String source, String publicKey) throws Exception {
|
||||
Key key = getPublicKey(publicKey);
|
||||
/** 得到Cipher对象来实现对源数据的RSA加密 */
|
||||
Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
|
||||
cipher.init(Cipher.ENCRYPT_MODE, key);
|
||||
byte[] b = source.getBytes();
|
||||
/** 执行加密操作 */
|
||||
byte[] b1 = cipher.doFinal(b);
|
||||
return new String(Base64.encodeBase64(b1), CHAR_ENCODING);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解密算法 cryptograph:密文
|
||||
*/
|
||||
private String decrypt(String cryptograph, String privateKey) throws Exception {
|
||||
Key key = getPrivateKey(privateKey);
|
||||
/** 得到Cipher对象对已用公钥加密的数据进行RSA解密 */
|
||||
Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
|
||||
cipher.init(Cipher.DECRYPT_MODE, key);
|
||||
byte[] b1 = Base64.decodeBase64(cryptograph.getBytes());
|
||||
/** 执行解密操作 */
|
||||
byte[] b = cipher.doFinal(b1);
|
||||
return new String(b);
|
||||
}
|
||||
|
||||
/**
|
||||
* 得到公钥
|
||||
*
|
||||
* @param key 密钥字符串(经过base64编码)
|
||||
* @throws Exception
|
||||
*/
|
||||
private static PublicKey getPublicKey(String key) throws Exception {
|
||||
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(Base64.decodeBase64(key.getBytes()));
|
||||
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
|
||||
return keyFactory.generatePublic(keySpec);
|
||||
}
|
||||
|
||||
/**
|
||||
* 得到私钥
|
||||
*
|
||||
* @param key 密钥字符串(经过base64编码)
|
||||
* @throws Exception
|
||||
*/
|
||||
private PrivateKey getPrivateKey(String key) throws Exception {
|
||||
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(key.getBytes()));
|
||||
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
|
||||
return keyFactory.generatePrivate(keySpec);
|
||||
}
|
||||
|
||||
private String sign(String content, String privateKey) {
|
||||
try {
|
||||
PKCS8EncodedKeySpec priPKCS8 =
|
||||
new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKey.getBytes()));
|
||||
KeyFactory keyf = KeyFactory.getInstance("RSA");
|
||||
PrivateKey priKey = keyf.generatePrivate(priPKCS8);
|
||||
|
||||
Signature signature = Signature.getInstance("SHA256WithRSA");
|
||||
|
||||
signature.initSign(priKey);
|
||||
signature.update(content.getBytes(CHAR_ENCODING));
|
||||
|
||||
byte[] signed = signature.sign();
|
||||
|
||||
return new String(Base64.encodeBase64(signed));
|
||||
} catch (Exception e) {
|
||||
LogUtil.Companion.e("sign Exception:" + e.getMessage());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean checkSign(String content, String sign, String publicKey) {
|
||||
try {
|
||||
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
|
||||
byte[] encodedKey = Base64.decode2(publicKey);
|
||||
PublicKey pubKey = keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey));
|
||||
|
||||
|
||||
Signature signature = Signature.getInstance("SHA256WithRSA");
|
||||
|
||||
signature.initVerify(pubKey);
|
||||
signature.update(content.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
return signature.verify(Base64.decode2(sign));
|
||||
|
||||
} catch (Exception e) {
|
||||
LogUtil.Companion.e("checkSign Exception:" + e.getMessage());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.abbidot.baselibrary
|
||||
|
||||
import org.junit.Test
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
class ExampleUnitTest {
|
||||
@Test
|
||||
fun addition_isCorrect() {
|
||||
assertEquals(4, 2 + 2)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user