From 04db94767916611e48098c2ea2873e596b950e3d Mon Sep 17 00:00:00 2001 From: cgspine Date: Thu, 19 Nov 2020 14:13:30 +0800 Subject: [PATCH] reflect helper and toast fix from booster. --- .../qmuiteam/qmui/util/QMUIReflectHelper.java | 240 ++++++++++++++++++ .../qmuiteam/qmui/util/QMUIToastHelper.java | 98 +++++++ 2 files changed, 338 insertions(+) create mode 100644 qmui/src/main/java/com/qmuiteam/qmui/util/QMUIReflectHelper.java create mode 100644 qmui/src/main/java/com/qmuiteam/qmui/util/QMUIToastHelper.java diff --git a/qmui/src/main/java/com/qmuiteam/qmui/util/QMUIReflectHelper.java b/qmui/src/main/java/com/qmuiteam/qmui/util/QMUIReflectHelper.java new file mode 100644 index 00000000..4a41e774 --- /dev/null +++ b/qmui/src/main/java/com/qmuiteam/qmui/util/QMUIReflectHelper.java @@ -0,0 +1,240 @@ +/* + * Tencent is pleased to support the open source community by making QMUI_Android available. + * + * Copyright (C) 2017-2018 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the MIT License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://opensource.org/licenses/MIT + * + * 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 com.qmuiteam.qmui.util; + +import android.util.Log; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Arrays; + +// Modify from https://github.com/didi/booster/blob/master/booster-android-instrument/src/main/java/com/didiglobal/booster/instrument/Reflection.java +public class QMUIReflectHelper { + private static final String TAG = "QMUIReflectHelper"; + + private QMUIReflectHelper() { + } + @SuppressWarnings("unchecked") + public static T getStaticFieldValue(final Class cls, final String name) { + if (null != cls && null != name) { + try { + final Field field = getField(cls, name); + if (null != field) { + field.setAccessible(true); + return (T) field.get(cls); + } + } catch (final Throwable t) { + Log.w(TAG, "get static field " + name + " of " + cls + " error", t); + } + } + + return null; + } + + public static boolean setStaticFieldValue(final Class cls, final String name, final Object value) { + if (null != cls && null != name) { + try { + final Field field = getField(cls, name); + if (null != field) { + field.setAccessible(true); + field.set(cls, value); + return true; + } + } catch (final Throwable t) { + Log.w(TAG, "set static field " + name + " of " + cls + " error", t); + } + } + + return false; + } + + @SuppressWarnings("unchecked") + public static T getFieldValue(final Object obj, final String name) { + if (null != obj && null != name) { + try { + final Field field = getField(obj.getClass(), name); + if (null != field) { + field.setAccessible(true); + return (T) field.get(obj); + } + } catch (final Throwable t) { + Log.w(TAG, "get field " + name + " of " + obj + " error", t); + } + } + + return null; + } + + @SuppressWarnings("unchecked") + public static T getFieldValue(final Object obj, final Class type) { + if (null != obj && null != type) { + try { + final Field field = getField(obj.getClass(), type); + if (null != field) { + field.setAccessible(true); + return (T) field.get(obj); + } + } catch (final Throwable t) { + Log.w(TAG, "get field with type " + type + " of " + obj + " error", t); + } + } + + return null; + } + + public static boolean setFieldValue(final Object obj, final String name, final Object value) { + if (null != obj && null != name) { + try { + final Field field = getField(obj.getClass(), name); + if (null != field) { + field.setAccessible(true); + field.set(obj, value); + return true; + } + } catch (final Throwable t) { + Log.w(TAG, "set field " + name + " of " + obj + " error", t); + } + } + + return false; + } + + public static T newInstance(final String className, final Object... args) { + try { + return newInstance(Class.forName(className), args); + } catch (final ClassNotFoundException e) { + Log.w(TAG, "new instance of " + className + " error", e); + return null; + } + } + + @SuppressWarnings("unchecked") + public static T newInstance(final Class clazz, Object... args) { + final Constructor[] ctors = clazz.getDeclaredConstructors(); + + loop: + for (final Constructor ctor : ctors) { + final Class[] types = ctor.getParameterTypes(); + if (types.length == args.length) { + for (int i = 0; i < types.length; i++) { + if (null != args[i] && !types[i].isAssignableFrom(args[i].getClass())) { + continue loop; + } + } + + try { + ctor.setAccessible(true); + return (T) ctor.newInstance(args); + } catch (final Throwable t) { + Log.w(TAG, "Invoke constructor " + ctor + " error", t); + return null; + } + } + } + + return null; + } + + @SuppressWarnings("unchecked") + public static T invokeStaticMethod(final Class klass, final String name) { + return invokeStaticMethod(klass, name, new Class[0], new Object[0]); + } + + @SuppressWarnings("unchecked") + public static T invokeStaticMethod(final Class klass, final String name, final Class[] types, final Object[] args) { + if (null != klass && null != name && null != types && null != args && types.length == args.length) { + try { + final Method method = getMethod(klass, name, types); + if (null != method) { + method.setAccessible(true); + return (T) method.invoke(klass, args); + } + } catch (final Throwable e) { + Log.w(TAG, "Invoke " + name + "(" + Arrays.toString(types) + ") of " + klass + " error", e); + } + } + + return null; + } + + + @SuppressWarnings("unchecked") + public static T invokeMethod(final Object obj, final String name) { + return invokeMethod(obj, name, new Class[0], new Object[0]); + } + + @SuppressWarnings("unchecked") + public static T invokeMethod(final Object obj, final String name, final Class[] types, final Object[] args) { + if (null != obj && null != name && null != types && null != args && types.length == args.length) { + try { + final Method method = getMethod(obj.getClass(), name, types); + if (null != method) { + method.setAccessible(true); + return (T) method.invoke(obj, args); + } + } catch (final Throwable e) { + Log.w(TAG, "Invoke " + name + "(" + Arrays.toString(types) + ") of " + obj + " error", e); + } + } + + return null; + } + + public static Field getField(final Class cls, final String name) { + try { + return cls.getDeclaredField(name); + } catch (final NoSuchFieldException e) { + final Class parent = cls.getSuperclass(); + if (null == parent) { + return null; + } + return getField(parent, name); + } + } + + public static Field getField(final Class cls, final Class type) { + final Field[] fields = cls.getDeclaredFields(); + if (fields.length <= 0) { + final Class parent = cls.getSuperclass(); + if (null == parent) { + return null; + } + return getField(parent, type); + } + + for (final Field field : fields) { + if (field.getType() == type) { + return field; + } + } + + return null; + } + + private static Method getMethod(final Class cls, final String name, final Class[] types) { + try { + return cls.getDeclaredMethod(name, types); + } catch (final NoSuchMethodException e) { + final Class parent = cls.getSuperclass(); + if (null == parent) { + return null; + } + return getMethod(parent, name, types); + } + } +} diff --git a/qmui/src/main/java/com/qmuiteam/qmui/util/QMUIToastHelper.java b/qmui/src/main/java/com/qmuiteam/qmui/util/QMUIToastHelper.java new file mode 100644 index 00000000..45092580 --- /dev/null +++ b/qmui/src/main/java/com/qmuiteam/qmui/util/QMUIToastHelper.java @@ -0,0 +1,98 @@ +/* + * Tencent is pleased to support the open source community by making QMUI_Android available. + * + * Copyright (C) 2017-2018 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the MIT License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://opensource.org/licenses/MIT + * + * 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 com.qmuiteam.qmui.util; + +import android.os.Build; +import android.os.Handler; +import android.os.Message; +import android.util.Log; +import android.widget.Toast; + +import androidx.annotation.NonNull; + +// Modify from https://github.com/didi/booster/blob/master/booster-android-instrument-toast/src/main/java/com/didiglobal/booster/instrument/ShadowToast.java +public class QMUIToastHelper { + private static final String TAG = "QMUIToastHelper"; + public static void show(Toast toast){ + if(Build.VERSION.SDK_INT == Build.VERSION_CODES.N_MR1){ + fixToastForAndroidN(toast).show(); + }else{ + toast.show(); + } + } + + private static Toast fixToastForAndroidN(Toast toast){ + Object tn = QMUIReflectHelper.getFieldValue(toast, "mTN"); + if(tn == null){ + Log.w(TAG, "The value of field mTN of " + toast + " is null"); + return toast; + } + Object handler = QMUIReflectHelper.getFieldValue(tn, "mHandler"); + if(handler instanceof Handler){ + if(QMUIReflectHelper.setFieldValue( + handler, "mCallback", new FixCallback((Handler) handler))){ + return toast; + } + } + + final Object show = QMUIReflectHelper.getFieldValue(tn, "mShow"); + if (show instanceof Runnable) { + if (QMUIReflectHelper.setFieldValue(tn, "mShow", new FixRunnable((Runnable) show))) { + return toast; + } + } + Log.w(TAG, "Neither field mHandler nor mShow of " + tn + " is accessible"); + return toast; + } + + public static class FixCallback implements Handler.Callback { + + private final Handler mHandler; + + public FixCallback(final Handler handler) { + mHandler = handler; + } + + @Override + public boolean handleMessage(@NonNull Message msg) { + try { + mHandler.handleMessage(msg); + } catch (Throwable e) { + // ignore + } + return true; + } + } + + public static class FixRunnable implements Runnable { + + private final Runnable mRunnable; + + public FixRunnable(final Runnable runnable) { + mRunnable = runnable; + } + + @Override + public void run() { + try { + mRunnable.run(); + } catch (final RuntimeException e) { + // ignore + } + } + } +}