Fix restart QtActivity

Previously, a restart of QtActivity on Android would make the
application fail with a blank screen. That happened because the
QtActivity tried to reload the whole application and failed.
With this patch, the QtActivity detects if the application is restarting
by checking if QtNative and QtActivityDelegate are live and updates the
connections on those objects accordingly. It allows the application to
continue as before. In case that is not possible, the QtActivity will
restart the application.

Fixes: QTBUG-38971
Fixes: QTBUG-102298
Pick-to: 5.15 6.2 6.3
Change-Id: Id500d20b185d57b39d45d34eeaa99745a3c2b95b
Reviewed-by: Ivan Solovev <ivan.solovev@qt.io>
Reviewed-by: Assam Boudjelthia <assam.boudjelthia@qt.io>
This commit is contained in:
Samuel Mira 2022-04-20 16:08:36 +03:00
parent 41d217829c
commit 5fd6704091
4 changed files with 176 additions and 79 deletions

View File

@ -594,6 +594,100 @@ public class QtActivityDelegate
}
}
private final DisplayManager.DisplayListener displayListener = new DisplayManager.DisplayListener()
{
@Override
public void onDisplayAdded(int displayId) { }
private boolean isSimilarRotation(int r1, int r2)
{
return (r1 == r2)
|| (r1 == Surface.ROTATION_0 && r2 == Surface.ROTATION_180)
|| (r1 == Surface.ROTATION_180 && r2 == Surface.ROTATION_0)
|| (r1 == Surface.ROTATION_90 && r2 == Surface.ROTATION_270)
|| (r1 == Surface.ROTATION_270 && r2 == Surface.ROTATION_90);
}
@Override
public void onDisplayChanged(int displayId)
{
Display display = (Build.VERSION.SDK_INT < Build.VERSION_CODES.R)
? m_activity.getWindowManager().getDefaultDisplay()
: m_activity.getDisplay();
m_currentRotation = display.getRotation();
m_layout.setActivityDisplayRotation(m_currentRotation);
// Process orientation change only if it comes after the size
// change, or if the screen is rotated by 180 degrees.
// Otherwise it will be processed in QtLayout.
if (isSimilarRotation(m_currentRotation, m_layout.displayRotation()))
QtNative.handleOrientationChanged(m_currentRotation, m_nativeOrientation);
float refreshRate = display.getRefreshRate();
QtNative.handleRefreshRateChanged(refreshRate);
}
@Override
public void onDisplayRemoved(int displayId) { }
};
public boolean updateActivity(Activity activity)
{
try {
// set new activity
loadActivity(activity);
// update the new activity content view to old layout
ViewGroup layoutParent = (ViewGroup)m_layout.getParent();
if (layoutParent != null)
layoutParent.removeView(m_layout);
m_activity.setContentView(m_layout);
// force c++ native activity object to update
return QtNative.updateNativeActivity();
} catch (Exception e) {
Log.w(QtNative.QtTAG, "Failed to update the activity.");
e.printStackTrace();
return false;
}
}
private void loadActivity(Activity activity)
throws NoSuchMethodException, PackageManager.NameNotFoundException
{
m_activity = activity;
QtNative.setActivity(m_activity, this);
setActionBarVisibility(false);
Class<?> activityClass = m_activity.getClass();
m_super_dispatchKeyEvent =
activityClass.getMethod("super_dispatchKeyEvent", KeyEvent.class);
m_super_onRestoreInstanceState =
activityClass.getMethod("super_onRestoreInstanceState", Bundle.class);
m_super_onRetainNonConfigurationInstance =
activityClass.getMethod("super_onRetainNonConfigurationInstance");
m_super_onSaveInstanceState =
activityClass.getMethod("super_onSaveInstanceState", Bundle.class);
m_super_onKeyDown =
activityClass.getMethod("super_onKeyDown", Integer.TYPE, KeyEvent.class);
m_super_onKeyUp =
activityClass.getMethod("super_onKeyUp", Integer.TYPE, KeyEvent.class);
m_super_onConfigurationChanged =
activityClass.getMethod("super_onConfigurationChanged", Configuration.class);
m_super_onActivityResult =
activityClass.getMethod("super_onActivityResult", Integer.TYPE, Integer.TYPE, Intent.class);
m_super_onWindowFocusChanged =
activityClass.getMethod("super_onWindowFocusChanged", Boolean.TYPE);
m_super_dispatchGenericMotionEvent =
activityClass.getMethod("super_dispatchGenericMotionEvent", MotionEvent.class);
m_softInputMode = m_activity.getPackageManager().getActivityInfo(m_activity.getComponentName(), 0).softInputMode;
DisplayManager displayManager = (DisplayManager)m_activity.getSystemService(Context.DISPLAY_SERVICE);
displayManager.registerDisplayListener(displayListener, null);
}
public boolean loadApplication(Activity activity, ClassLoader classLoader, Bundle loaderParams)
{
/// check parameters integrity
@ -603,10 +697,14 @@ public class QtActivityDelegate
return false;
}
m_activity = activity;
setActionBarVisibility(false);
QtNative.setActivity(m_activity, this);
QtNative.setClassLoader(classLoader);
try {
loadActivity(activity);
QtNative.setClassLoader(classLoader);
} catch (Exception e) {
e.printStackTrace();
return false;
}
if (loaderParams.containsKey(STATIC_INIT_CLASSES_KEY)) {
for (String className: Objects.requireNonNull(loaderParams.getStringArray(STATIC_INIT_CLASSES_KEY))) {
if (className.length() == 0)
@ -651,22 +749,6 @@ public class QtActivityDelegate
loaderParams.getBoolean(EXTRACT_STYLE_MINIMAL_KEY));
}
try {
m_super_dispatchKeyEvent = m_activity.getClass().getMethod("super_dispatchKeyEvent", KeyEvent.class);
m_super_onRestoreInstanceState = m_activity.getClass().getMethod("super_onRestoreInstanceState", Bundle.class);
m_super_onRetainNonConfigurationInstance = m_activity.getClass().getMethod("super_onRetainNonConfigurationInstance");
m_super_onSaveInstanceState = m_activity.getClass().getMethod("super_onSaveInstanceState", Bundle.class);
m_super_onKeyDown = m_activity.getClass().getMethod("super_onKeyDown", Integer.TYPE, KeyEvent.class);
m_super_onKeyUp = m_activity.getClass().getMethod("super_onKeyUp", Integer.TYPE, KeyEvent.class);
m_super_onConfigurationChanged = m_activity.getClass().getMethod("super_onConfigurationChanged", Configuration.class);
m_super_onActivityResult = m_activity.getClass().getMethod("super_onActivityResult", Integer.TYPE, Integer.TYPE, Intent.class);
m_super_onWindowFocusChanged = m_activity.getClass().getMethod("super_onWindowFocusChanged", Boolean.TYPE);
m_super_dispatchGenericMotionEvent = m_activity.getClass().getMethod("super_dispatchGenericMotionEvent", MotionEvent.class);
} catch (Exception e) {
e.printStackTrace();
return false;
}
QtNative.setEnvironmentVariables(loaderParams.getString(ENVIRONMENT_VARIABLES_KEY));
QtNative.setEnvironmentVariable("QT_ANDROID_FONTS_MONOSPACE",
"Droid Sans Mono;Droid Sans;Droid Sans Fallback");
@ -683,52 +765,6 @@ public class QtActivityDelegate
else
m_applicationParameters = "";
try {
m_softInputMode = m_activity.getPackageManager().getActivityInfo(m_activity.getComponentName(), 0).softInputMode;
} catch (Exception e) {
e.printStackTrace();
}
DisplayManager.DisplayListener displayListener = new DisplayManager.DisplayListener() {
@Override
public void onDisplayAdded(int displayId) { }
private boolean isSimilarRotation(int r1, int r2)
{
return (r1 == r2)
|| (r1 == Surface.ROTATION_0 && r2 == Surface.ROTATION_180)
|| (r1 == Surface.ROTATION_180 && r2 == Surface.ROTATION_0)
|| (r1 == Surface.ROTATION_90 && r2 == Surface.ROTATION_270)
|| (r1 == Surface.ROTATION_270 && r2 == Surface.ROTATION_90);
}
@Override
public void onDisplayChanged(int displayId) {
Display display = (Build.VERSION.SDK_INT < Build.VERSION_CODES.R)
? m_activity.getWindowManager().getDefaultDisplay()
: m_activity.getDisplay();
m_currentRotation = display.getRotation();
m_layout.setActivityDisplayRotation(m_currentRotation);
// Process orientation change only if it comes after the size
// change, or if the screen is rotated by 180 degrees.
// Otherwise it will be processed in QtLayout.
if (isSimilarRotation(m_currentRotation, m_layout.displayRotation()))
QtNative.handleOrientationChanged(m_currentRotation, m_nativeOrientation);
float refreshRate = display.getRefreshRate();
QtNative.handleRefreshRateChanged(refreshRate);
}
@Override
public void onDisplayRemoved(int displayId) { }
};
try {
DisplayManager displayManager = (DisplayManager) m_activity.getSystemService(Context.DISPLAY_SERVICE);
displayManager.registerDisplayListener(displayListener, null);
} catch (Exception e) {
e.printStackTrace();
}
m_mainLib = QtNative.loadMainLibrary(m_mainLib, nativeLibsDir);
return m_mainLib != null;
}

