1、flutter的混编在Android上有很多实现方法,activity、fragment、view等。项目里面不涉及fragment和小型view控件,直接采用activity,当然,也可以直接用FlutterActivity加载FlutterEngine,如下:
FlutterActivity.withCachedEngine(routerName).build(context)
返回的是一个intent。直接用startActivity就能跳转到Flutter页面了。这样的好处是采用withCachedEngine方式加快了启动速度。前提是要先有缓存,创建缓存也简单:分两步:
1、创建FlutterEngine
class EngineManager private constructor(){
fun registerEngine(routerName: String,context: Context) {
val engine = FlutterEngine(context)
engine.navigationChannel.setInitialRoute(routerName)
engine.dartExecutor.executeDartEntrypoint(DartExecutor.DartEntrypoint.createDefault())
FlutterEngineCache.getInstance().put(routerName,engine)
}
companion object {
private val singleTest by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { EngineManager() }
@JvmStatic
fun getInstance() = singleTest
}
}
2、在Application注册FlutterEngine
EngineManager.getInstance().registerEngine(FlutterRouters.HISTORICAL_RESERVATION,this);
FlutterRouters.HISTORICAL_RESERVATION是你的路由地址,比如:
public class FlutterRouters {
public static final String HISTORICAL_RESERVATION = "/historical_reservation";
}
上诉方式就能非常简单的调起Flutter页面。但是顶部导航栏就不方便的设置颜色了,而且还有一个弊端,FlutterActivity不是自定义的,如果要监听onActivityResult然后和flutter通信也不方便。那后面就痛定思痛,再改改。
1、创建自定义FlutterActivity
class MyFlutterActivity : FlutterActivity() {
private val CHANNEL_NAME = "methodChannel"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 设置透明状态栏
StatusBarHelper.translucent(this)
}
override fun onPostResume() {
super.onPostResume()
// 重新设置 状态栏 图标 mode
StatusBarHelper.setStatusBarDarkMode(this)
}
override fun getInitialRoute(): String? {
return if (intent.getStringExtra(CACHED_ENGINE_ID) == null) super.getInitialRoute() else intent.getStringExtra(CACHED_ENGINE_ID)
}
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
val methodChannel =
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL_NAME)
methodChannel.setMethodCallHandler { call, result ->
optionCall(methodChannel,call,result)
}
}
private fun optionCall(methodChannel: MethodChannel, call: MethodCall, result: MethodChannel.Result) {
if (call.method == MainConstant.GET_TOKEN){
// val name = call.argument<String>("name")
// val age = call.argument<Int>("age")
// Log.i("flutter", "android receive form:$name ,$age ")
result.success(UserDataManager.getInstance().token)
}
}
companion object {
var CACHED_ENGINE_ID = "cached_engine_id"
@JvmStatic
fun start(context: Context, cachedEngineId: String) {
val intent = Intent(context, MyFlutterActivity::class.java)
intent.putExtra(CACHED_ENGINE_ID,cachedEngineId)
context.startActivity(intent)
}
}
}
注册activity
<activity
android:name=".ui.mine.MyFlutterActivity"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:theme="@style/AppTheme"
android:windowSoftInputMode="adjustResize" />
这里要注意,var CACHED_ENGINE_ID = "cached_engine_id"不能修改参数,为什么呢?,我们追寻到源码里面看看。我们找到FlutterActivity的onCreate方法。
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
switchLaunchThemeForNormalTheme();
super.onCreate(savedInstanceState);
delegate = new FlutterActivityAndFragmentDelegate(this);
delegate.onAttach(this);
..........
}
很明显,flutter的创建全在一个叫FlutterActivityAndFragmentDelegate的代理类中,我们再去这个类的onAttach方法看看。
void onAttach(@NonNull Context context) {
ensureAlive();
// When "retain instance" is true, the FlutterEngine will survive configuration
// changes. Therefore, we create a new one only if one does not already exist.
if (flutterEngine == null) {
setupFlutterEngine();
}
..........
}
如果flutterEngine为空就调用setupFlutterEngine,这明显是初始化过程。再去setupFlutterEngine看。
@VisibleForTesting
/* package */ void setupFlutterEngine() {
Log.v(TAG, "Setting up FlutterEngine.");
// First, check if the host wants to use a cached FlutterEngine.
String cachedEngineId = host.getCachedEngineId();
if (cachedEngineId != null) {
flutterEngine = FlutterEngineCache.getInstance().get(cachedEngineId);
isFlutterEngineFromHost = true;
if (flutterEngine == null) {
throw new IllegalStateException(
"The requested cached FlutterEngine did not exist in the FlutterEngineCache: '"
+ cachedEngineId
+ "'");
}
return;
}
.........
}
这里就很明显了,如果getCachedEngineId不为空,就会用FlutterEngineCache.getInstance().get(cachedEngineId)去取缓存过得FlutterEngine。再点开getCachedEngineId方法看
@Nullable
public String getCachedEngineId() {
return getIntent().getStringExtra(EXTRA_CACHED_ENGINE_ID);
}
static final String EXTRA_CACHED_ENGINE_ID = "cached_engine_id";
所以上面才会说var CACHED_ENGINE_ID = "cached_engine_id"不能修改。底层代码会去读取的。
2、创建FlutterEngine和传参给它。
上面开头说过了,不重复贴代码了。
3、Flutter代码中
3.1设置main.dart代码
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: CustomColors.themeColor,
),
routes: {
'/historical_reservation':(BuildContext context) => const HistoricalReservationPage()
},
);
}
}
这里没什么好说的,主要是注册路由。但是有一点,千万不要在MaterialApp下写home: ....,不然你会发现第一次从flutter返回要返回两次!!当然这里还缺个onUnknownRoute,自己加。
3.2、业务路由页面
class HistoricalReservationPage extends StatefulWidget {
const HistoricalReservationPage({Key? key}) : super(key: key);
@override
State<HistoricalReservationPage> createState() =>
_HistoricalReservationPageState();
}
class _HistoricalReservationPageState
extends BaseState<HistoricalReservationPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("历史预定",style: TextStyle(fontSize: 18,),),
toolbarHeight: 50,
centerTitle: true,
elevation: 0,
leading: InkWell(
child: const Icon(
Icons.arrow_back_ios_outlined,
color: Colors.white,
),
onTap: () {
SystemNavigator.pop();
},
),
),
body: Container(
),
);
}
@override
void onResumed() {
getData(true);
}
@override
void onDetached() {}
@override
void onInactive() {}
@override
void onPaused() {}
}
关闭flutter返回原生页面用SystemNavigator.pop();。另外,,这里把State进行了封装,建了一个BaseState,做个一些基础封装。
abstract class BaseState<T extends StatefulWidget> extends State<T> with WidgetsBindingObserver{
final MethodChannel methodChannel = const MethodChannel('methodChannel');
String? token;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
methodChannel.setMethodCallHandler((call) async {
//原生调用flutter的回调
});
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
if (state == AppLifecycleState.resumed) {
// The application is visible and responding to user input.
// 可见,可操作
print("resumed ===== ");
_resumed();
}
if (state == AppLifecycleState.inactive) {
// The application is in an inactive state and is not receiving user input.
// 可见,不可操作
print("inactive ===== ");
onInactive();
}
if (state == AppLifecycleState.paused) {
// The application is in an inactive state and is not receiving user input.
// 不可见,不可操作
print("paused ===== ");
onPaused();
}
if (state == AppLifecycleState.detached) {
// The application is in an inactive state and is not receiving user input.
// 不可见,界面已销毁
print("detached ===== ");
onDetached();
}
}
void _resumed() async{
token = await methodChannel.invokeMethod(Configuration.GET_TOKEN);
setState(() {
});
onResumed();
}
void onResumed();
void onPaused();
void onInactive();
void onDetached();
}
这里加上了MethodChannel方便和原生传值。另外加了WidgetsBindingObserver监听生命周期。因为我们用的是缓存机制,当我们进入flutter页面时会调用AppLifecycleState.resumed,走到_resumed方法。这样我们就能去原生拿数据了。(我们项目请求接口必须要token传入请求头,而token又是在原生登录时生成的,所以只能去原生获取,所以每次进入页面拿一次就行。另外放在onResum里面也是想每次页面重新回来都要刷新数据。)
到这里就结束了,另外贴上透明状态栏的工具类。
package com.kmilesaway.golf.utils;
import android.annotation.TargetApi;
import android.app.Activity;
import android.graphics.Color;
import android.os.Build;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import androidx.annotation.ColorInt;
import androidx.annotation.IntDef;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class StatusBarHelper {
private final static int STATUSBAR_TYPE_DEFAULT = 0;
private final static int STATUSBAR_TYPE_MIUI = 1;
private final static int STATUSBAR_TYPE_FLYME = 2;
private final static int STATUSBAR_TYPE_ANDROID6 = 3; // Android 6.0
private final static int STATUS_BAR_DEFAULT_HEIGHT_DP = 25; // 大部分状态栏都是25dp
// 在某些机子上存在不同的density值,所以增加两个虚拟值
public static float sVirtualDensity = -1;
public static float sVirtualDensityDpi = -1;
private static int sStatusBarHeight = -1;
private static @StatusBarType
int mStatusBarType = STATUSBAR_TYPE_DEFAULT;
private static Integer sTransparentValue;
public static void translucent(Activity activity) {
translucent(activity.getWindow());
}
public static void translucent(Window window) {
translucent(window, 0x40000000);
}
private static boolean supportTranslucent() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT
// Essential Phone 在 Android 8 之前沉浸式做得不全,系统不从状态栏顶部开始布局却会下发 WindowInsets
&& !(DeviceHelper.isEssentialPhone() && Build.VERSION.SDK_INT < 26);
}
@TargetApi(19)
public static void translucent(Window window, @ColorInt int colorOn5x) {
if (!supportTranslucent()) {
// 版本小于4.4,绝对不考虑沉浸式
return;
}
// if (QMUINotchHelper.isNotchOfficialSupport()) {
// handleDisplayCutoutMode(window);
// }
// 小米和魅族4.4 以上版本支持沉浸式
// 小米 Android 6.0 ,开发版 7.7.13 及以后版本设置黑色字体又需要 clear FLAG_TRANSLUCENT_STATUS, 因此还原为官方模式
if (DeviceHelper.isFlymeLowerThan(8) || (DeviceHelper.isMIUI() && Build.VERSION.SDK_INT < Build.VERSION_CODES.M)) {
window.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS,
WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
return;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
int systemUiVisibility = window.getDecorView().getSystemUiVisibility();
systemUiVisibility |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
window.getDecorView().setSystemUiVisibility(systemUiVisibility);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && supportTransclentStatusBar6()) {
// android 6以后可以改状态栏字体颜色,因此可以自行设置为透明
// ZUK Z1是个另类,自家应用可以实现字体颜色变色,但没开放接口
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.setStatusBarColor(Color.TRANSPARENT);
} else {
// android 5不能修改状态栏字体颜色,因此直接用FLAG_TRANSLUCENT_STATUS,nexus表现为半透明
// 魅族和小米的表现如何?
// update: 部分手机运用FLAG_TRANSLUCENT_STATUS时背景不是半透明而是没有背景了。。。。。
// window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
// 采取setStatusBarColor的方式,部分机型不支持,那就纯黑了,保证状态栏图标可见
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.setStatusBarColor(colorOn5x);
}
// } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
// // android4.4的默认是从上到下黑到透明,我们的背景是白色,很难看,因此只做魅族和小米的
// } else if(Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR1){
// // 如果app 为白色,需要更改状态栏颜色,因此不能让19一下支持透明状态栏
// Window window = activity.getWindow();
// Integer transparentValue = getStatusBarAPITransparentValue(activity);
// if(transparentValue != null) {
// window.getDecorView().setSystemUiVisibility(transparentValue);
// }
}
}
/**
* 设置状态栏黑色字体图标,
* 支持 4.4 以上版本 MIUI 和 Flyme,以及 6.0 以上版本的其他 Android
*
* @param activity 需要被处理的 Activity
*/
public static boolean setStatusBarLightMode(Activity activity) {
if (activity == null) return false;
// 无语系列:ZTK C2016只能时间和电池图标变色。。。。
if (DeviceHelper.isZTKC2016()) {
return false;
}
if (mStatusBarType != STATUSBAR_TYPE_DEFAULT) {
return setStatusBarLightMode(activity, mStatusBarType);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
if (isMIUICustomStatusBarLightModeImpl() && MIUISetStatusBarLightMode(activity.getWindow(), true)) {
mStatusBarType = STATUSBAR_TYPE_MIUI;
return true;
} else if (FlymeSetStatusBarLightMode(activity.getWindow(), true)) {
mStatusBarType = STATUSBAR_TYPE_FLYME;
return true;
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Android6SetStatusBarLightMode(activity.getWindow(), true);
mStatusBarType = STATUSBAR_TYPE_ANDROID6;
return true;
}
}
return false;
}
/**
* 已知系统类型时,设置状态栏黑色字体图标。
* 支持 4.4 以上版本 MIUI 和 Flyme,以及 6.0 以上版本的其他 Android
*
* @param activity 需要被处理的 Activity
* @param type StatusBar 类型,对应不同的系统
*/
private static boolean setStatusBarLightMode(Activity activity, @StatusBarType int type) {
if (type == STATUSBAR_TYPE_MIUI) {
return MIUISetStatusBarLightMode(activity.getWindow(), true);
} else if (type == STATUSBAR_TYPE_FLYME) {
return FlymeSetStatusBarLightMode(activity.getWindow(), true);
} else if (type == STATUSBAR_TYPE_ANDROID6) {
return Android6SetStatusBarLightMode(activity.getWindow(), true);
}
return false;
}
/**
* 设置状态栏白色字体图标
* 支持 4.4 以上版本 MIUI 和 Flyme,以及 6.0 以上版本的其他 Android
*/
public static boolean setStatusBarDarkMode(Activity activity) {
if (activity == null) return false;
if (mStatusBarType == STATUSBAR_TYPE_DEFAULT) {
// 默认状态,不需要处理
return true;
}
if (mStatusBarType == STATUSBAR_TYPE_MIUI) {
return MIUISetStatusBarLightMode(activity.getWindow(), false);
} else if (mStatusBarType == STATUSBAR_TYPE_FLYME) {
return FlymeSetStatusBarLightMode(activity.getWindow(), false);
} else if (mStatusBarType == STATUSBAR_TYPE_ANDROID6) {
return Android6SetStatusBarLightMode(activity.getWindow(), false);
}
return true;
}
/**
* 设置状态栏字体图标为深色,Android 6
*
* @param window 需要设置的窗口
* @param light 是否把状态栏字体及图标颜色设置为深色
* @return boolean 成功执行返回true
*/
@TargetApi(23)
private static boolean Android6SetStatusBarLightMode(Window window, boolean light) {
View decorView = window.getDecorView();
int systemUi = decorView.getSystemUiVisibility();
if (light) {
systemUi |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
} else {
systemUi ^= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
}
decorView.setSystemUiVisibility(systemUi);
if (DeviceHelper.isMIUIV9()) {
// MIUI 9 低于 6.0 版本依旧只能回退到以前的方案
// https://github.com/Tencent/QMUI_Android/issues/160
MIUISetStatusBarLightMode(window, light);
}
return true;
}
/**
* 设置状态栏字体图标为深色,需要 MIUIV6 以上
*
* @param window 需要设置的窗口
* @param light 是否把状态栏字体及图标颜色设置为深色
* @return boolean 成功执行返回 true
*/
@SuppressWarnings("unchecked")
public static boolean MIUISetStatusBarLightMode(Window window, boolean light) {
boolean result = false;
if (window != null) {
Class clazz = window.getClass();
try {
int darkModeFlag;
Class layoutParams = Class.forName("android.view.MiuiWindowManager$LayoutParams");
Field field = layoutParams.getField("EXTRA_FLAG_STATUS_BAR_DARK_MODE");
darkModeFlag = field.getInt(layoutParams);
Method extraFlagField = clazz.getMethod("setExtraFlags", int.class, int.class);
if (light) {
extraFlagField.invoke(window, darkModeFlag, darkModeFlag);//状态栏透明且黑色字体
} else {
extraFlagField.invoke(window, 0, darkModeFlag);//清除黑色字体
}
result = true;
} catch (Exception ignored) {
}
}
return result;
}
/**
* 更改状态栏图标、文字颜色的方案是否是MIUI自家的, MIUI9 && Android 6 之后用回Android原生实现
* 见小米开发文档说明:https://dev.mi.com/console/doc/detail?pId=1159
*/
private static boolean isMIUICustomStatusBarLightModeImpl() {
if (DeviceHelper.isMIUIV9() && Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
return true;
}
return DeviceHelper.isMIUIV5() || DeviceHelper.isMIUIV6() ||
DeviceHelper.isMIUIV7() || DeviceHelper.isMIUIV8();
}
/**
* 设置状态栏图标为深色和魅族特定的文字风格
* 可以用来判断是否为 Flyme 用户
*
* @param window 需要设置的窗口
* @param light 是否把状态栏字体及图标颜色设置为深色
* @return boolean 成功执行返回true
*/
public static boolean FlymeSetStatusBarLightMode(Window window, boolean light) {
boolean result = false;
if (window != null) {
Android6SetStatusBarLightMode(window, light);
// flyme 在 6.2.0.0A 支持了 Android 官方的实现方案,旧的方案失效
// 高版本调用这个出现不可预期的 Bug,官方文档也没有给出完整的高低版本兼容方案
if (DeviceHelper.isFlymeLowerThan(7)) {
try {
WindowManager.LayoutParams lp = window.getAttributes();
Field darkFlag = WindowManager.LayoutParams.class
.getDeclaredField("MEIZU_FLAG_DARK_STATUS_BAR_ICON");
Field meizuFlags = WindowManager.LayoutParams.class
.getDeclaredField("meizuFlags");
darkFlag.setAccessible(true);
meizuFlags.setAccessible(true);
int bit = darkFlag.getInt(null);
int value = meizuFlags.getInt(lp);
if (light) {
value |= bit;
} else {
value &= ~bit;
}
meizuFlags.setInt(lp, value);
window.setAttributes(lp);
result = true;
} catch (Exception ignored) {
}
} else if (DeviceHelper.isFlyme()) {
result = true;
}
}
return result;
}
/**
* 检测 Android 6.0 是否可以启用 window.setStatusBarColor(Color.TRANSPARENT)。
*/
public static boolean supportTransclentStatusBar6() {
return !(DeviceHelper.isZUKZ1() || DeviceHelper.isZTKC2016());
}
@IntDef({STATUSBAR_TYPE_DEFAULT, STATUSBAR_TYPE_MIUI, STATUSBAR_TYPE_FLYME, STATUSBAR_TYPE_ANDROID6})
@Retention(RetentionPolicy.SOURCE)
private @interface StatusBarType {
}
}
package com.kmilesaway.golf.utils;
import android.annotation.TargetApi;
import android.app.AppOpsManager;
import android.content.Context;
import android.content.res.Configuration;
import android.os.Binder;
import android.os.Build;
import android.os.Environment;
import android.text.TextUtils;
import androidx.annotation.Nullable;
import java.io.File;
import java.io.FileInputStream;
import java.lang.reflect.Method;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class DeviceHelper {
private final static String TAG = "QMUIDeviceHelper";
private final static String KEY_MIUI_VERSION_NAME = "ro.miui.ui.version.name";
private static final String KEY_FLYME_VERSION_NAME = "ro.build.display.id";
private final static String FLYME = "flyme";
private final static String ZTEC2016 = "zte c2016";
private final static String ZUKZ1 = "zuk z1";
private final static String ESSENTIAL = "essential";
private final static String MEIZUBOARD[] = {"m9", "M9", "mx", "MX"};
private static String sMiuiVersionName;
private static String sFlymeVersionName;
private static boolean sIsTabletChecked = false;
private static boolean sIsTabletValue = false;
private static final String BRAND = Build.BRAND.toLowerCase();
static {
Properties properties = new Properties();
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
// android 8.0,读取 /system/uild.prop 会报 permission denied
FileInputStream fileInputStream = null;
try {
fileInputStream = new FileInputStream(new File(Environment.getRootDirectory(), "build.prop"));
properties.load(fileInputStream);
} catch (Exception e) {
// QMUILog.printErrStackTrace(TAG, e, "read file error");
} finally {
// QMUILangHelper.close(fileInputStream);
}
}
Class<?> clzSystemProperties = null;
try {
clzSystemProperties = Class.forName("android.os.SystemProperties");
Method getMethod = clzSystemProperties.getDeclaredMethod("get", String.class);
// miui
sMiuiVersionName = getLowerCaseName(properties, getMethod, KEY_MIUI_VERSION_NAME);
//flyme
sFlymeVersionName = getLowerCaseName(properties, getMethod, KEY_FLYME_VERSION_NAME);
} catch (Exception e) {
// QMUILog.printErrStackTrace(TAG, e, "read SystemProperties error");
}
}
private static boolean _isTablet(Context context) {
return (context.getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) >=
Configuration.SCREENLAYOUT_SIZE_LARGE;
}
/**
* 判断是否为平板设备
*/
public static boolean isTablet(Context context) {
if (sIsTabletChecked) {
return sIsTabletValue;
}
sIsTabletValue = _isTablet(context);
sIsTabletChecked = true;
return sIsTabletValue;
}
/**
* 判断是否是flyme系统
*/
public static boolean isFlyme() {
return !TextUtils.isEmpty(sFlymeVersionName) && sFlymeVersionName.contains(FLYME);
}
/**
* 判断是否是MIUI系统
*/
public static boolean isMIUI() {
return !TextUtils.isEmpty(sMiuiVersionName);
}
public static boolean isMIUIV5() {
return "v5".equals(sMiuiVersionName);
}
public static boolean isMIUIV6() {
return "v6".equals(sMiuiVersionName);
}
public static boolean isMIUIV7() {
return "v7".equals(sMiuiVersionName);
}
public static boolean isMIUIV8() {
return "v8".equals(sMiuiVersionName);
}
public static boolean isMIUIV9() {
return "v9".equals(sMiuiVersionName);
}
public static boolean isFlymeLowerThan(int majorVersion){
return isFlymeLowerThan(majorVersion, 0, 0);
}
public static boolean isFlymeLowerThan(int majorVersion, int minorVersion, int patchVersion) {
boolean isLower = false;
if (sFlymeVersionName != null && !sFlymeVersionName.equals("")) {
try{
Pattern pattern = Pattern.compile("(\\d+\\.){2}\\d");
Matcher matcher = pattern.matcher(sFlymeVersionName);
if (matcher.find()) {
String versionString = matcher.group();
if (versionString.length() > 0) {
String[] version = versionString.split("\\.");
if (version.length >= 1) {
if (Integer.parseInt(version[0]) < majorVersion) {
isLower = true;
}
}
if(version.length >= 2 && minorVersion > 0){
if (Integer.parseInt(version[1]) < majorVersion) {
isLower = true;
}
}
if(version.length >= 3 && patchVersion > 0){
if (Integer.parseInt(version[2]) < majorVersion) {
isLower = true;
}
}
}
}
}catch (Throwable ignore){
}
}
return isMeizu() && isLower;
}
public static boolean isMeizu() {
return isPhone(MEIZUBOARD) || isFlyme();
}
/**
* 判断是否为小米
* https://dev.mi.com/doc/?p=254
*/
public static boolean isXiaomi() {
return Build.MANUFACTURER.toLowerCase().equals("xiaomi");
}
public static boolean isVivo() {
return BRAND.contains("vivo") || BRAND.contains("bbk");
}
public static boolean isOppo() {
return BRAND.contains("oppo");
}
public static boolean isHuawei() {
return BRAND.contains("huawei") || BRAND.contains("honor");
}
public static boolean isEssentialPhone(){
return BRAND.contains("essential");
}
/**
* 判断是否为 ZUK Z1 和 ZTK C2016。
* 两台设备的系统虽然为 android 6.0,但不支持状态栏icon颜色改变,因此经常需要对它们进行额外判断。
*/
public static boolean isZUKZ1() {
final String board = android.os.Build.MODEL;
return board != null && board.toLowerCase().contains(ZUKZ1);
}
public static boolean isZTKC2016() {
final String board = android.os.Build.MODEL;
return board != null && board.toLowerCase().contains(ZTEC2016);
}
private static boolean isPhone(String[] boards) {
final String board = android.os.Build.BOARD;
if (board == null) {
return false;
}
for (String board1 : boards) {
if (board.equals(board1)) {
return true;
}
}
return false;
}
/**
* 判断悬浮窗权限(目前主要用户魅族与小米的检测)。
*/
public static boolean isFloatWindowOpAllowed(Context context) {
final int version = Build.VERSION.SDK_INT;
if (version >= 19) {
return checkOp(context, 24); // 24 是AppOpsManager.OP_SYSTEM_ALERT_WINDOW 的值,该值无法直接访问
} else {
try {
return (context.getApplicationInfo().flags & 1 << 27) == 1 << 27;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
}
@TargetApi(19)
private static boolean checkOp(Context context, int op) {
final int version = Build.VERSION.SDK_INT;
if (version >= Build.VERSION_CODES.KITKAT) {
AppOpsManager manager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
try {
Method method = manager.getClass().getDeclaredMethod("checkOp", int.class, int.class, String.class);
int property = (Integer) method.invoke(manager, op,
Binder.getCallingUid(), context.getPackageName());
return AppOpsManager.MODE_ALLOWED == property;
} catch (Exception e) {
e.printStackTrace();
}
}
return false;
}
@Nullable
private static String getLowerCaseName(Properties p, Method get, String key) {
String name = p.getProperty(key);
if (name == null) {
try {
name = (String) get.invoke(null, key);
} catch (Exception ignored) {
}
}
if (name != null) name = name.toLowerCase();
return name;
}
}