/*
* Copyright (C) 2016 Peng fei Pan <sky@xiaopan.me>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.xiaopan.sketch.util;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.ActivityManager;
import android.content.ComponentCallbacks2;
import android.content.Context;
import android.content.pm.ConfigurationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.opengl.EGL14;
import android.opengl.GLES10;
import android.opengl.GLES20;
import android.os.Build;
import android.os.Environment;
import android.os.Looper;
import android.os.StatFs;
import android.os.storage.StorageManager;
import android.text.TextUtils;
import android.text.format.Formatter;
import android.view.ViewGroup;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.net.URLEncoder;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import javax.microedition.khronos.egl.EGL10;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.egl.EGLContext;
import javax.microedition.khronos.egl.EGLDisplay;
import javax.microedition.khronos.egl.EGLSurface;
import me.xiaopan.sketch.SLog;
import me.xiaopan.sketch.SLogType;
import me.xiaopan.sketch.cache.BitmapPool;
import me.xiaopan.sketch.decode.ImageType;
import me.xiaopan.sketch.drawable.LoadingDrawable;
import me.xiaopan.sketch.drawable.SketchDrawable;
import me.xiaopan.sketch.feature.large.Tile;
import me.xiaopan.sketch.request.DisplayRequest;
import me.xiaopan.sketch.request.DownloadOptions;
import me.xiaopan.sketch.request.ImageViewInterface;
import me.xiaopan.sketch.request.LoadRequest;
public class SketchUtils {
private static final float[] MATRIX_VALUES = new float[9];
/**
* 读取APK的图标
*/
public static Bitmap readApkIcon(Context context, String apkFilePath, boolean lowQualityImage, String logName, BitmapPool bitmapPool) {
PackageManager packageManager = context.getPackageManager();
PackageInfo packageInfo = packageManager.getPackageArchiveInfo(apkFilePath, PackageManager.GET_ACTIVITIES);
if (packageInfo == null) {
if (SLogType.REQUEST.isEnabled()) {
SLog.w(SLogType.REQUEST, logName, "get packageInfo is null. %s", apkFilePath);
}
return null;
}
packageInfo.applicationInfo.sourceDir = apkFilePath;
packageInfo.applicationInfo.publicSourceDir = apkFilePath;
Drawable drawable = null;
try {
drawable = packageManager.getApplicationIcon(packageInfo.applicationInfo);
} catch (Resources.NotFoundException e) {
e.printStackTrace();
}
if (drawable == null) {
if (SLogType.REQUEST.isEnabled()) {
SLog.w(SLogType.REQUEST, logName, "app icon is null. %s", apkFilePath);
}
return null;
}
return drawableToBitmap(drawable, lowQualityImage, bitmapPool);
}
/**
* Drawable转成Bitmap
*/
public static Bitmap drawableToBitmap(Drawable drawable, boolean lowQualityImage, BitmapPool bitmapPool) {
if (drawable == null || drawable.getIntrinsicWidth() <= 0 || drawable.getIntrinsicHeight() <= 0) {
return null;
}
drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
Bitmap.Config config = lowQualityImage ? Bitmap.Config.ARGB_4444 : Bitmap.Config.ARGB_8888;
Bitmap bitmap;
if (bitmapPool != null) {
bitmap = bitmapPool.getOrMake(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), config);
} else {
bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), config);
}
Canvas canvas = new Canvas(bitmap);
drawable.draw(canvas);
return bitmap;
}
/**
* 清空目录
*
* @return true:清空成功;false:清空失败
*/
@SuppressWarnings("WeakerAccess")
public static boolean cleanDir(File dir) {
if (dir == null || !dir.exists() || !dir.isDirectory()) {
return true;
}
File[] files = dir.listFiles();
boolean cleanSuccess = true;
if (files != null) {
for (File tempFile : files) {
if (tempFile.isDirectory()) {
cleanSuccess &= cleanDir(tempFile);
}
cleanSuccess &= tempFile.delete();
}
}
return cleanSuccess;
}
/**
* 删除给定的文件,如果当前文件是目录则会删除其包含的所有的文件或目录
*
* @param file 给定的文件
* @return true:删除成功;false:删除失败
*/
@SuppressWarnings("unused")
public static boolean deleteFile(File file) {
if (file == null || !file.exists()) {
return true;
}
if (file.isDirectory()) {
cleanDir(file);
}
return file.delete();
}
/**
* 检查文件名是不是指定的后缀
*
* @param fileName 例如:test.txt
* @param suffix 例如:.txt
*/
public static boolean checkSuffix(String fileName, String suffix) {
if (fileName == null) {
return false;
}
// 截取后缀名
String fileNameSuffix;
int lastIndex = fileName.lastIndexOf(".");
if (lastIndex > -1) {
fileNameSuffix = fileName.substring(lastIndex);
} else {
return false;
}
return suffix.equalsIgnoreCase(fileNameSuffix);
}
public static String concat(Object... strings) {
if (strings == null || strings.length == 0) {
return null;
}
StringBuilder builder = new StringBuilder();
for (Object string : strings) {
builder.append(string);
}
return builder.toString();
}
@SuppressWarnings("unused")
@Deprecated
public static void mapping(int sourceWidth, int sourceHeight, int targetWidth, int targetHeight, Rect rect) {
float widthScale = (float) sourceWidth / targetWidth;
float heightScale = (float) sourceHeight / targetHeight;
float finalScale = widthScale < heightScale ? widthScale : heightScale;
int srcWidth = (int) (targetWidth * finalScale);
int srcHeight = (int) (targetHeight * finalScale);
int srcLeft = (sourceWidth - srcWidth) / 2;
int srcTop = (sourceHeight - srcHeight) / 2;
rect.set(srcLeft, srcTop, srcLeft + srcWidth, srcTop + srcHeight);
}
public static void close(Closeable closeable) {
if (closeable == null) {
return;
}
if (closeable instanceof OutputStream) {
try {
((OutputStream) closeable).flush();
} catch (IOException e) {
e.printStackTrace();
}
}
try {
closeable.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public static boolean isGifImage(Drawable drawable) {
if (drawable != null) {
LayerDrawable layerDrawable;
while (drawable instanceof LayerDrawable) {
layerDrawable = (LayerDrawable) drawable;
if (layerDrawable.getNumberOfLayers() > 0) {
drawable = layerDrawable.getDrawable(layerDrawable.getNumberOfLayers() - 1);
} else {
drawable = null;
}
}
return drawable instanceof SketchDrawable && ImageType.GIF.getMimeType().equals(((SketchDrawable) drawable).getMimeType());
}
return false;
}
public static String viewLayoutFormatted(int size) {
if (size >= 0) {
return String.valueOf(size);
} else if (size == ViewGroup.LayoutParams.MATCH_PARENT) {
return "MATCH_PARENT";
} else if (size == ViewGroup.LayoutParams.WRAP_CONTENT) {
return "WRAP_CONTENT";
} else {
return "Unknown";
}
}
/**
* 是不是主线程
*/
public static boolean isMainThread() {
return Looper.getMainLooper().getThread() == Thread.currentThread();
}
/**
* 获取当前进程的名字
*/
@SuppressWarnings("WeakerAccess")
public static String getProcessName(Context context) {
int pid = android.os.Process.myPid();
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningAppProcessInfo> runningApps = am.getRunningAppProcesses();
if (runningApps == null) {
return null;
}
for (ActivityManager.RunningAppProcessInfo procInfo : runningApps) {
if (procInfo.pid == pid) {
return procInfo.processName;
}
}
return null;
}
/**
* 当前进程是不是主进程
*/
@SuppressWarnings("unused")
public static boolean isMainProcess(Context context) {
return context.getPackageName().equalsIgnoreCase(getProcessName(context));
}
/**
* 获取短的当前进程的名字,例如进程名字为com.my.app:push,那么短名字就是:push
*/
@SuppressWarnings({"unused", "WeakerAccess"})
public static String getSimpleProcessName(Context context) {
String processName = getProcessName(context);
if (processName == null) {
return null;
}
String packageName = context.getPackageName();
int lastIndex = processName.lastIndexOf(packageName);
return lastIndex != -1 ? processName.substring(lastIndex + packageName.length()) : null;
}
/**
* 获取App缓存目录,优先考虑SDCard上的缓存目录
*/
@SuppressWarnings("WeakerAccess")
public static File getAppCacheDir(Context context) {
File appCacheDir = null;
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
appCacheDir = context.getExternalCacheDir();
}
if (appCacheDir == null) {
appCacheDir = context.getCacheDir();
}
return appCacheDir;
}
/**
* 获取给定目录的可用大小
*/
@SuppressWarnings("WeakerAccess")
public static long getAvailableBytes(File dir) {
if (!dir.exists() && !dir.mkdirs()) {
return 0;
}
StatFs dirStatFs = new StatFs(dir.getPath());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
return dirStatFs.getAvailableBytes();
} else {
//noinspection deprecation
return (long) dirStatFs.getAvailableBlocks() * dirStatFs.getBlockSize();
}
}
/**
* 获取给定目录的总大小
*/
@SuppressWarnings("unused")
public static long getTotalBytes(File dir) {
if (!dir.exists() && !dir.mkdirs()) {
return 0;
}
StatFs dirStatFs = new StatFs(dir.getPath());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
return dirStatFs.getTotalBytes();
} else {
//noinspection deprecation
return (long) dirStatFs.getBlockCount() * dirStatFs.getBlockSize();
}
}
/**
* 获取所有可用的SD卡的路径
*
* @return 所有可用的SD卡的路径
*/
@SuppressWarnings("WeakerAccess")
@SuppressLint("LongLogTag")
@TargetApi(Build.VERSION_CODES.GINGERBREAD)
public static String[] getAllAvailableSdcardPath(Context context) {
// 获取所有的存储器的路径
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.GINGERBREAD_MR1) {
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
return new String[]{Environment.getExternalStorageDirectory().getPath()};
} else {
return null;
}
}
String[] paths;
Method getVolumePathsMethod;
try {
getVolumePathsMethod = StorageManager.class.getMethod("getVolumePaths");
} catch (NoSuchMethodException e) {
SLog.e("getAllAvailableSdcardPath", "not found StorageManager.getVolumePaths() method");
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
return new String[]{Environment.getExternalStorageDirectory().getPath()};
} else {
return null;
}
}
StorageManager sm = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
try {
paths = (String[]) getVolumePathsMethod.invoke(sm);
} catch (IllegalAccessException e) {
e.printStackTrace();
return null;
} catch (InvocationTargetException e) {
e.printStackTrace();
return null;
}
if (paths == null || paths.length == 0) {
return null;
}
// 去掉不可用的存储器
List<String> storagePathList = new LinkedList<String>();
Collections.addAll(storagePathList, paths);
Iterator<String> storagePathIterator = storagePathList.iterator();
String path;
Method getVolumeStateMethod = null;
while (storagePathIterator.hasNext()) {
path = storagePathIterator.next();
if (getVolumeStateMethod == null) {
try {
getVolumeStateMethod = StorageManager.class.getMethod("getVolumeState", String.class);
} catch (NoSuchMethodException e) {
e.printStackTrace();
return null;
}
}
String status;
try {
status = (String) getVolumeStateMethod.invoke(sm, path);
} catch (Exception e) {
e.printStackTrace();
storagePathIterator.remove();
continue;
}
if (!(Environment.MEDIA_MOUNTED.equals(status) || Environment.MEDIA_MOUNTED_READ_ONLY.equals(status))) {
storagePathIterator.remove();
}
}
return storagePathList.toArray(new String[storagePathList.size()]);
}
@SuppressWarnings("WeakerAccess")
public static String appendProcessName(Context context, String dirName) {
// 目录名字加上进程名字的后缀,不同的进程不同目录,以兼容多进程
String simpleProcessName = SketchUtils.getSimpleProcessName(context);
if (simpleProcessName != null) {
try {
dirName += URLEncoder.encode(simpleProcessName, "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
return dirName;
}
public static File getDefaultSketchCacheDir(Context context, String dirName, boolean compatManyProcess) {
File appCacheDir = SketchUtils.getAppCacheDir(context);
return new File(appCacheDir, compatManyProcess ? appendProcessName(context, dirName) : dirName);
}
@SuppressWarnings("WeakerAccess")
public static boolean testCreateFile(File cacheDir) throws Exception {
File parentDir = cacheDir;
while (parentDir != null) {
// 先向上找到一个已存在的目录
if (!parentDir.exists()) {
parentDir = cacheDir.getParentFile();
continue;
}
// 然后尝试创建文件
File file = new File(parentDir, "create_test.temp");
// 已存在就先删除,删除失败就抛异常
if (file.exists() && !file.delete()) {
throw new Exception("Delete old test file failed: " + file.getPath());
}
//noinspection ResultOfMethodCallIgnored
file.createNewFile();
if (file.exists()) {
if (file.delete()) {
return true;
} else {
throw new Exception("Delete test file failed: " + file.getPath());
}
} else {
return false;
}
}
return false;
}
/**
* 创建缓存目录,会优先在sdcard上创建
*
* @param dirName 目录名称
* @param compatManyProcess 目录名称是否加上进程名
* @param minSpaceSize 最小空间
* @param cleanOnNoSpace 空间不够用时就尝试清理一下
* @param cleanOldCacheFiles 清除旧的缓存文件
* @param expandNumber 当dirName无法使用时就会尝试dirName1、dirName2、dirName3...
* @return 你应当以返回的目录为最终可用的目录
* @throws NoSpaceException 可用空间小于minSpaceSize;UnableCreateDirException:无法创建缓存目录;UnableCreateFileException:无法在缓存目录中创建文件
*/
public static File buildCacheDir(Context context, String dirName, boolean compatManyProcess, long minSpaceSize, boolean cleanOnNoSpace,
boolean cleanOldCacheFiles, int expandNumber) throws NoSpaceException, UnableCreateDirException, UnableCreateFileException {
List<File> appCacheDirs = new LinkedList<File>();
String[] sdcardPaths = getAllAvailableSdcardPath(context);
if (sdcardPaths != null && sdcardPaths.length > 0) {
for (String sdcardPath : sdcardPaths) {
appCacheDirs.add(new File(sdcardPath, "Android" + File.separator + "data" + File.separator + context.getPackageName() + File.separator + "cache"));
}
}
appCacheDirs.add(context.getCacheDir());
String diskCacheDirName = compatManyProcess ? appendProcessName(context, dirName) : dirName;
NoSpaceException noSpaceException = null;
UnableCreateFileException unableCreateFileException = null;
File diskCacheDir = null;
int expandCount;
for (File appCacheDir : appCacheDirs) {
expandCount = 0;
while (expandCount <= expandNumber) {
diskCacheDir = new File(appCacheDir, diskCacheDirName + (expandCount > 0 ? expandCount : ""));
if (diskCacheDir.exists()) {
// 目录已存在的话就尝试清除旧的缓存文件
if (cleanOldCacheFiles) {
File journalFile = new File(diskCacheDir, DiskLruCache.JOURNAL_FILE);
if (!journalFile.exists()) {
cleanDir(diskCacheDir);
}
}
} else {
// 目录不存在就创建,创建结果返回false后检查还是不存在就说明创建失败
if (!diskCacheDir.mkdirs() && !diskCacheDir.exists()) {
expandCount++;
continue;
}
}
// 检查空间,少于minSpaceSize就不能用了
long availableBytes = getAvailableBytes(diskCacheDir);
if (availableBytes < minSpaceSize) {
// 空间不够用的时候直接清空,然后再次计算可用空间
if (cleanOnNoSpace) {
cleanDir(diskCacheDir);
availableBytes = getAvailableBytes(diskCacheDir);
}
// 依然不够用,那不好意思了
if (availableBytes < minSpaceSize) {
String availableFormatted = Formatter.formatFileSize(context, availableBytes);
String minSpaceFormatted = Formatter.formatFileSize(context, minSpaceSize);
noSpaceException = new NoSpaceException("Need " + availableFormatted + ", with only " + minSpaceFormatted + " in " + diskCacheDir.getPath());
break;
}
}
// 创建文件测试
try {
if (testCreateFile(diskCacheDir)) {
return diskCacheDir;
} else {
unableCreateFileException = new UnableCreateFileException("Unable create file in " + diskCacheDir.getPath());
expandCount++;
}
} catch (Exception e) {
e.printStackTrace();
unableCreateFileException = new UnableCreateFileException(e.getClass().getSimpleName() + ": " + e.getMessage());
expandCount++;
}
}
}
if (noSpaceException != null) {
throw noSpaceException;
} else if (unableCreateFileException != null) {
throw unableCreateFileException;
} else {
throw new UnableCreateDirException("Unable create dir: " + (diskCacheDir != null ? diskCacheDir.getPath() : "null"));
}
}
/**
* 从ImageViewInterface上查找DisplayRequest
*/
public static DisplayRequest findDisplayRequest(ImageViewInterface imageViewInterface) {
if (imageViewInterface != null) {
final Drawable drawable = imageViewInterface.getDrawable();
if (drawable != null && drawable instanceof LoadingDrawable) {
return ((LoadingDrawable) drawable).getRequest();
}
}
return null;
}
/**
* 根据给定的信息,生成最终的图片信息
*
* @param type 类型
* @param bitmap 图片
* @param mimeType 图片格式
* @param byteCount 占用字节数
*/
public static String makeImageInfo(String type, Bitmap bitmap, String mimeType, long byteCount) {
if (bitmap != null) {
if (TextUtils.isEmpty(type)) {
type = "Bitmap";
}
String hashCode = Integer.toHexString(bitmap.hashCode());
String config = bitmap.getConfig() != null ? bitmap.getConfig().name() : null;
return String.format("%s(mimeType=%s; hashCode=%s; size=%dx%d; config=%s; byteCount=%d)",
type, mimeType, hashCode, bitmap.getWidth(), bitmap.getHeight(), config, byteCount);
} else {
return null;
}
}
/**
* 根据给定的信息,生成最终的图片信息
*
* @param type 类型
* @param bitmap 图片
* @param mimeType 图片格式
*/
public static String makeImageInfo(String type, Bitmap bitmap, String mimeType) {
return makeImageInfo(type, bitmap, mimeType, getByteCount(bitmap));
}
/**
* 如果是LayerDrawable,则返回其最后一张图片,不是的就返回自己
*/
public static Drawable getLastDrawable(Drawable drawable) {
if (drawable == null) {
return null;
}
if (!(drawable instanceof LayerDrawable)) {
return drawable;
}
LayerDrawable layerDrawable = (LayerDrawable) drawable;
for (int i = layerDrawable.getNumberOfLayers() - 1; i >= 0; i--) {
Drawable childDrawable = getLastDrawable(layerDrawable.getDrawable(i));
if (childDrawable != null) {
return childDrawable;
}
}
return null;
}
/**
* 获取矩阵中指定位置的值
*
* @param matrix Matrix
* @param whichValue 指定的位置,例如Matrix.MSCALE_X
*/
@SuppressWarnings("unused")
public static float getMatrixValue(Matrix matrix, int whichValue) {
synchronized (MATRIX_VALUES) {
matrix.getValues(MATRIX_VALUES);
return MATRIX_VALUES[whichValue];
}
}
/**
* 从Matrix中获取缩放比例
*/
public static float getMatrixScale(Matrix matrix) {
synchronized (MATRIX_VALUES) {
matrix.getValues(MATRIX_VALUES);
final float scaleX = MATRIX_VALUES[Matrix.MSCALE_X];
final float skewY = MATRIX_VALUES[Matrix.MSKEW_Y];
//noinspection SuspiciousNameCombination
return (float) Math.sqrt((float) Math.pow(scaleX, 2) + (float) Math.pow(skewY, 2));
}
}
/**
* 从Matrix中获取旋转角度
*/
@SuppressWarnings("unused")
public static int getMatrixRotateDegrees(Matrix matrix) {
synchronized (MATRIX_VALUES) {
matrix.getValues(MATRIX_VALUES);
final float skewX = MATRIX_VALUES[Matrix.MSKEW_X];
final float scaleX = MATRIX_VALUES[Matrix.MSCALE_X];
//noinspection SuspiciousNameCombination
final int degrees = (int) Math.round(Math.atan2(skewX, scaleX) * (180 / Math.PI));
if (degrees < 0) {
return Math.abs(degrees);
} else if (degrees > 0) {
return 360 - degrees;
} else {
return 0;
}
}
}
/**
* 从Matrix中获取偏移位置
*/
@SuppressWarnings("unused")
public static void getMatrixTranslation(Matrix matrix, PointF point) {
synchronized (MATRIX_VALUES) {
matrix.getValues(MATRIX_VALUES);
point.x = MATRIX_VALUES[Matrix.MTRANS_X];
point.y = MATRIX_VALUES[Matrix.MTRANS_Y];
}
}
/**
* 获取OpenGL的版本
*/
@SuppressWarnings("unused")
public static String getOpenGLVersion(Context context) {
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
ConfigurationInfo info = am.getDeviceConfigurationInfo();
return info.getGlEsVersion();
}
/**
* 获取OpenGL所允许的图片的最大尺寸(单边长)
*/
public static int getOpenGLMaxTextureSize() {
int maxTextureSize = 0;
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
maxTextureSize = getOpenGLMaxTextureSizeJB1();
} else {
maxTextureSize = getOpenGLMaxTextureSizeBase();
}
} catch (Exception e) {
e.printStackTrace();
}
if (maxTextureSize == 0) {
maxTextureSize = 4096;
}
return maxTextureSize;
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
private static int getOpenGLMaxTextureSizeJB1() {
// Then get a hold of the default display, and initialize.
// This could get more complex if you have to deal with devices that could have multiple displays,
// but will be sufficient for a typical phone/tablet:
android.opengl.EGLDisplay dpy = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
int[] vers = new int[2];
EGL14.eglInitialize(dpy, vers, 0, vers, 1);
// Next, we need to find a config. Since we won't use this context for rendering,
// the exact attributes aren't very critical:
int[] configAttr = {
EGL14.EGL_COLOR_BUFFER_TYPE, EGL14.EGL_RGB_BUFFER,
EGL14.EGL_LEVEL, 0,
EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
EGL14.EGL_SURFACE_TYPE, EGL14.EGL_PBUFFER_BIT,
EGL14.EGL_NONE
};
android.opengl.EGLConfig[] configs = new android.opengl.EGLConfig[1];
int[] numConfig = new int[1];
EGL14.eglChooseConfig(dpy, configAttr, 0,
configs, 0, 1, numConfig, 0);
//noinspection StatementWithEmptyBody
if (numConfig[0] == 0) {
// TROUBLE! No config found.
}
android.opengl.EGLConfig config = configs[0];
// To make a context current, which we will need later,
// you need a rendering surface, even if you don't actually plan to render.
// To satisfy this requirement, create a small offscreen (Pbuffer) surface:
int[] surfAttr = {
EGL14.EGL_WIDTH, 64,
EGL14.EGL_HEIGHT, 64,
EGL14.EGL_NONE
};
android.opengl.EGLSurface surf = EGL14.eglCreatePbufferSurface(dpy, config, surfAttr, 0);
// Next, create the context:
int[] ctxAttrib = {
EGL14.EGL_CONTEXT_CLIENT_VERSION, 2,
EGL14.EGL_NONE
};
android.opengl.EGLContext ctx = EGL14.eglCreateContext(dpy, config, EGL14.EGL_NO_CONTEXT, ctxAttrib, 0);
// Ready to make the context current now:
EGL14.eglMakeCurrent(dpy, surf, surf, ctx);
// If all of the above succeeded (error checking was omitted), you can make your OpenGL calls now:
int[] maxSize = new int[1];
GLES20.glGetIntegerv(GLES20.GL_MAX_TEXTURE_SIZE, maxSize, 0);
// Once you're all done, you can tear down everything:
EGL14.eglMakeCurrent(dpy, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE,
EGL14.EGL_NO_CONTEXT);
EGL14.eglDestroySurface(dpy, surf);
EGL14.eglDestroyContext(dpy, ctx);
EGL14.eglTerminate(dpy);
return maxSize[0];
}
private static int getOpenGLMaxTextureSizeBase() {
// In JELLY_BEAN will collapse
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN) {
return 0;
}
EGL10 egl = (EGL10) EGLContext.getEGL();
EGLDisplay dpy = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
int[] vers = new int[2];
egl.eglInitialize(dpy, vers);
int[] configAttr = {
EGL10.EGL_COLOR_BUFFER_TYPE, EGL10.EGL_RGB_BUFFER,
EGL10.EGL_LEVEL, 0,
EGL10.EGL_SURFACE_TYPE, EGL10.EGL_PBUFFER_BIT,
EGL10.EGL_NONE
};
EGLConfig[] configs = new EGLConfig[1];
int[] numConfig = new int[1];
egl.eglChooseConfig(dpy, configAttr, configs, 1, numConfig);
//noinspection StatementWithEmptyBody
if (numConfig[0] == 0) {
// TROUBLE! No config found.
}
EGLConfig config = configs[0];
int[] surfAttr = new int[]{
EGL10.EGL_WIDTH, 64,
EGL10.EGL_HEIGHT, 64,
EGL10.EGL_NONE
};
EGLSurface surf = egl.eglCreatePbufferSurface(dpy, config, surfAttr);
final int EGL_CONTEXT_CLIENT_VERSION = 0x3098; // missing in EGL10
int[] ctxAttrib = {
EGL_CONTEXT_CLIENT_VERSION, 1,
EGL10.EGL_NONE
};
EGLContext ctx = egl.eglCreateContext(dpy, config, EGL10.EGL_NO_CONTEXT, ctxAttrib);
egl.eglMakeCurrent(dpy, surf, surf, ctx);
int[] maxSize = new int[1];
GLES10.glGetIntegerv(GLES10.GL_MAX_TEXTURE_SIZE, maxSize, 0);
egl.eglMakeCurrent(dpy, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT);
egl.eglDestroySurface(dpy, surf);
egl.eglDestroyContext(dpy, ctx);
egl.eglTerminate(dpy);
return maxSize[0];
}
/**
* 格式化小数,可以指定保留多少位小数
*/
public static float formatFloat(float floatValue, int newScale) {
BigDecimal b = new BigDecimal(floatValue);
return b.setScale(newScale, BigDecimal.ROUND_HALF_UP).floatValue();
}
/**
* 根据API版本判断是否支持读取图片碎片
*/
public static boolean sdkSupportBitmapRegionDecoder() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD_MR1;
}
/**
* 根据图片格式型判断是否支持读取图片碎片
*/
public static boolean formatSupportBitmapRegionDecoder(ImageType imageType) {
return imageType != null && (imageType == ImageType.JPEG || imageType == ImageType.PNG ||
(imageType == ImageType.WEBP && Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH));
}
/**
* 根据请求和图片类型判断是否支持大图功能
*/
public static boolean supportLargeImage(LoadRequest loadRequest, ImageType imageType) {
return loadRequest instanceof DisplayRequest &&
((DisplayRequest) loadRequest).getViewInfo().isSupportLargeImage() &&
SketchUtils.sdkSupportBitmapRegionDecoder() &&
formatSupportBitmapRegionDecoder(imageType);
}
/**
* 判断两个矩形是否相交
*/
public static boolean isCross(Rect rect1, Rect rect2) {
return rect1.left < rect2.right && rect2.left < rect1.right && rect1.top < rect2.bottom && rect2.top < rect1.bottom;
}
/**
* dp转换成px
*/
public static int dp2px(Context context, int dpValue) {
return (int) ((dpValue * context.getResources().getDisplayMetrics().density) + 0.5);
}
/**
* 将一个旋转了一定度数的矩形转回来(只能是90度的倍数)
*/
public static void reverseRotateRect(Rect rect, int rotateDegrees, Point drawableSize) {
if (rotateDegrees % 90 != 0) {
return;
}
if (rotateDegrees == 90) {
int cache = rect.bottom;
//noinspection SuspiciousNameCombination
rect.bottom = rect.left;
//noinspection SuspiciousNameCombination
rect.left = rect.top;
//noinspection SuspiciousNameCombination
rect.top = rect.right;
rect.right = cache;
rect.top = drawableSize.y - rect.top;
rect.bottom = drawableSize.y - rect.bottom;
} else if (rotateDegrees == 180) {
int cache = rect.right;
rect.right = rect.left;
rect.left = cache;
cache = rect.bottom;
rect.bottom = rect.top;
rect.top = cache;
rect.top = drawableSize.y - rect.top;
rect.bottom = drawableSize.y - rect.bottom;
rect.left = drawableSize.x - rect.left;
rect.right = drawableSize.x - rect.right;
} else if (rotateDegrees == 270) {
int cache = rect.bottom;
//noinspection SuspiciousNameCombination
rect.bottom = rect.right;
//noinspection SuspiciousNameCombination
rect.right = rect.top;
//noinspection SuspiciousNameCombination
rect.top = rect.left;
rect.left = cache;
rect.left = drawableSize.x - rect.left;
rect.right = drawableSize.x - rect.right;
}
}
/**
* 旋转一个点(只能是90的倍数)
*/
public static void rotatePoint(PointF point, int rotateDegrees, Point drawableSize) {
if (rotateDegrees % 90 != 0) {
return;
}
if (rotateDegrees == 90) {
float newX = drawableSize.y - point.y;
//noinspection SuspiciousNameCombination
float newY = point.x;
point.x = newX;
point.y = newY;
} else if (rotateDegrees == 180) {
float newX = drawableSize.x - point.x;
float newY = drawableSize.y - point.y;
point.x = newX;
point.y = newY;
} else if (rotateDegrees == 270) {
//noinspection SuspiciousNameCombination
float newX = point.y;
float newY = drawableSize.x - point.x;
point.x = newX;
point.y = newY;
}
}
/**
* 生成请求KEY
*
* @param imageUri 图片地址
* @param options 选项
*/
public static String makeRequestKey(String imageUri, DownloadOptions options) {
StringBuilder builder = new StringBuilder();
builder.append(imageUri);
if (options != null) {
options.makeKey(builder);
}
return builder.toString();
}
/**
* 生成请求KEY
*
* @param imageUri 图片地址
* @param optionsKey 选项KEY
* @see me.xiaopan.sketch.SketchImageView#getOptionsKey()
*/
@SuppressWarnings("unused")
public static String makeRequestKey(String imageUri, String optionsKey) {
StringBuilder builder = new StringBuilder();
builder.append(imageUri);
if (!TextUtils.isEmpty(optionsKey)) {
builder.append(optionsKey);
}
return builder.toString();
}
/**
* 生成状态图片用的内存缓存KEY
*
* @param imageUri 图片地址
* @param options 配置
*/
public static String makeStateImageMemoryCacheKey(String imageUri, DownloadOptions options) {
StringBuilder builder = new StringBuilder();
builder.append(imageUri);
if (options != null) {
options.makeStateImageKey(builder);
}
return builder.toString();
}
/**
* 将一个碎片列表转换成字符串
*/
public static String tileListToString(List<Tile> tileList) {
if (tileList == null) {
return null;
}
StringBuilder builder = new StringBuilder();
builder.append("[");
for (Tile tile : tileList) {
if (builder.length() > 1) {
builder.append(",");
}
builder.append("\"");
builder.append(tile.drawRect.left).append(",");
builder.append(tile.drawRect.top).append(",");
builder.append(tile.drawRect.right).append(",");
builder.append(tile.drawRect.bottom);
builder.append("\"");
}
builder.append("]");
return builder.toString();
}
/**
* 根据指定的Bitmap配置获取合适的压缩格式
*/
public static Bitmap.CompressFormat bitmapConfigToCompressFormat(Bitmap.Config config) {
return config == Bitmap.Config.RGB_565 ?
Bitmap.CompressFormat.JPEG : Bitmap.CompressFormat.PNG;
}
/**
* 获取Bitmap占用内存大小,单位字节
*/
public static int getByteCount(Bitmap bitmap) {
// bitmap.isRecycled()过滤很关键,在4.4以及以下版本当bitmap已回收时调用其getAllocationByteCount()方法将直接崩溃
if (bitmap == null || bitmap.isRecycled()) {
return 0;
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
return bitmap.getAllocationByteCount();
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) {
return bitmap.getByteCount();
} else {
return bitmap.getRowBytes() * bitmap.getHeight();
}
}
/**
* 根据宽、高和配置计算所占用的字节数
*/
public static int computeByteCount(int width, int height, Bitmap.Config config) {
return width * height * getBytesPerPixel(config);
}
/**
* 获取指定配置单个像素所占的字节数
*/
public static int getBytesPerPixel(Bitmap.Config config) {
// A bitmap by decoding a gif has null "config" in certain environments.
if (config == null) {
config = Bitmap.Config.ARGB_8888;
}
int bytesPerPixel;
switch (config) {
case ALPHA_8:
bytesPerPixel = 1;
break;
case RGB_565:
case ARGB_4444:
bytesPerPixel = 2;
break;
case ARGB_8888:
default:
bytesPerPixel = 4;
}
return bytesPerPixel;
}
/**
* 获取修剪级别的名称
*/
public static String getTrimLevelName(int level) {
switch (level) {
case ComponentCallbacks2.TRIM_MEMORY_COMPLETE:
return "COMPLETE";
case ComponentCallbacks2.TRIM_MEMORY_MODERATE:
return "MODERATE";
case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND:
return "BACKGROUND";
case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN:
return "UI_HIDDEN";
case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL:
return "RUNNING_CRITICAL";
case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW:
return "RUNNING_LOW";
case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE:
return "RUNNING_MODERATE";
default:
return "UNKNOWN";
}
}
/**
* 指定的栈历史中是否存在指定的类的指定的方法
*/
public static boolean invokeIn(StackTraceElement[] stackTraceElements, Class<?> cla, String methodName) {
if (stackTraceElements == null || stackTraceElements.length == 0) {
return false;
}
String targetClassName = cla.getName();
StackTraceElement element;
String elementClassName;
String elementMethodName;
for (StackTraceElement stackTraceElement : stackTraceElements) {
element = stackTraceElement;
elementClassName = element.getClassName();
elementMethodName = element.getMethodName();
if (targetClassName.equals(elementClassName) && methodName.equals(elementMethodName)) {
return true;
}
}
return false;
}
public static String toHexString(Object object) {
if (object == null) {
return null;
}
return Integer.toHexString(object.hashCode());
}
public static int ceil(int value1, float value2) {
return (int) Math.ceil(value1 / value2);
}
public static boolean isDisabledARGB4444(){
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
}
}