一、背景
某些APP项目中需要针对高中低端安卓机型做不同的适配,例如:特效的开关、动画效果的调整等。怎么在项目中对Android进行高低端机型的区分?接下来的内容会进行分析。
二、区分标准
区分的标准最直观的就是跑分数据。参考现在最主流的跑分软件安兔兔,数据主要由4部分构成,内存(RAM)、CPU、GPU、IO(数据库、SD读写),其中内存、CPU、GPU性能构成主要占比,IO性能次要。内存和CPU是所有功能的根本,而GPU则是对游戏类应用影响更大些,因此在非游戏类的普通应用,更注重内存和CPU。
三、技术方案
我们看一下Android本身能提供哪些有用的数据给我们。先给出结论,CPU相关我们能获取到型号、核心数、最大主频;内存相关我们能获取到RAM值;GPU相关的暂时无法获取有关信息。
1.CPU相关
获取CPU型号
public static String getCPUName() {
try {
FileReader fr = new FileReader("/proc/cpuinfo");
BufferedReader br = new BufferedReader(fr);
String text;
String last = "";
while ((text = br.readLine()) != null) {
last = text;
}
if (last.contains("Hardware")) {
String[] hardWare = last.split(":\\s+", 2);
return hardWare[1];
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return Build.HARDWARE;
}
获取CPU核心数及最大主频
private static int getCoresFromFileInfo(String fileLocation) {
InputStream is = null;
try {
is = new FileInputStream(fileLocation);
BufferedReader buf = new BufferedReader(new InputStreamReader(is));
String fileContents = buf.readLine();
buf.close();
return getCoresFromFileString(fileContents);
} catch (IOException e) {
return DEVICEINFO_UNKNOWN;
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
}
}
}
}
public static int getNumberOfCPUCores() {
int cores;
try {
cores = getCoresFromFileInfo("/sys/devices/system/cpu/possible");
if (cores == DEVICEINFO_UNKNOWN) {
cores = getCoresFromFileInfo("/sys/devices/system/cpu/present");
}
if (cores == DEVICEINFO_UNKNOWN) {
cores = new File("/sys/devices/system/cpu/").listFiles(CPU_FILTER).length;;
}
} catch (SecurityException e) {
cores = DEVICEINFO_UNKNOWN;
} catch (NullPointerException e) {
cores = DEVICEINFO_UNKNOWN;
}
return cores;
}
public static int getCPUMaxFreqKHz() {
int maxFreq = DEVICEINFO_UNKNOWN;
try {
for (int i = 0; i < getNumberOfCPUCores(); i++) {
String filename =
"/sys/devices/system/cpu/cpu" + i + "/cpufreq/cpuinfo_max_freq";
File cpuInfoMaxFreqFile = new File(filename);
if (cpuInfoMaxFreqFile.exists() && cpuInfoMaxFreqFile.canRead()) {
byte[] buffer = new byte[128];
FileInputStream stream = new FileInputStream(cpuInfoMaxFreqFile);
try {
stream.read(buffer);
int endIndex = 0;
while (Character.isDigit(buffer[endIndex]) && endIndex < buffer.length) {
endIndex++;
}
String str = new String(buffer, 0, endIndex);
Integer freqBound = Integer.parseInt(str);
if (freqBound > maxFreq) {
maxFreq = freqBound;
}
} catch (NumberFormatException e) {
} finally {
stream.close();
}
}
}
if (maxFreq == DEVICEINFO_UNKNOWN) {
FileInputStream stream = new FileInputStream("/proc/cpuinfo");
try {
int freqBound = parseFileForValue("cpu MHz", stream);
freqBound *= 1000;
if (freqBound > maxFreq) maxFreq = freqBound;
} finally {
stream.close();
}
}
} catch (IOException e) {
maxFreq = DEVICEINFO_UNKNOWN;
}
return maxFreq;
}
- 我们可以通过对比CPU的核心数和最大主频来判断CPU的优劣,在同系列的CPU间这样判断是相对可靠的,但在不同系列之间单纯以此为依据就不可靠了。(由于还涉及兼容性以及其他技术因素影响,不同系列的CPU之间就算以上两个参数相近的情况下,表现出来的性能也可能差别很大)
- 直接通过CPU型号判断,截止2019年1月市面上大多安卓机型上的CPU可以分为这几个系列:高通骁龙、华为海思麒麟、联发科MTK、三星猎户座(主要面对欧美市场,中国市场的三星主要是高通,可暂时忽略)。联发科MTK主打中低端市场,高端处理器对标高通、麒麟、三星有明显差距,因此重点关注的是高通骁龙和海思麒麟这两个系列。高通主要分为200、400、600、700和800系列(不同系列适配不同机型,不代表800系列性能都比600系列好),目前最顶级是高通骁龙845。麒麟主要分为910、920、925、950、980系列,目前最顶级的是麒麟980。2018年的旗舰手机,基本都搭载了这两款CPU。
2.内存相关
获取RAM容量
public static long getTotalMemory(Context c) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
ActivityManager.MemoryInfo memInfo = new ActivityManager.MemoryInfo();
ActivityManager am = (ActivityManager) c.getSystemService(Context.ACTIVITY_SERVICE);
am.getMemoryInfo(memInfo);
if (memInfo != null) {
return memInfo.totalMem;
} else {
return DEVICEINFO_UNKNOWN;
}
} else {
long totalMem = DEVICEINFO_UNKNOWN;
try {
FileInputStream stream = new FileInputStream("/proc/meminfo");
try {
totalMem = parseFileForValue("MemTotal", stream);
totalMem *= 1024;
} finally {
stream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
return totalMem;
}
}
- 内存的对比就很直观了,大内存优于小内存。从2018年市场上大部分的主流手机来分析,内存大致分为2G以下、3G、4G、6G以及8G这几个档位。
3.手机相关信息
public static String getModel() {
return Build.MODEL;
}
public static String getBrand() {
return Build.BRAND;
}
四、结论
- 高端机型:CPU为骁龙845或麒麟980,RAM大于等于6GB
- 低端机型:骁龙或联发科系列,CPU最大主频小于等于1.8GHz且RAM小于4GB。麒麟系列,CPU最大主频小于等于2.1GHz且RAM小于等于4GB
- 其余为中端机型
五、代码实现
import android.annotation.TargetApi;
import android.app.ActivityManager;
import android.content.Context;
import android.os.Build;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
public class Themis {
public static final String TAG = "Themis";
public static final int DEVICE_LEVEL_HIGH = 2;
public static final int DEVICE_LEVEL_MID = 1;
public static final int DEVICE_LEVEL_LOW = 0;
public static final int DEVICE_LEVEL_UNKNOWN = -1;
public static final int DEVICEINFO_UNKNOWN = -1;
private static final FileFilter CPU_FILTER = new FileFilter() {
@Override
public boolean accept(File pathname) {
String path = pathname.getName();
if (path.startsWith("cpu")) {
for (int i = 3; i < path.length(); i++) {
if (!Character.isDigit(path.charAt(i))) {
return false;
}
}
return true;
}
return false;
}
};
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
public static long getTotalMemory(Context c) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
ActivityManager.MemoryInfo memInfo = new ActivityManager.MemoryInfo();
ActivityManager am = (ActivityManager) c.getSystemService(Context.ACTIVITY_SERVICE);
am.getMemoryInfo(memInfo);
if (memInfo != null) {
return memInfo.totalMem;
} else {
return DEVICEINFO_UNKNOWN;
}
} else {
long totalMem = DEVICEINFO_UNKNOWN;
try {
FileInputStream stream = new FileInputStream("/proc/meminfo");
try {
totalMem = parseFileForValue("MemTotal", stream);
totalMem *= 1024;
} finally {
stream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
return totalMem;
}
}
public static int getCPUMaxFreqKHz() {
int maxFreq = DEVICEINFO_UNKNOWN;
try {
for (int i = 0; i < getNumberOfCPUCores(); i++) {
String filename =
"/sys/devices/system/cpu/cpu" + i + "/cpufreq/cpuinfo_max_freq";
File cpuInfoMaxFreqFile = new File(filename);
if (cpuInfoMaxFreqFile.exists() && cpuInfoMaxFreqFile.canRead()) {
byte[] buffer = new byte[128];
FileInputStream stream = new FileInputStream(cpuInfoMaxFreqFile);
try {
stream.read(buffer);
int endIndex = 0;
while (Character.isDigit(buffer[endIndex]) && endIndex < buffer.length) {
endIndex++;
}
String str = new String(buffer, 0, endIndex);
Integer freqBound = Integer.parseInt(str);
if (freqBound > maxFreq) {
maxFreq = freqBound;
}
} catch (NumberFormatException e) {
} finally {
stream.close();
}
}
}
if (maxFreq == DEVICEINFO_UNKNOWN) {
FileInputStream stream = new FileInputStream("/proc/cpuinfo");
try {
int freqBound = parseFileForValue("cpu MHz", stream);
freqBound *= 1000;
if (freqBound > maxFreq) maxFreq = freqBound;
} finally {
stream.close();
}
}
} catch (IOException e) {
maxFreq = DEVICEINFO_UNKNOWN;
}
return maxFreq;
}
public static int getNumberOfCPUCores() {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.GINGERBREAD_MR1) {
return 1;
}
int cores;
try {
cores = getCoresFromFileInfo("/sys/devices/system/cpu/possible");
if (cores == DEVICEINFO_UNKNOWN) {
cores = getCoresFromFileInfo("/sys/devices/system/cpu/present");
}
if (cores == DEVICEINFO_UNKNOWN) {
cores = new File("/sys/devices/system/cpu/").listFiles(CPU_FILTER).length;;
}
} catch (SecurityException e) {
cores = DEVICEINFO_UNKNOWN;
} catch (NullPointerException e) {
cores = DEVICEINFO_UNKNOWN;
}
return cores;
}
private static int getCoresFromFileInfo(String fileLocation) {
InputStream is = null;
try {
is = new FileInputStream(fileLocation);
BufferedReader buf = new BufferedReader(new InputStreamReader(is));
String fileContents = buf.readLine();
buf.close();
return getCoresFromFileString(fileContents);
} catch (IOException e) {
return DEVICEINFO_UNKNOWN;
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
}
}
}
}
private static int getCoresFromFileString(String str) {
if (str == null || !str.matches("0-[\\d]+$")) {
return DEVICEINFO_UNKNOWN;
}
return Integer.valueOf(str.substring(2)) + 1;
}
private static int parseFileForValue(String textToMatch, FileInputStream stream) {
byte[] buffer = new byte[1024];
try {
int length = stream.read(buffer);
for (int i = 0; i < length; i++) {
if (buffer[i] == '\n' || i == 0) {
if (buffer[i] == '\n') i++;
for (int j = i; j < length; j++) {
int textIndex = j - i;
if (buffer[j] != textToMatch.charAt(textIndex)) {
break;
}
if (textIndex == textToMatch.length() - 1) {
return extractValue(buffer, j);
}
}
}
}
} catch (IOException e) {
} catch (NumberFormatException e) {
}
return DEVICEINFO_UNKNOWN;
}
private static int extractValue(byte[] buffer, int index) {
while (index < buffer.length && buffer[index] != '\n') {
if (Character.isDigit(buffer[index])) {
int start = index;
index++;
while (index < buffer.length && Character.isDigit(buffer[index])) {
index++;
}
String str = new String(buffer, 0, start, index - start);
return Integer.parseInt(str);
}
index++;
}
return DEVICEINFO_UNKNOWN;
}
public static long getAvailMemory(Context context) {
ActivityManager am = (ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE);
ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo();
am.getMemoryInfo(mi);
return mi.availMem;
}
public static String getBrand() {
return Build.BRAND;
}
public static String getModel() {
return Build.MODEL;
}
public static String getHardWare() {
try {
FileReader fr = new FileReader("/proc/cpuinfo");
BufferedReader br = new BufferedReader(fr);
String text;
String last = "";
while ((text = br.readLine()) != null) {
last = text;
}
if (last.contains("Hardware")) {
String[] hardWare = last.split(":\\s+", 2);
return hardWare[1];
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return Build.HARDWARE;
}
public static int judgeDeviceLevel(Context context) {
int level = DEVICE_LEVEL_UNKNOWN;
int ramLevel = judgeMemory(context);
int cpuLevel = judgeCPU();
if (ramLevel == 0 || ramLevel == 1 || cpuLevel == 0) {
level = DEVICE_LEVEL_LOW;
} else if (ramLevel == 2 && (cpuLevel >= 1)) {
level = DEVICE_LEVEL_MID;
} else if (ramLevel > 2) {
if (cpuLevel > 1) {
level = DEVICE_LEVEL_HIGH;
} else {
level = DEVICE_LEVEL_MID;
}
}
return level;
}
private static int judgeMemory(Context context) {
long ramMB = getTotalMemory(context) / (1024 * 1024);
int level = -1;
if (ramMB <= 2000) {
level = 0;
} else if (ramMB <= 3000) {
level = 1;
} else if (ramMB <= 4000) {
level = 2;
} else if (ramMB <= 6000) {
level = 3;
} else {
level = 4;
}
return level;
}
private static int judgeCPU() {
int level = -1;
String cpuName = getHardWare();
int freqMHz = getCPUMaxFreqKHz() / 1000;
if (freqMHz <= 1600) {
level = 0;
} else if (freqMHz <= 2000) {
level = 1;
} else if (freqMHz <= 2500) {
level = 2;
} else {
level = 3;
}
return level;
}
}
|