集成完直播后提交代码

This commit is contained in:
xuhuixiang
2026-02-06 14:55:21 +08:00
commit ea9ffa06ff
960 changed files with 75063 additions and 0 deletions

View File

@@ -0,0 +1 @@
/build

View File

@@ -0,0 +1,33 @@
plugins {
id 'com.android.library'
}
android {
compileSdkVersion androidCompileSdkVersion
buildToolsVersion androidBuildToolsVersion
defaultConfig {
minSdkVersion androidMinSdkVersion
targetSdkVersion androidTargetSdkVersion
versionCode 1
versionName "1.0"
consumerProguardFiles "consumer-rules.pro"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
api externalOKHTTP
implementation externalAndroidAnnotation
}

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="com.alivc.live.commonutils">
</manifest>

View File

@@ -0,0 +1,353 @@
package com.alivc.live.commonutils;
import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.ConfigurationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Build;
import android.provider.Settings;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.UUID;
/**
* @author keria
* @date 2021/7/27
*/
public class AppUtil {
private static final String BUILD_TYPE_DEBUG = "DEBUG";
private static final String BUILD_TYPE_RELEASE = "RELEASE";
private static final String CPU_INFO_FILE_PATH = "/proc/cpuinfo";
// read from cpu info file only once to reduce method cost.
private static final ArrayList<String> mCpuInfo = new ArrayList<>();
private AppUtil() {
}
/**
* Get app name
*
* @param context android context
* @return app name, not null
*/
@NonNull
public static String getAppName(@Nullable Context context) {
if (context == null) {
return "";
}
try {
ApplicationInfo ai = context.getApplicationInfo();
PackageManager pm = context.getPackageManager();
return ai.loadLabel(pm).toString();
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
/**
* Get app id
*
* @param context android context
* @return app id, not null
*/
@NonNull
public static String getAppId(@Nullable Context context) {
if (context == null) {
return "";
}
String pn = context.getPackageName();
return (pn != null) ? pn : "";
}
/**
* Get app build type
*
* @param context android context
* @return app build type, debug or release
*/
@NonNull
public static String getApkBuildType(@Nullable Context context) {
if (context == null) {
return "";
}
boolean isDebug = false;
try {
ApplicationInfo ai = context.getApplicationInfo();
isDebug = (ai.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
} catch (Exception e) {
e.printStackTrace();
}
return isDebug ? BUILD_TYPE_DEBUG : BUILD_TYPE_RELEASE;
}
/**
* Get package version name
*
* @param context android context
* @return package version name
*/
@NonNull
public static String getPackageVersionName(@Nullable Context context) {
if (context == null) {
return "";
}
PackageManager manager = context.getPackageManager();
String name = null;
try {
PackageInfo info = manager.getPackageInfo(context.getPackageName(), 0);
name = info.versionName;
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
return name != null ? name : "";
}
/**
* Get package version code
*
* @param context android context
* @return package version code
*/
public static int getPackageVersionCode(@Nullable Context context) {
if (context == null) {
return 0;
}
PackageManager manager = context.getPackageManager();
int code = 0;
try {
PackageInfo info = manager.getPackageInfo(context.getPackageName(), 0);
code = info.versionCode;
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
return code;
}
/**
* Get android device unique id
*
* @param context android context
* @return android device unique id, not null
* @link {https://developer.android.com/training/articles/user-data-ids}
*/
@SuppressLint("HardwareIds")
@NonNull
public static String getDeviceId(@Nullable Context context) {
if (context != null) {
try {
return Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID);
} catch (Exception e) {
e.printStackTrace();
}
}
return "uuid-" + UUID.randomUUID();
}
/**
* Get full cpu info
*
* @return full cpu info
*/
@NonNull
public static String getFullCPUInfo() {
if (mCpuInfo.isEmpty()) {
mCpuInfo.addAll(readFromCpuInfo());
}
StringBuilder sb = new StringBuilder();
for (String line : mCpuInfo) {
sb.append(line).append("\n");
}
return sb.toString().trim();
}
/**
* Get brief cpu info
*
* @return brief cpu info
*/
@NonNull
public static String getBriefCPUInfo() {
return getCPUHardwareInfo() + "\n" + getCPUProcessorArchInfo();
}
/**
* Get cpu processor name
*
* @return cpu processor name
*/
@NonNull
public static String getCPUProcessorArchInfo() {
return getSpecificCpuInfoByKey("Processor");
}
/**
* Get CPU hardware name
*
* @return cpu hardware name, not null
*/
@NonNull
public static String getCPUHardwareInfo() {
return getSpecificCpuInfoByKey("Hardware");
}
/**
* Get specific cpu info by key.
*
* @param key cpu info key
* @return cpu info value
*/
@NonNull
public static String getSpecificCpuInfoByKey(@NonNull String key) {
if (mCpuInfo.isEmpty()) {
mCpuInfo.addAll(readFromCpuInfo());
}
String value = "";
for (String str : mCpuInfo) {
if (str.startsWith(key)) {
String[] data = str.split(":");
if (data.length > 1) {
value = data[1].trim();
}
break;
}
}
return value;
}
/**
* Read from cpuinfo file
*
* @return file lines
*/
@NonNull
public static ArrayList<String> readFromCpuInfo() {
ArrayList<String> cpuInfo = new ArrayList<>();
try {
FileReader fr = new FileReader(CPU_INFO_FILE_PATH);
BufferedReader br = new BufferedReader(fr, 8192);
String str;
while ((str = br.readLine()) != null) {
cpuInfo.add(str.trim());
}
} catch (IOException e) {
e.printStackTrace();
}
return cpuInfo;
}
/**
* Get brief device info
*
* @return brief device info, not null
*/
@NonNull
public static String getBriefDeviceInfo() {
return DeviceUtil.getDeviceBrand()
+ " " + DeviceUtil.getDeviceModel()
+ ", Android " + DeviceUtil.getDeviceVersion()
+ ", " + DeviceUtil.getDeviceCPUArch();
}
/**
* Get detailed device info
*
* @return detailed device info, not null
*/
@SuppressLint("HardwareIds")
@NonNull
public static String getDeviceInfo() {
String s = "主板: " + Build.BOARD +
"\n系统启动程序版本号: " + Build.BOOTLOADER +
"\n系统定制商: " + Build.BRAND +
"\ncpu指令集: " + Build.CPU_ABI +
"\ncpu指令集2: " + Build.CPU_ABI2 +
"\n设置参数: " + Build.DEVICE +
"\n显示屏参数: " + Build.DISPLAY +
"\n无线电固件版本: " + Build.getRadioVersion() +
"\n硬件识别码: " + Build.FINGERPRINT +
"\n硬件名称: " + Build.HARDWARE +
"\nHOST: " + Build.HOST +
"\n修订版本列表: " + Build.ID +
"\n硬件制造商: " + Build.MANUFACTURER +
"\n版本: " + Build.MODEL +
"\n硬件序列号: " + Build.SERIAL +
"\n手机制造商: " + Build.PRODUCT +
"\n描述Build的标签: " + Build.TAGS +
"\nTIME: " + Build.TIME +
"\nbuilder类型: " + Build.TYPE +
"\nUSER: " + Build.USER;
return s.trim();
}
/**
* Get OpenGL ES info
*
* @param context android context, nullable
* @return OpenGL ES info, not null
*/
@NonNull
public static String getOpenGLESInfo(@Nullable Context context) {
if (context == null) {
return "";
}
try {
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
if (am == null) {
return "";
}
ConfigurationInfo ci = am.getDeviceConfigurationInfo();
if (ci == null) {
return "";
}
return "OpenGL ES " + ci.getGlEsVersion() + " " + ci.reqGlEsVersion;
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
/**
* Get android device network status
*
* @param context android context
* @return network connected, true or false
*/
public static boolean isNetworkAvailable(@Nullable Context context) {
if (context == null) {
return false;
}
try {
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
if (cm == null) {
return false;
}
NetworkInfo ni = cm.getActiveNetworkInfo();
if (ni == null) {
return false;
}
return ni.isConnected();
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
}

View File

@@ -0,0 +1,153 @@
package com.alivc.live.commonutils;
import android.content.Context;
import android.content.res.AssetManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
/**
* Created by baorunchen on 2021/4/29.
* <p>
* Android asset file util
*/
public class AssetUtil {
/**
* Copy asset file or folder to sdcard
*
* @param context android context
* @param srcFilePath src asset path, file or folder
* @param destFilePath dest sdcard path
* @return true->success, false->failed.
* <p>
* Such as:
* srcFilePath: ai.bundle/face.model
* destFilePath: /data/xxx/yyy/zzz/ai.bundle/face.model
* result: /data/xxx/yyy/zzz/ai.bundle/face.model
*/
public static boolean copyAssetToSdCard(@Nullable Context context, @Nullable String srcFilePath, @Nullable String destFilePath) {
if (context == null || srcFilePath == null || destFilePath == null || srcFilePath.isEmpty() || destFilePath.isEmpty()) {
return false;
}
ArrayList<String> assetFileList = getAssetFilePathList(context, srcFilePath);
boolean isFolder = !assetFileList.isEmpty();
// Check whether target file exists
File targetFile = new File(destFilePath);
// If file already exists, return.
// Only when target is a file and exists can return.
if (targetFile.exists() && targetFile.isFile()) {
return true;
}
if (!isFolder) {
return copyAssetFileToSdCard(context, srcFilePath, destFilePath);
} else {
// If file exists but not expected file type, remove it.
if (targetFile.isFile()) {
targetFile.delete();
}
targetFile.mkdirs();
boolean result = true;
for (String file : assetFileList) {
String src = srcFilePath + File.separator + file;
String dest = destFilePath + File.separator + file;
result = result && copyAssetToSdCard(context, src, dest);
}
return result;
}
}
/**
* Copy single asset file to sdcard
*
* @param context android context
* @param srcFilePath src asset file path
* @param destFilePath dest sdcard file path
* @return true->success, false->failed.
*/
private static boolean copyAssetFileToSdCard(@Nullable Context context, @Nullable String srcFilePath, @Nullable String destFilePath) {
if (context == null || srcFilePath == null || destFilePath == null || srcFilePath.isEmpty() || destFilePath.isEmpty()) {
return false;
}
File targetFile = new File(destFilePath);
if (targetFile.exists()) return true;
AssetManager am = context.getAssets();
if (am == null) return false;
InputStream inputStream = null;
FileOutputStream outputStream = null;
try {
inputStream = am.open(srcFilePath);
outputStream = new FileOutputStream(destFilePath);
byte[] buffer = new byte[1024];
int length = inputStream.read(buffer);
while (length > 0) {
outputStream.write(buffer, 0, length);
length = inputStream.read(buffer);
}
outputStream.flush();
inputStream.close();
outputStream.close();
} catch (Exception e) {
e.printStackTrace();
targetFile.delete();
return false;
} finally {
try {
if (inputStream != null) {
inputStream.close();
}
if (outputStream != null) {
outputStream.close();
outputStream.getChannel().close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return true;
}
/**
* Get asset file by assets folder name
*
* @param context android context
* @param assetFolder assets folder name
* @return asset file list with full path
*/
@NonNull
public static ArrayList<String> getAssetFilePathList(@Nullable Context context, @Nullable String assetFolder) {
ArrayList<String> fileList = new ArrayList<>();
if (context == null) return fileList;
AssetManager am = context.getAssets();
if (am == null) return fileList;
try {
String[] assetList = am.list(assetFolder);
fileList.addAll(Arrays.asList(assetList));
} catch (Exception e) {
e.printStackTrace();
}
return fileList;
}
}

View File

@@ -0,0 +1,73 @@
package com.alivc.live.commonutils;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.Matrix;
import android.text.TextUtils;
import android.view.Gravity;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;
/**
* @author keria
* @date 2023/9/15
* @brief
*/
public class BitmapUtil {
private BitmapUtil() {
}
/**
* 文字生成bitmap
*
* @param context android context
* @param contents 文字内容
* @return bitmap图像
*/
public static Bitmap createTextBitmap(Context context, String contents) {
if (context == null || TextUtils.isEmpty(contents)) {
return null;
}
float scale = context.getResources().getDisplayMetrics().scaledDensity;
TextView tv = new TextView(context);
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT);
tv.setLayoutParams(layoutParams);
tv.setText(contents);
tv.setTextSize(scale * 12);
tv.setGravity(Gravity.CENTER_HORIZONTAL);
tv.setDrawingCacheEnabled(true);
tv.setTextColor(Color.WHITE);
tv.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
tv.layout(0, 0, tv.getMeasuredWidth(), tv.getMeasuredHeight());
tv.setBackgroundColor(Color.TRANSPARENT);
tv.buildDrawingCache();
return tv.getDrawingCache();
}
/**
* 翻转bitmap
*
* @param bitmap bitmap图像
* @param isFlipX X轴flip
* @param isFlipY Y轴flip
* @return 翻转后的bitmap
*/
public static Bitmap flipBitmap(Bitmap bitmap, boolean isFlipX, boolean isFlipY) {
if (bitmap == null || bitmap.isRecycled()) {
return null;
}
Matrix matrix = new Matrix();
matrix.setScale(isFlipX ? -1 : 1, isFlipY ? -1 : 1);
return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
}
}

View File

@@ -0,0 +1,34 @@
package com.alivc.live.commonutils;
import android.annotation.SuppressLint;
import android.content.Context;
import androidx.annotation.NonNull;
/**
* Created by keria on 2022/4/6.
*/
public class ContextUtils {
@SuppressLint("StaticFieldLeak")
private static Context sContext = null;
@SuppressLint("StaticFieldLeak")
private static Context sSafeContext = null;
public static void setContext(@NonNull Context context) {
sContext = context;
sSafeContext = new SafeToastContext(context);
}
public static Context getContext() {
return sContext;
}
public static Context getApplicationContext() {
return sContext;
}
public static Context getSafeToastContext() {
return sSafeContext;
}
}

View File

@@ -0,0 +1,104 @@
package com.alivc.live.commonutils;
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Build;
import android.view.Surface;
import android.view.WindowManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
/**
* @author keria
* @date 2022/4/19
*/
public class DeviceUtil {
private DeviceUtil() {
}
/**
* Get device brand
*
* @return brand name
*/
@NonNull
public static String getDeviceBrand() {
return Build.BRAND;
}
/**
* Get device os version
*
* @return device os version
*/
@NonNull
public static String getDeviceVersion() {
return Build.VERSION.RELEASE;
}
/**
* Get device os version code
*
* @return device os version code
*/
public static int getDeviceVersionCode() {
return Build.VERSION.SDK_INT;
}
/**
* Get device model
*
* @return device model name
*/
@NonNull
public static String getDeviceModel() {
return Build.MODEL;
}
/**
* Get device cpu arch
*
* @return device cpu arch name
*/
@SuppressLint("ObsoleteSdkInt")
@NonNull
public static String getDeviceCPUArch() {
return (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) ? Build.CPU_ABI : Build.SUPPORTED_ABIS[0];
}
/**
* Get device orientation
*
* @param context android context, nullable
* @return device orientation
*/
public static int getDisplayOrientation(@Nullable Context context) {
int displayOrientation = 0;
if (context == null) {
return displayOrientation;
}
int angle = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getRotation();
switch (angle) {
case Surface.ROTATION_0:
displayOrientation = 0;
break;
case Surface.ROTATION_90:
displayOrientation = 90;
break;
case Surface.ROTATION_180:
displayOrientation = 180;
break;
case Surface.ROTATION_270:
displayOrientation = 270;
break;
default:
break;
}
return displayOrientation;
}
}

View File

@@ -0,0 +1,100 @@
package com.alivc.live.commonutils;
import android.app.DownloadManager;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import java.io.File;
import java.util.Timer;
import java.util.TimerTask;
import static android.content.Context.DOWNLOAD_SERVICE;
/**
* 下载类
* 使用 DownloadManager 实现下载功能
*/
public class DownloadUtil {
private static DownloadManager mDownloadManager;
private OnDownloadListener mOnDownloadListener;
public DownloadUtil(Context context) {
if (mDownloadManager == null) {
mDownloadManager = (DownloadManager) context.getApplicationContext().getSystemService(DOWNLOAD_SERVICE);
}
}
public long startDownload(String url, File file) {
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));
request.setDestinationUri(Uri.fromFile(file));
long downloadId = mDownloadManager.enqueue(request);
Timer timer = new Timer();
//检测下载进度和下载完成事件
TimerTask timerTask = new TimerTask() {
@Override
public void run() {
DownloadManager.Query query = new DownloadManager.Query();
Cursor cursor = mDownloadManager.query(query.setFilterById(downloadId));
if (cursor != null && cursor.moveToFirst()) {
//获取当前下载状态
int columnIndexForStatus = cursor.getColumnIndex(DownloadManager.COLUMN_STATUS);
switch (cursor.getInt(columnIndexForStatus)) {
case DownloadManager.STATUS_SUCCESSFUL:
if (mOnDownloadListener != null) {
mOnDownloadListener.onDownloadSuccess(downloadId);
}
cancel();
break;
case DownloadManager.ERROR_FILE_ALREADY_EXISTS:
if (mOnDownloadListener != null) {
mOnDownloadListener.onDownloadError(downloadId, DownloadManager.ERROR_FILE_ALREADY_EXISTS, "File already exists");
}
cancel();
break;
case DownloadManager.ERROR_HTTP_DATA_ERROR:
case DownloadManager.ERROR_FILE_ERROR:
if (mOnDownloadListener != null) {
mOnDownloadListener.onDownloadError(downloadId, DownloadManager.ERROR_HTTP_DATA_ERROR, "other error");
}
cancel();
break;
case DownloadManager.STATUS_RUNNING:
int columnIndexForDownloadBytes = cursor.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR);
int columnIndexForTotalSize = cursor.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES);
//已下载大小
int downloadedBytes = cursor.getInt(columnIndexForDownloadBytes);
//总大小
int totalBytes = cursor.getInt(columnIndexForTotalSize);
double percent = (downloadedBytes * 100.00) / totalBytes;
if (mOnDownloadListener != null) {
mOnDownloadListener.onDownloadProgress(downloadId, Math.round(percent));
}
break;
}
cursor.close();
}
}
};
timer.schedule(timerTask, 0, 2000);
return downloadId;
}
public void cancelDownload(long id) {
mDownloadManager.remove(id);
}
public void setDownloadListener(OnDownloadListener downloadListener) {
this.mOnDownloadListener = downloadListener;
}
public interface OnDownloadListener {
void onDownloadSuccess(long downloadId);
void onDownloadProgress(long downloadId, double percent);
void onDownloadError(long downloadId, int errorCode, String errorMsg);
}
}

View File

@@ -0,0 +1,49 @@
package com.alivc.live.commonutils;
/**
* 快速点击判断
*
* @author ragnar
* @date 2019-08-14
*/
public class FastClickUtil {
private static final long PROCESS_MIN_DURATION = 300;
private static final long PROCESS_MIDDLE_DURATION = 450;
private static final long PROCESS_LONG_DURATION = 600;
private static long mLastProcessTime = 0;
/**
* 点击间隔时间是否在[PROCESS_MIN_DURATION]ms内
*/
public static boolean isProcessing() {
return isProcessing(PROCESS_MIN_DURATION);
}
/**
* 点击间隔时间是否在[PROCESS_MIDDLE_DURATION]ms内
*/
public static boolean isMiddleProcessing() {
return isProcessing(PROCESS_MIDDLE_DURATION);
}
/**
* 点击间隔时间是否在[PROCESS_LONG_DURATION]ms内
*/
public static boolean isLongProcessing() {
return isProcessing(PROCESS_LONG_DURATION);
}
private static boolean isProcessing(long pass) {
if (mLastProcessTime == 0L) {
mLastProcessTime = System.currentTimeMillis();
return false;
}
long currentTime = System.currentTimeMillis();
long passTime = currentTime - mLastProcessTime;
if (passTime < 0 || passTime > pass) {
mLastProcessTime = currentTime;
return false;
}
return true;
}
}

View File

@@ -0,0 +1,409 @@
package com.alivc.live.commonutils;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.security.MessageDigest;
import java.util.ArrayList;
/**
* @author keria
* @date 2021/7/11
*/
public class FileUtil {
private FileUtil() {
}
/**
* Check if file exists
*
* @param path file path
* @return true->exists, false->not exist
*/
public static boolean fileExists(@Nullable String path) {
if (path == null) {
return false;
}
if (path.isEmpty()) {
return false;
}
File file = new File(path);
return file.exists();
}
/**
* Check if folder exists
*
* @param path folder path
* @return true->exists, false->not exist
*/
public static boolean folderExists(@Nullable String path) {
if (path == null) {
return false;
}
if (path.isEmpty()) {
return false;
}
File file = new File(path);
return file.exists() && file.isDirectory();
}
/**
* Get parent folder absolute path
*
* @param path file path
* @return parent folder absolute path
*/
@NonNull
public static String getFolderPath(@Nullable String path) {
if (path == null) {
return "";
}
if (path.isEmpty()) {
return "";
}
File file = new File(path);
String parent = file.getAbsoluteFile().getParent();
return parent != null ? parent : "";
}
/**
* Create folder if not exists
* <p>
* If the target is a file, remove it and then create folder.
*
* @param path folder path
*/
public static void safeCreateFolder(@Nullable String path) {
if (path == null || path.isEmpty()) {
return;
}
File file = new File(path);
if (file.isFile()) {
file.delete();
}
if (!file.exists()) {
file.mkdirs();
}
}
/**
* Remove file if exists
* <p>
* If the target exists, remove it
*
* @param path file path
*/
public static void safeDeleteFile(@Nullable String path) {
if (path == null || path.isEmpty()) {
return;
}
File file = new File(path);
if (file.exists()) {
file.delete();
}
}
/**
* Remove file if exists
* <p>
* If the target exists, remove it
*
* @param file file
*/
public static void safeDeleteFile(@Nullable File file) {
if (file == null || !file.exists()) {
return;
}
file.delete();
}
/**
* Get internal cache folder from sd card
*
* @param context android context
* @return internal cache folder from sdcard
*/
@NonNull
public static String getInternalCacheFolder(@Nullable Context context) {
if (context == null) {
return "";
}
return context.getCacheDir().getAbsolutePath();
}
/**
* Get internal files folder from sd card
*
* @param context android context
* @return internal files folder from sdcard
*/
public static String getInternalFilesFolder(@Nullable Context context) {
if (context == null) return "";
return context.getFilesDir().getAbsolutePath();
}
/**
* Get external cache folder from sd card
*
* @param context android context
* @return external cache folder from sdcard
*/
@NonNull
public static String getExternalCacheFolder(@Nullable Context context) {
if (context == null) return "";
return context.getExternalCacheDir().getAbsolutePath();
}
/**
* Get external files folder from sd card
*
* @param context android context
* @return external files folder from sdcard
*/
public static String getExternalFilesFolder(@Nullable Context context) {
if (context == null) return "";
return context.getExternalFilesDir(null).getAbsolutePath();
}
/**
* Combine paths
*
* @param varargs path list
* @return combined absolute path
*/
@NonNull
public static String combinePaths(String... varargs) {
if (varargs.length == 0) {
return "";
}
File parent = new File(varargs[0]);
int i = 1;
while (i < varargs.length) {
parent = new File(parent, varargs[i++]);
}
return parent.getAbsolutePath();
}
/**
* see this How-to for a faster way to convert
* a byte array to a HEX string
*
* @param filename file name
* @return md5 byte buffer
*/
@Nullable
public static byte[] createChecksum(@Nullable String filename) throws Exception {
if (!fileExists(filename)) {
return null;
}
InputStream fis = new FileInputStream(filename);
byte[] buffer = new byte[1024];
MessageDigest complete = MessageDigest.getInstance("MD5");
int numRead;
do {
numRead = fis.read(buffer);
if (numRead > 0) {
complete.update(buffer, 0, numRead);
}
} while (numRead != -1);
fis.close();
return complete.digest();
}
/**
* see this How-to for a faster way to convert
* a byte array to a HEX string
*
* @param filename file name
* @return md5 string
*/
@NonNull
public static String getMD5Checksum(@NonNull String filename) {
if (!fileExists(filename)) {
return "";
}
try {
byte[] b = createChecksum(filename);
if (b == null) {
return "";
}
StringBuilder result = new StringBuilder();
for (byte value : b) {
result.append(Integer.toString((value & 0xff) + 0x100, 16).substring(1));
}
return result.toString();
} catch (Exception e) {
e.printStackTrace();
return "";
}
}
/**
* list all files
*
* @param file file folder
* @param includeFolder includeFolder
* @return all files
*/
@Nullable
public static ArrayList<File> listAllFiles(@Nullable File file, boolean includeFolder) {
if (file == null || !file.exists()) {
return null;
}
ArrayList<File> files = new ArrayList<>();
if (file.isFile()) {
files.add(file);
} else if (file.isDirectory()) {
if (includeFolder) {
files.add(file);
}
File[] filesArray = file.listFiles();
if (filesArray == null || file.length() == 0) {
return null;
}
for (File fileFolder : filesArray) {
ArrayList<File> subFiles = listAllFiles(fileFolder, includeFolder);
if (subFiles == null || subFiles.isEmpty()) {
continue;
}
files.addAll(subFiles);
}
}
return files;
}
/**
* read lines from file
*
* @param file file
* @return file lines
*/
@Nullable
public static ArrayList<String> readLinesFromFile(@Nullable File file) {
if (file == null || !file.exists() || !file.canRead()) {
return null;
}
FileReader fileReader = null;
try {
fileReader = new FileReader(file.getAbsolutePath());
} catch (FileNotFoundException e) {
e.printStackTrace();
}
if (fileReader == null) {
return null;
}
ArrayList<String> lines = new ArrayList<>();
BufferedReader bufferedReader = new BufferedReader(fileReader, 1000 * 8192);
try {
String st;
while ((st = bufferedReader.readLine()) != null) {
lines.add(st);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
bufferedReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return lines;
}
@Nullable
public static String readTextFromFile(@Nullable File file) {
if (file == null || !file.exists() || !file.canRead()) {
return null;
}
FileReader fileReader = null;
try {
fileReader = new FileReader(file.getAbsolutePath());
} catch (FileNotFoundException e) {
e.printStackTrace();
}
if (fileReader == null) {
return null;
}
StringBuilder sb = new StringBuilder();
BufferedReader bufferedReader = new BufferedReader(fileReader, 1000 * 8192);
try {
String st;
while ((st = bufferedReader.readLine()) != null) {
sb.append(st).append('\n');
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
bufferedReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return sb.toString();
}
public static String getFileMD5(File file) {
if (file == null) {
return "";
}
String value = null;
FileInputStream in = null;
try {
in = new FileInputStream(file);
MappedByteBuffer byteBuffer = in.getChannel().map(FileChannel.MapMode.READ_ONLY, 0,
file.length());
MessageDigest md5 = MessageDigest.getInstance("MD5");
md5.update(byteBuffer);
BigInteger bi = new BigInteger(1, md5.digest());
value = bi.toString(16);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (null != in) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return value;
}
}

View File

@@ -0,0 +1,55 @@
package com.alivc.live.commonutils;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
public class HttpUtils {
private static final OkHttpClient mOkHttpClient = new OkHttpClient.Builder()
.connectTimeout(50000, TimeUnit.MILLISECONDS)
.readTimeout(50000, TimeUnit.MILLISECONDS)
.build();
public static void get(String url, OnNetWorkListener listener) {
Request okHttpRequest = new Request.Builder()
.url(url)
.head()
.build();
mOkHttpClient.newCall(okHttpRequest).enqueue(new Callback() {
@Override
public void onFailure(@NotNull Call call, @NotNull IOException e) {
if (listener != null) {
listener.onError();
}
}
@Override
public void onResponse(@NotNull Call call, @NotNull Response response) {
if (listener != null) {
if (response.isSuccessful()) {
listener.onSuccess(response);
} else {
listener.onFailure(response.code(), response.message());
}
}
}
});
}
public interface OnNetWorkListener {
void onSuccess(Object obj);
void onFailure(int code, String msg);
void onError();
}
}

View File

@@ -0,0 +1,232 @@
package com.alivc.live.commonutils;
import android.content.Context;
import android.util.Log;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.text.SimpleDateFormat;
import java.util.Date;
public class LogcatHelper {
private static LogcatHelper INSTANCE = null;
private static String PATH_LOGCAT;
private static String PATH_DIR = "LivePush";
private LogDumper mLogDumper = null;
private int mPId;
private void init(Context context) {
PATH_LOGCAT = context.getFilesDir().getAbsolutePath()
+ File.separator + PATH_DIR;
File file = new File(PATH_LOGCAT);
if (!file.exists()) {
file.mkdirs();
}
}
public static LogcatHelper getInstance(Context context) {
if (INSTANCE == null) {
INSTANCE = new LogcatHelper(context);
}
return INSTANCE;
}
private LogcatHelper(Context context) {
init(context);
mPId = android.os.Process.myPid();
}
public void start() {
autoClear(PATH_LOGCAT);
if (mLogDumper == null)
mLogDumper = new LogDumper(String.valueOf(mPId), PATH_LOGCAT);
Log.d("LogcatHelper", "logcat thread " + mLogDumper.isAlive());
if(!mLogDumper.isAlive()) {
try {
mLogDumper.start();
} catch (IllegalThreadStateException e) {
Log.e("LogcatHelper", "thread already started");
}
}
}
public void stop() {
if (mLogDumper != null) {
mLogDumper.stopLogs();
mLogDumper = null;
}
}
private class LogDumper extends Thread {
private Process logcatProc;
private BufferedReader mReader = null;
private boolean mRunning = true;
String cmds = null;
private String mPID;
private FileOutputStream out = null;
public LogDumper(String pid, String dir) {
mPID = pid;
try {
out = new FileOutputStream(new File(dir, PATH_DIR + "-"
+ LogcatDate.getFileName()+ ".log"), true);
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// cmds = "logcat *:e *:w | grep \"(" + mPID + ")\"";
cmds = "logcat | grep \"(" + mPID + ")\"";//打印所有日志信息
//cmds = "logcat";//打印所有日志信息
// cmds = "logcat -s way";//打印标签过滤信息
//cmds = "logcat *:e *:i | grep \"(" + mPID + ")\"";
}
public void stopLogs() {
mRunning = false;
}
@Override
public void run() {
try {
logcatProc = Runtime.getRuntime().exec(cmds);
mReader = new BufferedReader(new InputStreamReader(
logcatProc.getInputStream()), 1024);
String line = null;
while (mRunning && (line = mReader.readLine()) != null) {
if (!mRunning) {
break;
}
if (line.length() == 0) {
continue;
}
if(isCreatNewFile(new File(PATH_LOGCAT, PATH_DIR + "-"
+ LogcatDate.getFileName()+ ".log"))) {
new File(PATH_LOGCAT, PATH_DIR + "-"
+ LogcatDate.getFileName()+ ".log").renameTo(new File(PATH_LOGCAT, PATH_DIR + "-"
+ LogcatDate.getDateEN()+ ".log"));
try {
out = new FileOutputStream(new File(PATH_LOGCAT, PATH_DIR + "-"
+ LogcatDate.getFileName()+ ".log"), true);
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} else {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (out != null && line.contains(mPID)) {
out.write((LogcatDate.getDateEN() + " " + line + "\n")
.getBytes());
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (logcatProc != null) {
logcatProc.destroy();
logcatProc = null;
}
if (mReader != null) {
try {
mReader.close();
mReader = null;
} catch (IOException e) {
e.printStackTrace();
}
}
if (out != null) {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
out = null;
}
}
}
}
private boolean isCreatNewFile(File file) {
boolean bool = false;
FileInputStream inputStream = null;
if(file.exists()) {
try {
inputStream = new FileInputStream(file);
} catch (FileNotFoundException e) {
//e.printStackTrace();
return false;
}
}
if(inputStream != null) {
try {
if(inputStream.available()/1024.f/1024.f > 10) {
bool = true;
}
} catch (IOException e) {
e.printStackTrace();
}
}
return bool;
}
private void autoClear(String dir) {
if(new File(dir).exists() && new File(dir).isDirectory()) {
File[] files = new File(dir).listFiles(new FileFilter() {
@Override
public boolean accept(File file) {
//大于2天
if((System.currentTimeMillis() - file.lastModified()) / 1000 > 24 * 60 * 60 * 2) {
return true;
}
return false;
}
});
if(files != null && files.length > 0) {
for(File file : files) {
file.delete();
}
}
}
}
public static class LogcatDate {
public static String getFileName() {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
String date = format.format(new Date(System.currentTimeMillis()));
return date;
}
public static String getDateEN() {
SimpleDateFormat format1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
String date1 = format1.format(new Date(System.currentTimeMillis()));
return date1;
}
}
}

View File

@@ -0,0 +1,121 @@
package com.alivc.live.commonutils;
import android.content.Context;
import android.location.LocationManager;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.telephony.TelephonyManager;
public class NetWorkUtils {
/**
* 判断是否有网络连接
*
* @param context
* @return
*/
public static boolean isNetworkConnected(Context context) {
if (context != null) {
ConnectivityManager manager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = manager.getActiveNetworkInfo();
if (networkInfo != null)
return networkInfo.isAvailable();
}
return false;
}
/**
* 判断WIFI网络是否可用
*
* @param context
* @return
*/
public static boolean isWifiConnected(Context context) {
if (context != null) {
// 获取手机所有连接管理对象(包括对wi-fi,net等连接的管理)
ConnectivityManager manager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
// 获取NetworkInfo对象
NetworkInfo networkInfo = manager.getActiveNetworkInfo();
//判断NetworkInfo对象是否为空 并且类型是否为WIFI
if (networkInfo != null && networkInfo.getType() == ConnectivityManager.TYPE_WIFI)
return networkInfo.isAvailable();
}
return false;
}
/**
* 判断MOBILE网络是否可用
*
* @param context
* @return
*/
public static boolean isMobileConnected(Context context) {
if (context != null) {
//获取手机所有连接管理对象(包括对wi-fi,net等连接的管理)
ConnectivityManager manager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
//获取NetworkInfo对象
NetworkInfo networkInfo = manager.getActiveNetworkInfo();
//判断NetworkInfo对象是否为空 并且类型是否为MOBILE
if (networkInfo != null && networkInfo.getType() == ConnectivityManager.TYPE_MOBILE)
return networkInfo.isAvailable();
}
return false;
}
public static int getConnectedType(Context context) {
if (context != null) {
//获取手机所有连接管理对象
ConnectivityManager manager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
//获取NetworkInfo对象
NetworkInfo networkInfo = manager.getActiveNetworkInfo();
if (networkInfo != null && networkInfo.isAvailable()) {
//返回NetworkInfo的类型
return networkInfo.getType();
}
}
return -1;
}
/**
* 获取当前的网络状态 :没有网络-0WIFI网络14G网络-43G网络-32G网络-2
* 自定义
*
* @param context
* @return
*/
public static int getAPNType(Context context) {
int netType = 0;
ConnectivityManager manager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = manager.getActiveNetworkInfo();
//NetworkInfo对象为空 则代表没有网络
if (networkInfo == null) {
return netType;
}
int nType = networkInfo.getType();
if (nType == ConnectivityManager.TYPE_WIFI) {
//WIFI
netType = 1;
} else if (nType == ConnectivityManager.TYPE_MOBILE) {
int nSubType = networkInfo.getSubtype();
TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
//3G 联通的3G为UMTS或HSDPA 电信的3G为EVDO
if (nSubType == TelephonyManager.NETWORK_TYPE_LTE
&& !telephonyManager.isNetworkRoaming()) {
netType = 4;
} else if (nSubType == TelephonyManager.NETWORK_TYPE_UMTS
|| nSubType == TelephonyManager.NETWORK_TYPE_HSDPA
|| nSubType == TelephonyManager.NETWORK_TYPE_EVDO_0
&& !telephonyManager.isNetworkRoaming()) {
netType = 3;
//2G 移动和联通的2G为GPRS或EGDE电信的2G为CDMA
} else if (nSubType == TelephonyManager.NETWORK_TYPE_GPRS
|| nSubType == TelephonyManager.NETWORK_TYPE_EDGE
|| nSubType == TelephonyManager.NETWORK_TYPE_CDMA
&& !telephonyManager.isNetworkRoaming()) {
netType = 2;
} else {
netType = 2;
}
}
return netType;
}
}

View File

@@ -0,0 +1,83 @@
package com.alivc.live.commonutils;
import android.content.Context;
import android.content.ContextWrapper;
import android.util.Log;
import android.view.Display;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import androidx.annotation.NonNull;
/**
* Created by keria on 2022/4/6.
*/
final class SafeToastContext extends ContextWrapper {
SafeToastContext(@NonNull Context base) {
super(base);
}
@Override
public Context getApplicationContext() {
return new ApplicationContextWrapper(getBaseContext().getApplicationContext());
}
private static final class ApplicationContextWrapper extends ContextWrapper {
private ApplicationContextWrapper(@NonNull Context base) {
super(base);
}
@Override
public Object getSystemService(@NonNull String name) {
if (Context.WINDOW_SERVICE.equals(name)) {
return new WindowManagerWrapper((WindowManager) getBaseContext().getSystemService(name));
}
return super.getSystemService(name);
}
}
private static final class WindowManagerWrapper implements WindowManager {
private static final String TAG = "WindowManagerWrapper";
@NonNull
private final WindowManager base;
private WindowManagerWrapper(@NonNull WindowManager base) {
this.base = base;
}
@Override
public Display getDefaultDisplay() {
return base.getDefaultDisplay();
}
@Override
public void removeViewImmediate(View view) {
base.removeViewImmediate(view);
}
@Override
public void addView(View view, ViewGroup.LayoutParams params) {
try {
Log.d(TAG, "WindowManager's addView(view, params) has been hooked.");
base.addView(view, params);
} catch (BadTokenException e) {
Log.i(TAG, e.getMessage());
} catch (Throwable throwable) {
Log.e(TAG, throwable.toString());
}
}
@Override
public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
base.updateViewLayout(view, params);
}
@Override
public void removeView(View view) {
base.removeView(view);
}
}
}

View File

@@ -0,0 +1,105 @@
package com.alivc.live.commonutils;
import android.content.Context;
import android.content.SharedPreferences;
import androidx.annotation.NonNull;
/**
* Created by keria on 2022/10/12.
* <p>
* Android数据持久化
*/
public class SharedPrefUtils {
private static final String PREF_APP = "livepush";
private SharedPrefUtils() {
}
/**
* Gets boolean data.
*
* @param context the context
* @param key the key
* @param val default value
* @return the boolean data
*/
public static boolean getBooleanData(@NonNull Context context, String key, boolean val) {
return getSharedPref(context).getBoolean(key, val);
}
/**
* Gets int data.
*
* @param context the context
* @param key the key
* @param val default value
* @return the int data
*/
public static int getIntData(@NonNull Context context, String key, int val) {
return getSharedPref(context).getInt(key, val);
}
/**
* Gets string data.
*
* @param context the context
* @param key the key
* @param val default value
* @return the string data
*/
public static String getStringData(@NonNull Context context, String key, String val) {
return getSharedPref(context).getString(key, val);
}
/**
* Save data.
*
* @param context the context
* @param key the key
* @param val default value
*/
public static void saveData(@NonNull Context context, String key, String val) {
getSharedPrefEditor(context).putString(key, val).apply();
}
/**
* Save data.
*
* @param context the context
* @param key the key
* @param val default value
*/
public static void saveData(@NonNull Context context, String key, int val) {
getSharedPrefEditor(context).putInt(key, val).apply();
}
/**
* Save data.
*
* @param context the context
* @param key the key
* @param val default value
*/
public static void saveData(@NonNull Context context, String key, boolean val) {
getSharedPrefEditor(context).putBoolean(key, val).apply();
}
/**
* Clear all data
*
* @param context the context
*/
public static void clear(@NonNull Context context) {
getSharedPrefEditor(context).clear();
}
private static SharedPreferences getSharedPref(@NonNull Context context) {
return context.getSharedPreferences(PREF_APP, Context.MODE_PRIVATE);
}
private static SharedPreferences.Editor getSharedPrefEditor(@NonNull Context context) {
return getSharedPref(context).edit();
}
}

View File

@@ -0,0 +1,45 @@
package com.alivc.live.commonutils;
import android.content.Context;
import android.text.TextUtils;
public class TextFormatUtil {
public static final String REGULAR = "[0-9a-zA-Z]{1,20}";
public static String getTextFormat(Context context, int id, Object... o) {
String s = context.getResources().getString(id);
return String.format(s, o);
}
public static String getTextFormat(Context context, String paramString, Object... o) {
int id = context.getResources().getIdentifier(paramString, "string", context.getPackageName());
String s = context.getResources().getString(id);
return String.format(s, o);
}
public static String getTextFormat(Context context, int id) {
return context.getResources().getString(id);
}
public static String[] getTextArray(Context context, int id) {
return context.getResources().getStringArray(id);
}
public static int[] getIntArray(Context context, int id) {
return context.getResources().getIntArray(id);
}
public static int convertString2Int(String str) {
if (TextUtils.isEmpty(str)) {
return -1;
}
int number = -1;
try {
number = Integer.parseInt(str.trim());
} catch (NumberFormatException e) {
e.printStackTrace();
}
return number;
}
}

View File

@@ -0,0 +1,22 @@
package com.alivc.live.commonutils;
import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils;
import android.widget.Toast;
public class ToastUtils {
public static void show(final String content) {
if (!TextUtils.isEmpty(content)) {
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
Toast.makeText(ContextUtils.getSafeToastContext(), content, Toast.LENGTH_SHORT).show();
}
});
}
}
}

View File

@@ -0,0 +1,326 @@
package com.alivc.live.commonutils.testexecutor;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @author baorunchen
* @date 2019/7/10
* @brief 通过调度和执行任务来测试 API 在多线程、高并发场景下的可靠性
* @note 每秒指定次数地切换线程执行任务,并随机在主线程上执行任务
*/
public class TestScheduledExecutor {
private static final String TAG = "TestScheduledExecutor";
private static final int TASK_RETRY_COUNT = 3; // 可配置重试次数
private static final int THREAD_POOL_SIZE = 8; // 线程池大小
private static final long SHUTDOWN_TIMEOUT = 1000; // 线程池关闭等待时间,毫秒
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
private final ExecutorService[] threadPools = new ExecutorService[THREAD_POOL_SIZE];
private final AtomicInteger currentPoolIndex = new AtomicInteger(0);
private final Handler mainHandler = new Handler(Looper.getMainLooper());
private final Random random = new Random();
private final int taskInterval;
private final Strategy strategy;
private final PriorityBlockingQueue<PriorityTask> priorityQueue = new PriorityBlockingQueue<>();
public enum Strategy {
DEFAULT,
PRIORITY,
RETRY,
TIMEOUT,
DELAY
}
/**
* 公共构造函数,初始化并启动任务调度
*
* @param tasks 要执行的任务列表
* @param taskInterval 执行时间间隔(毫秒)
* @param strategy 启用的策略
*/
public TestScheduledExecutor(final List<Runnable> tasks, int taskInterval, Strategy strategy) {
this.taskInterval = taskInterval;
this.strategy = strategy;
// 初始化多个线程池
for (int i = 0; i < THREAD_POOL_SIZE; i++) {
threadPools[i] = Executors.newFixedThreadPool(1, new CustomThreadFactory("ThreadPool-" + (i + 1) + "-Thread-"));
}
// 调度任务:按照指定时间间隔执行
scheduler.scheduleAtFixedRate(() -> {
int index = currentPoolIndex.getAndIncrement() % THREAD_POOL_SIZE;
Runnable selectedTask = tasks.get(random.nextInt(tasks.size())); // 随机选择一个任务
Runnable wrappedTask = wrapWithStrategy(selectedTask);
RunnableTask task = new RunnableTask(wrappedTask);
if (strategy == Strategy.PRIORITY) {
int priority = random.nextInt(100); // 设置任务的优先级
priorityQueue.add(new PriorityTask(priority, task));
executePriorityTask();
} else {
executeTask(index, task, random.nextBoolean());
}
}, 0, taskInterval, TimeUnit.MILLISECONDS);
}
/**
* 公共构造函数,初始化并启动任务调度
*
* @param tasks 要执行的任务列表
* @param taskInterval 执行时间间隔(毫秒)
*/
public TestScheduledExecutor(final List<Runnable> tasks, int taskInterval) {
this(tasks, taskInterval, Strategy.DEFAULT);
}
/**
* 只接受单个Runnable的构造函数
*
* @param runnable 要执行的任务
* @param taskInterval 执行时间间隔(毫秒)
*/
public TestScheduledExecutor(Runnable runnable, int taskInterval) {
this(Collections.singletonList(runnable), taskInterval, Strategy.DEFAULT);
}
/**
* 只接受单个Runnable的构造函数
*
* @param runnable 要执行的任务
* @param taskInterval 执行时间间隔(毫秒)
* @param strategy 启用的策略
*/
public TestScheduledExecutor(Runnable runnable, int taskInterval, Strategy strategy) {
this(Collections.singletonList(runnable), taskInterval, strategy);
}
private Runnable wrapWithStrategy(Runnable task) {
switch (strategy) {
case DELAY:
int delay = random.nextInt(taskInterval * 2);
return new DelayTask(task, delay);
case RETRY:
return new RetryTask(task, TASK_RETRY_COUNT);
case TIMEOUT:
return new TimeoutTask(task, taskInterval * 2L);
case PRIORITY:
// 优先级调度在调度线程处理中
return task;
default:
return task;
}
}
private void executePriorityTask() {
if (!priorityQueue.isEmpty()) {
PriorityTask priorityTask = priorityQueue.poll();
if (priorityTask != null) {
int index = currentPoolIndex.getAndIncrement() % THREAD_POOL_SIZE;
executeTask(index, priorityTask, priorityTask.isMainThread);
}
}
}
private void executeTask(int index, Runnable task, boolean isMainThread) {
if (isMainThread) {
mainHandler.post(task);
} else {
threadPools[index].execute(task);
}
}
/**
* 销毁并清理资源
*/
public void destroy() {
shutdownExecutor(scheduler, "Scheduler");
for (ExecutorService threadPool : threadPools) {
shutdownExecutor(threadPool, "ThreadPool");
}
}
/**
* 封装的关闭 Executor 服务的方法
*
* @param executor 要关闭的 Executor 服务
* @param name Executor 服务的名称(用于日志)
*/
private static void shutdownExecutor(ExecutorService executor, String name) {
if (executor == null) return;
executor.shutdown(); // 禁止提交新任务
try {
// 首先试图优雅地关闭
if (!executor.awaitTermination(SHUTDOWN_TIMEOUT, TimeUnit.MILLISECONDS)) {
executor.shutdownNow(); // 如果优雅关闭超时,则强制关闭
if (!executor.awaitTermination(SHUTDOWN_TIMEOUT, TimeUnit.MILLISECONDS)) {
Log.e(TAG, name + " did not terminate");
}
}
} catch (InterruptedException e) {
executor.shutdownNow(); // 响应中断以强制关闭
Thread.currentThread().interrupt(); // 恢复中断状态
Log.e(TAG, "Interrupted while shutting down " + name, e);
}
}
/**
* 自定义线程工厂,用于设置线程的名称和属性
*/
private static class CustomThreadFactory implements ThreadFactory {
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
CustomThreadFactory(String namePrefix) {
this.namePrefix = namePrefix;
}
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setName(namePrefix + threadNumber.getAndIncrement());
t.setDaemon(false);
t.setPriority(Thread.NORM_PRIORITY);
return t;
}
}
/**
* 自定义Runnable任务类
*/
private static class RunnableTask implements Runnable {
private final Runnable runnable;
RunnableTask(Runnable runnable) {
this.runnable = runnable;
}
@Override
public void run() {
Log.d(TAG, "Executing task in " + Thread.currentThread().getName());
runnable.run();
}
}
/**
* 任务延迟策略
*/
private static class DelayTask implements Runnable {
private final Runnable task;
private final int delay;
public DelayTask(Runnable task, int delay) {
this.task = task;
this.delay = delay;
}
@Override
public void run() {
try {
Thread.sleep(delay);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
Log.e(TAG, "Task interrupted in delay", e);
}
task.run();
}
}
/**
* 任务重试策略
*/
private static class RetryTask implements Runnable {
private final Runnable task;
private final int maxRetries;
public RetryTask(Runnable task, int maxRetries) {
this.task = task;
this.maxRetries = maxRetries;
}
@Override
public void run() {
int retryCount = 0;
while (retryCount < maxRetries) {
try {
task.run();
break; // 成功执行后退出循环
} catch (Exception e) {
retryCount++;
if (retryCount >= maxRetries) {
Log.e(TAG, "Task failed after " + maxRetries + " retries", e);
}
}
}
}
}
/**
* 任务超时策略
*/
private static class TimeoutTask implements Runnable {
private final Runnable task;
private final long timeout;
public TimeoutTask(Runnable task, long timeout) {
this.task = task;
this.timeout = timeout;
}
@Override
public void run() {
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<?> future = executor.submit(task);
try {
future.get(timeout, TimeUnit.MILLISECONDS);
} catch (TimeoutException e) {
future.cancel(true);
Log.e(TAG, "Task timed out", e);
} catch (Exception e) {
Log.e(TAG, "Task execution failed", e);
} finally {
executor.shutdown();
}
}
}
/**
* 任务优先级策略
*/
private static class PriorityTask implements Runnable, Comparable<PriorityTask> {
private final int priority;
private final Runnable task;
private final boolean isMainThread;
public PriorityTask(int priority, Runnable task) {
this(priority, task, false);
}
public PriorityTask(int priority, Runnable task, boolean isMainThread) {
this.priority = priority;
this.task = task;
this.isMainThread = isMainThread;
}
@Override
public void run() {
task.run();
}
@Override
public int compareTo(PriorityTask other) {
return Integer.compare(other.priority, this.priority); // 高优先级任务优先
}
}
}

View File

@@ -0,0 +1,239 @@
package com.alivc.live.commonutils.viewhierarchy;
import android.graphics.Rect;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* @author baorunchen
* @date 2024/5/16
* @brief Monitor changes in view hierarchy and regularly print view hierarchy and overlaps
*/
/****
* @par Call Example
* @code
* private ViewHierarchyInspector mViewHierarchyInspector;
*
* @Override protected void onCreate(Bundle savedInstanceState) {
* super.onCreate(savedInstanceState);
*
* // xxx;
*
* mViewHierarchyInspector = new ViewHierarchyInspector();
* mViewHierarchyInspector.setTargetView(mOwnerFrameLayout);
* mViewHierarchyInspector.setDecorView(getWindow().getDecorView());
* }
*
* @Override protected void onDestroy() {
* super.onDestroy();
*
* // xxx;
*
* if (mViewHierarchyInspector != null) {
* mViewHierarchyInspector.destroy();
* mViewHierarchyInspector = null;
* }
* }
* @endcode
*/
public class ViewHierarchyInspector {
private static final String TAG = "ViewHierarchyInspector";
private static final long SCHEDULED_EXECUTOR_SERVICE_PERIOD = 2 * 1000L;
private ScheduledExecutorService mScheduledExecutorService = null;
private View mTargetView = null;
private View mDecorView = null;
/**
* Sets the target view to inspect and starts the periodic inspection timer.
* It logs the reference of the view assigned.
*
* @param targetView The target view to be inspected.
*/
public void setTargetView(View targetView) {
Log.w(TAG, "[UI] setTargetView: [" + targetView + "]");
mTargetView = targetView;
startTimer();
}
/**
* Sets the root view (decorView) from which the view hierarchy will be inspected.
* It logs the reference of the view assigned.
*
* @param decorView The decorView (root view) of the Activity or Window.
*/
public void setDecorView(View decorView) {
Log.w(TAG, "[UI] setDecorView: [" + decorView + "]");
mDecorView = decorView;
}
/**
* Stops the inspection timer and releases the references to the view and decorView.
* This should be called to clean up when the inspector is no longer needed.
*/
public void destroy() {
stopTimer();
mTargetView = null;
mDecorView = null;
}
/**
* Starts the traversal of the view tree to print the view hierarchy and check for overlaps.
*/
private void startInspector(View targetView) {
if (mTargetView != null) {
Log.i(TAG, "[UI][INSPECTOR][TRAVERSE]: ---------- " + targetView + " ----------");
traverseViewTree(targetView);
}
if (mTargetView != null && mDecorView != null) {
Log.i(TAG, "[UI][INSPECTOR][OVERLAP]: ---------- " + mDecorView + " ----------");
printViewHierarchyWithOverlap(mDecorView, mTargetView);
}
}
/**
* Recursively traverses a view tree, Logging information about each child view.
* This method should be used for debugging to understand the structure of a view tree.
*
* @param targetView The root of the view tree to traverse.
*/
private static void traverseViewTree(View targetView) {
if (targetView instanceof ViewGroup) {
ViewGroup viewGroup = (ViewGroup) targetView;
Log.i(TAG, "[UI][TRAVERSE][VIEW_GROUP]: [" + viewGroup
+ "], Children: [" + viewGroup.getChildCount() + "]");
for (int i = 0; i < viewGroup.getChildCount(); i++) {
View childView = viewGroup.getChildAt(i);
traverseViewTree(childView);
Log.i(TAG, "[UI][TRAVERSE][CHILD]: [" + childView + "]");
}
} else {
Log.i(TAG, "[UI][TRAVERSE][VIEW]: [" + targetView + "]");
}
}
/**
* Prints a single-line representation of the view hierarchy starting from the root view,
* marking the target view and any overlapping views.
*
* @param rootView The root view from which to start the traversal.
* @param targetView The view to mark and check for overlaps.
*/
private static void printViewHierarchyWithOverlap(View rootView, View targetView) {
StringBuilder builder = new StringBuilder();
Rect targetRect = new Rect();
targetView.getGlobalVisibleRect(targetRect); // Get the global position of the target view.
buildViewHierarchyStringWithOverlap(rootView, targetView, targetRect, 0, builder);
// Print the built hierarchy string
Log.i(TAG, "[UI][HIERARCHY][ " + builder + "]");
}
/**
* Recursively traverses the view hierarchy, checking for overlap with the target view
* and appending to the StringBuilder instance.
*
* @param rootView The current view being inspected.
* @param targetView The target view to mark and check for overlaps.
* @param targetRect The global visible rectangle of the target view for overlap testing.
* @param depth The depth in the view hierarchy.
* @param builder The StringBuilder used to build the output string.
*/
private static void buildViewHierarchyStringWithOverlap(View rootView, View targetView, Rect targetRect, int depth, StringBuilder builder) {
builder.append("\n|");
// Append dashes to indicate the depth of the view in the hierarchy
for (int i = 0; i < depth; i++) {
builder.append("-");
}
builder.append("[").append(depth).append("]");
// Append visibility status of the view.
String visibilityStatus = getVisibilityStatus(rootView);
builder.append("[").append(visibilityStatus).append("]");
if (rootView == targetView) {
builder.append("[!TARGET]");
}
// Check for overlap and append the overlap marker if needed
Rect viewRect = new Rect();
rootView.getGlobalVisibleRect(viewRect);
if (Rect.intersects(viewRect, targetRect) && rootView != targetView) {
builder.append("[!OVERLAP]");
}
builder.append(rootView);
// If the view is a ViewGroup, recursively process its children
if (rootView instanceof ViewGroup) {
ViewGroup viewGroup = (ViewGroup) rootView;
for (int i = 0; i < viewGroup.getChildCount(); i++) {
buildViewHierarchyStringWithOverlap(viewGroup.getChildAt(i), targetView, targetRect, depth + 1, builder);
}
}
}
/**
* Gets the visibility status of a view as a string.
*
* @param view The view whose visibility status is to be determined.
* @return A string representing the visibility status of the view.
*/
private static String getVisibilityStatus(View view) {
switch (view.getVisibility()) {
case View.VISIBLE:
return "VISIBLE";
case View.INVISIBLE:
return "INVISIBLE";
case View.GONE:
return "GONE";
default:
return "UNKNOWN";
}
}
/**
* Starts a scheduled executor service with a fixed delay to periodically inspect the view hierarchy.
* The inspection looks for overlapping views in the hierarchy and logs them.
*/
private void startTimer() {
stopTimer();
mScheduledExecutorService = Executors.newScheduledThreadPool(8);
mScheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
startInspector(mTargetView);
}
}, 0, SCHEDULED_EXECUTOR_SERVICE_PERIOD, TimeUnit.MILLISECONDS);
}
/**
* Stops the scheduled executor service and waits for its termination.
* If the service doesn't terminate within the specified timeout, it's shut down immediately.
*/
private void stopTimer() {
try {
if (mScheduledExecutorService != null) {
mScheduledExecutorService.shutdown();
if (!mScheduledExecutorService.awaitTermination(1000, TimeUnit.MICROSECONDS)) {
mScheduledExecutorService.shutdownNow();
}
mScheduledExecutorService = null;
}
} catch (InterruptedException e) {
if (mScheduledExecutorService != null) {
mScheduledExecutorService.shutdownNow();
}
e.printStackTrace();
}
}
}

View File

@@ -0,0 +1,60 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#3F51B5</color>
<color name="colorPrimaryDark">#303F9F</color>
<color name="colorAccent">#FF4081</color>
<color name="thumbGradient">#eeeeee</color>
<color name="trackGradient">#888888</color>
<color name="red">#ffff0000</color>
<color name="darker_gray">#aaa</color>
<color name="text_blue">#2d44f4</color>
<color name="yellow">#ffff00</color>
<color name="transparent">#50000000</color>
<color name="text_green">#00c0fb</color>
<color name="text_black">#333333</color>
<color name="wheel_white">#FFFFFF</color>
<color name="alivc_version_color">#8C8C8C</color>
<color name="wheel_text_color_2">#666666</color>
<color name="alivc_color_gry">#e0e0e0</color>
<color name="wheel_text_color_1">#333333</color>
<color name="alivc_color_black">#000</color>
<color name="color_bg_model">#F9F9F9</color>
<color name="text_color_model_name">#161A23</color>
<color name="color_bg_border_model">#dde0e5</color>
<color name="color_background_green">#1AED99</color>
<color name="color_background_black_alpha_30">#3323262F</color>
<color name="color_title_text_black">#111111</color>
<color name="color_background_white">#ffffffff</color>
<color name="wheel_black">#000000</color>
<color name="color_text_white">#fff</color>
<color name="color_text_grey">#A1A1A1</color>
<color name="grey_bg_color">#F4F4F4</color>
<color name="title_bg_grey_color">#FAFAFA</color>
<color name="switch_bar_on_color">#365AFF</color>
<color name="tips_color">#D93026</color>
<color name="title_color">#555555</color>
<color name="color_07C2DE">#07C2DE</color>
<color name="shape_red_rectangle">#F53F3F</color>
<color name="border_medium">#3A3D48</color>
<color name="text_strong">#FCFCFD</color>
<color name="fill_weak">#23262F</color>
<color name="colourful_border_weak">#B2EBF2</color>
<color name="bg_weak">#1C1D22</color>
<color name="text_medium">#E6E7EC</color>
<color name="text_ultraweak">#747A8C</color>
<color name="colourful_ic_strong">#4DCFE1</color>
<color name="border_infrared">#23262F</color>
<color name="ic_ultraweak">#3A3D48</color>
<color name="text_weak">#B2B7C4</color>
<color name="colourful_text_strong">#4DCFE1</color>
<color name="push_btn_border_color">#EDEDED</color>
<color name="body_txt_color">#ff16243c</color>
<color name="text_true_color">#FF6500</color>
<color name="main_color">#FFFFFF</color>
<color name="sound_effect_background">#DDDDDD</color>
</resources>

View File

@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="font_size_40px">20.0sp</dimen>
<dimen name="font_size_36px">18.0sp</dimen>
<dimen name="font_size_32px">16.0sp</dimen>
<dimen name="font_size_30px">15.0sp</dimen>
<dimen name="font_size_28px">14.0sp</dimen>
<dimen name="font_size_24px">12.0sp</dimen>
<dimen name="view_line_width">0.1dp</dimen>
<dimen name="view_size_text_14">14dp</dimen>
<dimen name="view_size_text_16">16dp</dimen>
<dimen name="view_size_text_17">17dp</dimen>
<dimen name="view_margin_36">36dp</dimen>
<dimen name="view_margin_2">2dp</dimen>
<dimen name="view_margin_8">8dp</dimen>
<dimen name="view_margin_9">9dp</dimen>
<dimen name="view_margin_10">10dp</dimen>
<dimen name="view_margin_12">12dp</dimen>
<dimen name="view_margin_15">15dp</dimen>
<dimen name="view_margin_18">18dp</dimen>
<dimen name="view_margin_20">20dp</dimen>
<dimen name="view_margin_22">22dp</dimen>
<dimen name="view_margin_24">24dp</dimen>
<dimen name="view_margin_25">25dp</dimen>
<dimen name="view_margin_30">30dp</dimen>
<dimen name="view_margin_40">40dp</dimen>
<dimen name="view_margin_65">65dp</dimen>
<dimen name="view_margin_75">75dp</dimen>
<dimen name="view_margin_85">85dp</dimen>
<dimen name="view_margin_110">110dp</dimen>
<dimen name="view_margin_140">140dp</dimen>
<dimen name="view_margin_261">261dp</dimen>
<dimen name="view_margin_310">310dp</dimen>
<dimen name="view_margin_330">330dp</dimen>
<dimen name="view_width_size_64">64dp</dimen>
<dimen name="view_height_size_32">32dp</dimen>
<dimen name="view_height_size_36">36dp</dimen>
<dimen name="view_height_size_44">44dp</dimen>
<dimen name="view_height_size_45">45dp</dimen>
<dimen name="view_version_size_text_12">12dp</dimen>
</resources>

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
</resources>