集成完直播后提交代码
This commit is contained in:
1
LiveCommon/live_commonutils/.gitignore
vendored
Normal file
1
LiveCommon/live_commonutils/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/build
|
||||
33
LiveCommon/live_commonutils/build.gradle
Normal file
33
LiveCommon/live_commonutils/build.gradle
Normal 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
|
||||
}
|
||||
0
LiveCommon/live_commonutils/consumer-rules.pro
Normal file
0
LiveCommon/live_commonutils/consumer-rules.pro
Normal file
4
LiveCommon/live_commonutils/src/main/AndroidManifest.xml
Normal file
4
LiveCommon/live_commonutils/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest package="com.alivc.live.commonutils">
|
||||
|
||||
</manifest>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
/**
|
||||
* 获取当前的网络状态 :没有网络-0:WIFI网络1:4G网络-4:3G网络-3:2G网络-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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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); // 高优先级任务优先
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
60
LiveCommon/live_commonutils/src/main/res/values/colors.xml
Normal file
60
LiveCommon/live_commonutils/src/main/res/values/colors.xml
Normal 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>
|
||||
42
LiveCommon/live_commonutils/src/main/res/values/dimens.xml
Normal file
42
LiveCommon/live_commonutils/src/main/res/values/dimens.xml
Normal 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>
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
</resources>
|
||||
Reference in New Issue
Block a user