基线备份:thirdparty 变体完成 + Android 4.2 兼容改造前
包含内容: - thirdparty flavor 独立 applicationId + Intent 入口(ThirdPartyEntryActivity) - 58 后端接口适配(单 /api 路径、code 0/200、camelCase 字段、UNBOUND 轮询) - MAC 硬件锚定校验(防一凭证多设备共用) - EPG mac 覆盖 SDK init(sMacOverride)+ ChannelManger 初始化对齐 - 提示文案优化(设备未注册/权益未绑定)
This commit is contained in:
26
libcore/src/androidTest/java/com/pukka/libcore/ExampleInstrumentedTest.java
Executable file
26
libcore/src/androidTest/java/com/pukka/libcore/ExampleInstrumentedTest.java
Executable file
@@ -0,0 +1,26 @@
|
||||
package com.pukka.libcore;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||
*/
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class ExampleInstrumentedTest {
|
||||
@Test
|
||||
public void useAppContext() {
|
||||
// Context of the app under test.
|
||||
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
|
||||
assertEquals("com.pukka.libcore.test", appContext.getPackageName());
|
||||
}
|
||||
}
|
||||
8
libcore/src/main/AndroidManifest.xml
Executable file
8
libcore/src/main/AndroidManifest.xml
Executable file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="com.pukka.libcore">
|
||||
<!--鸿蒙系统获取SN 尝试-->
|
||||
<!-- <uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE"-->
|
||||
<!-- tools:ignore="ProtectedPermissions" />-->
|
||||
</manifest>
|
||||
46
libcore/src/main/java/com/pukka/libcore/base/BaseConst.java
Executable file
46
libcore/src/main/java/com/pukka/libcore/base/BaseConst.java
Executable file
@@ -0,0 +1,46 @@
|
||||
package com.pukka.libcore.base;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.pukka.libcore.utils.Base64Util;
|
||||
import com.pukka.libcore.utils.LogUtils;
|
||||
import com.pukka.libcore.utils.StringUtils;
|
||||
|
||||
/**
|
||||
* @Auther: sunmm
|
||||
* @datetime: 2025/6/6 14:54
|
||||
* @desc:
|
||||
*/
|
||||
public class BaseConst {
|
||||
|
||||
public static final String header = "huashitv";
|
||||
|
||||
//渠道code
|
||||
public static String agentVendorCode = "1100101010208919998410060";
|
||||
//业务组合code
|
||||
public static String serviceComboCode = "1100121010208920760580061";
|
||||
//业务分组code
|
||||
public static String serviceGroupCode = "1100122010208920968780062";
|
||||
//业务线参数
|
||||
public static String businessLine = "4100450011967708592750001";
|
||||
|
||||
|
||||
public static String getMemberCode(String token){
|
||||
try {
|
||||
if (!StringUtils.isEmpty(token)){
|
||||
String str = token.split("\\.")[1];
|
||||
String memberCode = Base64Util.decode(str);
|
||||
LogUtils.d("getUserMemberInfo -- token is :"+token+" --str is:"+str);
|
||||
LogUtils.d("getUserMemberInfo -- memberCode is :"+memberCode);
|
||||
|
||||
if (!StringUtils.isEmpty(memberCode)){
|
||||
String code = JSON.parseObject(memberCode).getString("memberCode");
|
||||
return code;
|
||||
}
|
||||
}
|
||||
}catch (Exception e){
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
}
|
||||
68
libcore/src/main/java/com/pukka/libcore/base/BaseUrl.java
Executable file
68
libcore/src/main/java/com/pukka/libcore/base/BaseUrl.java
Executable file
@@ -0,0 +1,68 @@
|
||||
|
||||
package com.pukka.libcore.base;
|
||||
|
||||
|
||||
import com.pukka.libcore.utils.LogUtils;
|
||||
import com.pukka.libcore.utils.SharedPreferenceUtils;
|
||||
|
||||
public class BaseUrl {
|
||||
|
||||
/**
|
||||
* API域名,不带后面接口地址的
|
||||
*/
|
||||
private String baseHttpUrl = "";
|
||||
|
||||
private String baseHttpsUrl = "";
|
||||
|
||||
/**
|
||||
* 赋值baseurl前是哪个环境
|
||||
* "-1" 表示不会被赋值baseurl;
|
||||
* "0" 灰度
|
||||
* "1" 现网
|
||||
*/
|
||||
public String buildType = "-1";
|
||||
|
||||
private BaseUrl() {
|
||||
initEpgUrl();
|
||||
}
|
||||
|
||||
private void initEpgUrl() {
|
||||
baseHttpUrl = DifferenceUrl.getDefaultApi_http(this);
|
||||
baseHttpsUrl = DifferenceUrl.getDefaultApi_https(this);
|
||||
}
|
||||
|
||||
private static final class SingletonHolder {
|
||||
static final BaseUrl SINGLETON = new BaseUrl();
|
||||
}
|
||||
|
||||
public static BaseUrl getSingleton() {
|
||||
return SingletonHolder.SINGLETON;
|
||||
}
|
||||
|
||||
|
||||
public String getBaseHttpUrl() {
|
||||
return baseHttpUrl;
|
||||
}
|
||||
|
||||
public String getBaseHttpsUrl() {
|
||||
return baseHttpsUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前的是否是现网
|
||||
*/
|
||||
public String getBaseUrlState() {
|
||||
return SharedPreferenceUtils.getString("mishitong_data", "baseUrlState", "null");
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置url是预览还是现网
|
||||
* @param baseUrlState preview | debug
|
||||
*/
|
||||
public void setBaseUrlState(String baseUrlState) {
|
||||
LogUtils.d("setBaseUrlState --- " + baseUrlState);
|
||||
SharedPreferenceUtils.put("mishitong_data", "baseUrlState", baseUrlState);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
8
libcore/src/main/java/com/pukka/libcore/base/BuildConfig.java
Executable file
8
libcore/src/main/java/com/pukka/libcore/base/BuildConfig.java
Executable file
@@ -0,0 +1,8 @@
|
||||
package com.pukka.libcore.base;
|
||||
|
||||
public class BuildConfig {
|
||||
public static String FLAVOR = "";
|
||||
public static String BUILD_TYPE = "";
|
||||
public static int VERSION_CODE = 0;
|
||||
public static String VERSION_NAME = "";
|
||||
}
|
||||
79
libcore/src/main/java/com/pukka/libcore/base/DifferenceApi.java
Executable file
79
libcore/src/main/java/com/pukka/libcore/base/DifferenceApi.java
Executable file
@@ -0,0 +1,79 @@
|
||||
|
||||
package com.pukka.libcore.base;
|
||||
|
||||
import com.pukka.libcore.boxShared.BoxSharedParamsUtils;
|
||||
|
||||
/**
|
||||
* 全局配置api
|
||||
*/
|
||||
public class DifferenceApi {
|
||||
/**
|
||||
* Http配置
|
||||
*/
|
||||
public static class ApiHttp {
|
||||
/**
|
||||
* 获取默认api(现网环境)
|
||||
* 预发布=https://external-ep01.future.jetsentv.com:8010
|
||||
* 现网=https://servhub-ep03.future.jetsentv.com
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static String defaultApi_release() {
|
||||
if(BoxSharedParamsUtils.isJcHotel() || BoxSharedParamsUtils.isJcCloud()){
|
||||
return "https://servhub-ep01.future.jetsentv.com/";
|
||||
}
|
||||
return "https://servhub-ep03.future.jetsentv.com/";
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取默认api(灰度环境==预发布环境)
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static String defaultApi_pre() {
|
||||
return "https://external-ep01.future.jetsentv.com:8010";
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取默认api(测试环境)
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static String defaultApi_debug() {
|
||||
return "https://external-ep01.future.jetsentv.com:8010";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Https配置
|
||||
*/
|
||||
public static class ApiHttps {
|
||||
/**
|
||||
* 获取默认api(现网环境)
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static String defaultApi_release_https() {
|
||||
return "https://servhub-ep03.future.jetsentv.com/";
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取默认api(灰度环境)
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static String defaultApi_pre_https() {
|
||||
return "https://external-ep01.future.jetsentv.com:8010";
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取默认api(测试环境)
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static String defaultApi_debug_https() {
|
||||
return "https://external-ep01.future.jetsentv.com:8010";
|
||||
}
|
||||
}
|
||||
}
|
||||
59
libcore/src/main/java/com/pukka/libcore/base/DifferenceUrl.java
Executable file
59
libcore/src/main/java/com/pukka/libcore/base/DifferenceUrl.java
Executable file
@@ -0,0 +1,59 @@
|
||||
|
||||
package com.pukka.libcore.base;
|
||||
|
||||
/**
|
||||
* 各个分省差异化 url
|
||||
*/
|
||||
public class DifferenceUrl {
|
||||
|
||||
/**
|
||||
* 获取默认Api(http)
|
||||
*
|
||||
* @param singleton
|
||||
* @return
|
||||
*/
|
||||
public static String getDefaultApi_http(BaseUrl singleton) {
|
||||
|
||||
String baseUrlState = singleton.getBaseUrlState();
|
||||
if ("preview".equals(baseUrlState) || singleton.buildType.equals("0")) {
|
||||
return DifferenceApi.ApiHttp.defaultApi_pre();
|
||||
} else if ("release".equals(baseUrlState) || singleton.buildType.equals("1")) {
|
||||
return DifferenceApi.ApiHttp.defaultApi_release();
|
||||
} else if ("debug".equals(baseUrlState) || singleton.buildType.equals("2")) {
|
||||
return DifferenceApi.ApiHttp.defaultApi_release();
|
||||
} else{
|
||||
if (BuildConfig.BUILD_TYPE.contains("release")) {
|
||||
return DifferenceApi.ApiHttp.defaultApi_release();
|
||||
} else if (BuildConfig.BUILD_TYPE.contains("debug")) {
|
||||
return DifferenceApi.ApiHttp.defaultApi_release();
|
||||
} else {
|
||||
return DifferenceApi.ApiHttp.defaultApi_pre();
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 获取默认Api(https)
|
||||
*
|
||||
* @param singleton
|
||||
* @return
|
||||
*/
|
||||
public static String getDefaultApi_https(BaseUrl singleton) {
|
||||
String baseUrlState = singleton.getBaseUrlState();
|
||||
if ("preview".equals(baseUrlState) || singleton.buildType.equals("0")) {
|
||||
return DifferenceApi.ApiHttps.defaultApi_pre_https();
|
||||
} else if ("release".equals(baseUrlState) || singleton.buildType.equals("1")) {
|
||||
return DifferenceApi.ApiHttps.defaultApi_release_https();
|
||||
} else if ("debug".equals(baseUrlState) || singleton.buildType.equals("2")) {
|
||||
return DifferenceApi.ApiHttps.defaultApi_release_https();
|
||||
} else {
|
||||
if (BuildConfig.BUILD_TYPE.contains("release")) {
|
||||
return DifferenceApi.ApiHttps.defaultApi_release_https();
|
||||
} else if (BuildConfig.BUILD_TYPE.contains("debug")) {
|
||||
return DifferenceApi.ApiHttps.defaultApi_release_https();
|
||||
} else {
|
||||
return DifferenceApi.ApiHttps.defaultApi_pre_https();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
585
libcore/src/main/java/com/pukka/libcore/boxShared/BoxSharedParameters.java
Executable file
585
libcore/src/main/java/com/pukka/libcore/boxShared/BoxSharedParameters.java
Executable file
@@ -0,0 +1,585 @@
|
||||
package com.pukka.libcore.boxShared;
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.pukka.libcore.utils.ContextUtils;
|
||||
import com.pukka.libcore.utils.LogUtils;
|
||||
import com.pukka.libcore.utils.StringUtils;
|
||||
|
||||
|
||||
/**
|
||||
* 获取盒子共享参数
|
||||
*/
|
||||
public class BoxSharedParameters {
|
||||
|
||||
private static BoxSharedParameters instance;
|
||||
private static final String TAG = "BoxSharedParameters";
|
||||
|
||||
public static BoxSharedParameters getInstance() {
|
||||
if (instance == null) {
|
||||
synchronized (BoxSharedParameters.class) {
|
||||
if (instance == null) {
|
||||
instance = new BoxSharedParameters();
|
||||
}
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
private String mac;
|
||||
//盒子型号
|
||||
private String model;
|
||||
//串号
|
||||
private String sn;
|
||||
private String stbId;
|
||||
private String loginInfo;
|
||||
private String tongYongStbId;
|
||||
/**
|
||||
* EPG域名地址
|
||||
*/
|
||||
private String epgUrl;
|
||||
private String phoneNumber;
|
||||
private String token;
|
||||
private String userId;
|
||||
private String passId;
|
||||
private String IP;
|
||||
//渠道号
|
||||
private String channelId;
|
||||
//渠道类型 fs 分省 jd 基地
|
||||
private String channelType = "fs";
|
||||
//是否是通用母渠道
|
||||
private boolean isTongYong;
|
||||
//用户信息
|
||||
private String data = "";
|
||||
//登陆放通开关
|
||||
private boolean isToPass;
|
||||
|
||||
//搜索页vip角标
|
||||
private String searchCornerUrl;
|
||||
|
||||
//未来鉴权放通 true 放通 false 不放通
|
||||
private boolean isNewTvPass = false;
|
||||
//热搜榜id
|
||||
private String hotSearchId;
|
||||
|
||||
//热搜榜开关
|
||||
private boolean isShowSearchHot = true;
|
||||
//省份code
|
||||
private String provinceCode = "";
|
||||
//省份号
|
||||
private String provinceId = "";
|
||||
//播放器类型 0:系统播放器 1:杭研播放器
|
||||
private int videoPlayType = 0;
|
||||
|
||||
//0、代表端内访问; 1、牌照方直接拉起(例如直接拉起详情页、专题页,二级页等页面); 2、统一搜索拉起
|
||||
public String platform = "0";
|
||||
|
||||
//奥运前缀
|
||||
private String ayUrl = "";
|
||||
//是否使用https请求
|
||||
private boolean isDoHttps = false;
|
||||
//是否支持组播 1表示支持rtsp,0表示不支持
|
||||
private int ottRtspFlag = 0;
|
||||
//江苏Epg地址
|
||||
private String epgServer = "";
|
||||
//江苏ottUserToken
|
||||
private String ottUserToken;
|
||||
//江苏cspid
|
||||
private String csPid;
|
||||
//江苏cdn地址
|
||||
private String cdnAddress;
|
||||
//江苏cdn备份地址
|
||||
private String backCdnAddress;
|
||||
|
||||
//安徽机顶盒型号
|
||||
private String STBModel = "";
|
||||
|
||||
//安徽设备有线MAC地址
|
||||
private String STBMAC = "";
|
||||
|
||||
//浙江port
|
||||
private String port;
|
||||
|
||||
//鉴权主域名
|
||||
private String provInteface = "";
|
||||
|
||||
//vr播放器
|
||||
private String vrType;
|
||||
|
||||
//加载原生或H5首页 默认为2 h5页面
|
||||
private int loadType = 1;
|
||||
|
||||
//江苏是否开启首页自动全屏播放 默认开启
|
||||
private boolean silenceSwitch = true;
|
||||
//江苏首页自动全屏播放需要时间 默认时间15秒
|
||||
private int silenceTiming = 15;
|
||||
|
||||
//四川cdn信息
|
||||
private String cdnInfo;
|
||||
|
||||
//是否放通
|
||||
private boolean isHttpsPass = true;
|
||||
|
||||
|
||||
public void initBoxInfo() {
|
||||
isTongYong = BoxSharedParamsUtils.judgeTongYong();
|
||||
if (TextUtils.isEmpty(mac)) {
|
||||
mac = MacUtils.getMac(ContextUtils.getApplicationContext());
|
||||
}
|
||||
if (TextUtils.isEmpty(channelId)) {
|
||||
channelId = BoxSharedParamsUtils.getInstance().getChannelId();
|
||||
}
|
||||
if (TextUtils.isEmpty(tongYongStbId)) {
|
||||
tongYongStbId = stbId;
|
||||
}
|
||||
}
|
||||
|
||||
public void setDoHttps(boolean doHttps) {
|
||||
isDoHttps = doHttps;
|
||||
}
|
||||
|
||||
public void setProvInteface(String provinteface) {
|
||||
this.provInteface = provinteface;
|
||||
}
|
||||
|
||||
public String getProvInteface() {
|
||||
return provInteface;
|
||||
}
|
||||
|
||||
public void setVrType(String vrtype) {
|
||||
this.vrType = vrtype;
|
||||
}
|
||||
|
||||
public String getVrType() {
|
||||
return vrType;
|
||||
}
|
||||
|
||||
public void setLoadType(int loadType) {
|
||||
this.loadType = loadType;
|
||||
}
|
||||
|
||||
public int getLoadType() {
|
||||
return loadType;
|
||||
}
|
||||
|
||||
public boolean isDoHttps() {
|
||||
return isDoHttps;
|
||||
}
|
||||
|
||||
public void setPlatform(String platform) {
|
||||
if (!TextUtils.isEmpty(platform)) {
|
||||
this.platform = platform;
|
||||
}
|
||||
}
|
||||
|
||||
public String getSTBModel() {
|
||||
return STBModel;
|
||||
}
|
||||
|
||||
public void setSTBModel(String STBModel) {
|
||||
this.STBModel = STBModel;
|
||||
}
|
||||
|
||||
public String getSTBMAC() {
|
||||
return STBMAC;
|
||||
}
|
||||
|
||||
public void setSTBMAC(String STBMAC) {
|
||||
this.STBMAC = STBMAC;
|
||||
}
|
||||
|
||||
public String getPlatform() {
|
||||
return platform;
|
||||
}
|
||||
|
||||
|
||||
public String getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
public void setPort(String port) {
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
public String getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public void setData(String data) {
|
||||
if (TextUtils.isEmpty(data)) {
|
||||
return;
|
||||
}
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public void setEpgUrl(String epgUrl) {
|
||||
this.epgUrl = epgUrl;
|
||||
}
|
||||
|
||||
public String getEpgUrl() {
|
||||
return epgUrl;
|
||||
}
|
||||
|
||||
public void setAyUrl(String ayUrl) {
|
||||
this.ayUrl = ayUrl;
|
||||
}
|
||||
|
||||
public String getAyUrl() {
|
||||
return ayUrl;
|
||||
}
|
||||
|
||||
public boolean isNewTvPass() {
|
||||
return isNewTvPass;
|
||||
}
|
||||
|
||||
public void setNewTvPass(boolean newTvPass) {
|
||||
isNewTvPass = newTvPass;
|
||||
}
|
||||
|
||||
public boolean isShowSearchHot() {
|
||||
return isShowSearchHot;
|
||||
}
|
||||
|
||||
public void setShowSearchHot(boolean showSearchHot) {
|
||||
isShowSearchHot = showSearchHot;
|
||||
}
|
||||
|
||||
public int getVideoPlayType() {
|
||||
LogUtils.d("getVideoPlayType: " + videoPlayType);
|
||||
return videoPlayType;
|
||||
}
|
||||
|
||||
public void setVideoPlayType(int videoPlayType) {
|
||||
LogUtils.d("videoPlayType: " + videoPlayType);
|
||||
this.videoPlayType = videoPlayType;
|
||||
}
|
||||
|
||||
public boolean isToPass() {
|
||||
return isToPass;
|
||||
}
|
||||
|
||||
public String getSearchCornerUrl() {
|
||||
return searchCornerUrl;
|
||||
}
|
||||
|
||||
public void setSearchCornerUrl(String searchCornerUrl) {
|
||||
this.searchCornerUrl = searchCornerUrl;
|
||||
}
|
||||
|
||||
public String getHotSearchId() {
|
||||
return hotSearchId;
|
||||
}
|
||||
|
||||
public void setHotSearchId(String hotSearchId) {
|
||||
this.hotSearchId = hotSearchId;
|
||||
}
|
||||
|
||||
public String getProvinceCode() {
|
||||
return provinceCode;
|
||||
}
|
||||
|
||||
public void setProvinceCode(String provinceCode) { //3位数的code
|
||||
this.provinceCode = provinceCode;
|
||||
}
|
||||
|
||||
public String getProvinceId() {
|
||||
return provinceId;
|
||||
}
|
||||
|
||||
public void setProvinceId(String provinceId) { //2位数的id
|
||||
this.provinceId = provinceId;
|
||||
}
|
||||
|
||||
public void setToPass(boolean toPass) {
|
||||
isToPass = toPass;
|
||||
}
|
||||
|
||||
public void clearData() {
|
||||
this.data = "";
|
||||
this.phoneNumber = "";
|
||||
this.userId = "";
|
||||
this.passId = "";
|
||||
}
|
||||
|
||||
public boolean isTongYong() {
|
||||
return isTongYong;
|
||||
}
|
||||
|
||||
public void setChannelType(String channelType) { //通用基础值为jd 这里调用citycode.xml给某些渠道赋值成为分省
|
||||
if (BoxSharedParameters.getInstance().getProvinceCode().equals("90")) {
|
||||
return;
|
||||
}
|
||||
this.channelType = channelType;
|
||||
}
|
||||
|
||||
public void setChannelId(String channelId) {
|
||||
this.channelId = channelId;
|
||||
}
|
||||
|
||||
public String getChannelId() {
|
||||
if (TextUtils.isEmpty(channelId)) {
|
||||
initBoxInfo();
|
||||
}
|
||||
return TextUtils.isEmpty(channelId) ? "001" : channelId;
|
||||
}
|
||||
|
||||
public String getChannelType() {
|
||||
if (TextUtils.isEmpty(channelType)) {
|
||||
initBoxInfo();
|
||||
}
|
||||
return channelType;
|
||||
}
|
||||
|
||||
public String getUserId() {
|
||||
// if (isToPass) {
|
||||
// return stbId;
|
||||
// }
|
||||
// if (TextUtils.isEmpty(userId)) {
|
||||
// initBoxInfo();
|
||||
// }
|
||||
return TextUtils.isEmpty(userId)?"":userId;
|
||||
}
|
||||
/**
|
||||
* 是否已经登录
|
||||
* */
|
||||
public boolean isLogin(){
|
||||
//是否已登录
|
||||
boolean isLogin = false;
|
||||
if(!StringUtils.isEmpty(userId) && !StringUtils.isEmpty(token)){
|
||||
isLogin = true;
|
||||
}
|
||||
return isLogin;
|
||||
}
|
||||
/**
|
||||
* 退出登录
|
||||
* */
|
||||
public void logout(){
|
||||
userId = "";
|
||||
token="";
|
||||
}
|
||||
|
||||
|
||||
public void setUserId(String userId) {
|
||||
if (TextUtils.isEmpty(userId)) {
|
||||
return;
|
||||
}
|
||||
this.userId = userId;
|
||||
}
|
||||
|
||||
public String getPassId() {
|
||||
return passId;
|
||||
}
|
||||
|
||||
public void setPassId(String passId) {
|
||||
this.passId = passId;
|
||||
}
|
||||
|
||||
public String getToken() {
|
||||
// if (isToPass) {
|
||||
// return stbId;
|
||||
// }
|
||||
// if (TextUtils.isEmpty(token)) {
|
||||
// initBoxInfo();
|
||||
// }
|
||||
return token;
|
||||
}
|
||||
|
||||
public void setToken(String token) {
|
||||
if (TextUtils.isEmpty(token)) {
|
||||
return;
|
||||
}
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
public String getMac() {
|
||||
if (TextUtils.isEmpty(mac)) {
|
||||
initBoxInfo();
|
||||
}
|
||||
return mac;
|
||||
}
|
||||
|
||||
public void setMac(String mac) {
|
||||
if (TextUtils.isEmpty(mac)) {
|
||||
return;
|
||||
}
|
||||
this.mac = mac;
|
||||
}
|
||||
|
||||
public String getModel() {
|
||||
if (TextUtils.isEmpty(model)) {
|
||||
initBoxInfo();
|
||||
}
|
||||
return model;
|
||||
}
|
||||
|
||||
public void setModel(String model) {
|
||||
if (TextUtils.isEmpty(model)) {
|
||||
return;
|
||||
}
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
public String getSn() {
|
||||
if (TextUtils.isEmpty(sn)) {
|
||||
initBoxInfo();
|
||||
}
|
||||
return sn;
|
||||
}
|
||||
|
||||
public void setSn(String sn) {
|
||||
if (TextUtils.isEmpty(sn)) {
|
||||
return;
|
||||
}
|
||||
this.sn = sn;
|
||||
}
|
||||
|
||||
public String getLoginInfo() {
|
||||
return loginInfo;
|
||||
}
|
||||
|
||||
public void setLoginInfo(String loginInfo) {
|
||||
this.loginInfo = loginInfo;
|
||||
}
|
||||
|
||||
|
||||
public String getStbId() {
|
||||
if (TextUtils.isEmpty(stbId)) {
|
||||
initBoxInfo();
|
||||
}
|
||||
return stbId;
|
||||
}
|
||||
|
||||
public void setStbId(String stbId) {
|
||||
if (TextUtils.isEmpty(stbId)) {
|
||||
return;
|
||||
}
|
||||
this.stbId = stbId;
|
||||
}
|
||||
|
||||
public String getIP() {
|
||||
if (TextUtils.isEmpty(IP)) {
|
||||
initBoxInfo();
|
||||
}
|
||||
return IP;
|
||||
}
|
||||
|
||||
public void setIP(String IP) {
|
||||
if (TextUtils.isEmpty(IP)) {
|
||||
return;
|
||||
}
|
||||
this.IP = IP;
|
||||
}
|
||||
|
||||
public String getTongYongStbId() {
|
||||
if (isToPass) {
|
||||
return stbId;
|
||||
}
|
||||
return tongYongStbId;
|
||||
}
|
||||
|
||||
public String getPhoneNumber() {
|
||||
if (TextUtils.isEmpty(phoneNumber)) {
|
||||
initBoxInfo();
|
||||
}
|
||||
LogUtils.d(TAG + "get phoneNumber: " + phoneNumber);
|
||||
return phoneNumber;
|
||||
}
|
||||
|
||||
public void setPhoneNumber(String phoneNumber) {
|
||||
if (TextUtils.isEmpty(phoneNumber)) {
|
||||
return;
|
||||
}
|
||||
this.phoneNumber = phoneNumber;
|
||||
}
|
||||
|
||||
public int getOttRtspFlag() {
|
||||
return ottRtspFlag;
|
||||
}
|
||||
|
||||
public void setOttRtspFlag(String ottRtspFlag) {
|
||||
// try {
|
||||
// if (!StringUtils.isEmpty(ottRtspFlag) && ottRtspFlag.equals("dataNameNotExist")) {
|
||||
// this.ottRtspFlag = 0;
|
||||
// return;
|
||||
// }
|
||||
// if (!StringUtils.isEmpty(ottRtspFlag)) {
|
||||
// this.ottRtspFlag = Integer.valueOf(ottRtspFlag);
|
||||
// }
|
||||
// } catch (Exception ex) {
|
||||
// ex.printStackTrace();
|
||||
// }
|
||||
}
|
||||
|
||||
public String getEpgServer() {
|
||||
return epgServer;
|
||||
}
|
||||
|
||||
public void setEpgServer(String epgServer) {
|
||||
this.epgServer = epgServer;
|
||||
}
|
||||
|
||||
public String getOttUserToken() {
|
||||
LogUtils.d(TAG + "get ottUserToken: " + ottUserToken);
|
||||
return ottUserToken;
|
||||
}
|
||||
|
||||
public void setOttUserToken(String ottUserToken) {
|
||||
this.ottUserToken = ottUserToken;
|
||||
}
|
||||
|
||||
public String getCsPid() {
|
||||
return csPid;
|
||||
}
|
||||
|
||||
public void setCsPid(String csPid) {
|
||||
this.csPid = csPid;
|
||||
}
|
||||
|
||||
public String getCdnAddress() {
|
||||
return cdnAddress;
|
||||
}
|
||||
|
||||
public void setCdnAddress(String cdnAddress) {
|
||||
this.cdnAddress = cdnAddress;
|
||||
}
|
||||
|
||||
public String getBackCdnAddress() {
|
||||
return backCdnAddress;
|
||||
}
|
||||
|
||||
public void setBackCdnAddress(String backCdnAddress) {
|
||||
this.backCdnAddress = backCdnAddress;
|
||||
}
|
||||
|
||||
public boolean isSilenceSwitch() {
|
||||
return silenceSwitch;
|
||||
}
|
||||
|
||||
public void setSilenceSwitch(boolean silenceSwitch) {
|
||||
this.silenceSwitch = silenceSwitch;
|
||||
}
|
||||
|
||||
public int getSilenceTiming() {
|
||||
return silenceTiming;
|
||||
}
|
||||
|
||||
public void setSilenceTiming(int silenceTiming) {
|
||||
this.silenceTiming = silenceTiming;
|
||||
}
|
||||
|
||||
public String getCdnInfo() {
|
||||
return cdnInfo;
|
||||
}
|
||||
|
||||
public void setCdnInfo(String cdnInfo) {
|
||||
this.cdnInfo = cdnInfo;
|
||||
}
|
||||
|
||||
public boolean isHttpsPass() {
|
||||
return isHttpsPass;
|
||||
}
|
||||
|
||||
public void setHttpsPass(boolean httpsPass) {
|
||||
isHttpsPass = httpsPass;
|
||||
}
|
||||
}
|
||||
326
libcore/src/main/java/com/pukka/libcore/boxShared/BoxSharedParamsUtils.java
Executable file
326
libcore/src/main/java/com/pukka/libcore/boxShared/BoxSharedParamsUtils.java
Executable file
@@ -0,0 +1,326 @@
|
||||
package com.pukka.libcore.boxShared;
|
||||
|
||||
import android.os.Build;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import com.pukka.libcore.base.BuildConfig;
|
||||
import com.pukka.libcore.utils.AppUtils;
|
||||
import com.pukka.libcore.utils.ContextUtils;
|
||||
import com.pukka.libcore.utils.LogUtils;
|
||||
import com.pukka.libcore.utils.StringUtils;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* Author: wxf
|
||||
* Date: 2024/8/22 14:00
|
||||
* Description:获取机顶盒共享参数
|
||||
* mac, stbId, sn ,model、channelId等
|
||||
* 提供通过反射或命令getprop的方式获取共享参数(系统参数SystemProperties)
|
||||
*/
|
||||
public class BoxSharedParamsUtils {
|
||||
private static final String TAG = "BoxSharedParamsUtils";
|
||||
|
||||
private String mac;
|
||||
private String stbId;
|
||||
private String sn;
|
||||
private String model;
|
||||
private String channelId;
|
||||
private static BoxSharedParamsUtils instance;
|
||||
/**
|
||||
* 单例模式
|
||||
* @return BoxSharedParamsUtils
|
||||
* */
|
||||
public static BoxSharedParamsUtils getInstance() {
|
||||
if (instance == null) {
|
||||
synchronized (BoxSharedParamsUtils.class) {
|
||||
if (instance == null) {
|
||||
instance = new BoxSharedParamsUtils();
|
||||
}
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
public BoxSharedParamsUtils(){
|
||||
init();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前apk的渠道号
|
||||
* @return 渠道号
|
||||
*/
|
||||
public String getChannelId() {
|
||||
if(!StringUtils.isEmpty(channelId)){
|
||||
return channelId;
|
||||
}
|
||||
// try {
|
||||
// String str = AppUtils.getAppMetaData(ContextUtils.getApplicationContext(), "APP_CHANNEL_NAME");
|
||||
// if (str != null && str.length() >3) {
|
||||
// channelId = str.substring(1, 4);
|
||||
// }
|
||||
// LogUtils.d(TAG, "getChannelId str --" + str + " --channelId :" + channelId);
|
||||
// } catch (Exception e) {
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
return "001";
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化 mac,sn,stbId,model
|
||||
* 通过反射方法获取
|
||||
*/
|
||||
private void init() {
|
||||
try {
|
||||
//获取盒子mac地址
|
||||
String localMac = getSystemProperties("ro.mac");
|
||||
if (!TextUtils.isEmpty(localMac)) {
|
||||
mac = localMac;
|
||||
} else {
|
||||
mac = getSystemProperties("ro.boot.mac");
|
||||
}
|
||||
if (StringUtils.isEmpty(mac)) {
|
||||
//再添加一层mac地址的获取
|
||||
mac = MacUtils.getMac(ContextUtils.getApplicationContext());
|
||||
LogUtils.d("mac is getMac :" + mac);
|
||||
}
|
||||
|
||||
//获取盒子stbid
|
||||
String localStbId = getSystemProperties("android.os.Build.SERIAL");
|
||||
if (!TextUtils.isEmpty(localStbId)) {
|
||||
stbId = localStbId;
|
||||
} else {
|
||||
stbId = getSystemProperties("ro.serialno");
|
||||
}
|
||||
|
||||
//获取sn串号
|
||||
sn = getSystemProperties("ro.serialno");
|
||||
LogUtils.d("SN==="+sn);
|
||||
if(StringUtils.isEmpty(sn)) {
|
||||
String localSN = getSystemProperties("ro.boot.deviceid");
|
||||
if (!TextUtils.isEmpty(localSN)) {
|
||||
sn = localSN;
|
||||
} else {
|
||||
sn = getSystemProperties("ro.deviceid");
|
||||
}
|
||||
}
|
||||
//鸿蒙系统获取SN 尝试
|
||||
// if(StringUtils.isEmpty(sn)){
|
||||
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
// sn= getDeviceSerial();
|
||||
// }
|
||||
// if(StringUtils.isEmpty(sn)){
|
||||
// sn=getHMDeviceSerial();
|
||||
// }
|
||||
// }
|
||||
// LogUtils.d("SN 22==="+sn);
|
||||
|
||||
//获取盒子型号
|
||||
String localModel = getSystemProperties("android.os.Build.MODEL");
|
||||
if (!TextUtils.isEmpty(localModel)) {
|
||||
model = localModel;
|
||||
} else {
|
||||
model = getSystemProperties("ro.product.model");
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
LogUtils.d("mac:" + mac + " -- stbId:" + stbId + "-- sn:" + sn + "--model: " + model);
|
||||
}
|
||||
/**
|
||||
* 获取mac地址
|
||||
* @return mac地址
|
||||
* */
|
||||
public String getMac() {
|
||||
return mac;
|
||||
}
|
||||
/**
|
||||
* 如果通过反射获取不到mac地址,则需要通过厂商提供的方法,设置mac地址
|
||||
* @param mac mac地址
|
||||
* */
|
||||
public void setMac(String mac){
|
||||
this.mac = mac;
|
||||
}
|
||||
/**
|
||||
* 获取stbId
|
||||
* @return stbId
|
||||
* */
|
||||
public String getStbId() {
|
||||
return stbId;
|
||||
}
|
||||
/**
|
||||
* 如果通过反射获取不到stbId,则需要通过厂商提供的方法,设置stbId
|
||||
* @param stbId stbId地址
|
||||
* */
|
||||
public void setStbId(String stbId) {
|
||||
this.stbId = stbId;
|
||||
}
|
||||
/**
|
||||
* 获取sn
|
||||
* @return sn
|
||||
* */
|
||||
public String getSn() {
|
||||
return sn;
|
||||
}
|
||||
/**
|
||||
* 如果通过反射获取不到sn,则需要通过厂商提供的方法,设置sn
|
||||
* @param sn sn
|
||||
* */
|
||||
public void setSn(String sn) {
|
||||
this.sn = sn;
|
||||
}
|
||||
/**
|
||||
* 获取盒子型号
|
||||
* @return 盒子型号
|
||||
* */
|
||||
public String getModel() {
|
||||
return model;
|
||||
}
|
||||
/**
|
||||
* 如果通过反射获取不到盒子型号,则需要通过厂商提供的方法,设置盒子型号
|
||||
* @param model 盒子型号
|
||||
* */
|
||||
public void setModel(String model) {
|
||||
this.model = model;
|
||||
}
|
||||
/**
|
||||
* 通过反射获取SystemProperties存的共享参数
|
||||
* @param key 共享参数key 例如:ro.product.model
|
||||
* @return 共享参数值
|
||||
* */
|
||||
public String getSystemProperties(String key) {
|
||||
try {
|
||||
Class<?> mClassType = Class.forName("android.os.SystemProperties");
|
||||
Method mGetMethod = mClassType.getDeclaredMethod("get", String.class);
|
||||
String value = (String) mGetMethod.invoke(mClassType, key);
|
||||
return value;
|
||||
}catch (Exception ex){
|
||||
ex.printStackTrace();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
/**
|
||||
* 通过命令getprop获取共享参数
|
||||
* @param key 共享参数key 例如:ro.product.model
|
||||
* @return 共享参数值
|
||||
* */
|
||||
public String getValue(String key) {
|
||||
InputStreamReader ir = null;
|
||||
BufferedReader input = null;
|
||||
try {
|
||||
Process process = Runtime.getRuntime().exec("getprop " + key);
|
||||
ir = new InputStreamReader(process.getInputStream(), "UTF-8");
|
||||
input = new BufferedReader(ir);
|
||||
String line;
|
||||
while ((line = input.readLine()) != null) {
|
||||
if (!TextUtils.isEmpty(line)) {
|
||||
Log.i("PropertyUtils", "key:" + key + "value:" + line);
|
||||
return line;
|
||||
}
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
ex.printStackTrace();
|
||||
} finally {
|
||||
if (input != null) {
|
||||
try {
|
||||
input.close();
|
||||
} catch (IOException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
if (ir != null) {
|
||||
try {
|
||||
ir.close();
|
||||
} catch (IOException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public static boolean judgeTongYong() {
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* 是否是云主机版本 摩哈云游戏
|
||||
* true 是
|
||||
* false 不是
|
||||
* */
|
||||
public static boolean isJcCloud() {
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* 是否是酒店版本 摩哈云游戏
|
||||
* true 是
|
||||
* false 不是
|
||||
* */
|
||||
public static boolean isJcHotel() {
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* 兼容酒店版本 安卓4.4的EC6110的播放问题
|
||||
* 是否需要把播放地址由https转成http
|
||||
* */
|
||||
public boolean isNeed2http(){
|
||||
boolean isNeed = false;
|
||||
if(isJcHotel()){
|
||||
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT && "EC6110".equals(getModel())) {
|
||||
isNeed = true;
|
||||
}
|
||||
}
|
||||
return isNeed;
|
||||
}
|
||||
|
||||
/**
|
||||
* 兼容鸿蒙电视获取SN
|
||||
* */
|
||||
// @RequiresApi(api = Build.VERSION_CODES.O)
|
||||
// public static String getDeviceSerial() {
|
||||
// try {
|
||||
// LogUtils.d("getDeviceSerial:1111");
|
||||
// // 尝试标准Android方式
|
||||
// String serial = android.os.Build.getSerial();
|
||||
// if (!TextUtils.isEmpty(serial) && !"unknown".equals(serial)) {
|
||||
// return serial;
|
||||
// }
|
||||
// LogUtils.d("getDeviceSerial:2222 serial="+serial);
|
||||
// } catch (Exception e) {
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
// return "";
|
||||
// }
|
||||
// public static String getHMDeviceSerial(){
|
||||
// try {
|
||||
// // 尝试鸿蒙可能使用的属性
|
||||
// Class<?> systemProperties = Class.forName("android.os.SystemProperties");
|
||||
// Method get = systemProperties.getMethod("get", String.class, String.class);
|
||||
//
|
||||
// // 尝试多种可能的属性名
|
||||
// String[] propertyNames = {
|
||||
// "persist.sys.serialno",
|
||||
// "ro.hardware.serial",
|
||||
// "sys.serial_number",
|
||||
// "ro.boot.serialno"
|
||||
// };
|
||||
//
|
||||
// for (String name : propertyNames) {
|
||||
// String value = (String) get.invoke(null, name, "");
|
||||
// if (!TextUtils.isEmpty(value)) {
|
||||
// return value;
|
||||
// }
|
||||
// }
|
||||
// LogUtils.d("getDeviceSerial:333");
|
||||
// }catch (Exception ex){
|
||||
// ex.printStackTrace();
|
||||
// }
|
||||
// return "";
|
||||
// }
|
||||
|
||||
}
|
||||
99
libcore/src/main/java/com/pukka/libcore/boxShared/IpUtils.java
Executable file
99
libcore/src/main/java/com/pukka/libcore/boxShared/IpUtils.java
Executable file
@@ -0,0 +1,99 @@
|
||||
package com.pukka.libcore.boxShared;
|
||||
|
||||
import com.pukka.libcore.utils.LogUtils;
|
||||
import com.pukka.libcore.utils.StringUtils;
|
||||
|
||||
import java.net.Inet4Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.SocketException;
|
||||
import java.util.Enumeration;
|
||||
|
||||
/**
|
||||
* Author: wxf
|
||||
* Date: 2024/8/15 14:00
|
||||
* Description:获取盒子的有线,wifi等的ip地址
|
||||
*/
|
||||
public class IpUtils {
|
||||
|
||||
/**
|
||||
* 获取IP地址
|
||||
* @return IP地址
|
||||
*/
|
||||
public static String getIpAddress() {
|
||||
String ip =getEthIpAddress();
|
||||
if (StringUtils.isEmpty(ip)) {
|
||||
ip = getWlanIpAddress();
|
||||
}
|
||||
LogUtils.d("ip:"+ip);
|
||||
return ip;
|
||||
}
|
||||
/**
|
||||
* 获取有线IP地址
|
||||
* @return 有线IP地址
|
||||
*/
|
||||
public static String getEthIpAddress() {
|
||||
try {
|
||||
// 获取本地设备的所有网络接口
|
||||
Enumeration<NetworkInterface> enumerationNi = NetworkInterface
|
||||
.getNetworkInterfaces();
|
||||
while (enumerationNi.hasMoreElements()) {
|
||||
NetworkInterface networkInterface = enumerationNi.nextElement();
|
||||
String interfaceName = networkInterface.getDisplayName();
|
||||
LogUtils.i( "tag 网络名字" + interfaceName);
|
||||
// 有线网卡
|
||||
if (interfaceName.equals("eth0")) {
|
||||
Enumeration<InetAddress> enumIpAddr = networkInterface
|
||||
.getInetAddresses();
|
||||
while (enumIpAddr.hasMoreElements()) {
|
||||
// 返回枚举集合中的下一个IP地址信息
|
||||
InetAddress inetAddress = enumIpAddr.nextElement();
|
||||
// 不是回环地址,并且是ipv4的地址
|
||||
if (!inetAddress.isLoopbackAddress()
|
||||
&& inetAddress instanceof Inet4Address) {
|
||||
LogUtils.i("tag - "+ inetAddress.getHostAddress() + " ");
|
||||
return inetAddress.getHostAddress();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (SocketException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
/**
|
||||
* 获取无线网关的IP地址
|
||||
* @return 无线网关的IP地址
|
||||
*/
|
||||
public static String getWlanIpAddress() {
|
||||
try {
|
||||
// 获取本地设备的所有网络接口
|
||||
Enumeration<NetworkInterface> enumerationNi = NetworkInterface
|
||||
.getNetworkInterfaces();
|
||||
while (enumerationNi.hasMoreElements()) {
|
||||
NetworkInterface networkInterface = enumerationNi.nextElement();
|
||||
String interfaceName = networkInterface.getDisplayName();
|
||||
LogUtils.i("tag 网络名字" + interfaceName);
|
||||
// 如果是无线网卡
|
||||
if ("wlan0".equals(interfaceName)) {
|
||||
Enumeration<InetAddress> enumIpAddr = networkInterface
|
||||
.getInetAddresses();
|
||||
while (enumIpAddr.hasMoreElements()) {
|
||||
// 返回枚举集合中的下一个IP地址信息
|
||||
InetAddress inetAddress = enumIpAddr.nextElement();
|
||||
// 不是回环地址,并且是ipv4的地址
|
||||
if (!inetAddress.isLoopbackAddress()
|
||||
&& inetAddress instanceof Inet4Address) {
|
||||
LogUtils.i("tag "+ inetAddress.getHostAddress() + " ");
|
||||
return inetAddress.getHostAddress();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (SocketException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
}
|
||||
256
libcore/src/main/java/com/pukka/libcore/boxShared/MacUtils.java
Executable file
256
libcore/src/main/java/com/pukka/libcore/boxShared/MacUtils.java
Executable file
@@ -0,0 +1,256 @@
|
||||
package com.pukka.libcore.boxShared;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.net.wifi.WifiInfo;
|
||||
import android.net.wifi.WifiManager;
|
||||
import android.os.Build;
|
||||
import android.text.TextUtils;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.pukka.libcore.utils.LogUtils;
|
||||
import com.pukka.libcore.utils.SharedPreferenceUtils;
|
||||
import com.pukka.libcore.utils.StringUtil;
|
||||
import com.pukka.libcore.utils.StringUtils;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.LineNumberReader;
|
||||
import java.net.InetAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.SocketException;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Author: wxf
|
||||
* Date: 2024/8/8 14:00
|
||||
* Description:获取Mac地址
|
||||
*/
|
||||
public class MacUtils {
|
||||
/**
|
||||
* 获取mac地址(适配所有Android版本)
|
||||
*
|
||||
* @param context 上下文
|
||||
* @return mac地址
|
||||
*/
|
||||
public static String getMac(Context context) {
|
||||
// String mac = getMacAddressIP();
|
||||
// if (TextUtils.isEmpty(mac)) {
|
||||
// if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
||||
// mac = getMacDefault(context);
|
||||
// } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
|
||||
// mac = getMacAddress();
|
||||
// } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
// mac = getMacFromHardware();
|
||||
// }
|
||||
// }
|
||||
// if (TextUtils.isEmpty(mac)){
|
||||
String mac = SharedPreferenceUtils.getString("ETH_MAC","");
|
||||
LogUtils.d("从APK缓存中获取有线mac="+mac);
|
||||
if(StringUtils.isEmpty(mac)) {
|
||||
//安柠获取mac的方法
|
||||
mac = StringUtil.getLocalMac();
|
||||
LogUtils.d("从设备中获取mac="+mac);
|
||||
}
|
||||
if(StringUtils.isEmpty(mac)) {
|
||||
//自己生成mac的方法
|
||||
mac = MacUtils2.getMAC(context);
|
||||
LogUtils.d("从设备中自生成mac="+mac);
|
||||
}
|
||||
return mac;
|
||||
//}
|
||||
//return mac;
|
||||
}
|
||||
|
||||
public static String getWlan0Mac() {
|
||||
String mac = "";
|
||||
try {
|
||||
Process p = Runtime.getRuntime().exec("cat /sys/class/net/wlan0/address");
|
||||
InputStream is = p.getInputStream();
|
||||
InputStreamReader isr = new InputStreamReader(is);
|
||||
BufferedReader bf = new BufferedReader(isr);
|
||||
String line = "";
|
||||
if ((line = bf.readLine()) != null) {
|
||||
mac = line;
|
||||
}
|
||||
bf.close();
|
||||
isr.close();
|
||||
is.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return mac.toUpperCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Android 6.0 之前(不包括6.0)获取mac地址
|
||||
* 必须的权限 <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"></uses-permission>
|
||||
*
|
||||
* @param context * @return
|
||||
*/
|
||||
private static String getMacDefault(Context context) {
|
||||
String mac = "";
|
||||
if (context == null) {
|
||||
return mac;
|
||||
}
|
||||
WifiManager wifi = (WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
|
||||
WifiInfo info = null;
|
||||
try {
|
||||
info = wifi.getConnectionInfo();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
if (info == null) {
|
||||
return null;
|
||||
}
|
||||
mac = info.getMacAddress();
|
||||
if (!TextUtils.isEmpty(mac)) {
|
||||
mac = mac.toUpperCase(Locale.ENGLISH);
|
||||
}
|
||||
return mac;
|
||||
}
|
||||
|
||||
/**
|
||||
* Android 6.0-Android 7.0 获取mac地址
|
||||
*/
|
||||
private static String getMacAddress() {
|
||||
String macSerial = null;
|
||||
String str = "";
|
||||
|
||||
try {
|
||||
Process pp = Runtime.getRuntime().exec("cat /sys/class/net/wlan0/address");
|
||||
InputStreamReader ir = new InputStreamReader(pp.getInputStream());
|
||||
LineNumberReader input = new LineNumberReader(ir);
|
||||
|
||||
while (null != str) {
|
||||
str = input.readLine();
|
||||
if (str != null) {
|
||||
macSerial = str.trim();//去空格
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
// 赋予默认值
|
||||
ex.printStackTrace();
|
||||
}
|
||||
|
||||
return macSerial;
|
||||
}
|
||||
|
||||
/**
|
||||
* Android 7.0之后获取Mac地址
|
||||
* 遍历循环所有的网络接口,找到接口是 wlan0
|
||||
* 必须的权限 <uses-permission android:name="android.permission.INTERNET"></uses-permission>
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private static String getMacFromHardware() {
|
||||
Enumeration<NetworkInterface> interfaces = null;
|
||||
try {
|
||||
interfaces = NetworkInterface.getNetworkInterfaces();
|
||||
} catch (SocketException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
String hardWareAddress = null;
|
||||
NetworkInterface iF = null;
|
||||
if (interfaces == null) {
|
||||
return null;
|
||||
}
|
||||
while (interfaces.hasMoreElements()) {
|
||||
iF = interfaces.nextElement();
|
||||
try {
|
||||
hardWareAddress = bytesToString(iF.getHardwareAddress());
|
||||
if (hardWareAddress != null) {
|
||||
break;
|
||||
}
|
||||
} catch (SocketException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return hardWareAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Android 6.0 之前(不包括6.0)
|
||||
* 获取有线网络mac
|
||||
*
|
||||
* @return 有线网络mac
|
||||
*/
|
||||
private static String getMacAddressIP() {
|
||||
String strMacAddr = null;
|
||||
try {
|
||||
InetAddress ip = getLocalInetAddress();
|
||||
byte[] b = NetworkInterface.getByInetAddress(ip)
|
||||
.getHardwareAddress();
|
||||
StringBuffer buffer = new StringBuffer();
|
||||
if(b != null) {
|
||||
for (int i = 0; i < b.length; i++) {
|
||||
if (i != 0) {
|
||||
buffer.append(':');
|
||||
}
|
||||
String str = Integer.toHexString(b[i] & 0xFF);
|
||||
buffer.append(str.length() == 1 ? 0 + str : str);
|
||||
}
|
||||
}
|
||||
strMacAddr = buffer.toString().toUpperCase();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return strMacAddr;
|
||||
}
|
||||
|
||||
/***
|
||||
* byte转为String
|
||||
*
|
||||
* @param bytes
|
||||
* @return
|
||||
*/
|
||||
private static String bytesToString(byte[] bytes) {
|
||||
if (bytes == null || bytes.length == 0) {
|
||||
return null;
|
||||
}
|
||||
StringBuilder buf = new StringBuilder();
|
||||
for (byte b : bytes) {
|
||||
buf.append(String.format("%02X:", b));
|
||||
}
|
||||
if (buf.length() > 0) {
|
||||
buf.deleteCharAt(buf.length() - 1);
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取本地IP
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private static InetAddress getLocalInetAddress() {
|
||||
InetAddress ip = null;
|
||||
try {
|
||||
//列举
|
||||
Enumeration en_netInterface = NetworkInterface.getNetworkInterfaces();
|
||||
while (en_netInterface.hasMoreElements()) {//是否还有元素
|
||||
NetworkInterface ni = (NetworkInterface) en_netInterface.nextElement();//得到下一个元素
|
||||
Enumeration en_ip = ni.getInetAddresses();//得到一个ip地址的列举
|
||||
while (en_ip.hasMoreElements()) {
|
||||
ip = (InetAddress) en_ip.nextElement();
|
||||
if (!ip.isLoopbackAddress() && ip.getHostAddress().indexOf(":") == -1) {
|
||||
break;
|
||||
} else {
|
||||
ip = null;
|
||||
}
|
||||
}
|
||||
if (ip != null) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (SocketException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return ip;
|
||||
}
|
||||
}
|
||||
73
libcore/src/main/java/com/pukka/libcore/boxShared/MacUtils2.java
Executable file
73
libcore/src/main/java/com/pukka/libcore/boxShared/MacUtils2.java
Executable file
@@ -0,0 +1,73 @@
|
||||
package com.pukka.libcore.boxShared;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.provider.Settings;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.pukka.libcore.utils.LogUtils;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
|
||||
public class MacUtils2 {
|
||||
|
||||
public static String getMAC(Context context) {
|
||||
// 获取基础信息
|
||||
String baseInfo = getBaseDeviceInfo(context);
|
||||
// 生成MAC格式字符串
|
||||
String macAddress = generateMacFromHash(baseInfo);
|
||||
LogUtils.i("MacUtils", "return Mac:" + macAddress);
|
||||
return macAddress;
|
||||
}
|
||||
|
||||
public static String getBaseDeviceInfo(Context context) {
|
||||
// 获取设备基本信息
|
||||
String androidId = "";
|
||||
androidId = Settings.Secure.getString(
|
||||
context.getContentResolver(),
|
||||
Settings.Secure.ANDROID_ID
|
||||
);
|
||||
LogUtils.i("MacUtils", "androidId:" + androidId);
|
||||
String deviceInfo = Build.BRAND
|
||||
+ Build.DEVICE
|
||||
+ Build.MANUFACTURER
|
||||
+ Build.MODEL;
|
||||
LogUtils.i("MacUtils", "BRAND:" + Build.BRAND);
|
||||
LogUtils.i("MacUtils", "DEVICE:" + Build.DEVICE);
|
||||
LogUtils.i("MacUtils", "MANUFACTURER:" + Build.MANUFACTURER);
|
||||
LogUtils.i("MacUtils", "MODEL:" + Build.MODEL);
|
||||
return androidId + deviceInfo;
|
||||
}
|
||||
|
||||
public static String getModel() {
|
||||
LogUtils.i("getModel", Build.BRAND + " " + Build.MODEL);
|
||||
return Build.BRAND + " " + Build.MODEL;
|
||||
}
|
||||
|
||||
private static String generateMacFromHash(String input) {
|
||||
LogUtils.i("MacUtils", "generateMacFromHash:" + input);
|
||||
try {
|
||||
// 生成MD5
|
||||
MessageDigest md = MessageDigest.getInstance("MD5");
|
||||
byte[] hash = md.digest(input.getBytes());
|
||||
|
||||
// 取前6个字节构造MAC
|
||||
StringBuilder mac = new StringBuilder();
|
||||
for (int i = 0; i < 6; i++) {
|
||||
if (i > 0) mac.append(":");
|
||||
mac.append(String.format("%02X", hash[i] & 0xFF));
|
||||
}
|
||||
|
||||
// 确保第一个字节是偶数(本地管理的MAC地址)
|
||||
char firstChar = mac.charAt(0);
|
||||
if ((Character.digit(firstChar, 16) & 0x1) != 0) {
|
||||
mac.setCharAt(0, (char) (firstChar - 1));
|
||||
}
|
||||
|
||||
return mac.toString();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return "02:00:00:00:00:00"; // 默认值
|
||||
}
|
||||
}
|
||||
}
|
||||
215
libcore/src/main/java/com/pukka/libcore/cipher/AesUtils.java
Executable file
215
libcore/src/main/java/com/pukka/libcore/cipher/AesUtils.java
Executable file
@@ -0,0 +1,215 @@
|
||||
package com.pukka.libcore.cipher;
|
||||
|
||||
import com.pukka.libcore.utils.ByteConversionUtils;
|
||||
import com.pukka.libcore.utils.StringUtils;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
/**
|
||||
* Author: wxf
|
||||
* Date: 2024/8/15 14:00
|
||||
* Description:AES加密和解密
|
||||
* https://the-x.cn/hash/MessageDigestAlgorithm.aspx 联调过程中,查看是否正确
|
||||
*/
|
||||
public class AesUtils {
|
||||
|
||||
/**
|
||||
* 加密key
|
||||
*/
|
||||
public static final String AES_KEY = "!@#$%^&*FJGFJT78";
|
||||
|
||||
/**
|
||||
* 加密模式之 ECB,算法/模式/补码方式
|
||||
*/
|
||||
private static final String AES_ECB = "AES/ECB/PKCS5Padding";
|
||||
|
||||
private static final String UTF_8 = "utf-8";
|
||||
|
||||
private static final String ASCII = "ASCII";
|
||||
|
||||
|
||||
/***
|
||||
* 获取AES密钥
|
||||
* @param key 密钥
|
||||
* @return AES密钥
|
||||
*/
|
||||
public static SecretKeySpec getSecretKeySpec(String key) {
|
||||
SecretKeySpec secretKeySpec = new SecretKeySpec(ByteConversionUtils.getBytes(key, UTF_8), "AES");
|
||||
return secretKeySpec;
|
||||
}
|
||||
|
||||
/***
|
||||
*获取AES密钥
|
||||
* @param key 密钥
|
||||
* @param charsetName 字符集
|
||||
* @return AES密钥
|
||||
*/
|
||||
public static SecretKeySpec getSecretKeySpec(String key, String charsetName) {
|
||||
SecretKeySpec secretKeySpec = new SecretKeySpec(ByteConversionUtils.getBytes(key, charsetName), "AES");
|
||||
return secretKeySpec;
|
||||
}
|
||||
/**
|
||||
* AES加密-模式 ECB
|
||||
*
|
||||
* @param text 需要加密的文本内容
|
||||
* @param key 加密的密钥 key
|
||||
* @return 字节数组
|
||||
*/
|
||||
public static byte[] encrypt(String key,String text) {
|
||||
try {
|
||||
byte[] encryptedBytes = ByteConversionUtils.getBytes(text, UTF_8);
|
||||
// 加密
|
||||
return encrypt(key,encryptedBytes);
|
||||
} catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* AES加密-模式 ECB
|
||||
*
|
||||
* @param text 需要加密的字节数组
|
||||
* @param key 加密的密钥 key
|
||||
* @return 字节数组
|
||||
*/
|
||||
public static byte[] encrypt(String key,byte[] text) {
|
||||
try {
|
||||
// 创建AES密码器
|
||||
Cipher cipher = Cipher.getInstance(AES_ECB);
|
||||
// 获取AES密钥
|
||||
SecretKeySpec secretKeySpec = getSecretKeySpec(key);
|
||||
// 初始化加密器
|
||||
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
|
||||
// 加密
|
||||
return cipher.doFinal(text);
|
||||
} catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* AES加密-模式 ECB
|
||||
*
|
||||
* @param text 需要加密的文本内容
|
||||
* @param key 加密的密钥 key
|
||||
* @return Base64 编码字符串
|
||||
*/
|
||||
public static String encrypt2Base64(String key,String text) {
|
||||
if (StringUtils.isEmpty(text) || StringUtils.isEmpty(key)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
byte[] encryptedBytes = encrypt(key, text);
|
||||
// 将密文转换为 Base64 编码字符串
|
||||
return new String(android.util.Base64.encode(encryptedBytes, android.util.Base64.NO_WRAP));
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* AES加密-模式 ECB
|
||||
*
|
||||
* @param text 字节数组
|
||||
* @param key 加密的密钥 key
|
||||
* @return Base64编码字符串
|
||||
*/
|
||||
public static String encrypt2Base64(String key,byte[] text) {
|
||||
if (StringUtils.isEmpty(key)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
// 加密字节数组
|
||||
byte[] encryptedBytes = encrypt(key,text);
|
||||
// 将密文转换为 Base64 编码字符串
|
||||
return new String(android.util.Base64.encode(encryptedBytes, android.util.Base64.NO_WRAP));
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* AES加密-模式 ECB
|
||||
* @param key 加密的密钥 key
|
||||
* @param text 需要加密的明文
|
||||
* @return 加密后的密文(HEX字符串 16进制)
|
||||
*/
|
||||
public static String encrypt2HexStr(String key, String text) {
|
||||
try {
|
||||
// 加密字节数组
|
||||
byte[] encryptedBytes = encrypt(key, text);
|
||||
return ByteConversionUtils.byte2hex(encryptedBytes).toLowerCase();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* AES解密-模式 ECB
|
||||
*
|
||||
* @param key 密钥
|
||||
* @param text 解密文本(HEX文本)
|
||||
* @return 解密后的明文
|
||||
*/
|
||||
public static String decryptByHexStr(String key, String text) {
|
||||
if (StringUtils.isEmpty(text)) {
|
||||
return null;
|
||||
}
|
||||
String plainText = null;
|
||||
try {
|
||||
//HEX解密文本转字节数组
|
||||
byte[] decryptBytes = ByteConversionUtils.hex2byte(text);
|
||||
|
||||
// 创建AES密码器
|
||||
Cipher cipher = Cipher.getInstance("AES");
|
||||
// 获取AES密钥
|
||||
SecretKeySpec secretKeySpec = getSecretKeySpec(key);
|
||||
// 解密器初始化 DECRYPT_MODE
|
||||
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
|
||||
|
||||
// 解密
|
||||
byte[] original = cipher.doFinal(decryptBytes);
|
||||
plainText = new String(original);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return plainText;
|
||||
}
|
||||
/**
|
||||
* AES解密-模式 ECB
|
||||
*
|
||||
* @param key 密钥
|
||||
* @param text 解密文本(Base64编码字符串)
|
||||
* @return 解密后的明文
|
||||
*/
|
||||
public static String decryptByBase64(String key, String text) {
|
||||
if (StringUtils.isEmpty(text)) {
|
||||
return null;
|
||||
}
|
||||
String plainText = null;
|
||||
try {
|
||||
//HEX解密文本转字节数组
|
||||
byte[] decryptBytes = android.util.Base64.decode(text,android.util.Base64.NO_WRAP);
|
||||
// 创建AES密码器
|
||||
Cipher cipher = Cipher.getInstance("AES");
|
||||
// 获取AES密钥
|
||||
SecretKeySpec secretKeySpec = getSecretKeySpec(key);
|
||||
// 解密器初始化 DECRYPT_MODE
|
||||
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
|
||||
|
||||
// 解密
|
||||
byte[] original = cipher.doFinal(decryptBytes);
|
||||
plainText = new String(original);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return plainText;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
60
libcore/src/main/java/com/pukka/libcore/cipher/HMacShaUtils.java
Executable file
60
libcore/src/main/java/com/pukka/libcore/cipher/HMacShaUtils.java
Executable file
@@ -0,0 +1,60 @@
|
||||
package com.pukka.libcore.cipher;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
/**
|
||||
* Author: wxf
|
||||
* Date: 2024/9/3 14:00
|
||||
* Description:HmacSHA256 算法 加密
|
||||
*/
|
||||
public class HMacShaUtils {
|
||||
private static final String HMAC_SHA1_ALGORITHM = "HmacSHA1";
|
||||
private static final String HMAC_SHA256_ALGORITHM = "HmacSHA256";
|
||||
|
||||
/**
|
||||
* HMACSHA256加密
|
||||
* 传参:比如“123456”就传“123456”.getBytes()
|
||||
*
|
||||
* @param data 待加密字符串
|
||||
* @param key 密钥
|
||||
* @return 加密后的字节数组
|
||||
*/
|
||||
public static byte[] encryptSha256(String data, String key) {
|
||||
try {
|
||||
SecretKeySpec signingKey = new SecretKeySpec(key.getBytes(), HMAC_SHA256_ALGORITHM);
|
||||
Mac mac = Mac.getInstance(HMAC_SHA256_ALGORITHM);
|
||||
mac.init(signingKey);
|
||||
return mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
e.printStackTrace();
|
||||
} catch (InvalidKeyException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* HMACSHA1加密
|
||||
* 传参:比如“123456”就传“123456”.getBytes()
|
||||
*
|
||||
* @param data 待加密字符串
|
||||
* @param key 密钥
|
||||
* @return 加密后的字节数组
|
||||
*/
|
||||
public static byte[] encryptSha1(String data, String key) {
|
||||
try {
|
||||
SecretKeySpec signingKey = new SecretKeySpec(key.getBytes(), HMAC_SHA1_ALGORITHM);
|
||||
Mac mac = Mac.getInstance(HMAC_SHA1_ALGORITHM);
|
||||
mac.init(signingKey);
|
||||
return mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
|
||||
} catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
104
libcore/src/main/java/com/pukka/libcore/cipher/Md5Utils.java
Executable file
104
libcore/src/main/java/com/pukka/libcore/cipher/Md5Utils.java
Executable file
@@ -0,0 +1,104 @@
|
||||
package com.pukka.libcore.cipher;
|
||||
|
||||
import com.pukka.libcore.utils.ByteConversionUtils;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
|
||||
/**
|
||||
* Author: wxf
|
||||
* Date: 2024/8/15 14:00
|
||||
* Description:Md5加密和解密
|
||||
*/
|
||||
public class Md5Utils {
|
||||
|
||||
/**
|
||||
* 字符串转换为MD5加密的字符数组
|
||||
*
|
||||
* @param message 需要加密的字符串的字节数组
|
||||
* @return MD5加密的字符串(16进制 HEX)
|
||||
*/
|
||||
public static String encrypt(byte[] message) {
|
||||
String md5str = "";
|
||||
try {
|
||||
//2 计算后获得字节数组
|
||||
byte[] buff = encrypt2Bytes(message);
|
||||
//3 把数组每一字节(一个字节占八位)换成16进制连成md5字符串
|
||||
md5str = ByteConversionUtils.byte2hex(buff);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return md5str;
|
||||
}
|
||||
|
||||
/**
|
||||
* 字符串转换为MD5加密的字符串
|
||||
*
|
||||
* @param message 需要加密的字符串的字节数组
|
||||
* @return MD5加密后字节数组
|
||||
*/
|
||||
public static byte[] encrypt2Bytes(byte[] message) {
|
||||
byte[] buff;
|
||||
try {
|
||||
//1 创建一个提供信息摘要算法的对象,初始化为md5算法对象
|
||||
MessageDigest md = MessageDigest.getInstance("MD5");
|
||||
md.update(message);
|
||||
//2 计算后获得字节数组
|
||||
buff = md.digest(message);
|
||||
return buff;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 字符串转换为MD5加密的字符串
|
||||
*
|
||||
* @param message 需要加密的字符串的字节数组
|
||||
* @return MD5加密的字符串(16进制 HEX)
|
||||
*/
|
||||
public static String encrypt(String message) {
|
||||
String md5str = "";
|
||||
try {
|
||||
md5str = encrypt(message.getBytes(StandardCharsets.UTF_8));
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return md5str;
|
||||
}
|
||||
|
||||
public static String getMD5(String content) {
|
||||
byte[] md5 = encryptToByte(content);
|
||||
String buf = byte2HexStr(md5);
|
||||
return buf;
|
||||
}
|
||||
private static String byte2HexStr(byte[] buf) {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
|
||||
for(int i = 0; i < buf.length; ++i) {
|
||||
String hex = Integer.toHexString(buf[i] & 255);
|
||||
if (hex.length() == 1) {
|
||||
hex = '0' + hex;
|
||||
}
|
||||
|
||||
sb.append(hex.toLowerCase());
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static byte[] encryptToByte(String content) {
|
||||
try {
|
||||
MessageDigest mDigest = MessageDigest.getInstance("MD5");
|
||||
mDigest.update(content.getBytes("utf-8"));
|
||||
byte[] md5 = mDigest.digest();
|
||||
return md5;
|
||||
} catch (Exception var3) {
|
||||
var3.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
77
libcore/src/main/java/com/pukka/libcore/fresco/BaseSimpleDraweeView.java
Executable file
77
libcore/src/main/java/com/pukka/libcore/fresco/BaseSimpleDraweeView.java
Executable file
@@ -0,0 +1,77 @@
|
||||
package com.pukka.libcore.fresco;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import com.facebook.drawee.backends.pipeline.Fresco;
|
||||
import com.facebook.drawee.backends.pipeline.PipelineDraweeControllerBuilder;
|
||||
import com.facebook.drawee.generic.GenericDraweeHierarchy;
|
||||
import com.facebook.drawee.view.SimpleDraweeView;
|
||||
import com.facebook.imagepipeline.common.ResizeOptions;
|
||||
import com.facebook.imagepipeline.core.ImagePipeline;
|
||||
import com.facebook.imagepipeline.decoder.DecodeException;
|
||||
import com.facebook.imagepipeline.request.ImageRequest;
|
||||
import com.facebook.imagepipeline.request.ImageRequestBuilder;
|
||||
|
||||
/**
|
||||
* Author: wxf
|
||||
* Date: 2024/8/26 14:54
|
||||
* Description:继承SimpleDraweeView的基类,包含因为解码原因,重试机制
|
||||
*/
|
||||
public class BaseSimpleDraweeView extends SimpleDraweeView implements IPkSimpleDraweeView {
|
||||
protected Uri mUri;
|
||||
protected PkSimpleDraweeListener mSimpleListener;
|
||||
protected int mWidth = -1;
|
||||
protected int mHeight = -1;
|
||||
protected int mRetryCount;
|
||||
|
||||
public BaseSimpleDraweeView(Context context, GenericDraweeHierarchy hierarchy) {
|
||||
super(context, hierarchy);
|
||||
}
|
||||
|
||||
public BaseSimpleDraweeView(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public BaseSimpleDraweeView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public BaseSimpleDraweeView(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
}
|
||||
|
||||
public BaseSimpleDraweeView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
}
|
||||
|
||||
public void setSimpleDraweeListener(PkSimpleDraweeListener listener) {
|
||||
this.mSimpleListener = listener;
|
||||
}
|
||||
|
||||
public boolean retry(PkControllerListener controllerListener, Throwable throwable) {
|
||||
if (this.mUri == null) {
|
||||
return false;
|
||||
} else if (throwable instanceof DecodeException && this.mRetryCount < 1) {
|
||||
try {
|
||||
ImagePipeline imagePipeline = Fresco.getImagePipeline();
|
||||
imagePipeline.evictFromMemoryCache(this.mUri);
|
||||
imagePipeline.evictFromDiskCache(this.mUri);
|
||||
imagePipeline.evictFromCache(this.mUri);
|
||||
} catch (Exception var5) {
|
||||
var5.printStackTrace();
|
||||
}
|
||||
|
||||
PipelineDraweeControllerBuilder builder = Fresco.newDraweeControllerBuilder();
|
||||
ImageRequest imageRequest = ImageRequestBuilder.newBuilderWithSource(this.mUri).setResizeOptions(new ResizeOptions(this.mWidth, this.mHeight)).setProgressiveRenderingEnabled(true).build();
|
||||
(((builder.setOldController(this.getController())).setImageRequest(imageRequest)).setControllerListener(controllerListener)).setAutoPlayAnimations(true);
|
||||
|
||||
this.setController(builder.build());
|
||||
++this.mRetryCount;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
73
libcore/src/main/java/com/pukka/libcore/fresco/FrescoInit.java
Executable file
73
libcore/src/main/java/com/pukka/libcore/fresco/FrescoInit.java
Executable file
@@ -0,0 +1,73 @@
|
||||
|
||||
package com.pukka.libcore.fresco;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
|
||||
import com.facebook.common.memory.MemoryTrimType;
|
||||
import com.facebook.common.memory.MemoryTrimmableRegistry;
|
||||
import com.facebook.common.memory.NoOpMemoryTrimmableRegistry;
|
||||
import com.facebook.drawee.backends.pipeline.Fresco;
|
||||
import com.facebook.imagepipeline.backends.okhttp3.OkHttpImagePipelineConfigFactory;
|
||||
import com.facebook.imagepipeline.core.ImagePipelineConfig;
|
||||
import com.facebook.imagepipeline.core.ImagePipelineFactory;
|
||||
import com.pukka.libcore.utils.ContextUtils;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
import javax.net.ssl.TrustManager;
|
||||
|
||||
import okhttp3.OkHttpClient;
|
||||
|
||||
/**
|
||||
* Author: wxf
|
||||
* Date: 2024/8/26 14:49
|
||||
* Description:Fresco 初始化,最好是在Application或者过渡页中进行初始化动作
|
||||
*/
|
||||
public class FrescoInit {
|
||||
/**
|
||||
* Fresco 初始化
|
||||
*/
|
||||
public static void initFrescoConfig() {
|
||||
Context context = ContextUtils.getApplicationContext();
|
||||
/*
|
||||
* 设置内存紧张时的应对措施
|
||||
*/
|
||||
MemoryTrimmableRegistry memoryTrimmableRegistry = NoOpMemoryTrimmableRegistry.getInstance();
|
||||
memoryTrimmableRegistry.registerMemoryTrimmable(trimType -> {
|
||||
final double suggestedTrimRatio = trimType.getSuggestedTrimRatio();
|
||||
if (MemoryTrimType.OnCloseToDalvikHeapLimit.getSuggestedTrimRatio() == suggestedTrimRatio
|
||||
|| MemoryTrimType.OnSystemLowMemoryWhileAppInBackground.getSuggestedTrimRatio() == suggestedTrimRatio
|
||||
|| MemoryTrimType.OnSystemLowMemoryWhileAppInForeground.getSuggestedTrimRatio() == suggestedTrimRatio) {
|
||||
// 清空内存缓存
|
||||
ImagePipelineFactory.getInstance().getImagePipeline().clearMemoryCaches();
|
||||
}
|
||||
});
|
||||
OkHttpClient okHttpClient = getUnsafeOkHttpClient();
|
||||
ImagePipelineConfig.Builder builder = OkHttpImagePipelineConfigFactory.newBuilder(context, okHttpClient)
|
||||
.setDownsampleEnabled(true)
|
||||
.setBitmapsConfig(Bitmap.Config.RGB_565)
|
||||
.setMemoryTrimmableRegistry(memoryTrimmableRegistry);
|
||||
ImagePipelineConfig config = builder.build();
|
||||
Fresco.initialize(context, config);
|
||||
}
|
||||
|
||||
private static OkHttpClient getUnsafeOkHttpClient() {
|
||||
try {
|
||||
// Create a trust manager that does not validate certificate chains
|
||||
// Install the all-trusting trust manager
|
||||
final SSLContext sslContext = SSLContext.getInstance("TLS");
|
||||
TrustAllManager x509TrustManager = new TrustAllManager();
|
||||
sslContext.init(null, new TrustManager[]{x509TrustManager}, new java.security.SecureRandom());
|
||||
// Create an ssl socket factory with our all-trusting manager
|
||||
final SSLSocketFactory sslSocketFactory = new Tls12SocketFactory(sslContext.getSocketFactory());
|
||||
|
||||
return new OkHttpClient().newBuilder()
|
||||
.sslSocketFactory(sslSocketFactory, x509TrustManager)
|
||||
.hostnameVerifier((hostname, session) -> true)
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
10
libcore/src/main/java/com/pukka/libcore/fresco/IPkSimpleDraweeView.java
Executable file
10
libcore/src/main/java/com/pukka/libcore/fresco/IPkSimpleDraweeView.java
Executable file
@@ -0,0 +1,10 @@
|
||||
package com.pukka.libcore.fresco;
|
||||
|
||||
/**
|
||||
* Author: wxf
|
||||
* Date: 2023/1/4 14:49
|
||||
* Description:重试接口
|
||||
*/
|
||||
public interface IPkSimpleDraweeView {
|
||||
boolean retry(PkControllerListener var1, Throwable var2);
|
||||
}
|
||||
35
libcore/src/main/java/com/pukka/libcore/fresco/PkControllerListener.java
Executable file
35
libcore/src/main/java/com/pukka/libcore/fresco/PkControllerListener.java
Executable file
@@ -0,0 +1,35 @@
|
||||
package com.pukka.libcore.fresco;
|
||||
|
||||
import android.graphics.drawable.Animatable;
|
||||
|
||||
import com.facebook.drawee.controller.BaseControllerListener;
|
||||
import com.facebook.imagepipeline.image.ImageInfo;
|
||||
|
||||
/**
|
||||
* Author: wxf
|
||||
* Date: 2024/8/26 14:50
|
||||
* Description:fresco图片加载过程监听
|
||||
*/
|
||||
public class PkControllerListener extends BaseControllerListener<ImageInfo> {
|
||||
IPkSimpleDraweeView mDraweeView;
|
||||
private PkSimpleDraweeListener mSimpleListener;
|
||||
public PkControllerListener(IPkSimpleDraweeView draweeView, PkSimpleDraweeListener listener) {
|
||||
this.mDraweeView = draweeView;
|
||||
this.mSimpleListener = listener;
|
||||
}
|
||||
//图片加载成功
|
||||
public void onFinalImageSet(String id, ImageInfo imageInfo, Animatable animatable) {
|
||||
super.onFinalImageSet(id, imageInfo, animatable);
|
||||
if (this.mSimpleListener != null) {
|
||||
this.mSimpleListener.onFinalImageSet(id, imageInfo, animatable);
|
||||
}
|
||||
}
|
||||
//图片加载失败
|
||||
public void onFailure(String id, Throwable throwable) {
|
||||
super.onFailure(id, throwable);
|
||||
if ((this.mDraweeView == null || !this.mDraweeView.retry(this, throwable)) && this.mSimpleListener != null) {
|
||||
this.mSimpleListener.onFailure(id, throwable);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
29
libcore/src/main/java/com/pukka/libcore/fresco/PkSimpleDraweeListener.java
Executable file
29
libcore/src/main/java/com/pukka/libcore/fresco/PkSimpleDraweeListener.java
Executable file
@@ -0,0 +1,29 @@
|
||||
package com.pukka.libcore.fresco;
|
||||
|
||||
import android.graphics.drawable.Animatable;
|
||||
|
||||
import com.facebook.imagepipeline.image.ImageInfo;
|
||||
|
||||
/**
|
||||
* Author: wxf
|
||||
* Date: 2024/8/26 14:57
|
||||
* Description:图片加载成功或失败监听
|
||||
*/
|
||||
public interface PkSimpleDraweeListener {
|
||||
/**
|
||||
* 加载成功
|
||||
*
|
||||
* @param id controller id
|
||||
* @param imageInfo image info
|
||||
* @param animatable image load animatable
|
||||
*/
|
||||
void onFinalImageSet(String id, ImageInfo imageInfo, Animatable animatable);
|
||||
|
||||
/**
|
||||
* 加载失败
|
||||
*
|
||||
* @param id controller id
|
||||
* @param throwable 加载异常
|
||||
*/
|
||||
void onFailure(String id, Throwable throwable);
|
||||
}
|
||||
125
libcore/src/main/java/com/pukka/libcore/fresco/PkSimpleDraweeView.java
Executable file
125
libcore/src/main/java/com/pukka/libcore/fresco/PkSimpleDraweeView.java
Executable file
@@ -0,0 +1,125 @@
|
||||
|
||||
package com.pukka.libcore.fresco;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import com.facebook.drawee.backends.pipeline.Fresco;
|
||||
import com.facebook.drawee.backends.pipeline.PipelineDraweeControllerBuilder;
|
||||
import com.facebook.drawee.generic.GenericDraweeHierarchy;
|
||||
import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
|
||||
import com.facebook.drawee.generic.GenericDraweeHierarchyInflater;
|
||||
import com.facebook.drawee.interfaces.DraweeController;
|
||||
import com.facebook.imagepipeline.common.ResizeOptions;
|
||||
import com.facebook.imagepipeline.request.ImageRequest;
|
||||
import com.facebook.imagepipeline.request.ImageRequestBuilder;
|
||||
|
||||
/**
|
||||
* Author: wxf
|
||||
* Date: 2024/8/26 15:04
|
||||
* Description:常用Fresco UI 控件,用于加载图片
|
||||
*/
|
||||
|
||||
public class PkSimpleDraweeView extends BaseSimpleDraweeView {
|
||||
public PkSimpleDraweeView(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public PkSimpleDraweeView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public PkSimpleDraweeView(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
}
|
||||
|
||||
public PkSimpleDraweeView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
}
|
||||
|
||||
public PkSimpleDraweeView(Context context, GenericDraweeHierarchy hierarchy) {
|
||||
super(context, hierarchy);
|
||||
}
|
||||
|
||||
protected void inflateHierarchy(Context context, AttributeSet attrs) {
|
||||
GenericDraweeHierarchyBuilder builder = GenericDraweeHierarchyInflater.inflateBuilder(context, attrs);
|
||||
builder.setPlaceholderImageScaleType(com.facebook.drawee.drawable.ScalingUtils.ScaleType.FIT_XY);
|
||||
this.setAspectRatio(builder.getDesiredAspectRatio());
|
||||
this.setHierarchy(builder.build());
|
||||
}
|
||||
|
||||
/***
|
||||
* 加载图片
|
||||
* @param uriString 图片地址
|
||||
* */
|
||||
public void setImageURI(String uriString) {
|
||||
this.mUri = uriString != null ? Uri.parse(uriString) : null;
|
||||
if (this.mWidth > 0 && this.mHeight > 0) {
|
||||
this.setImageURI(mWidth, mHeight);
|
||||
}
|
||||
}
|
||||
|
||||
/***
|
||||
* 加载图片
|
||||
* @param uriString 图片地址
|
||||
* @param pkSimpleListener 加载成功和失败监听
|
||||
* */
|
||||
public void setImageURI(String uriString, PkSimpleDraweeListener pkSimpleListener) {
|
||||
this.mUri = uriString != null ? Uri.parse(uriString) : null;
|
||||
mSimpleListener = pkSimpleListener;
|
||||
}
|
||||
private void setImageURIUseCache(int width, int height) {
|
||||
if (null == mUri) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.mRetryCount = 0;
|
||||
PipelineDraweeControllerBuilder builder = Fresco.newDraweeControllerBuilder();
|
||||
ImageRequest imageRequest = ImageRequestBuilder.newBuilderWithSource(this.mUri).setResizeOptions(new ResizeOptions(width, height)).setProgressiveRenderingEnabled(true).build();
|
||||
PkControllerListener controllerListener = new PkControllerListener(this, this.mSimpleListener);
|
||||
((PipelineDraweeControllerBuilder)((PipelineDraweeControllerBuilder)((PipelineDraweeControllerBuilder)builder.setOldController(this.getController())).setImageRequest(imageRequest)).setControllerListener(controllerListener)).setAutoPlayAnimations(true);
|
||||
|
||||
this.setController(builder.build());
|
||||
}
|
||||
/***
|
||||
* 加载图片
|
||||
* @param uri 图片地址
|
||||
* */
|
||||
public void setImageURI(Uri uri) {
|
||||
this.mUri = uri;
|
||||
}
|
||||
|
||||
private void setImageURI(int width, int height) {
|
||||
if (null == mUri) {
|
||||
return;
|
||||
}
|
||||
this.mRetryCount = 0;
|
||||
ImageRequest imageRequest = ImageRequestBuilder.newBuilderWithSource(mUri)
|
||||
.setProgressiveRenderingEnabled(true)//启用渐进式加载
|
||||
.disableMemoryCache() //禁止内存缓存
|
||||
.setResizeOptions(new ResizeOptions(width, height)) //定义图片的宽和高
|
||||
.build();
|
||||
PkControllerListener pkControllerListener = new PkControllerListener(this, mSimpleListener);
|
||||
DraweeController controller = Fresco.newDraweeControllerBuilder()
|
||||
.setOldController(getController())
|
||||
.setImageRequest(imageRequest).setControllerListener(pkControllerListener)
|
||||
.setAutoPlayAnimations(true) // 设置是否自动播放动画
|
||||
.build();
|
||||
setController(controller);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
||||
super.onSizeChanged(w, h, oldw, oldh);
|
||||
//组件大小发生改变时被调用,用于获取组件的宽和高
|
||||
mWidth = w;
|
||||
mHeight = h;
|
||||
if (this.mWidth > 0 && this.mHeight > 0) {
|
||||
if (null == mUri) {
|
||||
return;
|
||||
}
|
||||
this.setImageURI(mWidth, mHeight);
|
||||
}
|
||||
}
|
||||
}
|
||||
69
libcore/src/main/java/com/pukka/libcore/fresco/Tls12SocketFactory.java
Executable file
69
libcore/src/main/java/com/pukka/libcore/fresco/Tls12SocketFactory.java
Executable file
@@ -0,0 +1,69 @@
|
||||
package com.pukka.libcore.fresco;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
import javax.net.ssl.SSLSocket;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
|
||||
/**
|
||||
* Author: wxf
|
||||
* Date: 2024/8/26 11:00
|
||||
* Description:设置支持TLS的版本 TLSv1.2等
|
||||
*/
|
||||
public class Tls12SocketFactory extends SSLSocketFactory {
|
||||
//支持TLS的版本
|
||||
private static final String[] TLS_SUPPORT_VERSION = {"TLSv1", "TLSv1.1", "TLSv1.2"};
|
||||
|
||||
final SSLSocketFactory delegate;
|
||||
|
||||
public Tls12SocketFactory(SSLSocketFactory delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getDefaultCipherSuites() {
|
||||
return delegate.getDefaultCipherSuites();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getSupportedCipherSuites() {
|
||||
return delegate.getSupportedCipherSuites();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
|
||||
return patch(delegate.createSocket(s, host, port, autoClose));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
|
||||
return patch(delegate.createSocket(host, port));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException {
|
||||
return patch(delegate.createSocket(host, port, localHost, localPort));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(InetAddress host, int port) throws IOException {
|
||||
return patch(delegate.createSocket(host, port));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
|
||||
return patch(delegate.createSocket(address, port, localAddress, localPort));
|
||||
}
|
||||
|
||||
private Socket patch(Socket s) {
|
||||
if (s instanceof SSLSocket) {
|
||||
((SSLSocket) s).setEnabledProtocols(TLS_SUPPORT_VERSION);
|
||||
}
|
||||
return s;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
26
libcore/src/main/java/com/pukka/libcore/fresco/TrustAllManager.java
Executable file
26
libcore/src/main/java/com/pukka/libcore/fresco/TrustAllManager.java
Executable file
@@ -0,0 +1,26 @@
|
||||
package com.pukka.libcore.fresco;
|
||||
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
|
||||
public class TrustAllManager implements X509TrustManager {
|
||||
|
||||
@Override
|
||||
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public X509Certificate[] getAcceptedIssuers() {
|
||||
return new X509Certificate[0];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
102
libcore/src/main/java/com/pukka/libcore/retrofit/DefaultObserver.java
Executable file
102
libcore/src/main/java/com/pukka/libcore/retrofit/DefaultObserver.java
Executable file
@@ -0,0 +1,102 @@
|
||||
package com.pukka.libcore.retrofit;
|
||||
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.pukka.libcore.utils.LogUtils;
|
||||
|
||||
import org.json.JSONException;
|
||||
|
||||
import java.io.InterruptedIOException;
|
||||
import java.net.ConnectException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.text.ParseException;
|
||||
|
||||
import io.reactivex.rxjava3.core.Observer;
|
||||
import io.reactivex.rxjava3.disposables.Disposable;
|
||||
import retrofit2.HttpException;
|
||||
|
||||
/**
|
||||
* Author: wxf
|
||||
* Date: 2024/8/28 16:27
|
||||
* Description:接口返回,默认处理抽象类(观察者)
|
||||
* 包含返回实体或异常情况
|
||||
* 使用方法:
|
||||
* RetrofitHelper.getApiService().getRecomment("795","6.0.1").subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new DefaultObserver
|
||||
*
|
||||
*/
|
||||
public abstract class DefaultObserver<T> implements Observer<T> {
|
||||
@Override
|
||||
public void onSubscribe(Disposable d) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNext(T response) {
|
||||
onSuccess(response);
|
||||
onFinish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable e) {
|
||||
LogUtils.e(e.getMessage());
|
||||
if (e instanceof HttpException) { // HTTP错误
|
||||
onException(ExceptionReason.BAD_NETWORK);
|
||||
} else if (e instanceof ConnectException
|
||||
|| e instanceof UnknownHostException) { // 连接错误
|
||||
onException(ExceptionReason.CONNECT_ERROR);
|
||||
} else if (e instanceof InterruptedIOException) { // 连接超时
|
||||
onException(ExceptionReason.CONNECT_TIMEOUT);
|
||||
} else if (e instanceof JsonParseException
|
||||
|| e instanceof JSONException
|
||||
|| e instanceof ParseException) { // 解析错误
|
||||
onException(ExceptionReason.PARSE_ERROR);
|
||||
} else {
|
||||
onException(ExceptionReason.UNKNOWN_ERROR);
|
||||
}
|
||||
onFinish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求成功
|
||||
*
|
||||
* @param response 服务器返回的数据
|
||||
*/
|
||||
public abstract void onSuccess(T response);
|
||||
|
||||
|
||||
public void onFinish() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求异常
|
||||
*
|
||||
* @param reason 异常原因
|
||||
*/
|
||||
public void onException(ExceptionReason reason) {
|
||||
switch (reason) {
|
||||
case CONNECT_ERROR:
|
||||
LogUtils.d("HTTP 连接错误");
|
||||
break;
|
||||
|
||||
case CONNECT_TIMEOUT:
|
||||
LogUtils.d("HTTP 连接超时");
|
||||
break;
|
||||
|
||||
case BAD_NETWORK:
|
||||
LogUtils.d("HTTP 网络问题");
|
||||
break;
|
||||
|
||||
case PARSE_ERROR:
|
||||
LogUtils.d("HTTP 解析数据失败");
|
||||
break;
|
||||
|
||||
case UNKNOWN_ERROR:
|
||||
LogUtils.d("HTTP 未知错误");
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
28
libcore/src/main/java/com/pukka/libcore/retrofit/ExceptionReason.java
Executable file
28
libcore/src/main/java/com/pukka/libcore/retrofit/ExceptionReason.java
Executable file
@@ -0,0 +1,28 @@
|
||||
package com.pukka.libcore.retrofit;
|
||||
/**
|
||||
* Author: wxf
|
||||
* Date: 2023/11/13 11:26
|
||||
* Description:请求网络失败原因
|
||||
*/
|
||||
public enum ExceptionReason {
|
||||
/**
|
||||
* 解析数据失败
|
||||
*/
|
||||
PARSE_ERROR,
|
||||
/**
|
||||
* 网络问题
|
||||
*/
|
||||
BAD_NETWORK,
|
||||
/**
|
||||
* 连接错误
|
||||
*/
|
||||
CONNECT_ERROR,
|
||||
/**
|
||||
* 连接超时
|
||||
*/
|
||||
CONNECT_TIMEOUT,
|
||||
/**
|
||||
* 未知错误
|
||||
*/
|
||||
UNKNOWN_ERROR,
|
||||
}
|
||||
129
libcore/src/main/java/com/pukka/libcore/retrofit/OkHttpClientUtil.java
Executable file
129
libcore/src/main/java/com/pukka/libcore/retrofit/OkHttpClientUtil.java
Executable file
@@ -0,0 +1,129 @@
|
||||
package com.pukka.libcore.retrofit;
|
||||
|
||||
import com.pukka.libcore.utils.LogUtils;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLDecoder;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.logging.HttpLoggingInterceptor;
|
||||
|
||||
/**
|
||||
* Author: wxf
|
||||
* Date: 2023/11/13 11:26
|
||||
* Description:设置okHttp链接超时、数据读取超时、写入服务器的超时等
|
||||
*/
|
||||
public class OkHttpClientUtil {
|
||||
/**
|
||||
* 默认超时时长
|
||||
*/
|
||||
public static final long DEFAULT_MILLISECONDS = 10_000L;
|
||||
// 是否开启拦截,默认情况下开启
|
||||
private boolean mIsIntercept = true;
|
||||
// 设置数据读取超时时间
|
||||
private long readTimeOut;
|
||||
// 设置网络连接超时时间
|
||||
private long connTimeOut;
|
||||
// 设置写入服务器的超时时间
|
||||
private long writeTimeOut;
|
||||
|
||||
private static volatile OkHttpClientUtil okHttpUtil;
|
||||
|
||||
public static OkHttpClientUtil getOkHttpClientUtil() {
|
||||
if (okHttpUtil == null) {
|
||||
synchronized (OkHttpClientUtil.class) {
|
||||
if (okHttpUtil == null) {
|
||||
okHttpUtil = new OkHttpClientUtil();
|
||||
}
|
||||
}
|
||||
}
|
||||
return okHttpUtil;
|
||||
}
|
||||
|
||||
/**
|
||||
* 私有构造函数,保证全局唯一
|
||||
*/
|
||||
private OkHttpClientUtil() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否开启拦截
|
||||
*
|
||||
* @param isIntercept true : 打印日志 false:不打印日志
|
||||
* @return this
|
||||
*/
|
||||
public OkHttpClientUtil setIntercept(boolean isIntercept) {
|
||||
this.mIsIntercept = isIntercept;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置读取超时时间
|
||||
*
|
||||
* @param readTimeOut 读取超时时间
|
||||
* @return this
|
||||
*/
|
||||
public OkHttpClientUtil setReadTimeOut(long readTimeOut) {
|
||||
this.readTimeOut = readTimeOut;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置连接超时时间
|
||||
*
|
||||
* @param connTimeOut 连接超时
|
||||
* @return this
|
||||
*/
|
||||
public OkHttpClientUtil setConnTimeOut(long connTimeOut) {
|
||||
this.connTimeOut = connTimeOut;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置写入超时时间
|
||||
*
|
||||
* @param writeTimeOut 写入超时
|
||||
* @return this
|
||||
*/
|
||||
public OkHttpClientUtil setWriteTimeOut(long writeTimeOut) {
|
||||
this.writeTimeOut = writeTimeOut;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取OkHttpClient
|
||||
* 设置Build方法
|
||||
* @return OkHttpClient
|
||||
*/
|
||||
public OkHttpClient build() {
|
||||
//日志拦截器,打印http请求
|
||||
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
|
||||
@Override
|
||||
public void log(String message) {
|
||||
try {
|
||||
LogUtils.d("OKHttp-----", URLDecoder.decode(message, "utf-8"));
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
e.printStackTrace();
|
||||
LogUtils.d("OKHttp-----", message);
|
||||
}
|
||||
}
|
||||
});
|
||||
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
|
||||
OkHttpClient.Builder okHttpClient = new OkHttpClient.Builder();
|
||||
|
||||
readTimeOut = readTimeOut > 0 ? readTimeOut : DEFAULT_MILLISECONDS;
|
||||
writeTimeOut = writeTimeOut > 0 ? writeTimeOut : DEFAULT_MILLISECONDS;
|
||||
connTimeOut = connTimeOut > 0 ? connTimeOut : DEFAULT_MILLISECONDS;
|
||||
|
||||
okHttpClient.readTimeout(readTimeOut, TimeUnit.MILLISECONDS);
|
||||
okHttpClient.connectTimeout(connTimeOut, TimeUnit.MILLISECONDS);
|
||||
okHttpClient.writeTimeout(writeTimeOut, TimeUnit.MILLISECONDS);
|
||||
if (mIsIntercept) {
|
||||
okHttpClient.addInterceptor(loggingInterceptor);
|
||||
}
|
||||
return okHttpClient.build();
|
||||
}
|
||||
|
||||
}
|
||||
96
libcore/src/main/java/com/pukka/libcore/retrofit/PkApiService.java
Executable file
96
libcore/src/main/java/com/pukka/libcore/retrofit/PkApiService.java
Executable file
@@ -0,0 +1,96 @@
|
||||
package com.pukka.libcore.retrofit;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import io.reactivex.rxjava3.core.Observable;
|
||||
import okhttp3.MultipartBody;
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.ResponseBody;
|
||||
import retrofit2.http.Body;
|
||||
import retrofit2.http.FieldMap;
|
||||
import retrofit2.http.FormUrlEncoded;
|
||||
import retrofit2.http.GET;
|
||||
import retrofit2.http.Multipart;
|
||||
import retrofit2.http.POST;
|
||||
import retrofit2.http.Part;
|
||||
import retrofit2.http.PartMap;
|
||||
import retrofit2.http.Path;
|
||||
import retrofit2.http.Query;
|
||||
import retrofit2.http.QueryMap;
|
||||
import retrofit2.http.Streaming;
|
||||
import retrofit2.http.Url;
|
||||
|
||||
/**
|
||||
* Author: wxf
|
||||
* Date: 2023/11/10 15:21
|
||||
* Description:网络请求SerVice样例
|
||||
*/
|
||||
public interface PkApiService {
|
||||
//方式一 =============================
|
||||
//=====HTTP GET方式,按照这种写法,清爽
|
||||
//建议使用这种
|
||||
//获取推荐位接口 参数比较少得情况
|
||||
// @GET("/api/epg/getRecommend")
|
||||
// Observable<Page> getRecomment(@Query("navid") String navid, @Query("apkVersion") String apkVersion);
|
||||
// //获取导航接口
|
||||
// @GET("/api/epg/getNav")
|
||||
// Observable<Nav> getNav(@Query("apkVersion") String apkVersion, @Query("areaid") String areaid, @Query("provinceid") String provinceid, @Query("licenseid") String licenseid
|
||||
// , @Query("epgCode") String epgCode, @Query("userGroup") String userGroup);
|
||||
// //方式一 =============================
|
||||
// //方式二 ==========
|
||||
// @GET("/api/epg/getRecommend")
|
||||
// Observable<Page> getRecommend(@QueryMap TreeMap<String, Object> map);
|
||||
@GET("/api/epg/getRecommend")
|
||||
Observable<String> getRecommend(@QueryMap TreeMap<String, Object> map);
|
||||
//方式二 ==========
|
||||
@GET
|
||||
Observable<String> getPath(@Url String url);
|
||||
|
||||
// GET请求(无参) api = news
|
||||
@GET("{api}")
|
||||
Observable<String> getData(@Path("api") String api);
|
||||
|
||||
// GET请求(带参) api = news
|
||||
@GET("{api}")
|
||||
Observable<String> getData(@Path(value = "api", encoded = true) String api, @QueryMap TreeMap<String, Object> map);
|
||||
|
||||
// POST请求(无参)
|
||||
@POST("{api}")
|
||||
Observable<String> postData(@Path(value = "api", encoded = true) String api);
|
||||
|
||||
// POST请求,以RequestBody方式提交 api = news
|
||||
// RequestBody:
|
||||
// {
|
||||
// "albumID": 2,
|
||||
// "sectionID": 16
|
||||
// }
|
||||
@POST("{api}")
|
||||
Observable<String> postData(@Path(value = "api", encoded = true) String api, @Body RequestBody requestBody);
|
||||
|
||||
// Post请求,以表单方式提交
|
||||
@FormUrlEncoded
|
||||
@POST("{api}")
|
||||
Observable<ResponseBody> postData(@Path("api") String api, @FieldMap Map<String, String> maps);
|
||||
|
||||
// Post请求(带数组)
|
||||
@FormUrlEncoded
|
||||
@POST("{api}")
|
||||
Observable<ResponseBody> postData(@Path("api") String api, @FieldMap Map<String, String> maps, @Query("meta[]") String... linked);
|
||||
|
||||
// 上传单个文件
|
||||
@Multipart
|
||||
@POST("{api}/")
|
||||
Observable<String> upload(@Path("api") String api, @PartMap Map<String, RequestBody> maps, @Part MultipartBody.Part file);
|
||||
|
||||
// 上传多个文件
|
||||
@Multipart
|
||||
@POST("{api}/")
|
||||
Observable<String> uploadMultipart(@Path("api") String api, @PartMap Map<String, RequestBody> maps, @Part List<MultipartBody.Part> params);
|
||||
|
||||
//下载文件
|
||||
@Streaming //大文件时要加不然会OOM
|
||||
@GET
|
||||
Observable<ResponseBody> downloadFile(@Url String fileUrl);
|
||||
}
|
||||
64
libcore/src/main/java/com/pukka/libcore/retrofit/RetrofitApi.java
Executable file
64
libcore/src/main/java/com/pukka/libcore/retrofit/RetrofitApi.java
Executable file
@@ -0,0 +1,64 @@
|
||||
package com.pukka.libcore.retrofit;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
import retrofit2.Retrofit;
|
||||
import retrofit2.adapter.rxjava3.RxJava3CallAdapterFactory;
|
||||
import retrofit2.converter.gson.GsonConverterFactory;
|
||||
import retrofit2.converter.simplexml.SimpleXmlConverterFactory;
|
||||
|
||||
/**
|
||||
* Author: wxf
|
||||
* Date: 2023/11/13 13:31
|
||||
* Description:获取接口Api
|
||||
* 支持json和xml
|
||||
*/
|
||||
public class RetrofitApi {
|
||||
/**
|
||||
* 获取接口API(json)
|
||||
* @param <T> API接口对象
|
||||
* @param cls API接口类名
|
||||
* @param baseUrl 请求的域名 http:xxx:端口
|
||||
* @return 接口API
|
||||
* */
|
||||
public static <T> T getApiService(Class<T> cls,String baseUrl) {
|
||||
Retrofit retrofit = getRetrofitBuilder(baseUrl).build();
|
||||
return retrofit.create(cls);
|
||||
}
|
||||
/**
|
||||
* 获取接口API (xml)
|
||||
* @param <T> API接口对象
|
||||
* @param cls API接口类名
|
||||
* @param baseUrl 请求的域名 http:xxx:端口
|
||||
* @return 接口API
|
||||
* */
|
||||
public static <T> T getXmlApiService(Class<T> cls,String baseUrl) {
|
||||
Retrofit retrofit = getXmlRetrofitBuilder(baseUrl).build();
|
||||
return retrofit.create(cls);
|
||||
}
|
||||
/**
|
||||
* 获取Retrofit.Builder
|
||||
* 支持Gson格式转换
|
||||
*/
|
||||
private static Retrofit.Builder getRetrofitBuilder(String baseUrl) {
|
||||
Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").serializeNulls().create();
|
||||
return new Retrofit.Builder()
|
||||
.client(OkHttpClientUtil.getOkHttpClientUtil().build())
|
||||
.addConverterFactory(GsonConverterFactory.create(gson))
|
||||
.addCallAdapterFactory(RxJava3CallAdapterFactory.create())
|
||||
.baseUrl(baseUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Retrofit.Builder
|
||||
* 支持Xml格式转换
|
||||
*/
|
||||
private static Retrofit.Builder getXmlRetrofitBuilder(String baseUrl) {
|
||||
return new Retrofit.Builder()
|
||||
.client(OkHttpClientUtil.getOkHttpClientUtil().build())
|
||||
.addConverterFactory(SimpleXmlConverterFactory.create())
|
||||
.addCallAdapterFactory(RxJava3CallAdapterFactory.create())
|
||||
.baseUrl(baseUrl);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,498 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Square, Inc.
|
||||
*
|
||||
* 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.pukka.libcore.retrofit.lowplatform;
|
||||
|
||||
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
import java.nio.charset.Charset;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.Security;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.TrustAnchor;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.List;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLPeerUnverifiedException;
|
||||
import javax.net.ssl.SSLSocket;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
|
||||
import okhttp3.Protocol;
|
||||
import okhttp3.internal.Util;
|
||||
import okhttp3.internal.platform.Platform;
|
||||
import okhttp3.internal.tls.BasicTrustRootIndex;
|
||||
import okhttp3.internal.tls.CertificateChainCleaner;
|
||||
import okhttp3.internal.tls.TrustRootIndex;
|
||||
import okio.Buffer;
|
||||
|
||||
class AndroidPlatform9 extends Platform {
|
||||
private static final int MAX_LOG_LENGTH = 4000;
|
||||
|
||||
private final Class<?> sslParametersClass;
|
||||
private final OptionalMethod<Socket> setUseSessionTickets;
|
||||
private final OptionalMethod<Socket> setHostname;
|
||||
|
||||
// Non-null on Android 5.0+.
|
||||
private final OptionalMethod<Socket> getAlpnSelectedProtocol;
|
||||
private final OptionalMethod<Socket> setAlpnProtocols;
|
||||
|
||||
private final CloseGuard closeGuard = CloseGuard.get();
|
||||
|
||||
AndroidPlatform9(Class<?> sslParametersClass, OptionalMethod<Socket> setUseSessionTickets,
|
||||
OptionalMethod<Socket> setHostname, OptionalMethod<Socket> getAlpnSelectedProtocol,
|
||||
OptionalMethod<Socket> setAlpnProtocols) {
|
||||
this.sslParametersClass = sslParametersClass;
|
||||
this.setUseSessionTickets = setUseSessionTickets;
|
||||
this.setHostname = setHostname;
|
||||
this.getAlpnSelectedProtocol = getAlpnSelectedProtocol;
|
||||
this.setAlpnProtocols = setAlpnProtocols;
|
||||
}
|
||||
|
||||
@Override public void connectSocket(Socket socket, InetSocketAddress address,
|
||||
int connectTimeout) throws IOException {
|
||||
try {
|
||||
socket.connect(address, connectTimeout);
|
||||
} catch (AssertionError e) {
|
||||
if (Util.isAndroidGetsocknameError(e)) throw new IOException(e);
|
||||
throw e;
|
||||
} catch (SecurityException e) {
|
||||
// Before android 4.3, socket.connect could throw a SecurityException
|
||||
// if opening a socket resulted in an EACCES error.
|
||||
IOException ioException = new IOException("Exception in connect");
|
||||
ioException.initCause(e);
|
||||
throw ioException;
|
||||
} catch (ClassCastException e) {
|
||||
// On android 8.0, socket.connect throws a ClassCastException due to a bug
|
||||
// see https://issuetracker.google.com/issues/63649622
|
||||
if (Build.VERSION.SDK_INT == 26) {
|
||||
IOException ioException = new IOException("Exception in connect");
|
||||
ioException.initCause(e);
|
||||
throw ioException;
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override protected @Nullable
|
||||
X509TrustManager trustManager(SSLSocketFactory sslSocketFactory) {
|
||||
Object context = readFieldOrNull(sslSocketFactory, sslParametersClass, "sslParameters");
|
||||
if (context == null) {
|
||||
// If that didn't work, try the Google Play Services SSL provider before giving up. This
|
||||
// must be loaded by the SSLSocketFactory's class loader.
|
||||
try {
|
||||
Class<?> gmsSslParametersClass = Class.forName(
|
||||
"com.google.android.gms.org.conscrypt.SSLParametersImpl", false,
|
||||
sslSocketFactory.getClass().getClassLoader());
|
||||
context = readFieldOrNull(sslSocketFactory, gmsSslParametersClass, "sslParameters");
|
||||
} catch (ClassNotFoundException e) {
|
||||
return super.trustManager(sslSocketFactory);
|
||||
}
|
||||
}
|
||||
|
||||
X509TrustManager x509TrustManager = readFieldOrNull(
|
||||
context, X509TrustManager.class, "x509TrustManager");
|
||||
if (x509TrustManager != null) return x509TrustManager;
|
||||
|
||||
return readFieldOrNull(context, X509TrustManager.class, "trustManager");
|
||||
}
|
||||
|
||||
static @Nullable <T> T readFieldOrNull(Object instance, Class<T> fieldType, String fieldName) {
|
||||
for (Class<?> c = instance.getClass(); c != Object.class; c = c.getSuperclass()) {
|
||||
try {
|
||||
Field field = c.getDeclaredField(fieldName);
|
||||
field.setAccessible(true);
|
||||
Object value = field.get(instance);
|
||||
if (value == null || !fieldType.isInstance(value)) return null;
|
||||
return fieldType.cast(value);
|
||||
} catch (NoSuchFieldException ignored) {
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
// Didn't find the field we wanted. As a last gasp attempt, try to find the value on a delegate.
|
||||
if (!fieldName.equals("delegate")) {
|
||||
Object delegate = readFieldOrNull(instance, Object.class, "delegate");
|
||||
if (delegate != null) return readFieldOrNull(delegate, fieldType, fieldName);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
static byte[] concatLengthPrefixed(List<Protocol> protocols) {
|
||||
Buffer result = new Buffer();
|
||||
for (int i = 0, size = protocols.size(); i < size; i++) {
|
||||
Protocol protocol = protocols.get(i);
|
||||
if (protocol == Protocol.HTTP_1_0) continue; // No HTTP/1.0 for ALPN.
|
||||
result.writeByte(protocol.toString().length());
|
||||
result.writeUtf8(protocol.toString());
|
||||
}
|
||||
return result.readByteArray();
|
||||
}
|
||||
|
||||
@Override public void configureTlsExtensions(
|
||||
SSLSocket sslSocket, String hostname, List<Protocol> protocols) {
|
||||
// Enable SNI and session tickets.
|
||||
if (hostname != null) {
|
||||
setUseSessionTickets.invokeOptionalWithoutCheckedException(sslSocket, true);
|
||||
setHostname.invokeOptionalWithoutCheckedException(sslSocket, hostname);
|
||||
}
|
||||
|
||||
// Enable ALPN.
|
||||
if (setAlpnProtocols != null && setAlpnProtocols.isSupported(sslSocket)) {
|
||||
Object[] parameters = {concatLengthPrefixed(protocols)};
|
||||
setAlpnProtocols.invokeWithoutCheckedException(sslSocket, parameters);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public @Nullable String getSelectedProtocol(SSLSocket socket) {
|
||||
if (getAlpnSelectedProtocol == null) return null;
|
||||
if (!getAlpnSelectedProtocol.isSupported(socket)) return null;
|
||||
|
||||
byte[] alpnResult = (byte[]) getAlpnSelectedProtocol.invokeWithoutCheckedException(socket);
|
||||
return alpnResult != null ? new String(alpnResult, Charset.forName("UTF-8")) : null;
|
||||
}
|
||||
|
||||
@Override public void log(int level, String message, @Nullable Throwable t) {
|
||||
int logLevel = level == WARN ? Log.WARN : Log.DEBUG;
|
||||
if (t != null) message = message + '\n' + Log.getStackTraceString(t);
|
||||
|
||||
// Split by line, then ensure each line can fit into Log's maximum length.
|
||||
for (int i = 0, length = message.length(); i < length; i++) {
|
||||
int newline = message.indexOf('\n', i);
|
||||
newline = newline != -1 ? newline : length;
|
||||
do {
|
||||
int end = Math.min(newline, i + MAX_LOG_LENGTH);
|
||||
Log.println(logLevel, "OkHttp", message.substring(i, end));
|
||||
i = end;
|
||||
} while (i < newline);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public Object getStackTraceForCloseable(String closer) {
|
||||
return closeGuard.createAndOpen(closer);
|
||||
}
|
||||
|
||||
@Override public void logCloseableLeak(String message, Object stackTrace) {
|
||||
boolean reported = closeGuard.warnIfOpen(stackTrace);
|
||||
if (!reported) {
|
||||
// Unable to report via CloseGuard. As a last-ditch effort, send it to the logger.
|
||||
log(WARN, message, null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public boolean isCleartextTrafficPermitted(String hostname) {
|
||||
try {
|
||||
Class<?> networkPolicyClass = Class.forName("android.security.NetworkSecurityPolicy");
|
||||
Method getInstanceMethod = networkPolicyClass.getMethod("getInstance");
|
||||
Object networkSecurityPolicy = getInstanceMethod.invoke(null);
|
||||
return api24IsCleartextTrafficPermitted(hostname, networkPolicyClass, networkSecurityPolicy);
|
||||
} catch (ClassNotFoundException | NoSuchMethodException e) {
|
||||
return super.isCleartextTrafficPermitted(hostname);
|
||||
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
|
||||
return super.isCleartextTrafficPermitted(hostname);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean api24IsCleartextTrafficPermitted(String hostname, Class<?> networkPolicyClass,
|
||||
Object networkSecurityPolicy) throws InvocationTargetException, IllegalAccessException {
|
||||
try {
|
||||
Method isCleartextTrafficPermittedMethod = networkPolicyClass
|
||||
.getMethod("isCleartextTrafficPermitted", String.class);
|
||||
return (boolean) isCleartextTrafficPermittedMethod.invoke(networkSecurityPolicy, hostname);
|
||||
} catch (NoSuchMethodException e) {
|
||||
return api23IsCleartextTrafficPermitted(hostname, networkPolicyClass, networkSecurityPolicy);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean api23IsCleartextTrafficPermitted(String hostname, Class<?> networkPolicyClass,
|
||||
Object networkSecurityPolicy) throws InvocationTargetException, IllegalAccessException {
|
||||
try {
|
||||
Method isCleartextTrafficPermittedMethod = networkPolicyClass
|
||||
.getMethod("isCleartextTrafficPermitted");
|
||||
return (boolean) isCleartextTrafficPermittedMethod.invoke(networkSecurityPolicy);
|
||||
} catch (NoSuchMethodException e) {
|
||||
return super.isCleartextTrafficPermitted(hostname);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks to see if Google Play Services Dynamic Security Provider is present which provides ALPN
|
||||
* support. If it isn't checks to see if device is Android 5.0+ since 4.x device have broken
|
||||
* ALPN support.
|
||||
*/
|
||||
private static boolean supportsAlpn() {
|
||||
if (Security.getProvider("GMSCore_OpenSSL") != null) {
|
||||
return true;
|
||||
} else {
|
||||
try {
|
||||
Class.forName("android.net.Network"); // Arbitrary class added in Android 5.0.
|
||||
return true;
|
||||
} catch (ClassNotFoundException ignored) { }
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public CertificateChainCleaner buildCertificateChainCleaner(X509TrustManager trustManager) {
|
||||
try {
|
||||
Class<?> extensionsClass = Class.forName("android.net.http.X509TrustManagerExtensions");
|
||||
Constructor<?> constructor = extensionsClass.getConstructor(X509TrustManager.class);
|
||||
Object extensions = constructor.newInstance(trustManager);
|
||||
Method checkServerTrusted = extensionsClass.getMethod(
|
||||
"checkServerTrusted", X509Certificate[].class, String.class, String.class);
|
||||
return new AndroidCertificateChainCleaner(extensions, checkServerTrusted);
|
||||
} catch (Exception e) {
|
||||
return super.buildCertificateChainCleaner(trustManager);
|
||||
}
|
||||
}
|
||||
|
||||
public static Platform buildIfSupported() {
|
||||
// Attempt to find Android 2.3+ APIs.
|
||||
try {
|
||||
Class<?> sslParametersClass;
|
||||
try {
|
||||
sslParametersClass = Class.forName("com.android.org.conscrypt.SSLParametersImpl");
|
||||
} catch (ClassNotFoundException e) {
|
||||
// Older platform before being unbundled.
|
||||
sslParametersClass = Class.forName(
|
||||
"org.apache.harmony.xnet.provider.jsse.SSLParametersImpl");
|
||||
}
|
||||
|
||||
OptionalMethod<Socket> setUseSessionTickets = new OptionalMethod<>(
|
||||
null, "setUseSessionTickets", boolean.class);
|
||||
OptionalMethod<Socket> setHostname = new OptionalMethod<>(
|
||||
null, "setHostname", String.class);
|
||||
OptionalMethod<Socket> getAlpnSelectedProtocol = null;
|
||||
OptionalMethod<Socket> setAlpnProtocols = null;
|
||||
|
||||
if (supportsAlpn()) {
|
||||
getAlpnSelectedProtocol
|
||||
= new OptionalMethod<>(byte[].class, "getAlpnSelectedProtocol");
|
||||
setAlpnProtocols
|
||||
= new OptionalMethod<>(null, "setAlpnProtocols", byte[].class);
|
||||
}
|
||||
|
||||
return new AndroidPlatform9(sslParametersClass, setUseSessionTickets, setHostname,
|
||||
getAlpnSelectedProtocol, setAlpnProtocols);
|
||||
} catch (ClassNotFoundException ignored) {
|
||||
// This isn't an Android runtime.
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TrustRootIndex buildTrustRootIndex(X509TrustManager trustManager) {
|
||||
|
||||
try {
|
||||
// From org.conscrypt.TrustManagerImpl, we want the method with this signature:
|
||||
// private TrustAnchor findTrustAnchorByIssuerAndSignature(X509Certificate lastCert);
|
||||
Method method = trustManager.getClass().getDeclaredMethod(
|
||||
"findTrustAnchorByIssuerAndSignature", X509Certificate.class);
|
||||
method.setAccessible(true);
|
||||
return new AndroidTrustRootIndex(trustManager, method);
|
||||
} catch (NoSuchMethodException e) {
|
||||
return super.buildTrustRootIndex(trustManager);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* X509TrustManagerExtensions was added to Android in API 17 (Android 4.2, released in late 2012).
|
||||
* This is the best way to get a clean chain on Android because it uses the same code as the TLS
|
||||
* handshake.
|
||||
*/
|
||||
static final class AndroidCertificateChainCleaner extends CertificateChainCleaner {
|
||||
private final Object x509TrustManagerExtensions;
|
||||
private final Method checkServerTrusted;
|
||||
|
||||
AndroidCertificateChainCleaner(Object x509TrustManagerExtensions, Method checkServerTrusted) {
|
||||
this.x509TrustManagerExtensions = x509TrustManagerExtensions;
|
||||
this.checkServerTrusted = checkServerTrusted;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked", "SuspiciousToArrayCall"}) // Reflection on List<Certificate>.
|
||||
@Override public List<Certificate> clean(List<Certificate> chain, String hostname)
|
||||
throws SSLPeerUnverifiedException {
|
||||
try {
|
||||
X509Certificate[] certificates = chain.toArray(new X509Certificate[chain.size()]);
|
||||
return (List<Certificate>) checkServerTrusted.invoke(
|
||||
x509TrustManagerExtensions, certificates, "RSA", hostname);
|
||||
} catch (InvocationTargetException e) {
|
||||
SSLPeerUnverifiedException exception = new SSLPeerUnverifiedException(e.getMessage());
|
||||
exception.initCause(e);
|
||||
throw exception;
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public boolean equals(Object other) {
|
||||
return other instanceof AndroidCertificateChainCleaner; // All instances are equivalent.
|
||||
}
|
||||
|
||||
@Override public int hashCode() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides access to the internal dalvik.system.CloseGuard class. Android uses this in
|
||||
* combination with android.os.StrictMode to report on leaked java.io.Closeable's. Available since
|
||||
* Android API 11.
|
||||
*/
|
||||
static final class CloseGuard {
|
||||
private final Method getMethod;
|
||||
private final Method openMethod;
|
||||
private final Method warnIfOpenMethod;
|
||||
|
||||
CloseGuard(Method getMethod, Method openMethod, Method warnIfOpenMethod) {
|
||||
this.getMethod = getMethod;
|
||||
this.openMethod = openMethod;
|
||||
this.warnIfOpenMethod = warnIfOpenMethod;
|
||||
}
|
||||
|
||||
Object createAndOpen(String closer) {
|
||||
if (getMethod != null) {
|
||||
try {
|
||||
Object closeGuardInstance = getMethod.invoke(null);
|
||||
openMethod.invoke(closeGuardInstance, closer);
|
||||
return closeGuardInstance;
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
boolean warnIfOpen(Object closeGuardInstance) {
|
||||
boolean reported = false;
|
||||
if (closeGuardInstance != null) {
|
||||
try {
|
||||
warnIfOpenMethod.invoke(closeGuardInstance);
|
||||
reported = true;
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
return reported;
|
||||
}
|
||||
|
||||
static CloseGuard get() {
|
||||
Method getMethod;
|
||||
Method openMethod;
|
||||
Method warnIfOpenMethod;
|
||||
|
||||
try {
|
||||
Class<?> closeGuardClass = Class.forName("dalvik.system.CloseGuard");
|
||||
getMethod = closeGuardClass.getMethod("get");
|
||||
openMethod = closeGuardClass.getMethod("open", String.class);
|
||||
warnIfOpenMethod = closeGuardClass.getMethod("warnIfOpen");
|
||||
} catch (Exception ignored) {
|
||||
getMethod = null;
|
||||
openMethod = null;
|
||||
warnIfOpenMethod = null;
|
||||
}
|
||||
return new CloseGuard(getMethod, openMethod, warnIfOpenMethod);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An index of trusted root certificates that exploits knowledge of Android implementation
|
||||
* details. This class is potentially much faster to initialize than {@link BasicTrustRootIndex}
|
||||
* because it doesn't need to load and index trusted CA certificates.
|
||||
*
|
||||
* <p>This class uses APIs added to Android in API 14 (Android 4.0, released October 2011). This
|
||||
* class shouldn't be used in Android API 17 or better because those releases are better served by
|
||||
* {@link AndroidCertificateChainCleaner}.
|
||||
*/
|
||||
static final class AndroidTrustRootIndex implements TrustRootIndex {
|
||||
private final X509TrustManager trustManager;
|
||||
private final Method findByIssuerAndSignatureMethod;
|
||||
|
||||
AndroidTrustRootIndex(X509TrustManager trustManager, Method findByIssuerAndSignatureMethod) {
|
||||
this.findByIssuerAndSignatureMethod = findByIssuerAndSignatureMethod;
|
||||
this.trustManager = trustManager;
|
||||
}
|
||||
|
||||
@Override public X509Certificate findByIssuerAndSignature(X509Certificate cert) {
|
||||
try {
|
||||
TrustAnchor trustAnchor = (TrustAnchor) findByIssuerAndSignatureMethod.invoke(
|
||||
trustManager, cert);
|
||||
return trustAnchor != null
|
||||
? trustAnchor.getTrustedCert()
|
||||
: null;
|
||||
} catch (IllegalAccessException e) {
|
||||
return null;
|
||||
} catch (InvocationTargetException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == this) {
|
||||
return true;
|
||||
}
|
||||
if (!(obj instanceof AndroidTrustRootIndex)) {
|
||||
return false;
|
||||
}
|
||||
AndroidTrustRootIndex that = (AndroidTrustRootIndex) obj;
|
||||
return trustManager.equals(that.trustManager)
|
||||
&& findByIssuerAndSignatureMethod.equals(that.findByIssuerAndSignatureMethod);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return trustManager.hashCode() + 31 * findByIssuerAndSignatureMethod.hashCode();
|
||||
}
|
||||
}
|
||||
|
||||
@Override public SSLContext getSSLContext() {
|
||||
boolean tryTls12;
|
||||
try {
|
||||
tryTls12 = (Build.VERSION.SDK_INT >= 16 && Build.VERSION.SDK_INT < 22);
|
||||
} catch (NoClassDefFoundError e) {
|
||||
// Not a real Android runtime; probably RoboVM or MoE
|
||||
// Try to load TLS 1.2 explicitly.
|
||||
tryTls12 = true;
|
||||
}
|
||||
|
||||
if (tryTls12) {
|
||||
try {
|
||||
return SSLContext.getInstance("TLSv1.2");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
// fallback to TLS
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
return SSLContext.getInstance("TLS");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new IllegalStateException("No TLS provider", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.pukka.libcore.retrofit.lowplatform;
|
||||
|
||||
import android.os.Build;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Properties;
|
||||
import okhttp3.internal.platform.Platform;
|
||||
|
||||
/**
|
||||
* Author: wxf
|
||||
* Date: 2024/8/15 14:00
|
||||
* Description:okhttp3 兼容android系统4.4
|
||||
*
|
||||
* 异常:java.lang.IllegalStateException: Expected Android API level 21+ but was 19
|
||||
* 使用方法:在Application 中 LowPlatformUtils.okhttp3InLowPlatform()
|
||||
* */
|
||||
public class LowPlatformUtils {
|
||||
public static void okhttp3InLowPlatform(){
|
||||
if (Build.VERSION.SDK_INT >= 21) {
|
||||
return;
|
||||
}
|
||||
try{
|
||||
//获取vm的名字
|
||||
String currentVmName = System.getProperty("java.vm.name", "Dalvik");
|
||||
|
||||
Properties properties = System.getProperties();
|
||||
//暂时把vm名字修改为 “jvm”,防止抛异常
|
||||
properties.setProperty("java.vm.name","jvm");
|
||||
|
||||
//反射,把PLATFORM 常量修改为 AndroidPlatform9.buildIfSupported()
|
||||
Platform platform = new Platform();
|
||||
|
||||
Field platformField = Platform.class.getDeclaredField("PLATFORM");
|
||||
platformField.setAccessible(true);
|
||||
|
||||
//重新赋值
|
||||
platformField.set(platform, AndroidPlatform9.buildIfSupported());
|
||||
|
||||
//重新把vm名字赋值回去
|
||||
properties.setProperty("java.vm.name",currentVmName);
|
||||
}catch (Throwable e){
|
||||
}
|
||||
}
|
||||
}
|
||||
174
libcore/src/main/java/com/pukka/libcore/retrofit/lowplatform/OptionalMethod.java
Executable file
174
libcore/src/main/java/com/pukka/libcore/retrofit/lowplatform/OptionalMethod.java
Executable file
@@ -0,0 +1,174 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You 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.pukka.libcore.retrofit.lowplatform;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
|
||||
/**
|
||||
* Duck-typing for methods: Represents a method that may or may not be present on an object.
|
||||
*
|
||||
* @param <T> the type of the object the method might be on, typically an interface or base class
|
||||
*/
|
||||
class OptionalMethod<T> {
|
||||
|
||||
/** The return type of the method. null means "don't care". */
|
||||
private final Class<?> returnType;
|
||||
|
||||
private final String methodName;
|
||||
|
||||
private final Class[] methodParams;
|
||||
|
||||
/**
|
||||
* Creates an optional method.
|
||||
*
|
||||
* @param returnType the return type to required, null if it does not matter
|
||||
* @param methodName the name of the method
|
||||
* @param methodParams the method parameter types
|
||||
*/
|
||||
OptionalMethod(Class<?> returnType, String methodName, Class... methodParams) {
|
||||
this.returnType = returnType;
|
||||
this.methodName = methodName;
|
||||
this.methodParams = methodParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the method exists on the supplied {@code target}.
|
||||
*/
|
||||
public boolean isSupported(T target) {
|
||||
return getMethod(target.getClass()) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the method on {@code target} with {@code args}. If the method does not exist or is not
|
||||
* public then {@code null} is returned. See also {@link #invokeOptionalWithoutCheckedException}.
|
||||
*
|
||||
* @throws IllegalArgumentException if the arguments are invalid
|
||||
* @throws InvocationTargetException if the invocation throws an exception
|
||||
*/
|
||||
public Object invokeOptional(T target, Object... args) throws InvocationTargetException {
|
||||
Method m = getMethod(target.getClass());
|
||||
if (m == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return m.invoke(target, args);
|
||||
} catch (IllegalAccessException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the method on {@code target}. If the method does not exist or is not public then
|
||||
* {@code null} is returned. Any RuntimeException thrown by the method is thrown, checked
|
||||
* exceptions are wrapped in an {@link AssertionError}.
|
||||
*
|
||||
* @throws IllegalArgumentException if the arguments are invalid
|
||||
*/
|
||||
public Object invokeOptionalWithoutCheckedException(T target, Object... args) {
|
||||
try {
|
||||
return invokeOptional(target, args);
|
||||
} catch (InvocationTargetException e) {
|
||||
Throwable targetException = e.getTargetException();
|
||||
if (targetException instanceof RuntimeException) {
|
||||
throw (RuntimeException) targetException;
|
||||
}
|
||||
AssertionError error = new AssertionError("Unexpected exception");
|
||||
error.initCause(targetException);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the method on {@code target} with {@code args}. Throws an error if the method is not
|
||||
* supported. See also {@link #invokeWithoutCheckedException(Object, Object...)}.
|
||||
*
|
||||
* @throws IllegalArgumentException if the arguments are invalid
|
||||
* @throws InvocationTargetException if the invocation throws an exception
|
||||
*/
|
||||
public Object invoke(T target, Object... args) throws InvocationTargetException {
|
||||
Method m = getMethod(target.getClass());
|
||||
if (m == null) {
|
||||
throw new AssertionError("Method " + methodName + " not supported for object " + target);
|
||||
}
|
||||
try {
|
||||
return m.invoke(target, args);
|
||||
} catch (IllegalAccessException e) {
|
||||
// Method should be public: we checked.
|
||||
AssertionError error = new AssertionError("Unexpectedly could not call: " + m);
|
||||
error.initCause(e);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the method on {@code target}. Throws an error if the method is not supported. Any
|
||||
* RuntimeException thrown by the method is thrown, checked exceptions are wrapped in an {@link
|
||||
* AssertionError}.
|
||||
*
|
||||
* @throws IllegalArgumentException if the arguments are invalid
|
||||
*/
|
||||
public Object invokeWithoutCheckedException(T target, Object... args) {
|
||||
try {
|
||||
return invoke(target, args);
|
||||
} catch (InvocationTargetException e) {
|
||||
Throwable targetException = e.getTargetException();
|
||||
if (targetException instanceof RuntimeException) {
|
||||
throw (RuntimeException) targetException;
|
||||
}
|
||||
AssertionError error = new AssertionError("Unexpected exception");
|
||||
error.initCause(targetException);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a lookup for the method. No caching. In order to return a method the method name and
|
||||
* arguments must match those specified when the {@link OptionalMethod} was created. If the return
|
||||
* type is specified (i.e. non-null) it must also be compatible. The method must also be public.
|
||||
*/
|
||||
private Method getMethod(Class<?> clazz) {
|
||||
Method method = null;
|
||||
if (methodName != null) {
|
||||
method = getPublicMethod(clazz, methodName, methodParams);
|
||||
if (method != null
|
||||
&& returnType != null
|
||||
&& !returnType.isAssignableFrom(method.getReturnType())) {
|
||||
|
||||
// If the return type is non-null it must be compatible.
|
||||
method = null;
|
||||
}
|
||||
}
|
||||
return method;
|
||||
}
|
||||
|
||||
private static Method getPublicMethod(Class<?> clazz, String methodName, Class[] parameterTypes) {
|
||||
Method method = null;
|
||||
try {
|
||||
method = clazz.getMethod(methodName, parameterTypes);
|
||||
if ((method.getModifiers() & Modifier.PUBLIC) == 0) {
|
||||
method = null;
|
||||
}
|
||||
} catch (NoSuchMethodException e) {
|
||||
// None.
|
||||
}
|
||||
return method;
|
||||
}
|
||||
}
|
||||
|
||||
168
libcore/src/main/java/com/pukka/libcore/threading/ConcurrentDispatchQueue.java
Executable file
168
libcore/src/main/java/com/pukka/libcore/threading/ConcurrentDispatchQueue.java
Executable file
@@ -0,0 +1,168 @@
|
||||
|
||||
package com.pukka.libcore.threading;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.WeakHashMap;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
||||
import java.util.concurrent.SynchronousQueue;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class ConcurrentDispatchQueue extends DispatchQueue implements ThreadFactory {
|
||||
private final WeakHashMap<GuardedRunnable, Future<?>> weakRunnableMap = new WeakHashMap<>();
|
||||
|
||||
private static final int CorePoolSize = Runtime.getRuntime().availableProcessors();
|
||||
|
||||
private final ScheduledThreadPoolExecutor scheduledThreadPoolExecutor;
|
||||
|
||||
private final ThreadPoolExecutor threadPoolExecutor;
|
||||
|
||||
private final DispatchQueue serialQueue;
|
||||
|
||||
private final AtomicInteger threadNumber;
|
||||
|
||||
ConcurrentDispatchQueue(String name) {
|
||||
super(name);
|
||||
this.serialQueue = DispatchQueue.createQueue("serialQueue", DispatchQueueType.Serial);
|
||||
this.scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(CorePoolSize, this);
|
||||
this.scheduledThreadPoolExecutor.setMaximumPoolSize(100);
|
||||
this.threadNumber = new AtomicInteger(1);
|
||||
this.threadPoolExecutor = new ThreadPoolExecutor(3, 100, 20L, TimeUnit.SECONDS, new SynchronousQueue<>(), this);
|
||||
this.threadPoolExecutor
|
||||
.setRejectedExecutionHandler((r, executor) -> ConcurrentDispatchQueue.this.serialQueue.async(() -> {
|
||||
if (!executor.isShutdown()) {
|
||||
try {
|
||||
executor.getQueue().put(r);
|
||||
} catch (Exception var2) {
|
||||
var2.printStackTrace();
|
||||
}
|
||||
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
void _sync(long timeout, Runnable runnable) {
|
||||
if (runnable != null) {
|
||||
GuardedRunnable guardedRunnable = new GuardedRunnable(runnable, (GuardedRunnable.Callback) null);
|
||||
Future<?> future = this.threadPoolExecutor.submit(guardedRunnable);
|
||||
if (future != null) {
|
||||
try {
|
||||
future.get(timeout, TimeUnit.MILLISECONDS);
|
||||
} catch (Exception var8) {
|
||||
var8.printStackTrace();
|
||||
}
|
||||
|
||||
future.cancel(true);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public void after(long delayMillis, Runnable runnable) {
|
||||
if (runnable != null) {
|
||||
GuardedRunnable guardedRunnable = new GuardedRunnable(runnable, guardedRunnable1 -> {
|
||||
synchronized (ConcurrentDispatchQueue.this) {
|
||||
ConcurrentDispatchQueue.this.weakRunnableMap.remove(guardedRunnable1);
|
||||
}
|
||||
});
|
||||
Future<?> future;
|
||||
if (delayMillis > 0L) {
|
||||
future = this.scheduledThreadPoolExecutor.schedule(guardedRunnable, delayMillis, TimeUnit.MILLISECONDS);
|
||||
} else {
|
||||
future = this.threadPoolExecutor.submit(guardedRunnable);
|
||||
}
|
||||
|
||||
if (future != null) {
|
||||
synchronized (this) {
|
||||
this.weakRunnableMap.put(guardedRunnable, future);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public Timer timer(long delayMillis, long periodMillis, Runnable runnable) {
|
||||
Timer timer = null;
|
||||
if (runnable == null) {
|
||||
return null;
|
||||
} else {
|
||||
timer = new Timer(this, runnable);
|
||||
GuardedRunnable guardedRunnable = new GuardedRunnable(runnable, (GuardedRunnable.Callback) null);
|
||||
Future<?> future = this.scheduledThreadPoolExecutor.scheduleAtFixedRate(guardedRunnable, delayMillis,
|
||||
periodMillis, TimeUnit.MILLISECONDS);
|
||||
if (future != null) {
|
||||
synchronized (this) {
|
||||
this.weakRunnableMap.put(guardedRunnable, future);
|
||||
}
|
||||
}
|
||||
|
||||
return timer;
|
||||
}
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
if (this != DispatchQueue.global) {
|
||||
try {
|
||||
this.scheduledThreadPoolExecutor.shutdownNow();
|
||||
} catch (Exception var4) {
|
||||
var4.printStackTrace();
|
||||
}
|
||||
|
||||
try {
|
||||
this.threadPoolExecutor.shutdownNow();
|
||||
} catch (Exception var3) {
|
||||
var3.printStackTrace();
|
||||
}
|
||||
|
||||
try {
|
||||
this.serialQueue.stop();
|
||||
} catch (Exception var2) {
|
||||
var2.printStackTrace();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public void remove(Runnable runnable) {
|
||||
if (runnable != null) {
|
||||
ArrayList<Future<?>> futures = new ArrayList<>();
|
||||
synchronized (this) {
|
||||
Object[] keySet = this.weakRunnableMap.keySet().toArray();
|
||||
for (Object obj : keySet) {
|
||||
GuardedRunnable guardedRunnable = (GuardedRunnable) obj;
|
||||
if (guardedRunnable.isEqualToRunnable(runnable)) {
|
||||
Future<?> future = this.weakRunnableMap.get(guardedRunnable);
|
||||
if (future != null) {
|
||||
futures.add(future);
|
||||
}
|
||||
|
||||
try {
|
||||
this.weakRunnableMap.remove(guardedRunnable);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (futures.size() != 0) {
|
||||
|
||||
for (Future<?> value : futures) {
|
||||
((Future<?>) value).cancel(false);
|
||||
}
|
||||
|
||||
try {
|
||||
this.scheduledThreadPoolExecutor.purge();
|
||||
this.threadPoolExecutor.purge();
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Thread newThread(Runnable r) {
|
||||
return new Thread(r, this.getName() + " (thread-" + this.threadNumber.getAndIncrement() + ")");
|
||||
}
|
||||
}
|
||||
125
libcore/src/main/java/com/pukka/libcore/threading/DispatchQueue.java
Executable file
125
libcore/src/main/java/com/pukka/libcore/threading/DispatchQueue.java
Executable file
@@ -0,0 +1,125 @@
|
||||
|
||||
package com.pukka.libcore.threading;
|
||||
|
||||
import android.os.Looper;
|
||||
|
||||
/**
|
||||
* Author: wxf
|
||||
* Date: 2024/8/28 14:00
|
||||
* Description:子线程、线程切换、延迟执行,定时器等功能
|
||||
* 参考Android线程封装使用说明
|
||||
*/
|
||||
public abstract class DispatchQueue {
|
||||
private final String name;
|
||||
/**
|
||||
* 主线程
|
||||
*/
|
||||
public static DispatchQueue main;
|
||||
/**
|
||||
* 子线程
|
||||
* */
|
||||
public static DispatchQueue global;
|
||||
|
||||
public static DispatchQueue serial;
|
||||
|
||||
DispatchQueue(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
/**
|
||||
* 停止接收新的任务,并且停止当前正在执行的任务
|
||||
* */
|
||||
public abstract void stop();
|
||||
|
||||
public void removeCallbacks() {
|
||||
}
|
||||
|
||||
abstract void _sync(long timeout, Runnable runnable);
|
||||
|
||||
public static void sync(long timeout, Runnable runnable) {
|
||||
global._sync(timeout, runnable);
|
||||
}
|
||||
/**
|
||||
* 异步
|
||||
* @param runnable 异步执行的方法体
|
||||
* */
|
||||
public void async(Runnable runnable) {
|
||||
this.after(0L, runnable);
|
||||
}
|
||||
/**
|
||||
* 延迟执行
|
||||
* @param delayMillis 延迟毫秒后执行
|
||||
* @param runnable 执行的方法体
|
||||
* */
|
||||
public void after(long delayMillis, Runnable runnable) {
|
||||
}
|
||||
/**
|
||||
* 定时器
|
||||
* @param delayMillis 延迟毫秒后执行
|
||||
* @param periodMillis 间隔多少毫秒后循环执行
|
||||
* @param runnable 执行的方法体
|
||||
* @return 定时器
|
||||
* */
|
||||
public Timer timer(long delayMillis, long periodMillis, Runnable runnable) {
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* 退出执行的方法体
|
||||
* @param runnable 执行的方法体
|
||||
* */
|
||||
public void remove(Runnable runnable) {
|
||||
}
|
||||
public static boolean isMainQueue() {
|
||||
return Looper.getMainLooper() == Looper.myLooper();
|
||||
}
|
||||
|
||||
public static DispatchQueue createQueue(String name, DispatchQueueType type) {
|
||||
if (type == DispatchQueueType.Serial) {
|
||||
return new SerialDispatchQueue(name);
|
||||
} else if (type == DispatchQueueType.Concurrent) {
|
||||
return new ConcurrentDispatchQueue(name);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unexpected queue type: " + type);
|
||||
}
|
||||
}
|
||||
|
||||
static {
|
||||
main = Holder.MAIN_QUEUE;
|
||||
global = Holder.GLOBAL_QUEUE;
|
||||
serial = Holder.SERIAL_QUEUE;
|
||||
}
|
||||
|
||||
private static class Holder {
|
||||
private static final DispatchQueue MAIN_QUEUE = createMainQueue();
|
||||
|
||||
private static final DispatchQueue GLOBAL_QUEUE = createGlobalQueue();
|
||||
|
||||
private static final DispatchQueue SERIAL_QUEUE = createSerialQueue();
|
||||
|
||||
private Holder() {
|
||||
}
|
||||
|
||||
private static DispatchQueue createMainQueue() {
|
||||
try {
|
||||
return new SerialDispatchQueue(Looper.getMainLooper(), "Main-Queue");
|
||||
} catch (Exception var1) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static DispatchQueue createSerialQueue() {
|
||||
try {
|
||||
return new SerialDispatchQueue("Serial-Queue");
|
||||
} catch (Exception var1) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static DispatchQueue createGlobalQueue() {
|
||||
return new ConcurrentDispatchQueue("Global-Queue");
|
||||
}
|
||||
}
|
||||
}
|
||||
9
libcore/src/main/java/com/pukka/libcore/threading/DispatchQueueType.java
Executable file
9
libcore/src/main/java/com/pukka/libcore/threading/DispatchQueueType.java
Executable file
@@ -0,0 +1,9 @@
|
||||
package com.pukka.libcore.threading;
|
||||
|
||||
public class DispatchQueueType {
|
||||
public static DispatchQueueType Serial = new DispatchQueueType();
|
||||
public static DispatchQueueType Concurrent = new DispatchQueueType();
|
||||
|
||||
private DispatchQueueType() {
|
||||
}
|
||||
}
|
||||
39
libcore/src/main/java/com/pukka/libcore/threading/GuardedRunnable.java
Executable file
39
libcore/src/main/java/com/pukka/libcore/threading/GuardedRunnable.java
Executable file
@@ -0,0 +1,39 @@
|
||||
|
||||
package com.pukka.libcore.threading;
|
||||
|
||||
public class GuardedRunnable implements Runnable {
|
||||
private Runnable realRunnable;
|
||||
|
||||
private Callback callback;
|
||||
|
||||
GuardedRunnable(Runnable realRunnable, Callback callback) {
|
||||
this.realRunnable = realRunnable;
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
if (this.realRunnable != null) {
|
||||
this.realRunnable.run();
|
||||
}
|
||||
} catch (Exception var3) {
|
||||
var3.printStackTrace();
|
||||
}
|
||||
|
||||
try {
|
||||
if (this.callback != null) {
|
||||
this.callback.run(this);
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
boolean isEqualToRunnable(Runnable realRunnable) {
|
||||
return this.realRunnable == realRunnable;
|
||||
}
|
||||
|
||||
interface Callback {
|
||||
void run(GuardedRunnable var1);
|
||||
}
|
||||
}
|
||||
128
libcore/src/main/java/com/pukka/libcore/threading/SerialDispatchQueue.java
Executable file
128
libcore/src/main/java/com/pukka/libcore/threading/SerialDispatchQueue.java
Executable file
@@ -0,0 +1,128 @@
|
||||
|
||||
package com.pukka.libcore.threading;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.Looper;
|
||||
|
||||
public class SerialDispatchQueue extends DispatchQueue {
|
||||
private final Handler handler;
|
||||
|
||||
private final HandlerThread handlerThread;
|
||||
|
||||
private final WeakHashMap<GuardedRunnable, String> runnables = new WeakHashMap<>();
|
||||
|
||||
SerialDispatchQueue(String name) {
|
||||
super(name);
|
||||
this.handlerThread = new HandlerThread(name);
|
||||
this.handlerThread.start();
|
||||
this.handler = new Handler(this.handlerThread.getLooper());
|
||||
}
|
||||
|
||||
SerialDispatchQueue(Looper looper, String name) {
|
||||
super(name);
|
||||
if (looper == null) {
|
||||
throw new NullPointerException("Looper is null");
|
||||
} else {
|
||||
this.handler = new Handler(looper);
|
||||
this.handlerThread = null;
|
||||
}
|
||||
}
|
||||
|
||||
void _sync(long timeout, Runnable runnable) {
|
||||
}
|
||||
|
||||
public void after(long delayMillis, Runnable runnable) {
|
||||
if (runnable != null) {
|
||||
GuardedRunnable guardedRunnable = new GuardedRunnable(runnable, guardedRunnable1 -> {
|
||||
synchronized (SerialDispatchQueue.this) {
|
||||
SerialDispatchQueue.this.runnables.remove(guardedRunnable1);
|
||||
}
|
||||
});
|
||||
synchronized (this) {
|
||||
this.runnables.put(guardedRunnable, "");
|
||||
}
|
||||
|
||||
if (delayMillis > 0L) {
|
||||
this.handler.postDelayed(guardedRunnable, delayMillis);
|
||||
} else {
|
||||
this.handler.post(guardedRunnable);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public Timer timer(long delayMillis, long periodMillis, final Runnable runnable) {
|
||||
if (runnable == null) {
|
||||
return null;
|
||||
} else {
|
||||
return DispatchQueue.global.timer(delayMillis, periodMillis,
|
||||
() -> SerialDispatchQueue.this.async(runnable));
|
||||
}
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
if (this != DispatchQueue.main && this != DispatchQueue.serial) {
|
||||
try {
|
||||
if (this.handlerThread != null) {
|
||||
this.handler.removeCallbacks(null);
|
||||
this.handlerThread.quit();
|
||||
}
|
||||
} catch (Exception var2) {
|
||||
var2.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void removeCallbacks() {
|
||||
if (this == DispatchQueue.main) {
|
||||
try {
|
||||
this.handler.removeCallbacksAndMessages(null);
|
||||
if (this.handlerThread != null) {
|
||||
this.handlerThread.quit();
|
||||
}
|
||||
} catch (Exception var2) {
|
||||
var2.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void remove(Runnable runnable) {
|
||||
if (runnable != null) {
|
||||
ArrayList<GuardedRunnable> guardedRunnables = new ArrayList<>();
|
||||
synchronized (this) {
|
||||
Object[] keySet = this.runnables.keySet().toArray();
|
||||
for (Object obj : keySet) {
|
||||
GuardedRunnable guardedRunnable = (GuardedRunnable) obj;
|
||||
if (guardedRunnable.isEqualToRunnable(runnable)) {
|
||||
guardedRunnables.add(guardedRunnable);
|
||||
|
||||
try {
|
||||
this.runnables.remove(guardedRunnable);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (guardedRunnables.size() != 0) {
|
||||
for (GuardedRunnable guardedRunnable : guardedRunnables) {
|
||||
try {
|
||||
if (Build.VERSION.SDK_INT >= 29) {
|
||||
if (this.handler.hasCallbacks(guardedRunnable)) {
|
||||
this.handler.removeCallbacks(guardedRunnable);
|
||||
}
|
||||
} else {
|
||||
this.handler.removeCallbacks(guardedRunnable);
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
15
libcore/src/main/java/com/pukka/libcore/threading/Timer.java
Executable file
15
libcore/src/main/java/com/pukka/libcore/threading/Timer.java
Executable file
@@ -0,0 +1,15 @@
|
||||
package com.pukka.libcore.threading;
|
||||
|
||||
public final class Timer {
|
||||
DispatchQueue queue;
|
||||
Runnable runnable;
|
||||
|
||||
protected Timer(DispatchQueue queue, Runnable runnable) {
|
||||
this.queue = queue;
|
||||
this.runnable = runnable;
|
||||
}
|
||||
|
||||
public void cancel() {
|
||||
this.queue.remove(this.runnable);
|
||||
}
|
||||
}
|
||||
21
libcore/src/main/java/com/pukka/libcore/utils/AppContext.java
Executable file
21
libcore/src/main/java/com/pukka/libcore/utils/AppContext.java
Executable file
@@ -0,0 +1,21 @@
|
||||
package com.pukka.libcore.utils;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
public final class AppContext {
|
||||
private static final String TAG = "AppContext";
|
||||
private static Context mContext;
|
||||
|
||||
public AppContext() {
|
||||
}
|
||||
|
||||
public static void init(Context context) {
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
public static Context getContext() {
|
||||
return mContext;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
21
libcore/src/main/java/com/pukka/libcore/utils/AppLifecycleObserver.java
Executable file
21
libcore/src/main/java/com/pukka/libcore/utils/AppLifecycleObserver.java
Executable file
@@ -0,0 +1,21 @@
|
||||
package com.pukka.libcore.utils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.DefaultLifecycleObserver;
|
||||
import androidx.lifecycle.LifecycleOwner;
|
||||
|
||||
public class AppLifecycleObserver implements DefaultLifecycleObserver{
|
||||
@Override
|
||||
public void onStart(@NonNull LifecycleOwner owner) {
|
||||
// 应用进入前台
|
||||
LogUtils.d("AppState", "App in foreground");
|
||||
AppManager.getAppManager().setNeedExit(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop(@NonNull LifecycleOwner owner) {
|
||||
// 应用进入后台
|
||||
LogUtils.d("AppState", "App in background");
|
||||
AppManager.getAppManager().appExit();
|
||||
}
|
||||
}
|
||||
256
libcore/src/main/java/com/pukka/libcore/utils/AppManager.java
Executable file
256
libcore/src/main/java/com/pukka/libcore/utils/AppManager.java
Executable file
@@ -0,0 +1,256 @@
|
||||
package com.pukka.libcore.utils;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.app.ActivityManager;
|
||||
import android.content.Context;
|
||||
|
||||
import com.pukka.libcore.threading.DispatchQueue;
|
||||
|
||||
import java.util.Stack;
|
||||
/**
|
||||
* Author: wxf
|
||||
* Date: 2024/8/15 14:00
|
||||
* Description:Activity管理类
|
||||
*/
|
||||
public class AppManager {
|
||||
private static final String TAG = "AppManager ";
|
||||
private static Stack<Activity> activityStack;
|
||||
private static AppManager instance;
|
||||
|
||||
/**
|
||||
* 是否保留进程 true:保留进程;false:杀死进程
|
||||
*/
|
||||
private boolean isKeep;
|
||||
|
||||
/**
|
||||
* Activity的count数
|
||||
*/
|
||||
private int mActivityCount = 0;
|
||||
/**
|
||||
* 应用进入后台,是否需要保持现状
|
||||
*
|
||||
* */
|
||||
private boolean isNeedExit = true;
|
||||
|
||||
private AppManager() {
|
||||
if (activityStack == null) {
|
||||
activityStack = new Stack<>();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 单例模式
|
||||
* @return AppManager
|
||||
*/
|
||||
public static AppManager getAppManager() {
|
||||
if (instance == null) {
|
||||
instance = new AppManager();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加Activity到栈
|
||||
* @param activity activity
|
||||
*/
|
||||
public void addActivity(Activity activity) {
|
||||
if (activityStack == null) {
|
||||
activityStack = new Stack<>();
|
||||
}
|
||||
LogUtils.d(TAG, "addActivity :" + activity.getClass().getSimpleName());
|
||||
boolean isHaveStack = false;
|
||||
for (Activity act : activityStack) {
|
||||
if ((act.getClass().equals(activity.getClass()))) {
|
||||
isHaveStack = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!isHaveStack) {
|
||||
activityStack.add(activity);
|
||||
}
|
||||
LogUtils.d(TAG, "All addActivity :" + activityStack.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前Activity(栈顶Activity)
|
||||
* @return Activity
|
||||
*/
|
||||
public Activity currentActivity() {
|
||||
if (activityStack == null || activityStack.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return activityStack.lastElement();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取栈顶下面一个Activity
|
||||
* @return Activity
|
||||
*/
|
||||
public Activity secondActivity() {
|
||||
if (activityStack == null || activityStack.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
Activity activity;
|
||||
if (activityStack.size() >= 2) {
|
||||
activity = activityStack.get(activityStack.size() - 2);
|
||||
return activity;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前Activity(栈顶Activity) 没有找到则返回null
|
||||
* @param cls Class
|
||||
* @return Activity
|
||||
*/
|
||||
public Activity findActivity(Class<?> cls) {
|
||||
Activity activity = null;
|
||||
for (Activity aty : activityStack) {
|
||||
if (aty.getClass().equals(cls)) {
|
||||
activity = aty;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return activity;
|
||||
}
|
||||
|
||||
/**
|
||||
* 结束当前Activity(栈顶Activity)
|
||||
*/
|
||||
public void finishActivity() {
|
||||
Activity activity = activityStack.lastElement();
|
||||
finishActivity(activity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 结束指定的Activity(重载)
|
||||
* @param activity activity
|
||||
*/
|
||||
public void finishActivity(Activity activity) {
|
||||
if (activity != null && (activity.isFinishing())) {
|
||||
activityStack.remove(activity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 结束指定的Activity(重载)
|
||||
* @param cls Class
|
||||
*/
|
||||
public void finishActivity(Class<?> cls) {
|
||||
for (Activity activity : activityStack) {
|
||||
if (activity.getClass().equals(cls)) {
|
||||
finishActivity(activity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭除了指定activity以外的全部activity 如果cls不存在于栈中,则栈全部清空
|
||||
*
|
||||
* @param cls Class
|
||||
*/
|
||||
public void finishOthersActivity(Class<?> cls) {
|
||||
for (Activity activity : activityStack) {
|
||||
if (!(activity.getClass().equals(cls))) {
|
||||
finishActivity(activity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭指定activity
|
||||
*
|
||||
* @param cls 要关闭的activity的类名
|
||||
*/
|
||||
public void finishAppointActivity(Class<?> cls) {
|
||||
for (Activity activity : activityStack) {
|
||||
if ((activity.getClass().equals(cls))) {
|
||||
LogUtils.d(TAG, "finishAppointActivity finish" + cls.getSimpleName());
|
||||
activity.finish();
|
||||
//finish以后 activityStack要及时清掉该activity
|
||||
activityStack.remove(activity);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 结束所有Activity
|
||||
*/
|
||||
public void finishAllActivity() {
|
||||
int activityStackSize = activityStack.size();
|
||||
LogUtils.d("wcg, finishAllActivity: " + activityStackSize);
|
||||
for (int i = 0; i < activityStackSize; i++) {
|
||||
if (null != activityStack.get(i)) {
|
||||
activityStack.get(i).finish();
|
||||
}
|
||||
}
|
||||
activityStack.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加Activity的count数
|
||||
*/
|
||||
public void addCount() {
|
||||
mActivityCount++;
|
||||
}
|
||||
|
||||
/**
|
||||
* 减少Activity的count数
|
||||
*/
|
||||
public void reduceCount() {
|
||||
mActivityCount--;
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用是否进入后台
|
||||
*
|
||||
* @return 是否进入后台
|
||||
*/
|
||||
public boolean isBackground() {
|
||||
LogUtils.d("isBackground:mActivityCount:"+mActivityCount);
|
||||
return mActivityCount <= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用程序退出
|
||||
*
|
||||
*/
|
||||
@SuppressLint("MissingPermission")
|
||||
public void appExit() {
|
||||
LogUtils.d(TAG, "appExit isKeep: " + isKeep);
|
||||
LogUtils.d(TAG,"appExit isNeedExit="+isNeedExit);
|
||||
if(isNeedExit) {
|
||||
finishAllActivity();
|
||||
if (!isKeep) {
|
||||
LogUtils.d(TAG,"appExit killProcess");
|
||||
//延迟1秒杀死,方便神策退出应用上报
|
||||
DispatchQueue.main.after(1000, ()->{
|
||||
LogUtils.d(TAG,"appExit killProcess 22");
|
||||
android.os.Process.killProcess(android.os.Process.myPid());
|
||||
System.exit(0);
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int getActivityStackSize() {
|
||||
return activityStack.size();
|
||||
}
|
||||
|
||||
public boolean isKeep() {
|
||||
return isKeep;
|
||||
}
|
||||
|
||||
public void setKeep(boolean keep) {
|
||||
LogUtils.d("wcg, setKeep: " + keep);
|
||||
isKeep = keep;
|
||||
}
|
||||
|
||||
public void setNeedExit(boolean needExit){
|
||||
LogUtils.d("setNeedExit: " + needExit);
|
||||
isNeedExit = needExit;
|
||||
}
|
||||
}
|
||||
365
libcore/src/main/java/com/pukka/libcore/utils/AppUtils.java
Executable file
365
libcore/src/main/java/com/pukka/libcore/utils/AppUtils.java
Executable file
@@ -0,0 +1,365 @@
|
||||
|
||||
package com.pukka.libcore.utils;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.app.usage.UsageStats;
|
||||
import android.app.usage.UsageStatsManager;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.text.TextUtils;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.SortedMap;
|
||||
import java.util.TreeMap;
|
||||
|
||||
/**
|
||||
* Author: wxf
|
||||
* Date: 2024/8/8 14:00
|
||||
* Description:获取app相关的属性
|
||||
* 包名,类名,跳转,渠道号等
|
||||
*/
|
||||
public class AppUtils {
|
||||
/**
|
||||
* 获取app的包名
|
||||
* @return app的包名
|
||||
*/
|
||||
public static String getPackageName() {
|
||||
return null != ContextUtils.getApplicationContext() ? ContextUtils.getApplicationContext().getPackageName() : "";
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取app的启动类名
|
||||
* @return app的启动类名
|
||||
*/
|
||||
public static String getClassName() {
|
||||
return null != ContextUtils.getApplicationContext() ? ContextUtils.getApplicationContext().getClass().getCanonicalName() : "";
|
||||
}
|
||||
/**
|
||||
* 获取app的版本号 VersionName
|
||||
* @return app的版本号 VersionName
|
||||
*/
|
||||
public static String getAppVersionName() {
|
||||
try {
|
||||
if (null == ContextUtils.getApplicationContext()) {
|
||||
return "";
|
||||
}
|
||||
PackageManager manager = ContextUtils.getApplicationContext().getPackageManager();
|
||||
if (manager != null) {
|
||||
PackageInfo packageInfo = manager.getPackageInfo(ContextUtils.getApplicationContext().getPackageName(), 0);
|
||||
return packageInfo.versionName;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
/**
|
||||
* 获取APP的版本编码 (VersionCode)
|
||||
*@return app的版本号 VersionCode
|
||||
*/
|
||||
public static String getVersionCode() {
|
||||
if (null == ContextUtils.getApplicationContext()) {
|
||||
return "";
|
||||
}
|
||||
Context context = ContextUtils.getApplicationContext();
|
||||
PackageManager packageManager = context.getPackageManager();
|
||||
PackageInfo packageInfo;
|
||||
String versionCode = "";
|
||||
try {
|
||||
packageInfo = packageManager.getPackageInfo(context.getPackageName(), 0);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
versionCode = String.valueOf(packageInfo.getLongVersionCode());
|
||||
} else {
|
||||
versionCode = String.valueOf(packageInfo.versionCode);
|
||||
}
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return versionCode;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 获取application中指定的meta-data
|
||||
*
|
||||
* @param ctx ctx
|
||||
* @param key key
|
||||
* @return 如果没有获取成功(没有对应值 , 或者异常),则返回值为空
|
||||
*/
|
||||
public static String getAppMetaData(Context ctx, String key) {
|
||||
if (ctx == null || TextUtils.isEmpty(key)) {
|
||||
return null;
|
||||
}
|
||||
String resultData = null;
|
||||
try {
|
||||
PackageManager packageManager = ctx.getPackageManager();
|
||||
if (packageManager != null) {
|
||||
ApplicationInfo applicationInfo =
|
||||
packageManager.getApplicationInfo(ctx.getPackageName(), PackageManager.GET_META_DATA);
|
||||
if (applicationInfo.metaData != null) {
|
||||
resultData = applicationInfo.metaData.getString(key);
|
||||
LogUtils.d("resultData:"+resultData);
|
||||
}
|
||||
}
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return resultData;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取渠道号
|
||||
*
|
||||
* @param context context
|
||||
* @param key key
|
||||
* @return 渠道号
|
||||
*/
|
||||
public static String getChannelId(Context context, String key) {
|
||||
if (context != null && !TextUtils.isEmpty(key)) {
|
||||
try {
|
||||
PackageManager manager = context.getPackageManager();
|
||||
if (manager != null) {
|
||||
ApplicationInfo info =
|
||||
manager.getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA);
|
||||
for (String str : info.metaData.keySet()) {
|
||||
LogUtils.d("getChannelId --- key: " + str);
|
||||
}
|
||||
if (info.metaData != null) {
|
||||
return info.metaData.getString(key);
|
||||
}
|
||||
}
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* 获取盒子最上层APP的包名
|
||||
* @param context 上下文
|
||||
* @return 盒子最上层APP的包名
|
||||
*/
|
||||
public static String getTopPackageName(Context context) {
|
||||
String currentApp = "";
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
UsageStatsManager usm = (UsageStatsManager) context.getSystemService(Context.USAGE_STATS_SERVICE);
|
||||
long time = System.currentTimeMillis();
|
||||
List<UsageStats> appList = usm.queryUsageStats(UsageStatsManager.INTERVAL_DAILY, time - 1000 * 1000, time);
|
||||
if (appList != null && appList.size() > 0) {
|
||||
SortedMap<Long, UsageStats> mySortedMap = new TreeMap<Long, UsageStats>();
|
||||
for (UsageStats usageStats : appList) {
|
||||
mySortedMap.put(usageStats.getLastTimeUsed(), usageStats);
|
||||
}
|
||||
if (!mySortedMap.isEmpty()) {
|
||||
currentApp = mySortedMap.get(mySortedMap.lastKey()).getPackageName();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
|
||||
List<ActivityManager.RunningAppProcessInfo> tasks = am.getRunningAppProcesses();
|
||||
currentApp = tasks.get(0).processName;
|
||||
}
|
||||
if(StringUtils.isEmpty(currentApp)){
|
||||
currentApp = getTopPackageName();
|
||||
}
|
||||
return currentApp;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过uri启动第三方应用
|
||||
* @param context 上下文
|
||||
* @param uri 跳转路径
|
||||
* @param mPackageName 包名
|
||||
* @return -1 表示 该apk未安装, 1 表示启动成功 0 表示启动失败
|
||||
*/
|
||||
public static int launchApkByUri(Context context, String uri, String mPackageName) {
|
||||
try {
|
||||
if (0 == isInstalled(context, mPackageName)) {
|
||||
return -1;
|
||||
}
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||
intent.setData(Uri.parse(uri));
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NO_USER_ACTION);
|
||||
context.startActivity(intent);
|
||||
return 1;
|
||||
} catch (Exception io) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 通过包名判断该apk是否已经安装
|
||||
* @param context 上下文
|
||||
* @param packageName 第三方应用包名
|
||||
* @return 1 安装 0 未安装
|
||||
*/
|
||||
public static int isInstalled(Context context, String packageName) {
|
||||
try {
|
||||
context.getPackageManager().getPackageInfo(packageName, 0);
|
||||
return 1;
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 通过包名类名来启动apk
|
||||
* @param context 上下文
|
||||
* @param packageName 包名
|
||||
* @param className 类名
|
||||
*/
|
||||
public static void startApkByPackageAndClass(Context context, String packageName, String className) {
|
||||
if (isInstalled(context, packageName) == 1) {
|
||||
if(!StringUtils.isEmpty(className)) {
|
||||
Intent intent = new Intent(Intent.ACTION_MAIN);
|
||||
ComponentName comp = new ComponentName(packageName, className);
|
||||
intent.setComponent(comp);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NO_USER_ACTION);
|
||||
intent.addCategory(Intent.CATEGORY_LAUNCHER);
|
||||
context.startActivity(intent);
|
||||
//AppManager.getAppManager().setNeedExit(false);
|
||||
}else{
|
||||
try {
|
||||
PackageInfo pi = context.getPackageManager().getPackageInfo(packageName, 0);
|
||||
Intent resolveIntent = new Intent(Intent.ACTION_MAIN, null);
|
||||
resolveIntent.addCategory(Intent.CATEGORY_LAUNCHER);
|
||||
resolveIntent.setPackage(pi.packageName);
|
||||
List<ResolveInfo> apps = context.getPackageManager().queryIntentActivities(resolveIntent, 0);
|
||||
if (apps.size() <= 0) {
|
||||
LogUtils.d("APK配置错误!未配置类名");
|
||||
return ;
|
||||
}
|
||||
ResolveInfo ri = apps.iterator().next();
|
||||
if (ri != null) {
|
||||
Intent intent = new Intent(Intent.ACTION_MAIN);
|
||||
intent.addCategory(Intent.CATEGORY_LAUNCHER);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NO_USER_ACTION);
|
||||
ComponentName cn = new ComponentName(ri.activityInfo.packageName, ri.activityInfo.name);
|
||||
intent.setComponent(cn);
|
||||
context.startActivity(intent);
|
||||
//AppManager.getAppManager().setNeedExit(false);
|
||||
}
|
||||
}catch (Exception ex){
|
||||
LogUtils.d("第三方APK启动异常=="+ex.getMessage());
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LogUtils.d("包名不正确或该apk不存在");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有非系统的应用列表 系统应用和普通应用
|
||||
* @param flag 0 : 普通应用 1 : 系统应用
|
||||
* @return 应用列表
|
||||
*/
|
||||
public static List<String> getPackageInfoList(int flag) {
|
||||
List<String> list = new ArrayList<>();
|
||||
if (null == ContextUtils.getApplicationContext()) {
|
||||
return list;
|
||||
}
|
||||
PackageManager pm = ContextUtils.getApplicationContext().getPackageManager();
|
||||
List<ApplicationInfo> infos = pm.getInstalledApplications(PackageManager.GET_META_DATA);
|
||||
for (ApplicationInfo info : infos) {
|
||||
if ((info.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
|
||||
if ((info.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) == 0) {
|
||||
String label = info.loadLabel(pm).toString();
|
||||
String packageName = info.packageName;
|
||||
String className = info.className;
|
||||
if (flag == 0) {
|
||||
list.add(packageName);
|
||||
}
|
||||
LogUtils.d("pkgInfo -- label - " + label + " packageName -- " + packageName + " -className -- "
|
||||
+ className);
|
||||
}
|
||||
} else if ((info.flags & ApplicationInfo.FLAG_SYSTEM) == 1) {
|
||||
// 系统应用
|
||||
String label = info.loadLabel(pm).toString();
|
||||
String packageName = info.packageName;
|
||||
String className = info.className;
|
||||
if (flag == 1) {
|
||||
list.add(packageName);
|
||||
}
|
||||
LogUtils.d("pkgInfo -- label - " + label + " packageName -- " + packageName + " -className -- "
|
||||
+ className);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取栈顶页面
|
||||
*
|
||||
* @return 栈顶页面的类名
|
||||
*/
|
||||
public static String getTopActivity() {
|
||||
Context context = ContextUtils.getApplicationContext();
|
||||
if (null == context) {
|
||||
return "";
|
||||
}
|
||||
ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
|
||||
if (null == manager) {
|
||||
return "";
|
||||
}
|
||||
List<ActivityManager.RunningTaskInfo> runningTaskInfo = manager.getRunningTasks(1);
|
||||
if (!ListUtils.isEmpty(runningTaskInfo) && null != runningTaskInfo.get(0).topActivity) {
|
||||
return (runningTaskInfo.get(0).topActivity.getClassName());
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
private static String getTopPackageName() {
|
||||
ActivityManager.RunningAppProcessInfo currentInfo = null;
|
||||
Field field = null;
|
||||
int START_TASK_TO_FRONT = 2;
|
||||
String currentApp = "CurrentNULL";
|
||||
if (null == ContextUtils.getApplicationContext()) {
|
||||
return "";
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
try {
|
||||
field = ActivityManager.RunningAppProcessInfo.class.getDeclaredField("processState");
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
ActivityManager am = (ActivityManager) ContextUtils.getApplicationContext().getSystemService(Context.ACTIVITY_SERVICE);
|
||||
List<ActivityManager.RunningAppProcessInfo> appList = am.getRunningAppProcesses();
|
||||
for (ActivityManager.RunningAppProcessInfo app : appList) {
|
||||
if (app.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
|
||||
Integer state = null;
|
||||
try {
|
||||
state = field.getInt(app);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
if (state != null && state == START_TASK_TO_FRONT) {
|
||||
currentInfo = app;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (currentInfo != null) {
|
||||
currentApp = currentInfo.processName;
|
||||
}
|
||||
} else {
|
||||
ActivityManager am = (ActivityManager) ContextUtils.getApplicationContext().getSystemService(Context.ACTIVITY_SERVICE);
|
||||
List<ActivityManager.RunningAppProcessInfo> tasks = am.getRunningAppProcesses();
|
||||
currentApp = tasks.get(0).processName;
|
||||
}
|
||||
LogUtils.d("top App is: " + currentApp);
|
||||
return currentApp;
|
||||
}
|
||||
}
|
||||
28
libcore/src/main/java/com/pukka/libcore/utils/Base64Decoder.java
Executable file
28
libcore/src/main/java/com/pukka/libcore/utils/Base64Decoder.java
Executable file
@@ -0,0 +1,28 @@
|
||||
package com.pukka.libcore.utils;
|
||||
|
||||
import android.os.Build;
|
||||
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import android.util.Base64;
|
||||
|
||||
/**
|
||||
* Author: wxf
|
||||
* Date: 2025/6/6 14:00
|
||||
* Description:
|
||||
* 包名,类名,跳转,渠道号等
|
||||
*/
|
||||
public class Base64Decoder {
|
||||
public static String base64Decoder(String encodedString){
|
||||
try {
|
||||
// Base64 解码
|
||||
byte[] decodedBytes = Base64.decode(encodedString, Base64.DEFAULT);
|
||||
String decodedString = new String(decodedBytes, "UTF-8");
|
||||
return decodedString;
|
||||
}catch (Exception ex){
|
||||
ex.printStackTrace();
|
||||
}
|
||||
return "";
|
||||
|
||||
}
|
||||
}
|
||||
33
libcore/src/main/java/com/pukka/libcore/utils/Base64Util.java
Executable file
33
libcore/src/main/java/com/pukka/libcore/utils/Base64Util.java
Executable file
@@ -0,0 +1,33 @@
|
||||
package com.pukka.libcore.utils;
|
||||
|
||||
/**
|
||||
* @Auther: sunmm
|
||||
* @datetime: 2025/6/6 14:09
|
||||
* @desc:
|
||||
*/
|
||||
import android.util.Base64;
|
||||
|
||||
public class Base64Util {
|
||||
|
||||
/**
|
||||
* Base64解码方法
|
||||
* @param encodedString 需要解码的Base64字符串
|
||||
* @return 解码后的原始字符串(若输入非法则返回null)
|
||||
*/
|
||||
public static String decode(String encodedString) {
|
||||
try {
|
||||
// 解码为字节数组
|
||||
byte[] decodedBytes = Base64.decode(encodedString, Base64.DEFAULT);
|
||||
// 转换为字符串(默认使用UTF-8编码)
|
||||
return new String(decodedBytes, "UTF-8");
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 可选扩展:解码二进制数据(如图片、文件)
|
||||
public static byte[] decodeToBytes(String encodedString) {
|
||||
return Base64.decode(encodedString, Base64.DEFAULT);
|
||||
}
|
||||
}
|
||||
84
libcore/src/main/java/com/pukka/libcore/utils/ByteConversionUtils.java
Executable file
84
libcore/src/main/java/com/pukka/libcore/utils/ByteConversionUtils.java
Executable file
@@ -0,0 +1,84 @@
|
||||
package com.pukka.libcore.utils;
|
||||
|
||||
/**
|
||||
* Author: wxf
|
||||
* Date: 2024/8/15 14:00
|
||||
* Description:字节数组相关转换
|
||||
*/
|
||||
public class ByteConversionUtils {
|
||||
/***
|
||||
* 字符串转字节数组
|
||||
* @param str 需要转换的字符串
|
||||
* @param charsetName 字符集
|
||||
* @return 字节数组
|
||||
*/
|
||||
public static byte[] getBytes(String str, String charsetName) {
|
||||
if (StringUtils.isEmpty(str)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return str.getBytes(charsetName);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 字节数组转16进制字符串(HEX)
|
||||
* @param b 字节数组
|
||||
* @return 16进制字符串(HEX)
|
||||
*/
|
||||
public static String byte2hex(byte[] b) {
|
||||
StringBuilder hs = new StringBuilder();
|
||||
String sTemp;
|
||||
for (byte value : b) {
|
||||
sTemp = (Integer.toHexString(value & 0XFF));
|
||||
if (sTemp.length() == 1) {
|
||||
hs.append("0").append(sTemp);
|
||||
} else {
|
||||
hs.append(sTemp);
|
||||
}
|
||||
}
|
||||
return hs.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 16进制字符串转字节数组
|
||||
* @param strHex 16进制字符串
|
||||
* @return 字节数组
|
||||
*/
|
||||
public static byte[] hex2byte(String strHex) {
|
||||
if (strHex == null) {
|
||||
return null;
|
||||
}
|
||||
int l = strHex.length();
|
||||
if (l % 2 == 1) {
|
||||
return null;
|
||||
}
|
||||
byte[] b = new byte[l / 2];
|
||||
for (int i = 0; i != l / 2; i++) {
|
||||
b[i] = (byte) Integer.parseInt(strHex.substring(i * 2, i * 2 + 2), 16);
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
/**
|
||||
* 多个字节数组合并
|
||||
* @param args 字节数组
|
||||
* @return 字节数组
|
||||
*/
|
||||
public static byte[] byteMergerAll(byte[]... args) {
|
||||
int length_byte = 0;
|
||||
for (byte[] b : args) {
|
||||
length_byte += b.length;
|
||||
}
|
||||
byte[] all_byte = new byte[length_byte];
|
||||
int countLength = 0;
|
||||
for (byte[] b : args) {
|
||||
System.arraycopy(b, 0, all_byte, countLength, b.length);
|
||||
countLength += b.length;
|
||||
}
|
||||
return all_byte;
|
||||
}
|
||||
}
|
||||
56
libcore/src/main/java/com/pukka/libcore/utils/ContextUtils.java
Executable file
56
libcore/src/main/java/com/pukka/libcore/utils/ContextUtils.java
Executable file
@@ -0,0 +1,56 @@
|
||||
|
||||
package com.pukka.libcore.utils;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
/**
|
||||
* Author: wxf
|
||||
* Date: 2024/8/8 14:00
|
||||
* Description:Context工具类
|
||||
*/
|
||||
public class ContextUtils {
|
||||
|
||||
private static Application mApplication;
|
||||
|
||||
/**
|
||||
* 初始化(在Application onCreate 初始化)
|
||||
* @param application application
|
||||
* */
|
||||
public static void init(Application application) {
|
||||
mApplication = application;
|
||||
AppContext.init(application);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取整个应用上下文
|
||||
* @return ApplicationContext
|
||||
*/
|
||||
public static Context getApplicationContext() {
|
||||
if (mApplication != null) {
|
||||
return mApplication.getApplicationContext();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Application
|
||||
* @return Application
|
||||
*/
|
||||
public static Application getApplication() {
|
||||
if (mApplication != null) {
|
||||
return mApplication;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* 获取ApplicationContext
|
||||
*
|
||||
* @return ApplicationContext
|
||||
*/
|
||||
public static Context getContext() {
|
||||
if (mApplication != null) {
|
||||
return mApplication.getApplicationContext();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
370
libcore/src/main/java/com/pukka/libcore/utils/DateUtils.java
Executable file
370
libcore/src/main/java/com/pukka/libcore/utils/DateUtils.java
Executable file
@@ -0,0 +1,370 @@
|
||||
package com.pukka.libcore.utils;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.os.Build;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.TimeZone;
|
||||
|
||||
/**
|
||||
* Author: wxf
|
||||
* Date: 2024/8/19 16:00
|
||||
* Description:时间工具包
|
||||
*/
|
||||
public class DateUtils {
|
||||
|
||||
/**yyyy-MM-dd HH:mm:ss*/
|
||||
public static final String FORMAT_STYLE_1 = "yyyy-MM-dd HH:mm:ss";
|
||||
/**yyyy-MM-dd HH:mm*/
|
||||
public static final String FORMAT_STYLE_2 = "yyyy-MM-dd HH:mm";
|
||||
/**yyyyMMddHHmmss*/
|
||||
public static final String FORMAT_STYLE_3 = "yyyyMMddHHmmss";
|
||||
/**yyyy年MM月dd日*/
|
||||
public static final String FORMAT_STYLE_4 = "yyyy年MM月dd日";
|
||||
/**MM月dd日 HH:mm*/
|
||||
public static final String FORMAT_STYLE_5 = "MM月dd日 HH:mm";
|
||||
/**yyyy-MM-dd*/
|
||||
public static final String FORMAT_STYLE_6 = "yyyy-MM-dd";
|
||||
/**MMdd*/
|
||||
public static final String FORMAT_STYLE_7 = "MMdd";
|
||||
/**MM月dd日*/
|
||||
public static final String FORMAT_STYLE_8 = "MM月dd日";
|
||||
/**yyyyMMdd*/
|
||||
public static final String FORMAT_STYLE_9 = "yyyyMMdd";
|
||||
/**HH:mm*/
|
||||
public static final String FORMAT_STYLE_10 = "HH:mm";
|
||||
/**HH:mm:ss*/
|
||||
public static final String FORMAT_STYLE_11 = "HH:mm:ss";
|
||||
/**yyyy-MM-dd HH:mm:ss SSS*/
|
||||
public static final String FORMAT_STYLE_12 = "yyyy-MM-dd HH:mm:ss SSS";
|
||||
/**HHmmss*/
|
||||
public static final String FORMAT_STYLE_13 = "HHmmss";
|
||||
|
||||
/**
|
||||
* 获取现在时间
|
||||
* Mon Aug 19 15:57:29 GMT+08:00 2024
|
||||
*
|
||||
* @return 现在时间
|
||||
*/
|
||||
public static Date getNowDate() {
|
||||
return new Date();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取现在时间
|
||||
*
|
||||
* @return 返回字符串格式 yyyy-MM-dd HH:mm:ss
|
||||
*/
|
||||
public static String getStringNowDate() {
|
||||
Date currentTime = new Date();
|
||||
@SuppressLint("SimpleDateFormat")
|
||||
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
return formatter.format(currentTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取现在时间
|
||||
* @param formatStyle 格式
|
||||
* @return 返回字符串格式 formatStyle
|
||||
*/
|
||||
public static String getStringNowDate(String formatStyle) {
|
||||
Date currentTime = new Date();
|
||||
@SuppressLint("SimpleDateFormat")
|
||||
SimpleDateFormat formatter = new SimpleDateFormat(formatStyle);
|
||||
return formatter.format(currentTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取现在时间
|
||||
*
|
||||
* @return 以上海时区获取的现在时间
|
||||
*/
|
||||
public static String getStringNowDateInShanghai() {
|
||||
@SuppressLint("SimpleDateFormat")
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
// 设置TimeZone为上海时间
|
||||
sdf.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
|
||||
// 获取本地时间
|
||||
Date now = new Date();
|
||||
try {
|
||||
// 将本地时间转换为转换时间为东八区
|
||||
now = sdf.parse(sdf.format(now));
|
||||
} catch (ParseException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return sdf.format(now);
|
||||
}
|
||||
|
||||
|
||||
/***
|
||||
* 时间字符串格式转换
|
||||
* @param fromDate 时间字符串
|
||||
* @param fromFormatStyle 转换前时间字符串格式
|
||||
* @param toFormatStyle 转换时间字符串格式
|
||||
* 例如:yyyy-MM-dd HH:mm:ss 转换 yyyy年MM月dd日
|
||||
* @return 时间字符串
|
||||
* @throws Exception exception
|
||||
*/
|
||||
public static String formatDate(String fromDate, String fromFormatStyle, String toFormatStyle) throws Exception {
|
||||
Date cDate = convertToDate(fromDate, fromFormatStyle);
|
||||
@SuppressLint("SimpleDateFormat")
|
||||
SimpleDateFormat sdf = new SimpleDateFormat(toFormatStyle);
|
||||
return sdf.format(cDate);
|
||||
}
|
||||
|
||||
/***
|
||||
* 将时间字符串转换为时间戳(毫秒)
|
||||
* @param timeStr 时间字符串
|
||||
* @param formatStyle 时间字符串格式
|
||||
* @return 时间戳
|
||||
* */
|
||||
public static long stringDateToStamp(String timeStr, String formatStyle) {
|
||||
@SuppressLint("SimpleDateFormat")
|
||||
SimpleDateFormat sdf = new SimpleDateFormat(formatStyle);
|
||||
try {
|
||||
Date date = sdf.parse(timeStr);
|
||||
long timestamp = date.getTime();
|
||||
return timestamp;
|
||||
} catch (ParseException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return 0L;
|
||||
}
|
||||
|
||||
/**
|
||||
* 时间戳转换成时间字符串
|
||||
*
|
||||
* @param stamp 时间戳字符串
|
||||
* @param formatStyle 时间字符串格式
|
||||
* @return 时间字符串
|
||||
*/
|
||||
public static String stampToStringDate(String stamp, String formatStyle) {
|
||||
if (TextUtils.isEmpty(stamp) || "null".equals(stamp)) {
|
||||
return "";
|
||||
}
|
||||
if (TextUtils.isEmpty(formatStyle)) {
|
||||
formatStyle = "yyyy-MM-dd HH:mm:ss";
|
||||
}
|
||||
@SuppressLint("SimpleDateFormat")
|
||||
SimpleDateFormat sdf = new SimpleDateFormat(formatStyle);
|
||||
return sdf.format(new Date(Long.parseLong(stamp)));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 把时间转为字符串
|
||||
* @param date 时间
|
||||
* @param formatStyle 时间字符串格式
|
||||
* @return 时间字符串
|
||||
*/
|
||||
public static String convertToStringDate(Date date, String formatStyle) {
|
||||
@SuppressLint("SimpleDateFormat")
|
||||
SimpleDateFormat df = new SimpleDateFormat(formatStyle);
|
||||
return df.format(date);
|
||||
}
|
||||
|
||||
/**
|
||||
* 把时间字符串转为时间
|
||||
* @param strDate 时间字符串
|
||||
* @param formatStyle 时间字符串格式
|
||||
* @return 时间
|
||||
* @throws Exception exception
|
||||
*/
|
||||
public static Date convertToDate(String strDate, String formatStyle) throws Exception {
|
||||
@SuppressLint("SimpleDateFormat")
|
||||
SimpleDateFormat df = new SimpleDateFormat(formatStyle);
|
||||
return df.parse(strDate);
|
||||
}
|
||||
|
||||
/**
|
||||
* 把时间字符串转为短时间(yyyy-MM-dd)
|
||||
* @param strDate 时间字符串
|
||||
* @return 时间
|
||||
* @throws Exception exception
|
||||
*/
|
||||
public static Date convertToDateShort(String strDate) throws Exception {
|
||||
@SuppressLint("SimpleDateFormat")
|
||||
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
|
||||
return df.parse(strDate);
|
||||
}
|
||||
|
||||
/**
|
||||
* 比较两个时间早晚
|
||||
* @param dt1 时间1
|
||||
* @param dt2 时间2
|
||||
* @return 等于返回0值, 大于返回大于0的值 小于返回小于0的值
|
||||
*/
|
||||
public static int compareDate(Date dt1, Date dt2) {
|
||||
return Long.compare(dt1.getTime(), dt2.getTime());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取两个时间间隔秒数
|
||||
* @param strDate1 时间字符1
|
||||
* @param strDate2 时间字符2
|
||||
* @return 获取两个时间间隔秒数
|
||||
*/
|
||||
public static int getSecondCompareStringDate(String strDate1, String strDate2) {
|
||||
// 一秒的毫秒数
|
||||
long nm = 1000 ;
|
||||
// 格式化时间
|
||||
@SuppressLint("SimpleDateFormat")
|
||||
DateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
try {
|
||||
Date beginTime = simpleDateFormat.parse(strDate1);
|
||||
Date endTime = simpleDateFormat.parse(strDate2);
|
||||
long diff = endTime.getTime() - beginTime.getTime();
|
||||
long second = diff / nm;
|
||||
LogUtils.d("timeCompare :mins:" + (int) second);
|
||||
return (int) second;
|
||||
} catch (ParseException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取两个时间间隔分钟数
|
||||
* @param strDate1 时间字符1
|
||||
* @param strDate2 时间字符2
|
||||
* @return 两个时间间隔分钟数
|
||||
*/
|
||||
public static int getMinCompareStringDate(String strDate1, String strDate2) {
|
||||
// 一分钟的毫秒数
|
||||
long nm = 1000 * 60;
|
||||
// 格式化时间
|
||||
@SuppressLint("SimpleDateFormat")
|
||||
DateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
try {
|
||||
Date beginTime = simpleDateFormat.parse(strDate1);
|
||||
Date endTime = simpleDateFormat.parse(strDate2);
|
||||
long diff = endTime.getTime() - beginTime.getTime();
|
||||
long mins = diff / nm;
|
||||
LogUtils.d("timeCompare :mins:" + (int) mins);
|
||||
return (int) mins;
|
||||
} catch (ParseException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取相隔时长
|
||||
* @param strDate1 时间字符1
|
||||
* @param strDate2 时间字符2
|
||||
* @return 153天00时00分00秒
|
||||
*/
|
||||
public static String getDistanceCompareStringDate(String strDate1, String strDate2) {
|
||||
String str;
|
||||
@SuppressLint("SimpleDateFormat")
|
||||
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
Date one;
|
||||
Date two;
|
||||
long day = 0;
|
||||
long hour = 0;
|
||||
long min = 0;
|
||||
long sec = 0;
|
||||
try {
|
||||
one = df.parse(strDate1);
|
||||
two = df.parse(strDate2);
|
||||
long time1 = one.getTime();
|
||||
long time2 = two.getTime();
|
||||
long diff;
|
||||
if (time1 < time2) {
|
||||
diff = time2 - time1;
|
||||
} else {
|
||||
diff = time1 - time2;
|
||||
}
|
||||
day = diff / (24 * 60 * 60 * 1000);
|
||||
hour = (diff / (60 * 60 * 1000) - day * 24);
|
||||
min = ((diff / (60 * 1000)) - day * 24 * 60 - hour * 60);
|
||||
sec = (diff / 1000 - day * 24 * 60 * 60 - hour * 60 * 60 - min * 60);
|
||||
} catch (ParseException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
if (day < 10) {
|
||||
str = "0" + day + "天";
|
||||
} else {
|
||||
str = day + "天";
|
||||
}
|
||||
if (hour < 10) {
|
||||
str += "0" + hour + "时";
|
||||
} else {
|
||||
str += hour + "时";
|
||||
}
|
||||
if (min < 10) {
|
||||
str += "0" + min + "分";
|
||||
} else {
|
||||
str += min + "分";
|
||||
}
|
||||
if (sec < 10) {
|
||||
str += "0" + sec + "秒";
|
||||
} else {
|
||||
str += sec + "秒";
|
||||
}
|
||||
return str;
|
||||
}
|
||||
/**
|
||||
* 获取相隔天数
|
||||
* @param strDate1 时间字符1
|
||||
* @param strDate2 时间字符2
|
||||
* @return 153天00时00分00秒
|
||||
*/
|
||||
public static long getDistanceCompareStringDate1(String strDate1, String strDate2) {
|
||||
@SuppressLint("SimpleDateFormat")
|
||||
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
Date one;
|
||||
Date two;
|
||||
long day = 0;
|
||||
try {
|
||||
one = df.parse(strDate1);
|
||||
two = df.parse(strDate2);
|
||||
long time1 = one.getTime();
|
||||
long time2 = two.getTime();
|
||||
long diff;
|
||||
if (time1 < time2) {
|
||||
diff = time2 - time1;
|
||||
} else {
|
||||
diff = time1 - time2;
|
||||
}
|
||||
day = diff / (24 * 60 * 60 * 1000);
|
||||
return day;
|
||||
} catch (ParseException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return day;
|
||||
}
|
||||
|
||||
public static String secToTime(int totalSeconds) {
|
||||
int hours = totalSeconds / 3600;
|
||||
int remainingSeconds = totalSeconds % 3600;
|
||||
int minutes = remainingSeconds / 60;
|
||||
int seconds = remainingSeconds % 60;
|
||||
|
||||
if (hours > 0){
|
||||
// 格式化为两位数(如 01:02:03)
|
||||
return String.format("%02d时%02d分%02d秒", hours, minutes, seconds);
|
||||
}else {
|
||||
// 格式化为两位数(如 01:02:03)
|
||||
return String.format("%02d分%02d秒", minutes, seconds);
|
||||
}
|
||||
|
||||
}
|
||||
/**
|
||||
* 时间间隔在5s以内
|
||||
* */
|
||||
public static int compareDateIsIn5s(Date dt1, Date dt2) {
|
||||
if (dt1.getTime() - dt2.getTime() < 5000) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
37
libcore/src/main/java/com/pukka/libcore/utils/GreyingManager.java
Executable file
37
libcore/src/main/java/com/pukka/libcore/utils/GreyingManager.java
Executable file
@@ -0,0 +1,37 @@
|
||||
package com.pukka.libcore.utils;
|
||||
|
||||
import android.graphics.ColorMatrix;
|
||||
import android.graphics.ColorMatrixColorFilter;
|
||||
import android.graphics.Paint;
|
||||
import android.view.View;
|
||||
import android.view.Window;
|
||||
|
||||
/**
|
||||
* Author: wxf
|
||||
* Date: 2023/3/24 11:00
|
||||
* Description:一键变灰
|
||||
*/
|
||||
public class GreyingManager {
|
||||
/***
|
||||
* 获取画笔
|
||||
* @param type 0:灰 1:正常
|
||||
* @return Paint 画笔
|
||||
* */
|
||||
private static Paint getMatrix(int type){
|
||||
Paint paint = new Paint();
|
||||
ColorMatrix colorMatrix = new ColorMatrix();
|
||||
//设置饱和度
|
||||
colorMatrix.setSaturation((float) type);
|
||||
paint.setColorFilter(new ColorMatrixColorFilter(colorMatrix));
|
||||
return paint;
|
||||
}
|
||||
/**
|
||||
* 一键变灰
|
||||
* @param greyFlag 0:变灰 1:正常
|
||||
* @param window 窗口
|
||||
* */
|
||||
public static void themeGrey(int greyFlag, Window window) {
|
||||
Paint paint = GreyingManager.getMatrix(greyFlag);
|
||||
window.getDecorView().setLayerType(View.LAYER_TYPE_HARDWARE, paint);
|
||||
}
|
||||
}
|
||||
73
libcore/src/main/java/com/pukka/libcore/utils/JsonUtils.java
Executable file
73
libcore/src/main/java/com/pukka/libcore/utils/JsonUtils.java
Executable file
@@ -0,0 +1,73 @@
|
||||
|
||||
package com.pukka.libcore.utils;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
/**
|
||||
* Author: wxf
|
||||
* Date: 2024/8/8 14:00
|
||||
* Description:Json工具类
|
||||
* json相关处理
|
||||
*/
|
||||
public class JsonUtils {
|
||||
private static Gson gson = (new GsonBuilder()).create();
|
||||
|
||||
public JsonUtils() {
|
||||
}
|
||||
/**
|
||||
* json字符串转对象
|
||||
* @param <T> 泛型
|
||||
* @param json json字符串
|
||||
* @param type 要转变对象的类型
|
||||
* @return 对象实体
|
||||
* */
|
||||
public static <T> T json2Object(String json, Type type) {
|
||||
try {
|
||||
return gson.fromJson(json, type);
|
||||
} catch (Exception var3) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 对象转json字符串
|
||||
* @param object 对象
|
||||
* @param type 对象类型
|
||||
* @return 对象的Json字符串
|
||||
* */
|
||||
public static String object2String(Object object, Type type) {
|
||||
try {
|
||||
String string = gson.toJson(object, type);
|
||||
return string;
|
||||
} catch (Exception var3) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
/**
|
||||
*对象转json字符串
|
||||
* @param object 对象
|
||||
* @return 对象的Json字符串
|
||||
* */
|
||||
public static String toJson(Object object) {
|
||||
try {
|
||||
return gson.toJson(object);
|
||||
} catch (Exception var2) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
/**
|
||||
*对象转json字符串
|
||||
* @param object 对象
|
||||
* @return 对象的Json字符串
|
||||
* */
|
||||
public static String toJsonDisableHtml(Object object) {
|
||||
try {
|
||||
GsonBuilder gsonBuilder = new GsonBuilder();
|
||||
gsonBuilder.disableHtmlEscaping();
|
||||
return gsonBuilder.create().toJson(object);
|
||||
} catch (Exception var2) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
48
libcore/src/main/java/com/pukka/libcore/utils/ListUtils.java
Executable file
48
libcore/src/main/java/com/pukka/libcore/utils/ListUtils.java
Executable file
@@ -0,0 +1,48 @@
|
||||
package com.pukka.libcore.utils;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* Author: wxf
|
||||
* Date: 2024/8/8 14:00
|
||||
* Description:数组工具类
|
||||
*/
|
||||
public class ListUtils {
|
||||
|
||||
/**
|
||||
* 获取数组第一个对象
|
||||
*
|
||||
* @param collection 数组
|
||||
* @param <T> 数组
|
||||
* @return 数组第一个对象
|
||||
*/
|
||||
public static <T> T getFirst(Collection<T> collection) {
|
||||
if (collection == null) {
|
||||
return null;
|
||||
} else {
|
||||
Iterator<T> iterator = collection.iterator();
|
||||
return iterator.hasNext() ? iterator.next() : null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断数组是否为空
|
||||
* @param <T> 泛型
|
||||
* @param collection 数组
|
||||
* @return 是否为空
|
||||
*/
|
||||
public static <T> boolean isEmpty(Collection<T> collection) {
|
||||
return collection == null || collection.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数组size
|
||||
* @param <T> 泛型
|
||||
* @param collection 数组
|
||||
* @return 数组数量
|
||||
*/
|
||||
public static <T> int getSize(Collection<T> collection) {
|
||||
return collection == null ? 0 : collection.size();
|
||||
}
|
||||
}
|
||||
248
libcore/src/main/java/com/pukka/libcore/utils/LogUtils.java
Executable file
248
libcore/src/main/java/com/pukka/libcore/utils/LogUtils.java
Executable file
@@ -0,0 +1,248 @@
|
||||
package com.pukka.libcore.utils;
|
||||
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* Author: wxf
|
||||
* Date: 2024/8/8 14:00
|
||||
* Description:日志工具类
|
||||
*/
|
||||
public class LogUtils {
|
||||
|
||||
private static String LOG_TAG = "pukka";
|
||||
//默认打开日志开关
|
||||
private static boolean isDebug = true;
|
||||
//类名
|
||||
static String className;
|
||||
//方法名
|
||||
static String methodName;
|
||||
//行数
|
||||
static int lineNumber;
|
||||
|
||||
/**
|
||||
* 判断是否打开日志打印
|
||||
*
|
||||
* @return 是否打开日志打印
|
||||
*/
|
||||
public static boolean isDebuggable() {
|
||||
return isDebug;
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭或打开日志
|
||||
*
|
||||
* @param debug 是否打开日志打印
|
||||
*/
|
||||
public static void setDebugAble(boolean debug) {
|
||||
isDebug = debug;
|
||||
}
|
||||
|
||||
private static String createLog(String log) {
|
||||
return createLog("", log);
|
||||
}
|
||||
|
||||
private static String createLog(String tag, String log) {
|
||||
StringBuffer buffer = new StringBuffer();
|
||||
buffer.append("===(");
|
||||
buffer.append(LOG_TAG + "->");
|
||||
buffer.append(methodName);
|
||||
buffer.append(")");
|
||||
buffer.append("(").append(className).append(":").append(lineNumber).append(")===:");
|
||||
if (!TextUtils.isEmpty(tag)) {
|
||||
buffer.append("{");
|
||||
buffer.append(tag);
|
||||
buffer.append("} ");
|
||||
}
|
||||
buffer.append(log);
|
||||
buffer.append("\n");
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 长打印
|
||||
*
|
||||
* @param msg 日志打印内容
|
||||
*/
|
||||
public static void longD(String msg) {
|
||||
getMethodNames(new Throwable().getStackTrace());
|
||||
msg = createLog(msg);
|
||||
//logcat打印的最长长度为1024*4字节,如果是纯中文,字符数量为1024*2
|
||||
int max_str_length = 1024 * 2;
|
||||
while (msg.length() > max_str_length) {
|
||||
Log.d(methodName, msg.substring(0, max_str_length));
|
||||
msg = msg.substring(max_str_length);
|
||||
}
|
||||
Log.d(methodName, msg);
|
||||
}
|
||||
|
||||
public static void longD(String tag, String msg) {
|
||||
msg = createLog(msg);
|
||||
//logcat打印的最长长度为1024*4字节,如果是纯中文,字符数量为1024*2
|
||||
int max_str_length = 1024 * 2 - tag.length();
|
||||
while (msg.length() > max_str_length) {
|
||||
LogUtils.logD(tag, msg.substring(0, max_str_length));
|
||||
msg = msg.substring(max_str_length);
|
||||
}
|
||||
LogUtils.logD(tag, msg);
|
||||
}
|
||||
|
||||
private static void logD(String tag, String message) {
|
||||
if (!isDebuggable()) {
|
||||
return;
|
||||
}
|
||||
getMethodNames(new Throwable().getStackTrace());
|
||||
Log.d(tag, createLongLog(tag, message));
|
||||
}
|
||||
private static String createLongLog(String tag, String log) {
|
||||
StringBuffer buffer = new StringBuffer();
|
||||
if (!TextUtils.isEmpty(tag)) {
|
||||
buffer.append("{");
|
||||
buffer.append(tag);
|
||||
buffer.append("} ");
|
||||
}
|
||||
buffer.append(log);
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 仅仅只输出信息,不进行拼接
|
||||
*
|
||||
* @param tag tag
|
||||
* @param message 日志打印内容
|
||||
*/
|
||||
public static void onlyD(String tag, String message) {
|
||||
if (!isDebuggable()) {
|
||||
return;
|
||||
}
|
||||
Log.d(tag, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件名、方法名、所在行数
|
||||
*
|
||||
* @param sElements
|
||||
*/
|
||||
private static void getMethodNames(StackTraceElement[] sElements) {
|
||||
className = sElements[1].getFileName();
|
||||
methodName = sElements[1].getMethodName();
|
||||
lineNumber = sElements[1].getLineNumber();
|
||||
}
|
||||
|
||||
/**
|
||||
* 打印
|
||||
*
|
||||
* @param message 日志打印内容
|
||||
*/
|
||||
public static void e(String message) {
|
||||
if (!isDebuggable()) {
|
||||
return;
|
||||
}
|
||||
getMethodNames(new Throwable().getStackTrace());
|
||||
Log.e(className, createLog(message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 打印
|
||||
*
|
||||
* @param tag tag
|
||||
* @param message 日志打印内容
|
||||
*/
|
||||
public static void e(String tag, String message) {
|
||||
if (!isDebuggable()) {
|
||||
return;
|
||||
}
|
||||
getMethodNames(new Throwable().getStackTrace());
|
||||
Log.e(tag, createLog(message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 打印
|
||||
*
|
||||
* @param tag tag
|
||||
* @param message 日志打印内容
|
||||
*/
|
||||
public static void i(String tag, String message) {
|
||||
if (!isDebuggable()) {
|
||||
return;
|
||||
}
|
||||
getMethodNames(new Throwable().getStackTrace());
|
||||
Log.i(tag, createLog(message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 打印
|
||||
*
|
||||
* @param message 日志打印内容
|
||||
*/
|
||||
public static void i(String message) {
|
||||
if (!isDebuggable()) {
|
||||
return;
|
||||
}
|
||||
getMethodNames(new Throwable().getStackTrace());
|
||||
Log.i(className, createLog(message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 打印
|
||||
*
|
||||
* @param tag tag
|
||||
* @param message 日志打印内容
|
||||
*/
|
||||
public static void d(String tag, String message) {
|
||||
if (!isDebuggable()) {
|
||||
return;
|
||||
}
|
||||
getMethodNames(new Throwable().getStackTrace());
|
||||
Log.d(tag, createLog(message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 打印
|
||||
*
|
||||
* @param message 日志打印内容
|
||||
*/
|
||||
public static void d(String message) {
|
||||
if (!isDebuggable()) {
|
||||
return;
|
||||
}
|
||||
getMethodNames(new Throwable().getStackTrace());
|
||||
Log.d(className, createLog(message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 打印
|
||||
*
|
||||
* @param message 日志打印内容
|
||||
*/
|
||||
public static void v(String message) {
|
||||
if (!isDebuggable()) {
|
||||
return;
|
||||
}
|
||||
getMethodNames(new Throwable().getStackTrace());
|
||||
Log.v(className, createLog(message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 打印
|
||||
*
|
||||
* @param message 日志打印内容
|
||||
*/
|
||||
public static void w(String message) {
|
||||
if (!isDebuggable()) {
|
||||
return;
|
||||
}
|
||||
getMethodNames(new Throwable().getStackTrace());
|
||||
Log.w(className, createLog(message));
|
||||
}
|
||||
|
||||
public static void w(String tag, String message) {
|
||||
if (!isDebuggable()) {
|
||||
return;
|
||||
}
|
||||
getMethodNames(new Throwable().getStackTrace());
|
||||
Log.d(tag, createLog(message));
|
||||
}
|
||||
}
|
||||
|
||||
118
libcore/src/main/java/com/pukka/libcore/utils/MD5Utils.java
Executable file
118
libcore/src/main/java/com/pukka/libcore/utils/MD5Utils.java
Executable file
@@ -0,0 +1,118 @@
|
||||
package com.pukka.libcore.utils;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
|
||||
/**
|
||||
* 生成md5
|
||||
*/
|
||||
|
||||
public class MD5Utils {
|
||||
/**
|
||||
* 生成md5
|
||||
*
|
||||
* @param message
|
||||
* @return
|
||||
*/
|
||||
// public static String getMD5(String message) {
|
||||
// String md5str = "";
|
||||
// try {
|
||||
// //1 创建一个提供信息摘要算法的对象,初始化为md5算法对象
|
||||
// MessageDigest md = MessageDigest.getInstance("MD5");
|
||||
//
|
||||
// //2 将消息变成byte数组
|
||||
// byte[] input = message.getBytes();
|
||||
//
|
||||
// //3 计算后获得字节数组,这就是那128位了
|
||||
// byte[] buff = md.digest(input);
|
||||
//
|
||||
// //4 把数组每一字节(一个字节占八位)换成16进制连成md5字符串
|
||||
// md5str = bytesToHex(buff);
|
||||
//
|
||||
// } catch (Exception e) {
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
// return md5str;
|
||||
// }
|
||||
|
||||
/**
|
||||
* 生成md5
|
||||
*
|
||||
* @param message
|
||||
* @return
|
||||
*/
|
||||
public static String getMD5(byte[] message) {
|
||||
String md5str = "";
|
||||
try {
|
||||
//1 创建一个提供信息摘要算法的对象,初始化为md5算法对象
|
||||
MessageDigest md = MessageDigest.getInstance("MD5");
|
||||
|
||||
|
||||
//3 计算后获得字节数组,这就是那128位了
|
||||
byte[] buff = md.digest(message);
|
||||
|
||||
//4 把数组每一字节(一个字节占八位)换成16进制连成md5字符串
|
||||
md5str = bytesToHex(buff);
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return md5str;
|
||||
}
|
||||
|
||||
/**
|
||||
* 二进制转十六进制
|
||||
*
|
||||
* @param bytes
|
||||
* @return
|
||||
*/
|
||||
public static String bytesToHex(byte[] bytes) {
|
||||
StringBuffer md5str = new StringBuffer();
|
||||
//把数组每一字节换成16进制连成md5字符串
|
||||
int digital;
|
||||
for (int i = 0; i < bytes.length; i++) {
|
||||
digital = bytes[i];
|
||||
|
||||
if (digital < 0) {
|
||||
digital += 256;
|
||||
}
|
||||
if (digital < 16) {
|
||||
md5str.append("0");
|
||||
}
|
||||
md5str.append(Integer.toHexString(digital));
|
||||
}
|
||||
return md5str.toString().toUpperCase();
|
||||
}
|
||||
|
||||
private static String byte2HexStr(byte[] buf) {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
|
||||
for(int i = 0; i < buf.length; ++i) {
|
||||
String hex = Integer.toHexString(buf[i] & 255);
|
||||
if (hex.length() == 1) {
|
||||
hex = '0' + hex;
|
||||
}
|
||||
|
||||
sb.append(hex.toLowerCase());
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static byte[] encryptToByte(String content) {
|
||||
try {
|
||||
MessageDigest mDigest = MessageDigest.getInstance("MD5");
|
||||
mDigest.update(content.getBytes("utf-8"));
|
||||
byte[] md5 = mDigest.digest();
|
||||
return md5;
|
||||
} catch (Exception var3) {
|
||||
var3.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static String getMD5(String content) {
|
||||
byte[] md5 = encryptToByte(content);
|
||||
String buf = byte2HexStr(md5);
|
||||
return buf;
|
||||
}
|
||||
}
|
||||
200
libcore/src/main/java/com/pukka/libcore/utils/MacAddressUtil.java
Executable file
200
libcore/src/main/java/com/pukka/libcore/utils/MacAddressUtil.java
Executable file
@@ -0,0 +1,200 @@
|
||||
package com.pukka.libcore.utils;
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.LineNumberReader;
|
||||
import java.net.InetAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.SocketException;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class MacAddressUtil {
|
||||
public static String getLocalMac(String mac) {
|
||||
String str = "";
|
||||
StringBuilder builder = new StringBuilder();
|
||||
try {
|
||||
Process pp = Runtime.getRuntime().exec(mac);
|
||||
InputStreamReader ir = new InputStreamReader(pp.getInputStream());
|
||||
LineNumberReader input = new LineNumberReader(ir);
|
||||
while (null != str) {
|
||||
str = input.readLine();
|
||||
if (str != null) {
|
||||
builder.append(str);
|
||||
}
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
String macAddress = "";
|
||||
Pattern macPattern = Pattern.compile("\\bLink\\s*encap:Ethernet\\s*HWaddr\\s*([0-9A-Fa-f]{2})(([-:])[0-9A-Fa-f]{2}){5}\\b");
|
||||
String targetStr = builder.toString();
|
||||
Matcher matcher = macPattern.matcher(targetStr);
|
||||
if (matcher.find()) {
|
||||
macAddress = matcher.group();
|
||||
}
|
||||
if (!TextUtils.isEmpty(macAddress)) {
|
||||
Pattern subPattern = Pattern.compile("([0-9A-Fa-f]{2})(([-:])[0-9A-Fa-f]{2}){5}");
|
||||
Matcher subMatcher = subPattern.matcher(macAddress);
|
||||
if (subMatcher.find()) {
|
||||
macAddress = subMatcher.group();
|
||||
}
|
||||
}
|
||||
return macAddress;
|
||||
}
|
||||
|
||||
|
||||
public static String getMacAddress() {
|
||||
String strMacAddr = null;
|
||||
try {
|
||||
// 获得IpD地址
|
||||
InetAddress ip = getLocalInetAddress();
|
||||
byte[] b = NetworkInterface.getByInetAddress(ip)
|
||||
.getHardwareAddress();
|
||||
StringBuffer buffer = new StringBuffer();
|
||||
for (int i = 0; i < b.length; i++) {
|
||||
if (i != 0) {
|
||||
buffer.append(':');
|
||||
}
|
||||
String str = Integer.toHexString(b[i] & 0xFF);
|
||||
buffer.append(str.length() == 1 ? 0 + str : str);
|
||||
}
|
||||
strMacAddr = buffer.toString().toUpperCase();
|
||||
} catch (Exception e) {
|
||||
}
|
||||
return strMacAddr;
|
||||
}
|
||||
|
||||
private static InetAddress getLocalInetAddress() {
|
||||
InetAddress ip = null;
|
||||
try {
|
||||
// 列举
|
||||
Enumeration<NetworkInterface> en_netInterface = NetworkInterface
|
||||
.getNetworkInterfaces();
|
||||
while (en_netInterface.hasMoreElements()) {// 是否还有元素
|
||||
NetworkInterface ni = (NetworkInterface) en_netInterface
|
||||
.nextElement();// 得到下一个元素
|
||||
Enumeration<InetAddress> en_ip = ni.getInetAddresses();// 得到一个ip地址的列举
|
||||
while (en_ip.hasMoreElements()) {
|
||||
ip = en_ip.nextElement();
|
||||
if (!ip.isLoopbackAddress()
|
||||
&& ip.getHostAddress().indexOf(":") == -1)
|
||||
break;
|
||||
else
|
||||
ip = null;
|
||||
}
|
||||
|
||||
if (ip != null) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (SocketException e) {
|
||||
|
||||
e.printStackTrace();
|
||||
}
|
||||
return ip;
|
||||
}
|
||||
|
||||
public static String getMachineHardwareAddress() {
|
||||
Enumeration<NetworkInterface> interfaces = null;
|
||||
try {
|
||||
interfaces = NetworkInterface.getNetworkInterfaces();
|
||||
} catch (SocketException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
String hardWareAddress = null;
|
||||
NetworkInterface iF = null;
|
||||
if (interfaces == null) {
|
||||
return null;
|
||||
}
|
||||
while (interfaces.hasMoreElements()) {
|
||||
iF = interfaces.nextElement();
|
||||
try {
|
||||
hardWareAddress = bytesToString(iF.getHardwareAddress());
|
||||
if (hardWareAddress != null)
|
||||
break;
|
||||
} catch (SocketException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return hardWareAddress;
|
||||
}
|
||||
|
||||
private static String bytesToString(byte[] bytes) {
|
||||
if (bytes == null || bytes.length == 0) {
|
||||
return null;
|
||||
}
|
||||
StringBuilder buf = new StringBuilder();
|
||||
for (byte b : bytes) {
|
||||
buf.append(String.format("%02X:", b));
|
||||
}
|
||||
if (buf.length() > 0) {
|
||||
buf.deleteCharAt(buf.length() - 1);
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
|
||||
public static String getLocalMacAddress() {
|
||||
try {
|
||||
List<NetworkInterface> all = Collections.list(NetworkInterface.getNetworkInterfaces());
|
||||
for (NetworkInterface nif : all) {
|
||||
// 优先处理有线网卡 eth0
|
||||
if (nif.getName().equalsIgnoreCase("eth0")) {
|
||||
LogUtils.d("getLocalMacAddress 有线 eth0 11");
|
||||
byte[] macBytes = nif.getHardwareAddress();
|
||||
if (macBytes != null) {
|
||||
return formatMacAddress(macBytes).toLowerCase();
|
||||
}
|
||||
}
|
||||
}
|
||||
// 如果没有找到 eth0,再尝试无线网卡 wlan0
|
||||
for (NetworkInterface nif : all) {
|
||||
if (nif.getName().equalsIgnoreCase("wlan0")) {
|
||||
byte[] macBytes = nif.getHardwareAddress();
|
||||
if (macBytes != null) {
|
||||
LogUtils.d("getLocalMacAddress 无线 wlan0 11");
|
||||
return formatMacAddress(macBytes).toLowerCase();
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
// 异常处理
|
||||
ex.printStackTrace();
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
public static String getMacAddressFromFile(String path) {
|
||||
try (FileInputStream fis = new FileInputStream(path)) {
|
||||
byte[] buffer = new byte[17];
|
||||
int byteCount = fis.read(buffer);
|
||||
if (byteCount > 0) {
|
||||
String mac = new String(buffer, 0, byteCount, "utf-8").trim();
|
||||
if (!TextUtils.isEmpty(mac)) {
|
||||
return mac;
|
||||
}
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private static String formatMacAddress(byte[] macBytes) {
|
||||
StringBuilder res = new StringBuilder();
|
||||
for (byte b : macBytes) {
|
||||
res.append(String.format("%02X:", b));
|
||||
}
|
||||
if (res.length() > 0) {
|
||||
res.deleteCharAt(res.length() - 1); // 去掉最后一个冒号
|
||||
}
|
||||
return res.toString();
|
||||
}
|
||||
}
|
||||
180
libcore/src/main/java/com/pukka/libcore/utils/PackageUtils.java
Executable file
180
libcore/src/main/java/com/pukka/libcore/utils/PackageUtils.java
Executable file
@@ -0,0 +1,180 @@
|
||||
package com.pukka.libcore.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.wifi.WifiInfo;
|
||||
import android.net.wifi.WifiManager;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
|
||||
import java.net.NetworkInterface;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class PackageUtils {
|
||||
/**
|
||||
* 获取指定包名的版本号
|
||||
*
|
||||
* @param context
|
||||
* 本应用程序上下文
|
||||
* @param packageName
|
||||
* 你想知道版本信息的应用程序的包名
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
public static String getVersionName(Context context, String packageName) {
|
||||
// 获取packagemanager的实例
|
||||
try {
|
||||
PackageManager packageManager = context.getPackageManager();
|
||||
PackageInfo packInfo = packageManager.getPackageInfo(packageName, 0);
|
||||
String version = packInfo.versionName;
|
||||
return version;
|
||||
}catch (Exception ex){
|
||||
ex.printStackTrace();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
/**
|
||||
* 得到当前应用版本名称的方法
|
||||
*
|
||||
* @param context
|
||||
* :上下文
|
||||
* @throws Exception
|
||||
*/
|
||||
public static String getVersionName(Context context) {
|
||||
try {
|
||||
// 获取packagemanager的实例
|
||||
PackageManager packageManager = context.getPackageManager();
|
||||
// getPackageName()是你当前类的包名
|
||||
PackageInfo packInfo = packageManager.getPackageInfo(context.getPackageName(), 0);
|
||||
String version = packInfo.versionName;
|
||||
return version;
|
||||
}catch (Exception ex){
|
||||
ex.printStackTrace();
|
||||
}
|
||||
return "";
|
||||
} /**
|
||||
* 得到当前应用版本名称的方法
|
||||
*
|
||||
* @param context
|
||||
* :上下文
|
||||
* @throws Exception
|
||||
*/
|
||||
public static String versionCode(Context context) {
|
||||
try {
|
||||
// 获取packagemanager的实例
|
||||
PackageManager packageManager = context.getPackageManager();
|
||||
// getPackageName()是你当前类的包名
|
||||
PackageInfo packInfo = packageManager.getPackageInfo(context.getPackageName(), 0);
|
||||
String version = String.valueOf(packInfo.versionCode);
|
||||
return version;
|
||||
}catch (Exception ex){
|
||||
ex.printStackTrace();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
/**
|
||||
* 得到当前应用版本名称的方法
|
||||
*
|
||||
* @param context :上下文
|
||||
* @throws Exception
|
||||
*/
|
||||
public static String getVersionCode(Context context) {
|
||||
try {
|
||||
// 获取packagemanager的实例
|
||||
PackageManager packageManager = context.getPackageManager();
|
||||
// getPackageName()是你当前类的包名
|
||||
PackageInfo packInfo = packageManager.getPackageInfo(context.getPackageName(), 0);
|
||||
String version = String.valueOf(packInfo.versionCode);
|
||||
return version;
|
||||
} catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
/**
|
||||
* 获取mac地址
|
||||
* */
|
||||
public static String getMac(Context context) {
|
||||
String strMac = null;
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
||||
Log.e("=====", "6.0以下");
|
||||
strMac = getLocalMacAddressFromWifiInfo(context);
|
||||
return strMac;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
private static String getLocalMacAddressFromWifiInfo(Context context) {
|
||||
WifiManager wifi = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
|
||||
WifiInfo winfo = null;
|
||||
try {
|
||||
winfo = wifi.getConnectionInfo();
|
||||
if(null != winfo){
|
||||
String mac = winfo.getMacAddress();
|
||||
if(null == mac){
|
||||
mac = getMacFromHardware();
|
||||
}
|
||||
return mac;
|
||||
}
|
||||
|
||||
}catch (Exception ex){
|
||||
ex.printStackTrace();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
/**
|
||||
* 遍历循环所有的网络接口,找到接口是 eth0 河南盒子是可以获取到
|
||||
* */
|
||||
public static String getMacFromHardware() {
|
||||
try {
|
||||
List<NetworkInterface> all = Collections.list(NetworkInterface.getNetworkInterfaces());
|
||||
for (NetworkInterface nif : all) {
|
||||
if (!nif.getName().equalsIgnoreCase("eth0")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
byte[] macBytes = nif.getHardwareAddress();
|
||||
if (macBytes == null) {
|
||||
return null;
|
||||
}
|
||||
Log.d("PackageUtils", "macBytes:" + macBytes.length + "," + nif.getName());
|
||||
|
||||
StringBuilder res1 = new StringBuilder();
|
||||
for (byte b : macBytes) {
|
||||
res1.append(String.format("%02X:", b));
|
||||
}
|
||||
|
||||
if (res1.length() > 0) {
|
||||
res1.deleteCharAt(res1.length() - 1);
|
||||
}
|
||||
return res1.toString();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public static String getMacFromWifi(Context context) {
|
||||
try {
|
||||
WifiManager wifi = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
|
||||
WifiInfo winfo = null;
|
||||
try {
|
||||
winfo = wifi.getConnectionInfo();
|
||||
if(null != winfo){
|
||||
String mac = winfo.getMacAddress();
|
||||
return mac;
|
||||
}
|
||||
|
||||
}catch (Exception ex){
|
||||
ex.printStackTrace();
|
||||
}
|
||||
return "";
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
101
libcore/src/main/java/com/pukka/libcore/utils/ProcessUtil.java
Executable file
101
libcore/src/main/java/com/pukka/libcore/utils/ProcessUtil.java
Executable file
@@ -0,0 +1,101 @@
|
||||
package com.pukka.libcore.utils;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
|
||||
public class ProcessUtil {
|
||||
|
||||
|
||||
private static String currentProcessName = "";
|
||||
|
||||
/**
|
||||
* @return 当前进程名
|
||||
*/
|
||||
@Nullable
|
||||
public static String getCurrentProcessName(@NonNull Context context) {
|
||||
if (!TextUtils.isEmpty(currentProcessName)) {
|
||||
return currentProcessName;
|
||||
}
|
||||
|
||||
//1)通过Application的API获取当前进程名
|
||||
currentProcessName = getCurrentProcessNameByApplication();
|
||||
if (!TextUtils.isEmpty(currentProcessName)) {
|
||||
return currentProcessName;
|
||||
}
|
||||
|
||||
//2)通过反射ActivityThread获取当前进程名
|
||||
currentProcessName = getCurrentProcessNameByActivityThread();
|
||||
if (!TextUtils.isEmpty(currentProcessName)) {
|
||||
return currentProcessName;
|
||||
}
|
||||
|
||||
//3)通过ActivityManager获取当前进程名
|
||||
currentProcessName = getCurrentProcessNameByActivityManager(context);
|
||||
|
||||
return currentProcessName;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 通过Application新的API获取进程名,无需反射,无需IPC,效率最高。
|
||||
*/
|
||||
public static String getCurrentProcessNameByApplication() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
return Application.getProcessName();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过反射ActivityThread获取进程名,避免了ipc
|
||||
*/
|
||||
public static String getCurrentProcessNameByActivityThread() {
|
||||
String processName = null;
|
||||
try {
|
||||
final Method declaredMethod = Class.forName("android.app.ActivityThread", false, Application.class.getClassLoader())
|
||||
.getDeclaredMethod("currentProcessName", (Class<?>[]) new Class[0]);
|
||||
declaredMethod.setAccessible(true);
|
||||
final Object invoke = declaredMethod.invoke(null, new Object[0]);
|
||||
if (invoke instanceof String) {
|
||||
processName = (String) invoke;
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return processName;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过ActivityManager 获取进程名,需要IPC通信
|
||||
*/
|
||||
public static String getCurrentProcessNameByActivityManager(@NonNull Context context) {
|
||||
if (context == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int pid = android.os.Process.myPid();
|
||||
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
|
||||
if (am != null) {
|
||||
List<ActivityManager.RunningAppProcessInfo> runningAppList = am.getRunningAppProcesses();
|
||||
if (runningAppList != null) {
|
||||
for (ActivityManager.RunningAppProcessInfo processInfo : runningAppList) {
|
||||
if (processInfo.pid == pid) {
|
||||
return processInfo.processName;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
54
libcore/src/main/java/com/pukka/libcore/utils/ScreenUtil.java
Executable file
54
libcore/src/main/java/com/pukka/libcore/utils/ScreenUtil.java
Executable file
@@ -0,0 +1,54 @@
|
||||
|
||||
package com.pukka.libcore.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
|
||||
/**
|
||||
* [获取屏幕宽高工具类]<BR>
|
||||
*
|
||||
* @author wcg
|
||||
* @version [V0.1.0.1, 2023/8/29 0029]
|
||||
* @since V0.1.0.1
|
||||
*/
|
||||
public class ScreenUtil {
|
||||
/**
|
||||
* 获取屏幕宽
|
||||
*
|
||||
* @param view view
|
||||
* @return 屏幕宽
|
||||
*/
|
||||
public static int getDeviceWidth(View view) {
|
||||
Context context = ContextUtils.getContext();
|
||||
if (view != null && view.getContext() != null) {
|
||||
context = view.getContext();
|
||||
}
|
||||
|
||||
if (null == context) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return Math.min(context.getResources().getDisplayMetrics().widthPixels,
|
||||
(context).getResources().getDisplayMetrics().heightPixels);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取屏幕高
|
||||
*
|
||||
* @param view view
|
||||
* @return 屏幕高
|
||||
*/
|
||||
public static int getDeviceHeight(View view) {
|
||||
Context context = ContextUtils.getContext();
|
||||
if (view != null && view.getContext() != null) {
|
||||
context = view.getContext();
|
||||
}
|
||||
|
||||
if (null == context) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return Math.max(context.getResources().getDisplayMetrics().widthPixels,
|
||||
context.getResources().getDisplayMetrics().heightPixels);
|
||||
}
|
||||
}
|
||||
309
libcore/src/main/java/com/pukka/libcore/utils/SharedPreferenceUtils.java
Executable file
309
libcore/src/main/java/com/pukka/libcore/utils/SharedPreferenceUtils.java
Executable file
@@ -0,0 +1,309 @@
|
||||
package com.pukka.libcore.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import androidx.core.content.SharedPreferencesCompat;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Author: wxf
|
||||
* Date: 2024/8/22 17:00
|
||||
* Description:SharedPreference工具类 存值和取值
|
||||
* 默认存储在data/data/包名/Shared_prefs/config.xml中,也可以自己定义文件名称
|
||||
*/
|
||||
public final class SharedPreferenceUtils {
|
||||
//SP文件名称
|
||||
private static final String DEFAULT_SP_NAME = "config";
|
||||
/**
|
||||
* 存放基本数据类型到sharedPreference中fileName文件中
|
||||
* data/data/包名/Shared_prefs/fileName.xml
|
||||
*
|
||||
* @param fileName 内容存放的xml文件名
|
||||
* @param key 存放内容的key值
|
||||
* @param value 存放value值
|
||||
*/
|
||||
public static void put(String fileName, String key, Object value) {
|
||||
if (TextUtils.isEmpty(key)) {
|
||||
return;
|
||||
}
|
||||
LogUtils.d("shard --pu --fileName " + fileName);
|
||||
LogUtils.d("shard --pu --key " + key);
|
||||
LogUtils.d("shard --pu --value " + value);
|
||||
if (TextUtils.isEmpty(fileName)) {
|
||||
fileName = DEFAULT_SP_NAME;
|
||||
}
|
||||
SharedPreferences sharedPreferences = ContextUtils.getApplicationContext().getSharedPreferences(
|
||||
fileName, Context.MODE_PRIVATE);
|
||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
if (value instanceof Integer) {
|
||||
editor.putInt(key, (Integer) value);
|
||||
} else if (value instanceof Long) {
|
||||
editor.putLong(key, (Long) value);
|
||||
} else if (value instanceof Float) {
|
||||
editor.putFloat(key, (Float) value);
|
||||
} else if (value instanceof String) {
|
||||
editor.putString(key, (String) value);
|
||||
} else if (value instanceof Boolean) {
|
||||
editor.putBoolean(key, (Boolean) value);
|
||||
} else {
|
||||
Gson gson = new Gson();
|
||||
String json = gson.toJson(value);
|
||||
editor.putString(key, json);
|
||||
}
|
||||
SharedPreferencesCompat.EditorCompat.getInstance().apply(editor);
|
||||
}
|
||||
/**
|
||||
* 存放基本数据类型到sharedPreference中config文件中
|
||||
* data/data/包名/Shared_prefs/config.xml
|
||||
* @param key 存放内容的key值
|
||||
* @param value 存放value值
|
||||
*/
|
||||
public static void put(String key, Object value) {
|
||||
if (TextUtils.isEmpty(key)) {
|
||||
return;
|
||||
}
|
||||
LogUtils.d("shard --pu --key " + key);
|
||||
LogUtils.d("shard --pu --value " + value);
|
||||
|
||||
SharedPreferences sharedPreferences = ContextUtils.getApplicationContext().getSharedPreferences(
|
||||
DEFAULT_SP_NAME, Context.MODE_PRIVATE);
|
||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
if (value instanceof Integer) {
|
||||
editor.putInt(key, (Integer) value);
|
||||
} else if (value instanceof Long) {
|
||||
editor.putLong(key, (Long) value);
|
||||
} else if (value instanceof Float) {
|
||||
editor.putFloat(key, (Float) value);
|
||||
} else if (value instanceof String) {
|
||||
editor.putString(key, (String) value);
|
||||
} else if (value instanceof Boolean) {
|
||||
editor.putBoolean(key, (Boolean) value);
|
||||
} else {
|
||||
Gson gson = new Gson();
|
||||
String json = gson.toJson(value);
|
||||
editor.putString(key, json);
|
||||
}
|
||||
SharedPreferencesCompat.EditorCompat.getInstance().apply(editor);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从sharedPreference中获取指定key的int值
|
||||
*
|
||||
* @param fileName 指定从哪个文件中获取
|
||||
* @param key 关键字
|
||||
* @param defaultValue 默认值
|
||||
* @return 指定key的int值
|
||||
*/
|
||||
public static int getInt(String fileName, String key, int defaultValue) {
|
||||
if (TextUtils.isEmpty(fileName)) {
|
||||
fileName = DEFAULT_SP_NAME;
|
||||
}
|
||||
return ContextUtils.getApplicationContext().getSharedPreferences(fileName,
|
||||
Context.MODE_PRIVATE)
|
||||
.getInt(key, defaultValue);
|
||||
}
|
||||
/**
|
||||
* 从sharedPreference中获取指定key的int值
|
||||
*
|
||||
* @param key 关键字
|
||||
* @param defaultValue 默认值
|
||||
* @return 指定key的int值
|
||||
*/
|
||||
public static int getInt(String key, int defaultValue) {
|
||||
return ContextUtils.getApplicationContext().getSharedPreferences(DEFAULT_SP_NAME,
|
||||
Context.MODE_PRIVATE)
|
||||
.getInt(key, defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从sharedPreference中获取指定key的布尔值
|
||||
*
|
||||
* @param fileName 指定从哪个文件中获取
|
||||
* @param key 关键字
|
||||
* @param defaultValue 默认值
|
||||
* @return 指定key的布尔值
|
||||
*/
|
||||
public static boolean getBoolean(String fileName, String key,
|
||||
boolean defaultValue) {
|
||||
if (TextUtils.isEmpty(fileName)) {
|
||||
fileName = DEFAULT_SP_NAME;
|
||||
}
|
||||
return ContextUtils.getApplicationContext().getSharedPreferences(fileName,
|
||||
Context.MODE_PRIVATE)
|
||||
.getBoolean(key, defaultValue);
|
||||
}
|
||||
/**
|
||||
* 从sharedPreference中获取指定key的布尔值
|
||||
*
|
||||
* @param key 关键字
|
||||
* @param defaultValue 默认值
|
||||
* @return 指定key的布尔值
|
||||
*/
|
||||
public static boolean getBoolean(String key,
|
||||
boolean defaultValue) {
|
||||
return ContextUtils.getApplicationContext().getSharedPreferences(DEFAULT_SP_NAME,
|
||||
Context.MODE_PRIVATE)
|
||||
.getBoolean(key, defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从sharedPreference中获取指定key的Long值
|
||||
*
|
||||
* @param fileName 指定从哪个文件中获取
|
||||
* @param key 关键字
|
||||
* @param defaultValue 默认值
|
||||
* @return 指定key的Long值
|
||||
*/
|
||||
public static long getLong(String fileName, String key, long defaultValue) {
|
||||
if (TextUtils.isEmpty(fileName)) {
|
||||
fileName = DEFAULT_SP_NAME;
|
||||
}
|
||||
return ContextUtils.getApplicationContext().getSharedPreferences(fileName,
|
||||
Context.MODE_PRIVATE)
|
||||
.getLong(key, defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从sharedPreference中获取指定key的Long值
|
||||
*
|
||||
* @param key 关键字
|
||||
* @param defaultValue 默认值
|
||||
* @return 指定key的Long值
|
||||
*/
|
||||
public static long getLong(String key, long defaultValue) {
|
||||
return ContextUtils.getApplicationContext().getSharedPreferences(DEFAULT_SP_NAME,
|
||||
Context.MODE_PRIVATE)
|
||||
.getLong(key, defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从sharedPreference中获取指定key的String值
|
||||
*
|
||||
* @param fileName 指定从哪个文件中获取
|
||||
* @param key 关键字
|
||||
* @param defaultValue 默认值
|
||||
* @return 指定key的String值
|
||||
*/
|
||||
public static String getString(String fileName, String key,
|
||||
String defaultValue) {
|
||||
if (TextUtils.isEmpty(key)) {
|
||||
return "";
|
||||
}
|
||||
if (TextUtils.isEmpty(fileName)) {
|
||||
fileName = DEFAULT_SP_NAME;
|
||||
}
|
||||
return ContextUtils.getApplicationContext().getSharedPreferences(fileName,
|
||||
Context.MODE_PRIVATE)
|
||||
.getString(key, defaultValue);
|
||||
}
|
||||
/**
|
||||
* 从sharedPreference中获取指定key的String值
|
||||
*
|
||||
* @param key 关键字
|
||||
* @param defaultValue 默认值
|
||||
* @return 指定key的String值
|
||||
*/
|
||||
public static String getString(String key,
|
||||
String defaultValue) {
|
||||
if (TextUtils.isEmpty(key)) {
|
||||
return "";
|
||||
}
|
||||
return ContextUtils.getApplicationContext().getSharedPreferences(DEFAULT_SP_NAME,
|
||||
Context.MODE_PRIVATE)
|
||||
.getString(key, defaultValue);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 从sharedPreference中获取指定key的float值
|
||||
*
|
||||
* @param fileName 指定从哪个文件中获取
|
||||
* @param key 关键字
|
||||
* @param defaultValue 默认值
|
||||
* @return 指定key的float值
|
||||
*/
|
||||
public static float getFloat(String fileName, String key,
|
||||
float defaultValue) {
|
||||
if (TextUtils.isEmpty(fileName)) {
|
||||
fileName = DEFAULT_SP_NAME;
|
||||
}
|
||||
return ContextUtils.getApplicationContext().getSharedPreferences(fileName,
|
||||
Context.MODE_PRIVATE)
|
||||
.getFloat(key, defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从sharedPreference中获取指定key的float值
|
||||
*
|
||||
* @param key 关键字
|
||||
* @param defaultValue 默认值
|
||||
* @return 指定key的float值
|
||||
*/
|
||||
public static float getFloat(String key,
|
||||
float defaultValue) {
|
||||
return ContextUtils.getApplicationContext().getSharedPreferences(DEFAULT_SP_NAME,
|
||||
Context.MODE_PRIVATE)
|
||||
.getFloat(key, defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空sharedPreference指定文件
|
||||
*
|
||||
* @param fileName 指定xml文件名
|
||||
* @return 清楚是否成功
|
||||
*/
|
||||
public static boolean clearFile(String fileName) {
|
||||
SharedPreferences sharedPreferences = ContextUtils.getApplicationContext().getSharedPreferences(
|
||||
fileName, Context.MODE_PRIVATE);
|
||||
return sharedPreferences != null && sharedPreferences.edit().clear().commit();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取sharedPreference指定文件存储的所有内容
|
||||
*
|
||||
* @param fileName 指定xml文件名
|
||||
* @return 存储的所有内容
|
||||
*/
|
||||
public static Map<String, ?> getAll(String fileName) {
|
||||
SharedPreferences sharedPreferences = ContextUtils.getApplicationContext().getSharedPreferences(
|
||||
fileName, Context.MODE_PRIVATE);
|
||||
if (sharedPreferences != null) {
|
||||
return sharedPreferences.getAll();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void remove(String key) {
|
||||
if (TextUtils.isEmpty(key)) {
|
||||
return;
|
||||
}
|
||||
LogUtils.d("shard --rm --key " + key);
|
||||
SharedPreferences sharedPreferences = ContextUtils.getApplicationContext().getSharedPreferences(
|
||||
DEFAULT_SP_NAME, Context.MODE_PRIVATE);
|
||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
editor.remove(key);
|
||||
SharedPreferencesCompat.EditorCompat.getInstance().apply(editor);
|
||||
}
|
||||
|
||||
public static void remove(String fileName, String key) {
|
||||
if (TextUtils.isEmpty(key)) {
|
||||
return;
|
||||
}
|
||||
if (TextUtils.isEmpty(fileName)) {
|
||||
fileName = DEFAULT_SP_NAME;
|
||||
}
|
||||
LogUtils.d("shard --rm --fileName " + fileName);
|
||||
LogUtils.d("shard --rm --key " + key);
|
||||
SharedPreferences sharedPreferences = ContextUtils.getApplicationContext().getSharedPreferences(
|
||||
fileName, Context.MODE_PRIVATE);
|
||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
editor.remove(key);
|
||||
SharedPreferencesCompat.EditorCompat.getInstance().apply(editor);
|
||||
}
|
||||
|
||||
}
|
||||
42
libcore/src/main/java/com/pukka/libcore/utils/SpSaveModel.java
Executable file
42
libcore/src/main/java/com/pukka/libcore/utils/SpSaveModel.java
Executable file
@@ -0,0 +1,42 @@
|
||||
package com.pukka.libcore.utils;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public class SpSaveModel<T> implements Serializable {
|
||||
private int saveTime;
|
||||
private T value;
|
||||
private long currentTime;
|
||||
|
||||
public SpSaveModel() {
|
||||
}
|
||||
|
||||
public SpSaveModel(int saveTime, T value, long currentTime) {
|
||||
this.saveTime = saveTime;
|
||||
this.value = value;
|
||||
this.currentTime=currentTime;
|
||||
}
|
||||
|
||||
public long getCurrentTime() {
|
||||
return currentTime;
|
||||
}
|
||||
|
||||
public void setCurrentTime(long currentTime) {
|
||||
this.currentTime = currentTime;
|
||||
}
|
||||
|
||||
public int getSaveTime() {
|
||||
return saveTime;
|
||||
}
|
||||
|
||||
public void setSaveTime(int saveTime) {
|
||||
this.saveTime = saveTime;
|
||||
}
|
||||
|
||||
public T getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(T value) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
166
libcore/src/main/java/com/pukka/libcore/utils/SpTimeUtils.java
Executable file
166
libcore/src/main/java/com/pukka/libcore/utils/SpTimeUtils.java
Executable file
@@ -0,0 +1,166 @@
|
||||
package com.pukka.libcore.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.TypeReference;
|
||||
|
||||
public class SpTimeUtils {
|
||||
//保存时间单位
|
||||
public static final int TIME_SECOND = 1;
|
||||
public static final int TIME_MINUTES = 60 * TIME_SECOND;
|
||||
public static final int TIME_HOUR = 60 * TIME_MINUTES;
|
||||
public static final int TIME_DAY = TIME_HOUR * 24;
|
||||
/**
|
||||
* 不限制存放数据的数量
|
||||
*/
|
||||
public static final int TIME_MAX = Integer.MAX_VALUE;
|
||||
public static final int DURATION_UNIT = 1000;
|
||||
|
||||
private static final String fileName = "config";
|
||||
private SharedPreferences sp;
|
||||
private SharedPreferences.Editor editor;
|
||||
|
||||
private static SpTimeUtils INSTANCE = null;
|
||||
|
||||
public static SpTimeUtils getInstance(Context context) {
|
||||
if (null == INSTANCE) {
|
||||
INSTANCE = new SpTimeUtils(context);
|
||||
}
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
private SpTimeUtils(Context context) {
|
||||
sp = context.getSharedPreferences(fileName, Context.MODE_PRIVATE);
|
||||
editor = sp.edit();
|
||||
}
|
||||
|
||||
|
||||
public void setString(String e, String value) {
|
||||
SpSaveModel<String> spSaveModel = new SpSaveModel<>(TIME_MAX, value, System.currentTimeMillis());
|
||||
String json = JSON.toJSONString(spSaveModel);
|
||||
editor.putString(e, json);
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param e 存放的key
|
||||
* @param value 存放的value
|
||||
* @param saveTime 缓存时间
|
||||
*/
|
||||
public void setString(String e, String value, int saveTime) {
|
||||
SpSaveModel<String> spSaveModel = new SpSaveModel<>(saveTime, value, System.currentTimeMillis());
|
||||
String json = JSON.toJSONString(spSaveModel);
|
||||
editor.putString(e, json);
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param e 存放的key
|
||||
* @param defValue 该key不存在或者过期时,返回的默认值
|
||||
* @return
|
||||
*/
|
||||
public String getString(String e, String defValue) {
|
||||
String json = sp.getString(e, "");
|
||||
if (!TextUtils.isEmpty(json)) {
|
||||
SpSaveModel<String> spSaveModel = JSON.parseObject(json, new TypeReference<SpSaveModel<String>>() {
|
||||
});
|
||||
if (isTimeOut(spSaveModel.getCurrentTime(), spSaveModel.getSaveTime())) {
|
||||
return spSaveModel.getValue();
|
||||
}
|
||||
}
|
||||
return defValue;
|
||||
}
|
||||
|
||||
public void setInt(String e, int value) {
|
||||
SpSaveModel<Integer> spSaveModel = new SpSaveModel<>(TIME_MAX, value, System.currentTimeMillis());
|
||||
String json = JSON.toJSONString(spSaveModel);
|
||||
editor.putString(e, json);
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
public void setInt(String e, int value, int saveTime) {
|
||||
SpSaveModel<Integer> spSaveModel = new SpSaveModel<>(saveTime, value, System.currentTimeMillis());
|
||||
String json = JSON.toJSONString(spSaveModel);
|
||||
editor.putString(e, json);
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
public Integer getInt(String e, int defValue) {
|
||||
String json = sp.getString(e, "");
|
||||
if (!TextUtils.isEmpty(json)) {
|
||||
SpSaveModel<Integer> spSaveModel = JSON.parseObject(json, new TypeReference<SpSaveModel<Integer>>() {
|
||||
});
|
||||
if (isTimeOut(spSaveModel.getCurrentTime(), spSaveModel.getSaveTime())) {
|
||||
return spSaveModel.getValue();
|
||||
}
|
||||
}
|
||||
return defValue;
|
||||
}
|
||||
|
||||
public void setBoolean(String e, boolean value) {
|
||||
SpSaveModel<Boolean> spSaveModel = new SpSaveModel<>(TIME_MAX, value, System.currentTimeMillis());
|
||||
String json = JSON.toJSONString(spSaveModel);
|
||||
editor.putString(e, json);
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
public void setBoolean(String e, boolean value, int saveTime) {
|
||||
SpSaveModel<Boolean> spSaveModel = new SpSaveModel<>(saveTime, value, System.currentTimeMillis());
|
||||
String json = JSON.toJSONString(spSaveModel);
|
||||
editor.putString(e, json);
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
public boolean getBoolean(String e, boolean defValue) {
|
||||
String json = sp.getString(e, "");
|
||||
if (!TextUtils.isEmpty(json)) {
|
||||
SpSaveModel<Boolean> spSaveModel = JSON.parseObject(json, new TypeReference<SpSaveModel<Boolean>>() {
|
||||
});
|
||||
if (isTimeOut(spSaveModel.getCurrentTime(), spSaveModel.getSaveTime())) {
|
||||
return spSaveModel.getValue();
|
||||
}
|
||||
}
|
||||
return defValue;
|
||||
}
|
||||
|
||||
public void setLong(String e, long value) {
|
||||
SpSaveModel<Long> spSaveModel = new SpSaveModel<>(TIME_MAX, value, System.currentTimeMillis());
|
||||
String json = JSON.toJSONString(spSaveModel);
|
||||
editor.putString(e, json);
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
public void setLong(String e, long value, int saveTime) {
|
||||
SpSaveModel<Long> spSaveModel = new SpSaveModel<>(saveTime, value, System.currentTimeMillis());
|
||||
String json = JSON.toJSONString(spSaveModel);
|
||||
editor.putString(e, json);
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
public long getLong(String e, long defValue) {
|
||||
String json = sp.getString(e, "");
|
||||
if (!TextUtils.isEmpty(json)) {
|
||||
SpSaveModel<Long> spSaveModel = JSON.parseObject(json, new TypeReference<SpSaveModel<Long>>() {
|
||||
});
|
||||
if (isTimeOut(spSaveModel.getCurrentTime(), spSaveModel.getSaveTime())) {
|
||||
return spSaveModel.getValue();
|
||||
}
|
||||
}
|
||||
return defValue;
|
||||
}
|
||||
|
||||
public boolean isTimeOut(long saveCurrentTime, int saveTime) {
|
||||
return (System.currentTimeMillis() - saveCurrentTime) / DURATION_UNIT < saveTime;
|
||||
}
|
||||
|
||||
public void set(String e, Object value, int saveTime) {
|
||||
SpSaveModel<Object> spSaveModel = new SpSaveModel<>(saveTime, value, System.currentTimeMillis());
|
||||
String json = JSON.toJSONString(spSaveModel);
|
||||
editor.putString(e, json);
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
}
|
||||
62
libcore/src/main/java/com/pukka/libcore/utils/StringUtil.java
Executable file
62
libcore/src/main/java/com/pukka/libcore/utils/StringUtil.java
Executable file
@@ -0,0 +1,62 @@
|
||||
package com.pukka.libcore.utils;
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public class StringUtil {
|
||||
|
||||
//获取mac地址
|
||||
public static String getLocalMac() {
|
||||
String mac = "";
|
||||
|
||||
mac= MacAddressUtil.getMacAddressFromFile("/sys/class/net/eth0/address");
|
||||
LogUtils.d("有线(eth0)mac:"+mac);
|
||||
if (!TextUtils.isEmpty(mac)) {
|
||||
//本地存有线mac
|
||||
if (!StringUtils.isEmpty(mac)) {
|
||||
SharedPreferenceUtils.put("ETH_MAC", mac.toLowerCase(Locale.getDefault()));
|
||||
}
|
||||
return mac.toLowerCase(Locale.getDefault());
|
||||
}
|
||||
|
||||
mac= MacAddressUtil.getLocalMacAddress();
|
||||
LogUtils.d("有线(LocalMac)mac:"+mac);
|
||||
if (!TextUtils.isEmpty(mac)) {
|
||||
return mac.toLowerCase(Locale.getDefault());
|
||||
}
|
||||
|
||||
mac= MacAddressUtil.getMacAddressFromFile("/sys/class/net/wlan0/address");
|
||||
LogUtils.d("无线mac:"+mac);
|
||||
if (!TextUtils.isEmpty(mac)) {
|
||||
return mac.toLowerCase(Locale.getDefault());
|
||||
}
|
||||
|
||||
mac = MacAddressUtil.getMacAddress();
|
||||
LogUtils.d("通过InetAddress mac:"+mac);
|
||||
if (!TextUtils.isEmpty(mac)) {
|
||||
return mac.toLowerCase(Locale.getDefault());
|
||||
}
|
||||
|
||||
mac = MacAddressUtil.getLocalMac("ifconfig eth0");
|
||||
LogUtils.d("通过ifconfig eth0 mac:"+mac);
|
||||
if (!TextUtils.isEmpty(mac)) {
|
||||
return mac.toLowerCase(Locale.getDefault());
|
||||
}
|
||||
|
||||
mac = MacAddressUtil.getLocalMac("ifconfig wlan0");
|
||||
LogUtils.d("通过ifconfig wlan0 mac:"+mac);
|
||||
if (!TextUtils.isEmpty(mac)) {
|
||||
return mac.toLowerCase(Locale.getDefault());
|
||||
}
|
||||
|
||||
mac = MacAddressUtil.getMachineHardwareAddress();
|
||||
LogUtils.d("通过MachineHardwareAddress mac:"+mac);
|
||||
if (!TextUtils.isEmpty(mac)) {
|
||||
return mac.toLowerCase(Locale.getDefault());
|
||||
}
|
||||
// 如果所有方法都失败,返回空字符串
|
||||
return "";
|
||||
}
|
||||
|
||||
}
|
||||
285
libcore/src/main/java/com/pukka/libcore/utils/StringUtils.java
Executable file
285
libcore/src/main/java/com/pukka/libcore/utils/StringUtils.java
Executable file
@@ -0,0 +1,285 @@
|
||||
package com.pukka.libcore.utils;
|
||||
|
||||
/**
|
||||
* Author: wxf
|
||||
* Date: 2024/8/8 14:00
|
||||
* Description:字符串相关工具类
|
||||
* (字符串拼接、判空、是否相等、类型转换等)
|
||||
*/
|
||||
public class StringUtils {
|
||||
|
||||
/**
|
||||
* 字符串拼接,线程安全
|
||||
* @param array 多个字符串
|
||||
* @return 拼接后的字符串
|
||||
*/
|
||||
public static String buffer(String... array) {
|
||||
StringBuffer s = new StringBuffer();
|
||||
for (String str : array) {
|
||||
s.append(str);
|
||||
}
|
||||
return s.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 字符串拼接,线程不安全,效率高
|
||||
* @param array 多个字符串
|
||||
* @return 拼接后的字符串
|
||||
*/
|
||||
public static String builder(String... array) {
|
||||
StringBuilder s = new StringBuilder();
|
||||
for (String str : array) {
|
||||
s.append(str);
|
||||
}
|
||||
return s.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断字符串是否为null或长度为0
|
||||
*
|
||||
* @param s 待校验字符串
|
||||
* @return {@code true}: 空<br> {@code false}: 不为空
|
||||
*/
|
||||
public static boolean isEmpty(CharSequence s) {
|
||||
return s == null || s.length() == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断字符串是否为null或全为空格
|
||||
*
|
||||
* @param s 待校验字符串
|
||||
* @return {@code true}: null或全空格<br> {@code false}: 不为null且不全空格
|
||||
*/
|
||||
public static boolean isSpace(String s) {
|
||||
return (s == null || s.trim().length() == 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断两字符串是否相等
|
||||
*
|
||||
* @param a 待校验字符串a
|
||||
* @param b 待校验字符串b
|
||||
* @return {@code true}: 相等<br>{@code false}: 不相等
|
||||
*/
|
||||
public static boolean equals(CharSequence a, CharSequence b) {
|
||||
if (a == b) {
|
||||
return true;
|
||||
}
|
||||
int length;
|
||||
if (a != null && b != null && (length = a.length()) == b.length()) {
|
||||
if (a instanceof String && b instanceof String) {
|
||||
return a.equals(b);
|
||||
} else {
|
||||
for (int i = 0; i < length; i++) {
|
||||
if (a.charAt(i) != b.charAt(i)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断两字符串忽略大小写是否相等
|
||||
*
|
||||
* @param a 待校验字符串a
|
||||
* @param b 待校验字符串b
|
||||
* @return {@code true}: 相等<br>{@code false}: 不相等
|
||||
*/
|
||||
public static boolean equalsIgnoreCase(String a, String b) {
|
||||
return (a == b) || (b != null) && (a.length() == b.length()) && a.regionMatches(true, 0, b, 0, b.length());
|
||||
}
|
||||
|
||||
/**
|
||||
* null转为长度为0的字符串
|
||||
*
|
||||
* @param str 待转字符串
|
||||
* @return s为null转为长度为0字符串,否则不改变
|
||||
*/
|
||||
public static String null2EmptyStr(String str) {
|
||||
return str == null ? "" : str;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回字符串长度
|
||||
*
|
||||
* @param str 字符串
|
||||
* @return null返回0,其他返回自身长度
|
||||
*/
|
||||
public static int getStrLength(CharSequence str) {
|
||||
return str == null ? 0 : str.length();
|
||||
}
|
||||
|
||||
/**
|
||||
* 转化为半角字符
|
||||
*
|
||||
* @param str 待转字符串
|
||||
* @return 半角字符串
|
||||
*/
|
||||
public static String str2HalfAngle(String str) {
|
||||
if (isEmpty(str)) {
|
||||
return str;
|
||||
}
|
||||
char[] chars = str.toCharArray();
|
||||
for (int i = 0, len = chars.length; i < len; i++) {
|
||||
if (chars[i] == 12288) {
|
||||
chars[i] = ' ';
|
||||
} else if (65281 <= chars[i] && chars[i] <= 65374) {
|
||||
chars[i] = (char) (chars[i] - 65248);
|
||||
} else {
|
||||
chars[i] = chars[i];
|
||||
}
|
||||
}
|
||||
return new String(chars);
|
||||
}
|
||||
|
||||
/**
|
||||
* 转化为全角字符
|
||||
*
|
||||
* @param str 待转字符串
|
||||
* @return 全角字符串
|
||||
*/
|
||||
public static String str2FullAngle(String str) {
|
||||
if (isEmpty(str)) {
|
||||
return str;
|
||||
}
|
||||
char[] chars = str.toCharArray();
|
||||
for (int i = 0, len = chars.length; i < len; i++) {
|
||||
if (chars[i] == ' ') {
|
||||
chars[i] = (char) 12288;
|
||||
} else if (33 <= chars[i] && chars[i] <= 126) {
|
||||
chars[i] = (char) (chars[i] + 65248);
|
||||
} else {
|
||||
chars[i] = chars[i];
|
||||
}
|
||||
}
|
||||
return new String(chars);
|
||||
}
|
||||
|
||||
/**
|
||||
* 数字字符串转换成Int
|
||||
*
|
||||
* @param numberStr 数字字符串
|
||||
* @return int类型数字
|
||||
*/
|
||||
public static int convertString2Int(String numberStr) {
|
||||
int number = 0;
|
||||
if (!isEmpty(numberStr)) {
|
||||
try {
|
||||
number = Integer.parseInt(numberStr);
|
||||
} catch (NumberFormatException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 数字字符串转换成Int
|
||||
*
|
||||
* @param numberStr 数字字符串
|
||||
* @param defaultNum 异常默认值
|
||||
* @return int类型数字
|
||||
*/
|
||||
public static int convertString2Int(String numberStr, int defaultNum) {
|
||||
int number = defaultNum;
|
||||
if (!isEmpty(numberStr)) {
|
||||
try {
|
||||
number = Integer.parseInt(numberStr);
|
||||
} catch (NumberFormatException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 数字字符串转换成Float
|
||||
*
|
||||
* @param numberStr 数字字符串
|
||||
* @return float类型数字
|
||||
*/
|
||||
public static float convertString2Float(String numberStr) {
|
||||
float number = 0;
|
||||
if (!isEmpty(numberStr)) {
|
||||
try {
|
||||
number = Float.parseFloat(numberStr);
|
||||
} catch (NumberFormatException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 数字字符串转换成Float
|
||||
*
|
||||
* @param numberStr 数字字符串
|
||||
* @param defaultNum 异常默认值
|
||||
* @return float类型数字
|
||||
*/
|
||||
public static float convertString2Float(String numberStr, float defaultNum) {
|
||||
float number = defaultNum;
|
||||
if (!isEmpty(numberStr)) {
|
||||
try {
|
||||
number = Float.parseFloat(numberStr);
|
||||
} catch (NumberFormatException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 数字字符串转换成Double
|
||||
*
|
||||
* @param numberStr 数字字符串
|
||||
* @return double类型数字
|
||||
*/
|
||||
public static double convertString2Double(String numberStr) {
|
||||
double number = 0;
|
||||
if (!isEmpty(numberStr)) {
|
||||
try {
|
||||
number = Double.parseDouble(numberStr);
|
||||
} catch (NumberFormatException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 数字字符串转换成Double
|
||||
*
|
||||
* @param numberStr 数字字符串
|
||||
* @param defaultNum 异常默认值
|
||||
* @return double类型数字
|
||||
*/
|
||||
public static double convertString2Double(String numberStr, double defaultNum) {
|
||||
double number = defaultNum;
|
||||
if (!isEmpty(numberStr)) {
|
||||
try {
|
||||
number = Double.parseDouble(numberStr);
|
||||
} catch (NumberFormatException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return number;
|
||||
}
|
||||
/**
|
||||
* 获取字符串字段的值,已做非空判断
|
||||
*
|
||||
* @param value 字符串
|
||||
* @return 字符串
|
||||
*/
|
||||
public static String getStringValue(String value) {
|
||||
if (isEmpty(value)) {
|
||||
return "";
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
76
libcore/src/main/java/com/pukka/libcore/viewmodel/BaseVM.java
Executable file
76
libcore/src/main/java/com/pukka/libcore/viewmodel/BaseVM.java
Executable file
@@ -0,0 +1,76 @@
|
||||
|
||||
package com.pukka.libcore.viewmodel;
|
||||
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.lifecycle.LifecycleOwner;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
import com.pukka.libcore.utils.ContextUtils;
|
||||
|
||||
|
||||
/**
|
||||
* @author wcg
|
||||
* @version 1.0
|
||||
* @description ViewModel基类
|
||||
* @createTime 2023/8/4 0004 12:26
|
||||
*/
|
||||
public class BaseVM extends ViewModel {
|
||||
|
||||
public static <T extends ViewModel> T getViewModel(Fragment fragment, Class<T> modelClass) {
|
||||
if (null == ContextUtils.getApplication()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new ViewModelProvider(fragment,
|
||||
ViewModelProvider.AndroidViewModelFactory.getInstance(ContextUtils.getApplication())).get(modelClass);
|
||||
}
|
||||
|
||||
public static <T extends ViewModel> T getSafeViewModel(Fragment fragment, Class<T> modelClass) {
|
||||
// fragment 被移除 或者未加入的时候
|
||||
if (fragment == null || fragment.getActivity() == null || fragment.isDetached()) {
|
||||
return null;
|
||||
}
|
||||
if (null == ContextUtils.getApplication()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new ViewModelProvider(fragment,
|
||||
ViewModelProvider.AndroidViewModelFactory.getInstance(ContextUtils.getApplication())).get(modelClass);
|
||||
}
|
||||
|
||||
public static <T extends ViewModel> T getViewModel(FragmentActivity activity, Class<T> modelClass) {
|
||||
if (null == ContextUtils.getApplication()) {
|
||||
return null;
|
||||
}
|
||||
return new ViewModelProvider(activity,
|
||||
ViewModelProvider.AndroidViewModelFactory.getInstance(ContextUtils.getApplication())).get(modelClass);
|
||||
}
|
||||
|
||||
public static <T extends ViewModel> T getSafeViewModel(LifecycleOwner lifecycleOwner, Class<T> modelClass) {
|
||||
if (lifecycleOwner instanceof Fragment) {
|
||||
// fragment 被移除 或者未加入的时候
|
||||
if (((Fragment) lifecycleOwner).getActivity() == null || ((Fragment) lifecycleOwner).isDetached()) {
|
||||
return null;
|
||||
}
|
||||
return getViewModel((Fragment) lifecycleOwner, modelClass);
|
||||
} else if (lifecycleOwner instanceof FragmentActivity) {
|
||||
return getViewModel(((FragmentActivity) lifecycleOwner), modelClass);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static <T extends ViewModel> T getViewModel(LifecycleOwner lifecycleOwner, Class<T> modelClass) {
|
||||
if (lifecycleOwner instanceof Fragment) {
|
||||
// fragment 被移除 或者未加入的时候
|
||||
if (((Fragment) lifecycleOwner).getActivity() == null) {
|
||||
return null;
|
||||
}
|
||||
return getViewModel((Fragment) lifecycleOwner, modelClass);
|
||||
} else if (lifecycleOwner instanceof FragmentActivity) {
|
||||
return getViewModel(((FragmentActivity) lifecycleOwner), modelClass);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
39
libcore/src/main/java/com/pukka/libcore/widget/AlwaysMarqueeTextView.java
Executable file
39
libcore/src/main/java/com/pukka/libcore/widget/AlwaysMarqueeTextView.java
Executable file
@@ -0,0 +1,39 @@
|
||||
package com.pukka.libcore.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Rect;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import androidx.appcompat.widget.AppCompatTextView;
|
||||
|
||||
/**
|
||||
* Author: wxf
|
||||
* Date: 2024/9/13 17:44
|
||||
* Description:无焦点一直滚动TextView控件
|
||||
* 主要场景:头部通知,标题滚动等等
|
||||
*/
|
||||
public class AlwaysMarqueeTextView extends AppCompatTextView {
|
||||
public AlwaysMarqueeTextView(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public AlwaysMarqueeTextView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public AlwaysMarqueeTextView(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFocused() {
|
||||
return true;
|
||||
}
|
||||
@Override
|
||||
protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWindowFocusChanged(boolean hasWindowFocus) {
|
||||
}
|
||||
}
|
||||
122
libcore/src/main/java/com/pukka/libcore/widget/BrowserWebView.java
Executable file
122
libcore/src/main/java/com/pukka/libcore/widget/BrowserWebView.java
Executable file
@@ -0,0 +1,122 @@
|
||||
package com.pukka.libcore.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.webkit.WebSettings;
|
||||
import android.webkit.WebView;
|
||||
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import com.pukka.libcore.utils.LogUtils;
|
||||
|
||||
/**
|
||||
* WebView封装
|
||||
*/
|
||||
public class BrowserWebView extends WebView {
|
||||
|
||||
|
||||
public BrowserWebView(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public BrowserWebView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public BrowserWebView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
clearCache(true);
|
||||
clearFormData();
|
||||
clearMatches();
|
||||
clearSslPreferences();
|
||||
clearDisappearingChildren();
|
||||
clearHistory();
|
||||
clearAnimation();
|
||||
loadUrl("about:blank");
|
||||
removeAllViews();
|
||||
|
||||
if (getParent() != null && (ViewGroup) getParent() != null) {
|
||||
((ViewGroup) getParent()).removeView(this);
|
||||
}
|
||||
super.destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置webview的焦点等信息
|
||||
*/
|
||||
public void setViewFocus() {
|
||||
setFocusable(true);
|
||||
setNextFocusUpId(this.getId());
|
||||
setNextFocusDownId(this.getId());
|
||||
setNextFocusLeftId(this.getId());
|
||||
setNextFocusRightId(this.getId());
|
||||
setBackgroundColor(ContextCompat.getColor(getContext(), android.R.color.transparent));
|
||||
setBackgroundResource(android.R.color.transparent);
|
||||
//防止开启硬件加速之后闪烁(Manifest里面 hardwareAccelerated=true 开起加速)
|
||||
//方法打开之后,浙江盒子卡顿
|
||||
}
|
||||
|
||||
public void setWebSpeed(String speed) {
|
||||
LogUtils.d("setWebSpeed -- speed : " + speed);
|
||||
if ("0".equals(speed)) {
|
||||
LogUtils.d("setWebSpeed -- LAYER_TYPE_HARDWARE ");
|
||||
setLayerType(View.LAYER_TYPE_HARDWARE, null);
|
||||
} else {
|
||||
LogUtils.d("setWebSpeed -- LAYER_TYPE_SOFTWARE ");
|
||||
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置webView
|
||||
*/
|
||||
public void initConfig() {
|
||||
WebSettings webSettings = this.getSettings();
|
||||
//适配页面放大
|
||||
setInitialScale(100);
|
||||
//如果访问的页面中要与Javascript交互,则webview必须设置支持Javascript
|
||||
webSettings.setJavaScriptEnabled(true);
|
||||
//设置自适应屏幕,两者合用
|
||||
webSettings.setUseWideViewPort(true); //将图片调整到适合webview的大小
|
||||
webSettings.setLoadWithOverviewMode(true); // 缩放至屏幕的大小
|
||||
// 安全
|
||||
webSettings.setSavePassword(false); // false不许明文密码保存
|
||||
webSettings.setAllowFileAccess(false); // false不许使用file协议
|
||||
if (Build.VERSION.SDK_INT >= 16) {
|
||||
webSettings.setAllowFileAccessFromFileURLs(false); // false为js不许读取本地文件, api≤16:默认允许, api≥17:默认关闭
|
||||
webSettings.setAllowUniversalAccessFromFileURLs(false); // false为js不许读取其他资源链接, api≤16:默认允许, api≥17:默认关闭
|
||||
}
|
||||
// >= 5.0的版本添加支持 https 和 http 混合加载
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
|
||||
}
|
||||
//设置编码格式
|
||||
webSettings.setDefaultTextEncodingName("UTF-8");
|
||||
//设置webview中缓存
|
||||
// webSettings.setCacheMode(WebSettings.LOAD_NO_CACHE);
|
||||
webSettings.setCacheMode(WebSettings.LOAD_DEFAULT);
|
||||
//设置可以访问文件
|
||||
webSettings.setAllowFileAccess(true);
|
||||
//支持通过JS打开新窗口
|
||||
webSettings.setJavaScriptCanOpenWindowsAutomatically(true);
|
||||
//缩放操作
|
||||
webSettings.setSupportZoom(false); //不支持缩放
|
||||
// 开启 DOM storage API 功能
|
||||
webSettings.setDomStorageEnabled(true);
|
||||
//开启 database storage API 功能
|
||||
webSettings.setDatabaseEnabled(true);
|
||||
}
|
||||
@Override
|
||||
public boolean dispatchKeyEvent(KeyEvent event) {
|
||||
LogUtils.d("dispatchKeyEvent -- webview : " + event.getKeyCode());
|
||||
return super.dispatchKeyEvent(event);
|
||||
}
|
||||
}
|
||||
525
libcore/src/main/java/com/pukka/libcore/widget/ContentRecyclerViewTv.java
Executable file
525
libcore/src/main/java/com/pukka/libcore/widget/ContentRecyclerViewTv.java
Executable file
@@ -0,0 +1,525 @@
|
||||
package com.pukka.libcore.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Rect;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.FocusFinder;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.GridLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.pukka.libcore.boxShared.BoxSharedParamsUtils;
|
||||
import com.pukka.libcore.utils.LogUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* @Author:wxf
|
||||
* @Date:2025-05-30
|
||||
* @Description: 内容列表控件
|
||||
*/
|
||||
public class ContentRecyclerViewTv extends RecyclerView {
|
||||
public final static String TAG = "ContentRecyclerView";
|
||||
private int mSelectedPosition = 0;
|
||||
/**
|
||||
* 子adapter当前选中的position
|
||||
*/
|
||||
public int mChildAdapterPosition;
|
||||
protected int screenWidth;
|
||||
protected int screenHeight;
|
||||
protected int currentKeyCode;
|
||||
/**
|
||||
* 焦点是否竖向居中
|
||||
*/
|
||||
protected boolean isVerticalCenter = false;
|
||||
/**
|
||||
* 是否拦截TOP焦点 默认 不拦截
|
||||
* */
|
||||
protected boolean isTopIntercept = false;
|
||||
/**
|
||||
* 是否拦截BOTTOM焦点 默认 不拦截
|
||||
* */
|
||||
protected boolean isBottomIntercept =false;
|
||||
/**
|
||||
* 是否焦点长按
|
||||
* */
|
||||
private boolean isLongKey = false;
|
||||
|
||||
/**
|
||||
* 失去焦点的监听
|
||||
* */
|
||||
private IonFocusLostListener mFocusLostListener;
|
||||
/**
|
||||
* 获取焦点的监听
|
||||
* */
|
||||
private IonFocusGainListener mFocusGainListener;
|
||||
|
||||
/**
|
||||
* 列表页向下滑动,数据加载中,焦点禁止滑动
|
||||
* */
|
||||
private boolean isPageLoading = false;
|
||||
|
||||
private long lastKeyPressTime = 0;
|
||||
private final long KEY_PRESS_DELAY = 300; // 300毫秒延迟
|
||||
|
||||
/**
|
||||
* 是否对焦点速度进行限制
|
||||
* */
|
||||
private boolean isFocusMoveSpeed = false;
|
||||
/**
|
||||
* 是否需要分页
|
||||
* 默认需要分页加载
|
||||
* */
|
||||
private boolean isNeedPage = true;
|
||||
|
||||
/**
|
||||
* 设置向下滑动的偏移量
|
||||
* */
|
||||
private int downOffset;
|
||||
/**
|
||||
* 设置向上滑动的偏移量
|
||||
* */
|
||||
private int upOffset;
|
||||
|
||||
|
||||
|
||||
public ContentRecyclerViewTv(@NonNull Context context) {
|
||||
super(context);
|
||||
init(context);
|
||||
}
|
||||
/**
|
||||
* 设置Top拦截,移动到TOP选项,焦点按上不移动
|
||||
* */
|
||||
public void setTopIntercept() {
|
||||
isTopIntercept = true;
|
||||
}
|
||||
/**
|
||||
* 设置Bottom拦截,移动到BOTTOM选项,焦点按下不移动
|
||||
* */
|
||||
public void setBottomIntercept() {
|
||||
isBottomIntercept = true;
|
||||
}
|
||||
/**
|
||||
* 对焦点移动速度进行限制
|
||||
* */
|
||||
public void setFocusMoveSpeedLimit(){
|
||||
isFocusMoveSpeed = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否需要分页
|
||||
* true 需要分页加载滑动
|
||||
* false 不需要
|
||||
* */
|
||||
public void setNeedPage(boolean needPage) {
|
||||
isNeedPage = needPage;
|
||||
}
|
||||
|
||||
public ContentRecyclerViewTv(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init(context);
|
||||
}
|
||||
|
||||
public ContentRecyclerViewTv(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init(context);
|
||||
}
|
||||
|
||||
private void init(Context context) {
|
||||
this.screenWidth = context.getResources().getDisplayMetrics().widthPixels;
|
||||
this.screenHeight = context.getResources().getDisplayMetrics().heightPixels;
|
||||
// 先分发给Child View进行处理,如果所有的Child View都没有处理,则自己再处理
|
||||
//setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
|
||||
this.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
|
||||
setHasFixedSize(true);
|
||||
setWillNotDraw(true);
|
||||
//滑动到边界,不出现光晕
|
||||
setOverScrollMode(View.OVER_SCROLL_NEVER);
|
||||
// 改变子类的绘制顺序就要重写该方法
|
||||
setChildrenDrawingOrderEnabled(true);
|
||||
setClipChildren(false);
|
||||
setClipToPadding(false);
|
||||
setClickable(false);
|
||||
setFocusable(false);
|
||||
|
||||
}
|
||||
/**
|
||||
* 请求布局系统将指定子视图的指定区域滚动到屏幕上
|
||||
*
|
||||
* */
|
||||
@Override
|
||||
public boolean requestChildRectangleOnScreen(View child, Rect rect, boolean immediate) {
|
||||
View focusView = child.findFocus();
|
||||
int[] focusViewLocation = new int[2];
|
||||
focusView.getLocationOnScreen(focusViewLocation);
|
||||
// 获取RecyclerView在父容器中的位置
|
||||
int[] location = new int[2];
|
||||
this.getLocationOnScreen(location);
|
||||
//获取recyclerView到顶部的距离
|
||||
int recyclerViewTop = location[1];
|
||||
// 获取RecyclerView的可见高度
|
||||
int recyclerViewHeight = this.getHeight();
|
||||
// 子控件居中Y坐标
|
||||
int focusViewCenterY = focusViewLocation[1] + focusView.getHeight() / 2;
|
||||
//RecyclerView的居中的坐标
|
||||
int centerScreenH = recyclerViewTop + (recyclerViewHeight / 2);
|
||||
int dy = 0;
|
||||
int position = getPositionByView(child);
|
||||
switch (currentKeyCode) {
|
||||
case KeyEvent.KEYCODE_DPAD_UP:
|
||||
if (isVerticalCenter) {
|
||||
if (centerScreenH > focusViewCenterY && this.canScrollVertically(-1)) {
|
||||
dy = focusViewCenterY - centerScreenH;
|
||||
if (Math.abs(dy) > this.computeVerticalScrollOffset()) {
|
||||
dy = -this.computeVerticalScrollOffset();
|
||||
}
|
||||
dy = dy -upOffset;
|
||||
if(position == 0){
|
||||
//滑动到顶部的时候,置顶
|
||||
dy = -this.computeVerticalScrollOffset();
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case KeyEvent.KEYCODE_DPAD_DOWN:
|
||||
if (isVerticalCenter) {
|
||||
if (focusViewCenterY > centerScreenH && this.canScrollVertically(1)) {
|
||||
dy = focusViewCenterY - centerScreenH;
|
||||
int canScrollOffsetY = this.computeVerticalScrollRange() - this.computeVerticalScrollExtent() - this.computeVerticalScrollOffset();
|
||||
if (dy > canScrollOffsetY) {
|
||||
dy = canScrollOffsetY;
|
||||
}
|
||||
}
|
||||
dy = dy + downOffset;
|
||||
if(position == 0) {
|
||||
//第一行
|
||||
dy = 0;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
this.currentKeyCode = -1;
|
||||
LogUtils.d("wxf","focusViewCenterY="+focusViewCenterY+" centerScreenH="+centerScreenH+" dy="+dy);
|
||||
LogUtils.d("wxf","recyclerViewTop="+recyclerViewTop+" recyclerViewHeight="+recyclerViewHeight);
|
||||
if (immediate) {
|
||||
scrollBy(0, dy);
|
||||
} else {
|
||||
smoothScrollBy(0, dy);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据子View获取Position
|
||||
* @param view 列表项
|
||||
* @return 获取Position
|
||||
*/
|
||||
protected int getPositionByView(View view) {
|
||||
if (view == null) {
|
||||
return NO_POSITION;
|
||||
}
|
||||
LayoutParams params = (LayoutParams) view.getLayoutParams();
|
||||
if (params == null || params.isItemRemoved()) {
|
||||
// when item is removed, the position value can be any value.
|
||||
return NO_POSITION;
|
||||
}
|
||||
return params.getViewPosition();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean dispatchKeyEvent(KeyEvent event) {
|
||||
View nextFocusView = null;
|
||||
boolean consumed = false;
|
||||
LogUtils.d("22手柄按键:"+event.getKeyCode());
|
||||
if(BoxSharedParamsUtils.isJcCloud() || BoxSharedParamsUtils.isJcHotel()){
|
||||
if(event.getKeyCode() == 4 || event.getKeyCode() == 97 || event.getKeyCode() == 96 || event.getKeyCode() == 107) {
|
||||
LogUtils.d("22手柄按键:"+event.getKeyCode());
|
||||
return super.dispatchKeyEvent(event);
|
||||
}
|
||||
}
|
||||
if(isFocusMoveSpeed) {
|
||||
boolean isSpeedFast = recyclerFocusSpeed(event.getKeyCode(), event);
|
||||
if (isSpeedFast) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
View mVideoFocusView = getFocusedChild();
|
||||
mChildAdapterPosition = getLayoutManager().getPosition(mVideoFocusView);
|
||||
LogUtils.i("mChildAdapterPosition:" + mChildAdapterPosition);
|
||||
switch (event.getAction()) {
|
||||
case KeyEvent.ACTION_DOWN: {
|
||||
switch (event.getKeyCode()) {
|
||||
case KeyEvent.KEYCODE_DPAD_UP:
|
||||
int repeatCount = event.getRepeatCount();
|
||||
if (repeatCount > 0) {
|
||||
isLongKey = true;
|
||||
} else {
|
||||
isLongKey = false;
|
||||
}
|
||||
nextFocusView = FocusFinder.getInstance().findNextFocus(this, findFocus(), FOCUS_UP);
|
||||
if (nextFocusView != null) {
|
||||
currentKeyCode = event.getKeyCode();
|
||||
} else {
|
||||
LogUtils.d("nextFocusView isTopIntercept=" + isTopIntercept);
|
||||
View view = this.getFocusedChild();
|
||||
int pos = this.getPositionByView(view);
|
||||
LogUtils.d("nextFocusView pos=" + pos);
|
||||
//列表在最上边,则禁止焦点移动
|
||||
if (isTopIntercept && pos == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case KeyEvent.KEYCODE_DPAD_DOWN:
|
||||
repeatCount = event.getRepeatCount();
|
||||
if (repeatCount > 0) {
|
||||
isLongKey = true;
|
||||
} else {
|
||||
isLongKey = false;
|
||||
}
|
||||
nextFocusView = FocusFinder.getInstance().findNextFocus(this, findFocus(), FOCUS_DOWN);
|
||||
if (nextFocusView != null) {
|
||||
currentKeyCode = event.getKeyCode();
|
||||
}else {
|
||||
LogUtils.d("nextFocusView isBottomIntercept=" + isBottomIntercept);
|
||||
//列表在最右边,则禁止焦点移动
|
||||
View view = this.getFocusedChild();
|
||||
int pos = this.getPositionByView(view);
|
||||
LogUtils.d("nextFocusView pos=" + pos);
|
||||
int size = this.getAdapter().getItemCount();
|
||||
LogUtils.d("nextFocusView size=" + this.getAdapter().getItemCount());
|
||||
if (isBottomIntercept && pos == (size - 1)) {
|
||||
return true;
|
||||
}
|
||||
LogUtils.d("isPageLoading "+isPageLoading);
|
||||
LogUtils.d("nextFocusView= "+nextFocusView);
|
||||
if(isPageLoading){
|
||||
LogUtils.d("页面正在加载中,禁止滑动 isPageLoading ");
|
||||
return true;
|
||||
}
|
||||
if(nextFocusView == null){
|
||||
if(!isNeedPage){
|
||||
LogUtils.d("页面不需要分页,数据在一页中展示,焦点去其他地方");
|
||||
return false;
|
||||
}
|
||||
if (getLayoutManager() instanceof GridLayoutManager) {
|
||||
int spanCount = ((GridLayoutManager)getLayoutManager()).getSpanCount();
|
||||
LogUtils.d("scrollToAndRequestFocus() =="+mChildAdapterPosition + spanCount);
|
||||
scrollToAndRequestFocus(mChildAdapterPosition + spanCount);
|
||||
}
|
||||
LogUtils.i("keyEvent拦截======下");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case KeyEvent.KEYCODE_DPAD_RIGHT:
|
||||
isLongKey =false;
|
||||
nextFocusView = FocusFinder.getInstance().findNextFocus(this, findFocus(), FOCUS_RIGHT);
|
||||
LogUtils.d("nextFocusView=" + nextFocusView);
|
||||
if (nextFocusView != null) {
|
||||
currentKeyCode = event.getKeyCode();
|
||||
}
|
||||
break;
|
||||
case KeyEvent.KEYCODE_DPAD_LEFT:
|
||||
isLongKey =false;
|
||||
nextFocusView = FocusFinder.getInstance().findNextFocus(this, findFocus(), FOCUS_LEFT);
|
||||
if (nextFocusView != null) {
|
||||
currentKeyCode = event.getKeyCode();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return consumed || super.dispatchKeyEvent(event);
|
||||
}
|
||||
/**
|
||||
* 滚动到并落焦到某个位置
|
||||
*
|
||||
* @param position Scroll to this adapter position
|
||||
*/
|
||||
public void scrollToAndRequestFocus(int position) {
|
||||
mSelectedPosition = position;
|
||||
|
||||
scrollToPosition(position);
|
||||
|
||||
post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
View view = getLayoutManager().findViewByPosition(position);
|
||||
view.requestFocus();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
public boolean recyclerFocusSpeed(int keyCode,KeyEvent event){
|
||||
if (event.getAction() == KeyEvent.ACTION_DOWN) {
|
||||
long currentTime = System.currentTimeMillis();
|
||||
LogUtils.d("获取当前时间 currentTime="+currentTime);
|
||||
if (currentTime - lastKeyPressTime < KEY_PRESS_DELAY) {
|
||||
LogUtils.d("拦截过快按键");
|
||||
return true; // 拦截过快按键
|
||||
}
|
||||
LogUtils.d("设置最新按键时间 lastKeyPressTime="+lastKeyPressTime);
|
||||
lastKeyPressTime = currentTime;
|
||||
// 处理方向键
|
||||
if (keyCode == KeyEvent.KEYCODE_DPAD_UP || keyCode == KeyEvent.KEYCODE_DPAD_DOWN
|
||||
|| keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
|
||||
// 让系统处理焦点移动
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestChildFocus(View child, View focused) {
|
||||
LogUtils.d("nextChild=" + focused + ",focusView=" + focused);
|
||||
if (!hasFocus()) {
|
||||
if (this.mFocusGainListener != null) {
|
||||
this.mFocusGainListener.onGainLost(child, focused);
|
||||
}
|
||||
}
|
||||
super.requestChildFocus(child, focused);
|
||||
}
|
||||
|
||||
//覆写focusSearch寻焦策略
|
||||
@Override
|
||||
public View focusSearch(View focused, int direction) {
|
||||
LogUtils.d("focusView=" + focused + ",direction=" + direction);
|
||||
View view = super.focusSearch(focused, direction);
|
||||
if (focused == null) {
|
||||
LogUtils.d("focusView1=");
|
||||
return view;
|
||||
}
|
||||
if (view != null) {
|
||||
//该方法返回焦点view所在的父view,如果是在recyclerview之外,就会是null.所以根据是否是null,来判断是否是移出了recyclerview
|
||||
View nextView = findContainingItemView(view);
|
||||
if (nextView == null) {
|
||||
if (this.mFocusLostListener != null) {
|
||||
View lView = mFocusLostListener.onFocusLost(focused, direction);
|
||||
if (lView != null) {
|
||||
return lView;
|
||||
}
|
||||
}
|
||||
LogUtils.d( "ContentRecyclerViewTv lost focus");
|
||||
return view;
|
||||
}
|
||||
}
|
||||
LogUtils.d( "focusView3=");
|
||||
return view;
|
||||
}
|
||||
// @Override
|
||||
// public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
|
||||
// View lastView = getLayoutManager().findViewByPosition(mLastFocusPosition);
|
||||
// LogUtils.d("requestFocus = " + lastView + "mLastFocusPosition=" + mLastFocusPosition);
|
||||
// if (lastView == null) {
|
||||
// lastView = getLayoutManager().findViewByPosition(0);
|
||||
// }
|
||||
// if (lastView != null) {
|
||||
// lastView.requestFocus();
|
||||
// }
|
||||
// return false;
|
||||
// }
|
||||
// @Override
|
||||
// public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
|
||||
//// View view = null;
|
||||
//// view = getLayoutManager().findViewByPosition(this.mLastFocusPosition);
|
||||
//// Boolean hasF = this.hasFocus();
|
||||
//// LogUtils.d("hasFocus=" + hasF + ",this.mLastFocusPosition=" + this.mLastFocusPosition + ",view=" + view);
|
||||
//// if (this.hasFocus() || this.mLastFocusPosition < 0 || view == null) {
|
||||
//// LogUtils.d("1");
|
||||
//// super.addFocusables(views, direction, focusableMode);
|
||||
//// } else if (view.isFocusable()) {
|
||||
//// LogUtils.d("2");
|
||||
//// views.add(view);
|
||||
//// } else {
|
||||
//// LogUtils.d("3");
|
||||
//// super.addFocusables(views, direction, focusableMode);
|
||||
//// }
|
||||
// }
|
||||
|
||||
/**
|
||||
* 设置焦点横向居中
|
||||
*/
|
||||
public void setVerticalCenter() {
|
||||
isVerticalCenter = true;
|
||||
}
|
||||
/**
|
||||
* 焦点移动是否是长按操作
|
||||
* @return true 长按 false 短按
|
||||
* */
|
||||
public boolean isLongKey() {
|
||||
return isLongKey;
|
||||
}
|
||||
public void setIsPageLoading(boolean isPageLoading){
|
||||
this.isPageLoading = isPageLoading;
|
||||
}
|
||||
/**
|
||||
* 设置控件整体失去焦点的监控
|
||||
* */
|
||||
public void setInFocusLostListener(IonFocusLostListener f) {
|
||||
this.mFocusLostListener = f;
|
||||
}
|
||||
/**
|
||||
* 控件整体获得焦点的监控
|
||||
* */
|
||||
public void setInFocusGainListener(IonFocusGainListener f) {
|
||||
this.mFocusGainListener = f;
|
||||
}
|
||||
/**
|
||||
* 控件整体失去焦点的监控
|
||||
* */
|
||||
public interface IonFocusLostListener {
|
||||
View onFocusLost(View lastFocusView, int direction);
|
||||
}
|
||||
/**
|
||||
* 控件整体获得焦点的监控
|
||||
* */
|
||||
public interface IonFocusGainListener {
|
||||
void onGainLost(View child, View focused);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置向下的偏移量
|
||||
* */
|
||||
public void setDownOffset(int downOffset) {
|
||||
this.downOffset = downOffset;
|
||||
}
|
||||
/**
|
||||
* 设置向上的偏移量
|
||||
* */
|
||||
public void setUpOffset(int upOffset) {
|
||||
this.upOffset = upOffset;
|
||||
}
|
||||
// public View findLastFocusView() {
|
||||
// if (mLastFocusPosition >= getAdapter().getItemCount()) {
|
||||
// mLastFocusPosition = 0;
|
||||
// }
|
||||
// if(mLastFocusPosition == -1){
|
||||
// mLastFocusPosition = 0;
|
||||
// }
|
||||
// View view = getLayoutManager().findViewByPosition(mLastFocusPosition);
|
||||
// if(view == null){
|
||||
// //找不到焦点,默认第一个
|
||||
// mLastFocusPosition = 0;
|
||||
// view = getLayoutManager().findViewByPosition(mLastFocusPosition);
|
||||
// }
|
||||
// return view;
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
|
||||
package com.pukka.libcore.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Rect;
|
||||
import android.view.View;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.pukka.libcore.utils.LogUtils;
|
||||
|
||||
/**
|
||||
* Author: wxf
|
||||
* Date: 2024/9/12 16:19
|
||||
* Description:横向列表控件 线性布局管理类
|
||||
* 支持焦点移动监听
|
||||
*/
|
||||
public class HorizontalLinearLayoutManagerTv extends LinearLayoutManager {
|
||||
|
||||
private IonFocusSearchListener focusSearchListener;
|
||||
|
||||
public HorizontalLinearLayoutManagerTv(Context context, int orientation, boolean reverseLayout) {
|
||||
super(context, orientation, reverseLayout);
|
||||
}
|
||||
@Override
|
||||
public boolean requestChildRectangleOnScreen(@NonNull RecyclerView parent, @NonNull View child, @NonNull Rect rect,
|
||||
boolean immediate, boolean focusedChildVisible) {
|
||||
if (parent instanceof HorizontalRecyclerViewTv) {
|
||||
parent.requestChildRectangleOnScreen(child, rect, immediate);
|
||||
}
|
||||
return super.requestChildRectangleOnScreen(parent, child, rect, immediate, focusedChildVisible);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onInterceptFocusSearch(@NonNull View focused, int direction) {
|
||||
LogUtils.i("direction=" + direction + " focusedView=" + focused);
|
||||
if(null != focusSearchListener){
|
||||
focusSearchListener.onInterceptFocusSearch(focused,direction);
|
||||
}
|
||||
return super.onInterceptFocusSearch(focused, direction);
|
||||
}
|
||||
/**
|
||||
* 设置列表项焦点监听
|
||||
* @param onInterceptFocusSearch 焦点拦截事件
|
||||
* */
|
||||
public void setOnInterceptFocusSearchListener(IonFocusSearchListener onInterceptFocusSearch){
|
||||
this.focusSearchListener = onInterceptFocusSearch;
|
||||
}
|
||||
/**
|
||||
* 横向列表焦点移动监听
|
||||
* */
|
||||
public interface IonFocusSearchListener{
|
||||
/**
|
||||
* 横向列表焦点移动监听
|
||||
* @param direction 焦点移动方向
|
||||
* @param focused 焦点
|
||||
* */
|
||||
void onInterceptFocusSearch(@NonNull View focused, int direction);
|
||||
}
|
||||
}
|
||||
239
libcore/src/main/java/com/pukka/libcore/widget/HorizontalRecyclerViewTv.java
Executable file
239
libcore/src/main/java/com/pukka/libcore/widget/HorizontalRecyclerViewTv.java
Executable file
@@ -0,0 +1,239 @@
|
||||
package com.pukka.libcore.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Rect;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.FocusFinder;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import com.pukka.libcore.utils.LogUtils;
|
||||
|
||||
/**
|
||||
* Author: wxf
|
||||
* Date: 2024/9/12 16:19
|
||||
* Description:横向列表控件 支持焦点是否横向剧中,支持最左侧和最右侧焦点拦截
|
||||
*/
|
||||
public class HorizontalRecyclerViewTv extends RecyclerView {
|
||||
protected int screenWidth;
|
||||
protected int screenHeight;
|
||||
protected int currentKeyCode;
|
||||
/**
|
||||
* 焦点是否横向居中
|
||||
*/
|
||||
protected boolean isHorizontalCenter = false;
|
||||
/**
|
||||
* 是否拦截最右侧焦点 默认 不拦截
|
||||
* */
|
||||
protected boolean isRightIntercept = false;
|
||||
/**
|
||||
* 是否拦截最左侧焦点 默认 不拦截
|
||||
* */
|
||||
protected boolean isLeftIntercept =false;
|
||||
/**
|
||||
* 是否焦点长按
|
||||
* */
|
||||
private boolean isLongKey = false;
|
||||
|
||||
|
||||
public HorizontalRecyclerViewTv(@NonNull Context context) {
|
||||
super(context);
|
||||
init(context);
|
||||
}
|
||||
/**
|
||||
* 设置最左侧拦截,移动到最左侧,焦点按左不移动
|
||||
* */
|
||||
public void setLeftIntercept() {
|
||||
isLeftIntercept = true;
|
||||
}
|
||||
/**
|
||||
* 设置最右侧拦截,移动到最右侧,焦点按右不移动
|
||||
* */
|
||||
public void setRightIntercept() {
|
||||
isRightIntercept = true;
|
||||
}
|
||||
|
||||
public HorizontalRecyclerViewTv(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init(context);
|
||||
}
|
||||
|
||||
public HorizontalRecyclerViewTv(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init(context);
|
||||
}
|
||||
|
||||
private void init(Context context) {
|
||||
this.screenWidth = context.getResources().getDisplayMetrics().widthPixels;
|
||||
this.screenHeight = context.getResources().getDisplayMetrics().heightPixels;
|
||||
// 先分发给Child View进行处理,如果所有的Child View都没有处理,则自己再处理
|
||||
setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
|
||||
setHasFixedSize(true);
|
||||
setWillNotDraw(true);
|
||||
//滑动到边界,不出现光晕
|
||||
setOverScrollMode(View.OVER_SCROLL_NEVER);
|
||||
// 改变子类的绘制顺序就要重写该方法
|
||||
setChildrenDrawingOrderEnabled(true);
|
||||
setClipChildren(false);
|
||||
setClipToPadding(false);
|
||||
setClickable(true);
|
||||
setFocusable(true);
|
||||
setFocusableInTouchMode(true);
|
||||
}
|
||||
/**
|
||||
* 请求布局系统将指定子视图的指定区域滚动到屏幕上
|
||||
*
|
||||
* */
|
||||
@Override
|
||||
public boolean requestChildRectangleOnScreen(View child, Rect rect, boolean immediate) {
|
||||
View focusView = child.findFocus();
|
||||
int[] focusViewLocation = new int[2];
|
||||
focusView.getLocationOnScreen(focusViewLocation);
|
||||
int focusViewCenterX = focusViewLocation[0] + focusView.getWidth() / 2;
|
||||
//RecyclerView显示的区域宽度一半 computeHorizontalScrollExtent【水平滚动视图的宽度】
|
||||
int centerScreenW = this.computeHorizontalScrollExtent() / 2;
|
||||
int dx = 0;
|
||||
switch (currentKeyCode) {
|
||||
case KeyEvent.KEYCODE_DPAD_LEFT:
|
||||
if (isHorizontalCenter) {
|
||||
if (centerScreenW > focusViewCenterX && this.canScrollHorizontally(-1)) {
|
||||
dx = focusViewCenterX - centerScreenW;
|
||||
if (Math.abs(dx) > this.computeHorizontalScrollOffset()) {
|
||||
dx = -this.computeHorizontalScrollOffset();
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case KeyEvent.KEYCODE_DPAD_RIGHT:
|
||||
if (isHorizontalCenter) {
|
||||
if (focusViewCenterX > centerScreenW && this.canScrollHorizontally(1)) {
|
||||
dx = focusViewCenterX - centerScreenW;
|
||||
int canScrollOffsetX = this.computeHorizontalScrollRange() - this.computeHorizontalScrollExtent() - this.computeHorizontalScrollOffset();
|
||||
if (dx > canScrollOffsetX) {
|
||||
dx = canScrollOffsetX;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
this.currentKeyCode = -1;
|
||||
if (immediate) {
|
||||
scrollBy(dx, 0);
|
||||
} else {
|
||||
smoothScrollBy(dx, 0);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据子View获取Position
|
||||
* @param view 列表项
|
||||
* @return 获取Position
|
||||
*/
|
||||
protected int getPositionByView(View view) {
|
||||
if (view == null) {
|
||||
return NO_POSITION;
|
||||
}
|
||||
LayoutParams params = (LayoutParams) view.getLayoutParams();
|
||||
if (params == null || params.isItemRemoved()) {
|
||||
// when item is removed, the position value can be any value.
|
||||
return NO_POSITION;
|
||||
}
|
||||
return params.getViewPosition();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean dispatchKeyEvent(KeyEvent event) {
|
||||
View nextFocusView = null;
|
||||
boolean consumed = false;
|
||||
switch (event.getAction()) {
|
||||
case KeyEvent.ACTION_DOWN: {
|
||||
switch (event.getKeyCode()) {
|
||||
case KeyEvent.KEYCODE_DPAD_UP:
|
||||
nextFocusView = FocusFinder.getInstance().findNextFocus(this, findFocus(), FOCUS_UP);
|
||||
if (nextFocusView != null) {
|
||||
currentKeyCode = event.getKeyCode();
|
||||
}
|
||||
break;
|
||||
case KeyEvent.KEYCODE_DPAD_DOWN:
|
||||
nextFocusView = FocusFinder.getInstance().findNextFocus(this, findFocus(), FOCUS_DOWN);
|
||||
if (nextFocusView != null) {
|
||||
currentKeyCode = event.getKeyCode();
|
||||
}
|
||||
break;
|
||||
case KeyEvent.KEYCODE_DPAD_RIGHT:
|
||||
int repeatCount = event.getRepeatCount();
|
||||
if (repeatCount > 0) {
|
||||
isLongKey = true;
|
||||
} else {
|
||||
isLongKey = false;
|
||||
}
|
||||
nextFocusView = FocusFinder.getInstance().findNextFocus(this, findFocus(), FOCUS_RIGHT);
|
||||
LogUtils.d("nextFocusView=" + nextFocusView);
|
||||
if (nextFocusView != null) {
|
||||
currentKeyCode = event.getKeyCode();
|
||||
} else {
|
||||
LogUtils.d("nextFocusView isRightIntercept=" + isRightIntercept);
|
||||
//列表在最右边,则禁止焦点移动
|
||||
View view = this.getFocusedChild();
|
||||
int pos = this.getPositionByView(view);
|
||||
LogUtils.d("nextFocusView pos=" + pos);
|
||||
int size = this.getAdapter().getItemCount();
|
||||
LogUtils.d("nextFocusView size=" + this.getAdapter().getItemCount());
|
||||
|
||||
if (isRightIntercept && pos == (size - 1)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case KeyEvent.KEYCODE_DPAD_LEFT:
|
||||
repeatCount = event.getRepeatCount();
|
||||
if (repeatCount > 0) {
|
||||
isLongKey = true;
|
||||
} else {
|
||||
isLongKey = false;
|
||||
}
|
||||
nextFocusView = FocusFinder.getInstance().findNextFocus(this, findFocus(), FOCUS_LEFT);
|
||||
if (nextFocusView != null) {
|
||||
currentKeyCode = event.getKeyCode();
|
||||
} else {
|
||||
LogUtils.d("nextFocusView isLeftIntercept=" + isLeftIntercept);
|
||||
View view = this.getFocusedChild();
|
||||
int pos = this.getPositionByView(view);
|
||||
LogUtils.d("nextFocusView pos=" + pos);
|
||||
//列表在最左边,则禁止焦点移动
|
||||
if (isLeftIntercept && pos == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return consumed || super.dispatchKeyEvent(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置焦点横向居中
|
||||
*/
|
||||
public void setHorizontalCenter() {
|
||||
isHorizontalCenter = true;
|
||||
}
|
||||
/**
|
||||
* 焦点移动是否是长按操作
|
||||
* @return true 长按 false 短按
|
||||
* */
|
||||
public boolean isLongKey() {
|
||||
return isLongKey;
|
||||
}
|
||||
}
|
||||
45
libcore/src/main/java/com/pukka/libcore/widget/TVLinearLayoutManager.java
Executable file
45
libcore/src/main/java/com/pukka/libcore/widget/TVLinearLayoutManager.java
Executable file
@@ -0,0 +1,45 @@
|
||||
package com.pukka.libcore.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Rect;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
/**
|
||||
* @author wangkai
|
||||
* @version [V1.0.0, 2023/7/19]
|
||||
* @brief TVGridLayoutManager
|
||||
* @since V1.0.0
|
||||
*/
|
||||
public class TVLinearLayoutManager extends LinearLayoutManager {
|
||||
|
||||
public TVLinearLayoutManager(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public TVLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
|
||||
super(context, orientation, reverseLayout);
|
||||
}
|
||||
|
||||
public TVLinearLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
|
||||
try {
|
||||
super.onLayoutChildren(recycler, state);
|
||||
} catch (IndexOutOfBoundsException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean requestChildRectangleOnScreen(@NonNull RecyclerView parent, @NonNull View child, @NonNull Rect rect, boolean immediate, boolean focusedChildVisible) {
|
||||
return parent.requestChildRectangleOnScreen(child, rect, immediate);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
|
||||
package com.pukka.libcore.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Rect;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.GridLayoutManager;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.pukka.libcore.utils.LogUtils;
|
||||
|
||||
/**
|
||||
* Author: wxf
|
||||
* Date: 2024/9/12 16:19
|
||||
* Description:竖向列表控件 线性布局管理类
|
||||
* 支持焦点移动监听
|
||||
*/
|
||||
public class VerticalGridLayoutManagerTv extends GridLayoutManager {
|
||||
|
||||
private IonFocusSearchListener focusSearchListener;
|
||||
|
||||
public VerticalGridLayoutManagerTv(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
}
|
||||
|
||||
public VerticalGridLayoutManagerTv(Context context, int spanCount) {
|
||||
super(context, spanCount);
|
||||
}
|
||||
|
||||
public VerticalGridLayoutManagerTv(Context context, int spanCount, int orientation, boolean reverseLayout) {
|
||||
super(context, spanCount, orientation, reverseLayout);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean requestChildRectangleOnScreen(@NonNull RecyclerView parent, @NonNull View child, @NonNull Rect rect,
|
||||
boolean immediate, boolean focusedChildVisible) {
|
||||
if (parent instanceof ContentRecyclerViewTv) {
|
||||
parent.requestChildRectangleOnScreen(child, rect, immediate);
|
||||
}
|
||||
return super.requestChildRectangleOnScreen(parent, child, rect, immediate, focusedChildVisible);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onInterceptFocusSearch(@NonNull View focused, int direction) {
|
||||
LogUtils.i("direction=" + direction + " focusedView=" + focused);
|
||||
if(null != focusSearchListener){
|
||||
focusSearchListener.onInterceptFocusSearch(focused,direction);
|
||||
}
|
||||
return super.onInterceptFocusSearch(focused, direction);
|
||||
}
|
||||
/**
|
||||
* 设置列表项焦点监听
|
||||
* @param onInterceptFocusSearch 焦点拦截事件
|
||||
* */
|
||||
public void setOnInterceptFocusSearchListener(IonFocusSearchListener onInterceptFocusSearch){
|
||||
this.focusSearchListener = onInterceptFocusSearch;
|
||||
}
|
||||
/**
|
||||
* 横向列表焦点移动监听
|
||||
* */
|
||||
public interface IonFocusSearchListener{
|
||||
/**
|
||||
* 横向列表焦点移动监听
|
||||
* @param direction 焦点移动方向
|
||||
* @param focused 焦点
|
||||
* */
|
||||
void onInterceptFocusSearch(@NonNull View focused, int direction);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
|
||||
package com.pukka.libcore.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Rect;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.pukka.libcore.utils.LogUtils;
|
||||
|
||||
/**
|
||||
* Author: wxf
|
||||
* Date: 2025/6/20 16:19
|
||||
* Description:竖向列表控件 线性布局管理类
|
||||
* 支持焦点移动监听
|
||||
*/
|
||||
public class VerticalLinearLayoutManagerTv extends LinearLayoutManager {
|
||||
|
||||
private IonFocusSearchListener focusSearchListener;
|
||||
|
||||
public VerticalLinearLayoutManagerTv(Context context, int orientation, boolean reverseLayout) {
|
||||
super(context, orientation, reverseLayout);
|
||||
}
|
||||
@Override
|
||||
public boolean requestChildRectangleOnScreen(@NonNull RecyclerView parent, @NonNull View child, @NonNull Rect rect,
|
||||
boolean immediate, boolean focusedChildVisible) {
|
||||
if (parent instanceof VerticalRecyclerViewTv) {
|
||||
parent.requestChildRectangleOnScreen(child, rect, immediate);
|
||||
}
|
||||
return super.requestChildRectangleOnScreen(parent, child, rect, immediate, focusedChildVisible);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onInterceptFocusSearch(@NonNull View focused, int direction) {
|
||||
LogUtils.i("direction=" + direction + " focusedView=" + focused);
|
||||
if(null != focusSearchListener){
|
||||
focusSearchListener.onInterceptFocusSearch(focused,direction);
|
||||
}
|
||||
return super.onInterceptFocusSearch(focused, direction);
|
||||
}
|
||||
/**
|
||||
* 设置列表项焦点监听
|
||||
* @param onInterceptFocusSearch 焦点拦截事件
|
||||
* */
|
||||
public void setOnInterceptFocusSearchListener(IonFocusSearchListener onInterceptFocusSearch){
|
||||
this.focusSearchListener = onInterceptFocusSearch;
|
||||
}
|
||||
/**
|
||||
* 横向列表焦点移动监听
|
||||
* */
|
||||
public interface IonFocusSearchListener{
|
||||
/**
|
||||
* 横向列表焦点移动监听
|
||||
* @param direction 焦点移动方向
|
||||
* @param focused 焦点
|
||||
* */
|
||||
void onInterceptFocusSearch(@NonNull View focused, int direction);
|
||||
}
|
||||
}
|
||||
510
libcore/src/main/java/com/pukka/libcore/widget/VerticalRecyclerViewTv.java
Executable file
510
libcore/src/main/java/com/pukka/libcore/widget/VerticalRecyclerViewTv.java
Executable file
@@ -0,0 +1,510 @@
|
||||
package com.pukka.libcore.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Rect;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.FocusFinder;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.pukka.libcore.boxShared.BoxSharedParamsUtils;
|
||||
import com.pukka.libcore.utils.LogUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Author: wxf
|
||||
* Date: 2025/5/30 16:19
|
||||
* Description:竖列表控件 支持焦点是否竖向剧中,支持最上侧和最下侧焦点拦截 支持焦点记忆功能 支持失去或获得焦点的回调
|
||||
*(使用场景:列表页竖列 列数1)
|
||||
*/
|
||||
public class VerticalRecyclerViewTv extends RecyclerView {
|
||||
protected int screenWidth;
|
||||
protected int screenHeight;
|
||||
protected int currentKeyCode;
|
||||
/**
|
||||
* 焦点是否竖向居中
|
||||
*/
|
||||
protected boolean isVerticalCenter = false;
|
||||
/**
|
||||
* 是否拦截TOP焦点 默认 不拦截
|
||||
* */
|
||||
protected boolean isTopIntercept = false;
|
||||
/**
|
||||
* 是否拦截BOTTOM焦点 默认 不拦截
|
||||
* */
|
||||
protected boolean isBottomIntercept =false;
|
||||
/**
|
||||
* 是否焦点长按
|
||||
* */
|
||||
private boolean isLongKey = false;
|
||||
|
||||
/**
|
||||
* 记录点
|
||||
* */
|
||||
protected int mLastFocusPosition = 0;
|
||||
/**
|
||||
* 失去焦点的监听
|
||||
* */
|
||||
private IonFocusLostListener mFocusLostListener;
|
||||
/**
|
||||
* 获取焦点的监听
|
||||
* */
|
||||
private IonFocusGainListener mFocusGainListener;
|
||||
|
||||
private long lastKeyPressTime = 0;
|
||||
private final long KEY_PRESS_DELAY = 300; // 300毫秒延迟
|
||||
|
||||
/**
|
||||
* 是否对焦点速度进行限制
|
||||
* */
|
||||
private boolean isFocusMoveSpeed = false;
|
||||
|
||||
/**
|
||||
* 列表页向下滑动,数据加载中,焦点禁止滑动
|
||||
* */
|
||||
private boolean isPageLoading = false;
|
||||
|
||||
|
||||
public VerticalRecyclerViewTv(@NonNull Context context) {
|
||||
super(context);
|
||||
init(context);
|
||||
}
|
||||
/**
|
||||
* 设置Top拦截,移动到TOP选项,焦点按上不移动
|
||||
* */
|
||||
public void setTopIntercept() {
|
||||
isTopIntercept = true;
|
||||
}
|
||||
/**
|
||||
* 设置Bottom拦截,移动到BOTTOM选项,焦点按下不移动
|
||||
* */
|
||||
public void setBottomIntercept() {
|
||||
isBottomIntercept = true;
|
||||
}
|
||||
/**
|
||||
* 对焦点移动速度进行限制
|
||||
* */
|
||||
public void setFocusMoveSpeedLimit(){
|
||||
isFocusMoveSpeed = true;
|
||||
}
|
||||
|
||||
public VerticalRecyclerViewTv(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init(context);
|
||||
}
|
||||
|
||||
public VerticalRecyclerViewTv(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init(context);
|
||||
}
|
||||
|
||||
private void init(Context context) {
|
||||
this.screenWidth = context.getResources().getDisplayMetrics().widthPixels;
|
||||
this.screenHeight = context.getResources().getDisplayMetrics().heightPixels;
|
||||
// 先分发给Child View进行处理,如果所有的Child View都没有处理,则自己再处理
|
||||
setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
|
||||
setHasFixedSize(true);
|
||||
setWillNotDraw(true);
|
||||
//滑动到边界,不出现光晕
|
||||
setOverScrollMode(View.OVER_SCROLL_NEVER);
|
||||
// 改变子类的绘制顺序就要重写该方法
|
||||
setChildrenDrawingOrderEnabled(true);
|
||||
setClipChildren(false);
|
||||
setClipToPadding(false);
|
||||
setClickable(false);
|
||||
setFocusable(false);
|
||||
|
||||
}
|
||||
/**
|
||||
* 请求布局系统将指定子视图的指定区域滚动到屏幕上
|
||||
*
|
||||
* */
|
||||
@Override
|
||||
public boolean requestChildRectangleOnScreen(View child, Rect rect, boolean immediate) {
|
||||
View focusView = child.findFocus();
|
||||
int[] focusViewLocation = new int[2];
|
||||
focusView.getLocationOnScreen(focusViewLocation);
|
||||
// 获取RecyclerView在父容器中的位置
|
||||
int[] location = new int[2];
|
||||
this.getLocationOnScreen(location);
|
||||
//获取recyclerView到顶部的距离
|
||||
int recyclerViewTop = location[1];
|
||||
// 获取RecyclerView的可见高度
|
||||
int recyclerViewHeight = this.getHeight();
|
||||
// 子控件居中Y坐标
|
||||
int focusViewCenterY = focusViewLocation[1] + focusView.getHeight() / 2;
|
||||
//RecyclerView的居中的坐标
|
||||
int centerScreenH = recyclerViewTop + (recyclerViewHeight / 2);
|
||||
int dy = 0;
|
||||
int position = getPositionByView(child);
|
||||
switch (currentKeyCode) {
|
||||
case KeyEvent.KEYCODE_DPAD_UP:
|
||||
if (isVerticalCenter) {
|
||||
if (centerScreenH > focusViewCenterY && this.canScrollVertically(-1)) {
|
||||
dy = focusViewCenterY - centerScreenH;
|
||||
if (Math.abs(dy) > this.computeVerticalScrollOffset()) {
|
||||
dy = -this.computeVerticalScrollOffset();
|
||||
}
|
||||
if(position == 0){
|
||||
//滑动到顶部的时候,置顶
|
||||
dy = -this.computeVerticalScrollOffset();
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case KeyEvent.KEYCODE_DPAD_DOWN:
|
||||
if (isVerticalCenter) {
|
||||
if (focusViewCenterY > centerScreenH && this.canScrollVertically(1)) {
|
||||
dy = focusViewCenterY - centerScreenH;
|
||||
int canScrollOffsetY = this.computeVerticalScrollRange() - this.computeVerticalScrollExtent() - this.computeVerticalScrollOffset();
|
||||
if (dy > canScrollOffsetY) {
|
||||
dy = canScrollOffsetY;
|
||||
}
|
||||
}
|
||||
if(position == 0) {
|
||||
//第一行
|
||||
dy = 0;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
this.currentKeyCode = -1;
|
||||
LogUtils.d("wxf","focusViewCenterY="+focusViewCenterY+" centerScreenH="+centerScreenH+" dy="+dy);
|
||||
LogUtils.d("wxf","recyclerViewTop="+recyclerViewTop+" recyclerViewHeight="+recyclerViewHeight);
|
||||
if (immediate) {
|
||||
scrollBy(0, dy);
|
||||
} else {
|
||||
smoothScrollBy(0, dy);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据子View获取Position
|
||||
* @param view 列表项
|
||||
* @return 获取Position
|
||||
*/
|
||||
protected int getPositionByView(View view) {
|
||||
if (view == null) {
|
||||
return NO_POSITION;
|
||||
}
|
||||
LayoutParams params = (LayoutParams) view.getLayoutParams();
|
||||
if (params == null || params.isItemRemoved()) {
|
||||
// when item is removed, the position value can be any value.
|
||||
return NO_POSITION;
|
||||
}
|
||||
return params.getViewPosition();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean dispatchKeyEvent(KeyEvent event) {
|
||||
View nextFocusView = null;
|
||||
boolean consumed = false;
|
||||
LogUtils.d("手柄按键:"+event.getKeyCode());
|
||||
if(BoxSharedParamsUtils.isJcCloud() || BoxSharedParamsUtils.isJcHotel()){
|
||||
if(event.getKeyCode() == 4 || event.getKeyCode() == 97 || event.getKeyCode() == 96 || event.getKeyCode() == 107) {
|
||||
LogUtils.d("手柄按键:"+event.getKeyCode());
|
||||
return super.dispatchKeyEvent(event);
|
||||
}
|
||||
}
|
||||
if(isFocusMoveSpeed) {
|
||||
boolean isSpeedFast = recyclerFocusSpeed(event.getKeyCode(), event);
|
||||
if (isSpeedFast) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
switch (event.getAction()) {
|
||||
case KeyEvent.ACTION_DOWN: {
|
||||
switch (event.getKeyCode()) {
|
||||
case KeyEvent.KEYCODE_DPAD_UP:
|
||||
int repeatCount = event.getRepeatCount();
|
||||
if (repeatCount > 0) {
|
||||
isLongKey = true;
|
||||
} else {
|
||||
isLongKey = false;
|
||||
}
|
||||
nextFocusView = FocusFinder.getInstance().findNextFocus(this, findFocus(), FOCUS_UP);
|
||||
if (nextFocusView != null) {
|
||||
currentKeyCode = event.getKeyCode();
|
||||
} else {
|
||||
LogUtils.d("nextFocusView isTopIntercept=" + isTopIntercept);
|
||||
View view = this.getFocusedChild();
|
||||
int pos = this.getPositionByView(view);
|
||||
LogUtils.d("nextFocusView pos=" + pos);
|
||||
//列表在最上边,则禁止焦点移动
|
||||
if (isTopIntercept && pos == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case KeyEvent.KEYCODE_DPAD_DOWN:
|
||||
repeatCount = event.getRepeatCount();
|
||||
if (repeatCount > 0) {
|
||||
isLongKey = true;
|
||||
} else {
|
||||
isLongKey = false;
|
||||
}
|
||||
nextFocusView = FocusFinder.getInstance().findNextFocus(this, findFocus(), FOCUS_DOWN);
|
||||
if (nextFocusView != null) {
|
||||
currentKeyCode = event.getKeyCode();
|
||||
}else {
|
||||
LogUtils.d("nextFocusView isBottomIntercept=" + isBottomIntercept);
|
||||
//列表在最右边,则禁止焦点移动
|
||||
View view = this.getFocusedChild();
|
||||
int pos = this.getPositionByView(view);
|
||||
LogUtils.d("nextFocusView pos=" + pos);
|
||||
int size = this.getAdapter().getItemCount();
|
||||
LogUtils.d("nextFocusView size=" + this.getAdapter().getItemCount());
|
||||
if (isBottomIntercept && pos == (size - 1)) {
|
||||
return true;
|
||||
}
|
||||
LogUtils.d("isPageLoading "+isPageLoading);
|
||||
LogUtils.d("nextFocusView= "+nextFocusView);
|
||||
if(isPageLoading){
|
||||
LogUtils.d("页面正在加载中,禁止滑动 isPageLoading ");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case KeyEvent.KEYCODE_DPAD_RIGHT:
|
||||
isLongKey =false;
|
||||
nextFocusView = FocusFinder.getInstance().findNextFocus(this, findFocus(), FOCUS_RIGHT);
|
||||
LogUtils.d("nextFocusView=" + nextFocusView);
|
||||
if (nextFocusView != null) {
|
||||
currentKeyCode = event.getKeyCode();
|
||||
}
|
||||
break;
|
||||
case KeyEvent.KEYCODE_DPAD_LEFT:
|
||||
isLongKey =false;
|
||||
nextFocusView = FocusFinder.getInstance().findNextFocus(this, findFocus(), FOCUS_LEFT);
|
||||
if (nextFocusView != null) {
|
||||
currentKeyCode = event.getKeyCode();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return consumed || super.dispatchKeyEvent(event);
|
||||
}
|
||||
|
||||
public boolean recyclerFocusSpeed(int keyCode,KeyEvent event){
|
||||
if (event.getAction() == KeyEvent.ACTION_DOWN) {
|
||||
long currentTime = System.currentTimeMillis();
|
||||
LogUtils.d("获取当前时间 currentTime="+currentTime);
|
||||
if (currentTime - lastKeyPressTime < KEY_PRESS_DELAY) {
|
||||
LogUtils.d("拦截过快按键");
|
||||
return true; // 拦截过快按键
|
||||
}
|
||||
LogUtils.d("设置最新按键时间 lastKeyPressTime="+lastKeyPressTime);
|
||||
lastKeyPressTime = currentTime;
|
||||
// 处理方向键
|
||||
if (keyCode == KeyEvent.KEYCODE_DPAD_UP || keyCode == KeyEvent.KEYCODE_DPAD_DOWN
|
||||
|| keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
|
||||
// 让系统处理焦点移动
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestChildFocus(View child, View focused) {
|
||||
LogUtils.d("nextChild=" + focused + ",focusView=" + focused);
|
||||
if (!hasFocus()) {
|
||||
if (this.mFocusGainListener != null) {
|
||||
this.mFocusGainListener.onGainLost(child, focused);
|
||||
}
|
||||
}
|
||||
super.requestChildFocus(child, focused);
|
||||
try {
|
||||
if (child != null) {
|
||||
mLastFocusPosition = getChildViewHolder(child).getAdapterPosition();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
mLastFocusPosition = 0;
|
||||
}
|
||||
LogUtils.d("requestFocus mLastFocusPosition = " + mLastFocusPosition);
|
||||
}
|
||||
|
||||
//覆写focusSearch寻焦策略
|
||||
@Override
|
||||
public View focusSearch(View focused, int direction) {
|
||||
LogUtils.d("focusView=" + focused + ",direction=" + direction);
|
||||
if(getVisibility() != VISIBLE){
|
||||
LogUtils.d("列表控件隐藏,不执行下面的寻焦");
|
||||
return focused;
|
||||
}
|
||||
View view = super.focusSearch(focused, direction);
|
||||
if (focused == null) {
|
||||
LogUtils.d("focusView1=");
|
||||
return view;
|
||||
}
|
||||
if (view != null) {
|
||||
//该方法返回焦点view所在的父view,如果是在recyclerview之外,就会是null.所以根据是否是null,来判断是否是移出了recyclerview
|
||||
View nextView = findContainingItemView(view);
|
||||
if (nextView == null) {
|
||||
if (this.mFocusLostListener != null) {
|
||||
View lView = mFocusLostListener.onFocusLost(focused, direction);
|
||||
if (lView != null) {
|
||||
return lView;
|
||||
}
|
||||
}
|
||||
LogUtils.d( "verticalRecyclerView lost focus");
|
||||
return view;
|
||||
}
|
||||
}
|
||||
LogUtils.d( "focusView3=");
|
||||
return view;
|
||||
}
|
||||
@Override
|
||||
public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
|
||||
View lastView = getLayoutManager().findViewByPosition(mLastFocusPosition);
|
||||
LogUtils.d("requestFocus = " + lastView + "mLastFocusPosition=" + mLastFocusPosition);
|
||||
if (lastView == null) {
|
||||
lastView = getLayoutManager().findViewByPosition(0);
|
||||
}
|
||||
if (lastView != null) {
|
||||
lastView.requestFocus();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
public void requestLastItemFocus(){
|
||||
View lastView = getLayoutManager().findViewByPosition(mLastFocusPosition);
|
||||
LogUtils.d("requestFocus = " + lastView + "mLastFocusPosition=" + mLastFocusPosition);
|
||||
if (lastView == null) {
|
||||
lastView = getLayoutManager().findViewByPosition(0);
|
||||
}
|
||||
if (lastView != null) {
|
||||
lastView.requestFocus();
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
|
||||
try {
|
||||
View view = getLayoutManager().findViewByPosition(this.mLastFocusPosition);
|
||||
Boolean hasF = this.hasFocus();
|
||||
LogUtils.d("hasFocus=" + hasF + ",this.mLastFocusPosition=" + this.mLastFocusPosition + ",view=" + view);
|
||||
if (this.hasFocus() || this.mLastFocusPosition < 0 || view == null) {
|
||||
LogUtils.d("1");
|
||||
super.addFocusables(views, direction, focusableMode);
|
||||
} else if (view.isFocusable()) {
|
||||
LogUtils.d("2");
|
||||
views.add(view);
|
||||
} else {
|
||||
LogUtils.d("3");
|
||||
super.addFocusables(views, direction, focusableMode);
|
||||
}
|
||||
}catch (Exception ex){
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置焦点横向居中
|
||||
*/
|
||||
public void setVerticalCenter() {
|
||||
isVerticalCenter = true;
|
||||
}
|
||||
/**
|
||||
* 焦点移动是否是长按操作
|
||||
* @return true 长按 false 短按
|
||||
* */
|
||||
public boolean isLongKey() {
|
||||
return isLongKey;
|
||||
}
|
||||
/**
|
||||
* 设置控件整体失去焦点的监控
|
||||
* */
|
||||
public void setInFocusLostListener(IonFocusLostListener f) {
|
||||
this.mFocusLostListener = f;
|
||||
}
|
||||
/**
|
||||
* 控件整体获得焦点的监控
|
||||
* */
|
||||
public void setInFocusGainListener(IonFocusGainListener f) {
|
||||
this.mFocusGainListener = f;
|
||||
}
|
||||
/**
|
||||
* 控件整体失去焦点的监控
|
||||
* */
|
||||
public interface IonFocusLostListener {
|
||||
View onFocusLost(View lastFocusView, int direction);
|
||||
}
|
||||
/**
|
||||
* 控件整体获得焦点的监控
|
||||
* */
|
||||
public interface IonFocusGainListener {
|
||||
void onGainLost(View child, View focused);
|
||||
}
|
||||
|
||||
public int getLastFocusPosition(){
|
||||
return mLastFocusPosition;
|
||||
}
|
||||
public void initLastFocusPosition(){
|
||||
mLastFocusPosition = 0;
|
||||
}
|
||||
|
||||
public void requestNextPositionFocus(){
|
||||
try {
|
||||
int itemCount = this.getAdapter().getItemCount();
|
||||
if(mLastFocusPosition <itemCount-1) {
|
||||
int mNextPosition = mLastFocusPosition + 1;
|
||||
LogUtils.d("ShortVideoFragment itemCount="+itemCount+" mNextPosition="+mNextPosition);
|
||||
// 找到下一个item的View
|
||||
RecyclerView.ViewHolder nextHolder = this.findViewHolderForAdapterPosition(mNextPosition);
|
||||
if (nextHolder != null) {
|
||||
// 请求焦点
|
||||
nextHolder.itemView.requestFocus();
|
||||
// 确保item可见
|
||||
if(mNextPosition+2<itemCount) {
|
||||
this.smoothScrollToPosition(mNextPosition + 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}catch (Exception ex){
|
||||
ex.printStackTrace();
|
||||
}
|
||||
|
||||
}
|
||||
/**
|
||||
* 寻找上一个焦点
|
||||
* */
|
||||
public void requestPrePositionFocus(){
|
||||
try {
|
||||
if(mLastFocusPosition > 0) {
|
||||
int mNextPosition = mLastFocusPosition - 1;
|
||||
LogUtils.d("向上请求焦点 ShortVideoFragment mNextPosition="+mNextPosition);
|
||||
// 找到下一个item的View
|
||||
RecyclerView.ViewHolder nextHolder = this.findViewHolderForAdapterPosition(mNextPosition);
|
||||
if (nextHolder != null) {
|
||||
// 请求焦点
|
||||
nextHolder.itemView.requestFocus();
|
||||
}
|
||||
}
|
||||
}catch (Exception ex){
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页加载后面的数据,表示数据加载过程中,不向下滑动
|
||||
* */
|
||||
public void setIsPageLoading(boolean isPageLoading){
|
||||
this.isPageLoading = isPageLoading;
|
||||
}
|
||||
|
||||
}
|
||||
17
libcore/src/test/java/com/pukka/libcore/ExampleUnitTest.java
Executable file
17
libcore/src/test/java/com/pukka/libcore/ExampleUnitTest.java
Executable file
@@ -0,0 +1,17 @@
|
||||
package com.pukka.libcore;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
*
|
||||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||
*/
|
||||
public class ExampleUnitTest {
|
||||
@Test
|
||||
public void addition_isCorrect() {
|
||||
assertEquals(4, 2 + 2);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user