View File

@ -129,6 +129,13 @@ public class QtNative
}
};
public static boolean isStarted()
{
boolean hasActivity = m_activity != null && m_activityDelegate != null;
boolean hasService = m_service != null && m_serviceDelegate != null;
return m_started && (hasActivity || hasService);
}
private static ClassLoader m_classLoader = null;
public static ClassLoader classLoader()
{
@ -700,9 +707,10 @@ public class QtNative
public static native void quitQtCoreApplication();
public static native void quitQtAndroidPlugin();
public static native void terminateQt();
public static native boolean updateNativeActivity();
// application methods
private static void quitApp()
public static void quitApp()
{
runAction(new Runnable() {
@Override
@ -712,6 +720,8 @@ public class QtNative
m_activity.finish();
if (m_service != null)
m_service.stopSelf();
m_started = false;
}
});
}

View File

@ -47,6 +47,7 @@ import android.os.Build;
import android.os.Bundle;
import android.view.Window;
import org.qtproject.qt.android.QtNative;
import java.lang.reflect.Field;
@ -113,9 +114,20 @@ public class QtActivityLoader extends QtLoader {
}
m_activity.requestWindowFeature(Window.FEATURE_ACTION_BAR);
if (QtNative.isStarted()) {
boolean updated = QtNative.activityDelegate().updateActivity(m_activity);
if (!updated) {
// could not update the activity so restart the application
Intent intent = Intent.makeRestartActivityTask(m_activity.getComponentName());
m_activity.startActivity(intent);
QtNative.quitApp();
Runtime.getRuntime().exit(0);
}
// there can only be a valid delegate object if the QtNative was started.
if (QtApplication.m_delegateObject != null && QtApplication.onCreate != null)
QtApplication.invokeDelegateMethod(QtApplication.onCreate, savedInstanceState);
if (QtApplication.m_delegateObject != null && QtApplication.onCreate != null) {
QtApplication.invokeDelegateMethod(QtApplication.onCreate, savedInstanceState);
return;
}
@ -124,15 +136,14 @@ public class QtActivityLoader extends QtLoader {
ENVIRONMENT_VARIABLES += "\tQT_ANDROID_THEME=" + QT_ANDROID_DEFAULT_THEME
+ "/\tQT_ANDROID_THEME_DISPLAY_DPI=" + m_displayDensity + "\t";
if (null == m_activity.getLastNonConfigurationInstance()) {
if (m_contextInfo.metaData.containsKey("android.app.background_running")
&& m_contextInfo.metaData.getBoolean("android.app.background_running")) {
ENVIRONMENT_VARIABLES += "QT_BLOCK_EVENT_LOOPS_WHEN_SUSPENDED=0\t";
} else {
ENVIRONMENT_VARIABLES += "QT_BLOCK_EVENT_LOOPS_WHEN_SUSPENDED=1\t";
}
startApp(true);
if (m_contextInfo.metaData.containsKey("android.app.background_running")
&& m_contextInfo.metaData.getBoolean("android.app.background_running")) {
ENVIRONMENT_VARIABLES += "QT_BLOCK_EVENT_LOOPS_WHEN_SUSPENDED=0\t";
} else {
ENVIRONMENT_VARIABLES += "QT_BLOCK_EVENT_LOOPS_WHEN_SUSPENDED=1\t";
}
startApp(true);
}
}

View File

@ -74,6 +74,8 @@ Q_GLOBAL_STATIC(QMutex, g_onBindListenerMutex);
Q_GLOBAL_STATIC(QSemaphore, g_waitForServiceSetupSemaphore);
Q_GLOBAL_STATIC(QAtomicInt, g_serviceSetupLockers);
Q_GLOBAL_STATIC(QReadWriteLock, g_updateMutex);
namespace {
struct GenericMotionEventListeners {
QMutex mutex;
@ -108,6 +110,41 @@ static jboolean dispatchKeyEvent(JNIEnv *, jclass, jobject event)
return ret;
}
static jboolean updateNativeActivity(JNIEnv *env, jclass = nullptr)
{
jclass jQtNative = env->FindClass("org/qtproject/qt/android/QtNative");
if (QJniEnvironment::checkAndClearExceptions(env))
return JNI_FALSE;
jmethodID activityMethodID =
env->GetStaticMethodID(jQtNative, "activity", "()Landroid/app/Activity;");
if (QJniEnvironment::checkAndClearExceptions(env))
return JNI_FALSE;
jobject activity = env->CallStaticObjectMethod(jQtNative, activityMethodID);
if (QJniEnvironment::checkAndClearExceptions(env))
return JNI_FALSE;
QWriteLocker locker(g_updateMutex());
if (g_jActivity) {
env->DeleteGlobalRef(g_jActivity);
g_jActivity = nullptr;
}
if (activity) {
g_jActivity = env->NewGlobalRef(activity);
env->DeleteLocalRef(activity);
}
env->DeleteLocalRef(jQtNative);
if (QJniEnvironment::checkAndClearExceptions(env))
return JNI_FALSE;
return JNI_TRUE;
}
namespace {
class ActivityResultListeners
{
@ -271,6 +308,7 @@ jint QtAndroidPrivate::initJNI(JavaVM *vm, JNIEnv *env)
static const JNINativeMethod methods[] = {
{"dispatchGenericMotionEvent", "(Landroid/view/MotionEvent;)Z", reinterpret_cast<void *>(dispatchGenericMotionEvent)},
{"dispatchKeyEvent", "(Landroid/view/KeyEvent;)Z", reinterpret_cast<void *>(dispatchKeyEvent)},
{"updateNativeActivity", "()Z", reinterpret_cast<void *>(updateNativeActivity) },
};
const bool regOk = (env->RegisterNatives(jQtNative, methods, sizeof(methods) / sizeof(methods[0])) == JNI_OK);
@ -289,6 +327,7 @@ jint QtAndroidPrivate::initJNI(JavaVM *vm, JNIEnv *env)
jobject QtAndroidPrivate::activity()
{
QReadLocker locker(g_updateMutex());
return g_jActivity;
}
@ -299,12 +338,13 @@ jobject QtAndroidPrivate::service()
jobject QtAndroidPrivate::context()
{
QReadLocker locker(g_updateMutex());
if (g_jActivity)
return g_jActivity;
if (g_jService)
return g_jService;
return 0;
return nullptr;
}
JavaVM *QtAndroidPrivate::javaVM()