first commit

This commit is contained in:
yezhiqiu
2025-09-30 10:01:20 +08:00
commit 0214fb09ed
1862 changed files with 119528 additions and 0 deletions

View File

@@ -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)
}
}

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
</manifest>

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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"
}
}

View File

@@ -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"
}
}

View File

@@ -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
}
}

View File

@@ -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()
}
}
}

View File

@@ -0,0 +1,4 @@
package com.abbidot.baselibrary.eventbus.core
object EmptyMessage {
}

View File

@@ -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)
}
}

View File

@@ -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)
}
}

View File

@@ -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 &amp; 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");
}
}
}

View File

@@ -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);
}
}

View File

@@ -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()
}
}

View File

@@ -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
}
}

View File

@@ -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()
}
}
}

View File

@@ -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
)

View File

@@ -0,0 +1,4 @@
package com.abbidot.baselibrary.network.base
interface IService<I> {
}

View File

@@ -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
}
}

View File

@@ -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)
}
}

View File

@@ -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)
}
}

View File

@@ -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())
}
}

View File

@@ -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
}
}

View File

@@ -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()
}
}
}

View File

@@ -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
}
}
}

View File

@@ -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
}
}
}

View File

@@ -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)
}
}
}
}

View File

@@ -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)
}
}

View 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里程mkm 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
}
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -0,0 +1,187 @@
package com.abbidot.baselibrary.util.rsa;
/*
--------------------------------------------**********--------------------------------------------
该算法于1977年由美国麻省理工学院MIT(Massachusetts Institute of Technology)的Ronal RivestAdi Shamir和Len
Adleman三位年轻教授提出并以三人的姓氏RivestShamir和Adlernan命名为RSA算法是一个支持变长密钥的公共密钥算法需要加密的文件快的长度也是可变的!
所谓RSA加密算法是世界上第一个非对称加密算法也是数论的第一个实际应用。它的算法如下
1.找两个非常大的质数p和q通常p和q都有155十进制位或都有512十进制位并计算n=pqk=(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;
}
}

View File

@@ -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)
}
}