美文网首页
Flutter笔记:自定义FlutterActivity实现跳转

Flutter笔记:自定义FlutterActivity实现跳转

作者: 坑逼的严 | 来源:发表于2023-09-14 11:33 被阅读0次

    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;
        }
    }
    
    

    相关文章

      网友评论

          本文标题:Flutter笔记:自定义FlutterActivity实现跳转

          本文链接:https://www.haomeiwen.com/subject/btemvdtx.html