初始化橘视厅酒店版Android项目

This commit is contained in:
freeboss
2026-07-26 12:43:59 +08:00
commit e1a088ef8e
442 changed files with 68672 additions and 0 deletions

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

View 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>

View 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 "";
}
}

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

View 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 = "";
}

View 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";
}
}
}

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

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

View 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 "";
// }
}

View 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 "";
}
}

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

View 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"; // 默认值
}
}
}

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

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

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

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

View File

@@ -0,0 +1,55 @@
package com.pukka.libcore.fresco;
import android.content.Context;
import android.graphics.Bitmap;
import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.imagepipeline.backends.okhttp3.OkHttpImagePipelineConfigFactory;
import com.facebook.imagepipeline.core.ImagePipelineConfig;
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();
OkHttpClient okHttpClient = getUnsafeOkHttpClient();
ImagePipelineConfig.Builder builder = OkHttpImagePipelineConfigFactory.newBuilder(context, okHttpClient)
.setDownsampleEnabled(true)
.setBitmapsConfig(Bitmap.Config.RGB_565);
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);
}
}
}

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

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

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

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

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

View 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];
}
}

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

View 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,
}

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

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

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

View File

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

View File

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

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

View 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() + ")");
}
}

View 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");
}
}
}

View 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() {
}
}

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

View 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) {
}
}
}
}
}
}

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

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

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

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

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

View 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 "";
}
}

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

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

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

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

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

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

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

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

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

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

View 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 "";
}
}

View 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 "";
}
}

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

View File

@@ -0,0 +1,281 @@
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;
}
}

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

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

View 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 "";
}
}

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

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

View 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) {
}
}

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

View 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;
// }
}

View File

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

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

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

View File

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

View File

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

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

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