Skip to content

Commit 36a4be7

Browse files
committed
[wpeview] Track the current Java VM and Activity instances
Add WPEApplication, a subclass of android.app.Application which registers a WKActivityObserver to track activity lifecycle. For now the observer only tracks the last started android.app.Activity and forwards it to its native handleActivity{Started,Stopped}() methods. The current VM instance and Activity are exposed to native code through the following functions, which have C linkage and are publicly exposed by libWPEAndroidCommon: JavaVM* wpe_android_runtime_get_current_java_vm() jobject wpe_android_runtime_get_current_activity() The intention is that WebKit will use dlsym() to resolve those, thus avoiding a circular dependency at build time. Due to WPEApplication being instantiated much earlier than other classes with native methods, it must take care itself of issuing the needed System.loadLibrary() calls. Moreover, application services also get to use the same application class declared in the manifest, which means WPEApplication must now handle loading the native libraries needed by services as well. Fortunately, the static Application.getProcessName() method may be used to determine in which kind of process we are, and act accordingly.
1 parent 40eb468 commit 36a4be7

File tree

13 files changed

+216
-26
lines changed

13 files changed

+216
-26
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ override fun onCreate(savedInstanceState: Bundle?) {
4040
}
4141
```
4242

43+
Note that applications that want to subclass `android.app.Application`
44+
*must* use `org.wpewebkit.WPEApplication` as their base class instead.
45+
4346
To see WPEView in action check the [tools](tools) folder.
4447

4548
## Setting up your environment

wpeview/src/main/AndroidManifest.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
<uses-permission android:name="android.permission.INTERNET" />
55

6-
<application android:extractNativeLibs="true">
6+
<application android:name=".WPEApplication" android:extractNativeLibs="true">
77
<!-- Do not remove this section. Service definition is generated at build time -->
88
<!-- SERVICES PLACEHOLDER -->
99
</application>

wpeview/src/main/cpp/Common/Init.cpp

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,45 @@ static void initializeWKVersions()
4646
Logging::logVerbose("WKRuntime::Init: WPE WebKit %s, GStreamer %s", webkitVersion, gstVersion);
4747
}
4848

49+
DECLARE_JNI_CLASS_SIGNATURE(JNIActivity, "android/app/Activity");
50+
51+
static jobject s_currentActivity {nullptr};
52+
53+
extern "C" __attribute__((visibility("default"))) jobject wpe_android_runtime_get_current_activity()
54+
{
55+
Logging::logDebug("wpe_android_runtime_get_current_activity -> %p", s_currentActivity);
56+
return s_currentActivity;
57+
}
58+
59+
static void initializeWKActivityObserver()
60+
{
61+
auto klass = JNI::Class("org/wpewebkit/wpe/WKActivityObserver");
62+
if (!klass) {
63+
Logging::logDebug("Init::initializeWKActivityObserver: No observer class, skipping.");
64+
return;
65+
}
66+
67+
klass.registerNativeMethods(JNI::StaticNativeMethod<void(JNIActivity)>(
68+
"handleActivityStarted",
69+
+[](JNIEnv*, jclass, JNIActivity activity) {
70+
Logging::logDebug(
71+
"WKActivityObserver::handleActivityStarted(%p) current=%p [tid %d]",
72+
activity, s_currentActivity, gettid());
73+
s_currentActivity = activity;
74+
}),
75+
JNI::StaticNativeMethod<void(JNIActivity)>(
76+
"handleActivityStopped", +[](JNIEnv*, jclass, JNIActivity activity) {
77+
Logging::logDebug("WKActivityObserver::handleActivityStopped(%p) current=%p [tid %d]", activity,
78+
s_currentActivity, gettid());
79+
if (s_currentActivity == activity)
80+
s_currentActivity = nullptr;
81+
}));
82+
}
83+
4984
JNIEnv* Init::initialize(JavaVM* javaVM)
5085
{
5186
auto* env = JNI::initVM(javaVM);
5287
initializeWKVersions();
88+
initializeWKActivityObserver();
5389
return env;
5490
}

wpeview/src/main/cpp/Common/JNI/JNIEnv.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@ pthread_key_t globalJNIEnvKey = 0;
3232
std::atomic_bool globalEnableJavaExceptionDescription = true;
3333
// NOLINTEND(cppcoreguidelines-avoid-non-const-global-variables)
3434

35+
extern "C" __attribute__((visibility("default"))) JavaVM* wpe_android_runtime_get_current_java_vm()
36+
{
37+
return globalJavaVM;
38+
}
39+
3540
void detachTerminatedNativeThread(void* /*keyValue*/)
3641
{
3742
if (globalJavaVM != nullptr)

wpeview/src/main/cpp/Runtime/EntryPoint.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
*/
1919

2020
#include "Init.h"
21+
#include "Logging.h"
2122
#include "WKCallback.h"
2223
#include "WKCookieManager.h"
2324
#include "WKNetworkSession.h"
@@ -42,7 +43,8 @@ extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* javaVM, void* /*reserved*/)
4243
WKSettings::configureJNIMappings();
4344

4445
return JNI::VERSION;
45-
} catch (...) {
46+
} catch (const std::exception& e) {
47+
Logging::logError("Runtime: JNI_OnLoad: %s", e.what());
4648
return JNI_ERR;
4749
}
4850
}

wpeview/src/main/cpp/Service/EntryPoint.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,8 @@ extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* javaVM, void* /*reserved*/)
107107
JNI::StaticNativeMethod<void(jlong, jint, jint)>("initializeNativeMain", initializeNativeMain));
108108

109109
return JNI::VERSION;
110-
} catch (...) {
110+
} catch (const std::exception& e) {
111+
Logging::logError("Service: JNI_OnLoad: %s", e.what());
111112
return JNI_ERR;
112113
}
113114
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/**
2+
* Copyright (C) 2025 Igalia S.L. <[email protected]>
3+
* Author: Adrian Perez de Castro <[email protected]>
4+
*
5+
* This library is free software; you can redistribute it and/or
6+
* modify it under the terms of the GNU Lesser General Public
7+
* License as published by the Free Software Foundation; either
8+
* version 2.1 of the License, or (at your option) any later version.
9+
*
10+
* This library is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13+
* Lesser General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU Lesser General Public
16+
* License along with this library; if not, write to the Free Software
17+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18+
*/
19+
20+
package org.wpewebkit;
21+
22+
import android.app.Activity;
23+
import android.app.Application;
24+
import android.os.Bundle;
25+
import android.util.Log;
26+
27+
public class WPEApplication extends Application {
28+
static final String LOGTAG = "WPEApplication";
29+
30+
private enum ProcessKind {
31+
MAIN {
32+
@Override
33+
public String[] getLibraryNames() {
34+
return new String[] {"WPEAndroidRuntime"};
35+
}
36+
},
37+
NETWORK {
38+
@Override
39+
public String[] getLibraryNames() {
40+
return new String[] {"WPEAndroidService"};
41+
}
42+
},
43+
WEBCONTENT {
44+
@Override
45+
public String[] getLibraryNames() {
46+
return new String[] {"gstreamer-1.0", "WPEAndroidService"};
47+
}
48+
},
49+
WEBDRIVER {
50+
@Override
51+
public String[] getLibraryNames() {
52+
return new String[] {"WPEWebDriver", "WPEAndroidService"};
53+
}
54+
};
55+
56+
abstract public String[] getLibraryNames();
57+
}
58+
59+
private static final ProcessKind getProcessKind() {
60+
String name = getProcessName();
61+
int colonPosition = name.indexOf(':');
62+
if (colonPosition < 0)
63+
return ProcessKind.MAIN;
64+
65+
name = name.substring(colonPosition + 1).replaceAll("[0-9]", "");
66+
switch (name) {
67+
case "WPEWebProcess":
68+
return ProcessKind.WEBCONTENT;
69+
case "WPENetworkProcess":
70+
return ProcessKind.NETWORK;
71+
case "WPEWebDriverProcess":
72+
return ProcessKind.WEBDRIVER;
73+
default:
74+
throw new IllegalStateException("Cannot derive process kind from '" + getProcessName() + "'");
75+
}
76+
}
77+
78+
public static final ProcessKind processKind = getProcessKind();
79+
80+
static {
81+
final String libraries[] = processKind.getLibraryNames();
82+
Log.d(LOGTAG, "Process: " + processKind.toString() + " - Libraries: " + String.join(", ", libraries));
83+
for (String libraryName : libraries)
84+
System.loadLibrary(libraryName);
85+
}
86+
87+
public WPEApplication() {
88+
super();
89+
if (processKind == ProcessKind.MAIN)
90+
registerActivityLifecycleCallbacks(new org.wpewebkit.wpe.WKActivityObserver());
91+
}
92+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/**
2+
* Copyright (C) 2025 Igalia S.L. <[email protected]>
3+
* Author: Adrian Perez de Castro <[email protected]>
4+
*
5+
* This library is free software; you can redistribute it and/or
6+
* modify it under the terms of the GNU Lesser General Public
7+
* License as published by the Free Software Foundation; either
8+
* version 2.1 of the License, or (at your option) any later version.
9+
*
10+
* This library is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13+
* Lesser General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU Lesser General Public
16+
* License along with this library; if not, write to the Free Software
17+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18+
*/
19+
20+
package org.wpewebkit.wpe;
21+
22+
import android.app.Activity;
23+
import android.app.Application;
24+
import android.os.Bundle;
25+
import android.util.Log;
26+
27+
import androidx.annotation.NonNull;
28+
29+
public class WKActivityObserver implements Application.ActivityLifecycleCallbacks {
30+
private static final String LOGTAG = "WKActivityObserver";
31+
32+
@Override
33+
public void onActivityCreated(@NonNull Activity a, @NonNull Bundle b) {
34+
Log.d(LOGTAG, "activity created: " + a);
35+
}
36+
37+
@Override
38+
public void onActivityStarted(@NonNull Activity a) {
39+
Log.d(LOGTAG, "activity started: " + a);
40+
handleActivityStarted(a);
41+
}
42+
43+
@Override
44+
public void onActivityPaused(@NonNull Activity a) {
45+
Log.d(LOGTAG, "activity paused: " + a);
46+
}
47+
48+
@Override
49+
public void onActivityResumed(@NonNull Activity a) {
50+
Log.d(LOGTAG, "activity resumed: " + a);
51+
}
52+
53+
@Override
54+
public void onActivityStopped(@NonNull Activity a) {
55+
Log.d(LOGTAG, "activity stopped: " + a);
56+
handleActivityStopped(a);
57+
}
58+
59+
@Override
60+
public void onActivityDestroyed(@NonNull Activity a) {
61+
Log.d(LOGTAG, "activity destroyed: " + a);
62+
}
63+
64+
@Override
65+
public void onActivitySaveInstanceState(@NonNull Activity a, @NonNull Bundle b) {
66+
Log.d(LOGTAG, "activity save state: " + a);
67+
}
68+
69+
private static native void handleActivityStarted(@NonNull Activity a);
70+
private static native void handleActivityStopped(@NonNull Activity a);
71+
}

wpeview/src/main/java/org/wpewebkit/wpe/WKRuntime.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,6 @@
4848
public final class WKRuntime {
4949
private static final String LOGTAG = "WKRuntime";
5050

51-
static { System.loadLibrary("WPEAndroidRuntime"); }
52-
5351
protected static native void startNativeLooper();
5452
private static native void setupNativeEnvironment(@NonNull String[] envStringsArray);
5553
private native void nativeInit();

wpeview/src/main/java/org/wpewebkit/wpe/services/NetworkProcessService.java

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public class NetworkProcessService extends WPEService {
3636
private static final String LOGTAG = "WPENetworkProcess";
3737

3838
@Override
39-
protected void loadNativeLibraries() {
39+
protected void setupServiceEnvironment() {
4040
// To debug the sub-process with Android Studio (Java and native code), you must:
4141
// 1- Uncomment the following instruction to wait for the debugger before loading native code.
4242
// 2- Force the dual debugger (Java + Native) in Run/Debug configuration (the automatic detection won't work).
@@ -45,11 +45,6 @@ protected void loadNativeLibraries() {
4545

4646
// android.os.Debug.waitForDebugger();
4747

48-
System.loadLibrary("WPEAndroidService");
49-
}
50-
51-
@Override
52-
protected void setupServiceEnvironment() {
5348
final String assetsVersion = WKVersions.versionedAssets("network_process");
5449
Context context = getApplicationContext();
5550
if (ServiceUtils.needAssets(context, assetsVersion)) {

0 commit comments

Comments
 (0)