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:
parent
41d217829c
commit
5fd6704091
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
|
Loading…
x
Reference in New Issue
Block a user