blob: 1a5a1088a013418cfc88e1f8a2f345ceaad53d0e [file] [log] [blame] [view]
Ted Choc2db9f6f52023-07-18 13:35:181# Android Java <-> C++ Ownership Best Practices
2
3Aims to provide best practices for maintaining the ownership and lifecycle of
4logically paired Java / C++ objects (e.g. if you have a Foo object in Java and
5one in C++ that are meant to represent the same concept).
6
7[TOC]
8
9## Establish clear ownership
10Either the Java or C++ object should own the lifecycle of the corresponding
11object in the other language. The initially created object (either in C++ or
12Java) should be the object that creates and owns the corresponding other
13object.
14
15It is the responsibility of the initial object to handle destruction / teardown
16of the corresponding other object.
17
18### [Option #1] Java owns the C++ counterpart
19Because Java objects are garbage collected and finalizers are prohibited in
20Chromium ([link](/styleguide/java/java.md#finalizers)), an explicit
21destroy / teardown method on the Java object is required to prevent leaking the
22corresponding C++ object. The destroy / teardown method on the Java object
23would call an appropriate function on the C++ object (via JNI) to trigger the
24deletion of the C++ object. At this point, the Java object should reset its
25pointer reference to the C++ object to prevent any calls to the now destroyed
26C++ instance.
27
28### [Option #2] C++ owns the Java counterpart
29For C++ objects, utilizing the appropriate smart java references
Sam Maiere1df6f22023-08-11 14:20:4030([link](/third_party/jni_zero/README.md#java-objects-and-garbage-collection),
Ted Choc2db9f6f52023-07-18 13:35:1831[code ref](/base/android/scoped_java_ref.h)) will ensure corresponding Java
32objects can be garbage collected. But if the Java object requires cleaning up
33dependencies, the C++ object should call a corresponding teardown method on the
34Java object in its destructor.
35
36Even in cases where the Java object does not have dependencies requiring clean
37up, the C++ object should notify the Java object that is has gone away. Then the
38Java object can reset its pointer reference to the C++ object and prevent any
39calls to the already destroyed object.
40
41## Enforce relationship cardinality
42There should be one Java object per native object (and vice versa) to keep the
43lifecycle simple and easily understood.
44
45For example, there is one BookmarkModel per Chrome profile in C++, and
46therefore, there should only be one BookmarkModel instance per Profile in Java.
47
48## Pick a side for your business logic
49Where possible, keep the business logic in either C++ or Java, and have the
50other object simply act as a shim to the other.
51
52To facilitate cross-platform development, C++ is the preferred place for
53business logic that could be shared in the future.
54
55## Prefer colocation
56The code of the Java and C++ object should be colocated to ensure consistent
57layering and dependencies..
58
59If the C++ object is in //components/[foo], then the corresponding Java object
60should also reside in //components/[foo].
61
62## Keep your C++ code close and your Java code closer
63The C++ code shared across platforms and the corresponding Java class should be
64as close as possible in the code.
65
66For cases where there are just a few Java <-> C++ calls, try to simply inline
67those into the same C++ file to minimize indirection.
68
69**Example:**
70
71//components/[foo]/foo_factory.cc
72```c++
73<...> cross platform includes
74
75#if BUILDFLAG(IS_ANDROID)
76#include “base/android/scoped_java_ref.h”
77#include “components/[foo]/android/jni_headers/FooFactory_jni.h”
78#endif // BUILDFLAG(IS_ANDROID)
79
80<...> shared functions
81
82#if BUILDFLAG(IS_ANDROID)
83static ScopedJavaLocalRef<jobject> JNI_FooFactory_Get(JNIEnv* env) {
84 return FooFactory::Get()->GetJavaObject();
85}
86#endif // BUILDFLAG(IS_ANDROID)
87```
88
89For cases where the Java <-> C++ API surface is substantial (e.g. if you have a
90C++ object with a large public API and you want to expose all those functions to
91Java), you can split out a JNI methods to a separate class that is owned by the
92primary C++ object. This approach is suitable when we want to minimize the JNI
93boilerplate in the C++ class.
94
95**Example:**
96
97//components/[foo]/foo.h
98```c++
99class Foo {
100 public:
101 <...>
102
103#if BUILDFLAG(IS_ANDROID)
104 void DoSomething();
105#endif // BUILDFLAG(IS_ANDROID)
106
107 private:
108#if BUILDFLAG(IS_ANDROID)
109 std::unique_ptr<FooAndroid> foo_android_;
110#endif // BUILDFLAG(IS_ANDROID)
111}
112```
113
114//components/[foo]/foo.cc
115```c++
116<...>
117
118#if BUILDFLAG(IS_ANDROID)
119void Foo::DoSomething() {
120 if (!foo_android_) {
121 foo_android_ = std::make_unique<FooAndroid>(this);
122 }
123 foo_android_->DoSomething();
124}
125#endif // BUILDFLAG(IS_ANDROID)
126```
127
128//components/[foo]/android/foo_android.h
129```c++
130class FooAndroid {
131 public:
132 void DoAThing();
133
134 // JNI methods called from Java.
135 void SomethingElse(JNIEnv* env);
136 jboolean AndABooleanToo(JNIEnv* env);
137 <...>
138
139 private:
140 const raw_ptr<Foo> foo_;
141 base::android::ScopedJavaGlobalRef<jobject> java_ref_;
142}
143```
144
145//components/[foo]/android/foo_android.cc
146```c++
147FooAndroid::FooAndroid(Foo* foo) : foo_(foo) {}
148
149FooAndroid::DoAThing() {
150 Java_Foo_DoAThing(base::android::AttachCurrentThread(), java_ref_);
151}
152
153void FooAndroid::SomethingElse(JNIEnv* env) {
154 foo_->SomethingElse();
155}
156
157jboolean FooAndroid::AndABooleanToo(JNIEnv* env) {
158 return foo->AndABooleanToo();
159}
160```
161
162## When Lifetime is Hard
163We do not allow the [use of finalizers](/styleguide/java/java.md#Finalizers),
164but there are a couple of other tricks that have been used to clean up objects
165besides explicit lifetimes:
1661. Destroy and re-create the native object every time you need it
167 ([GURL does this](/url/android/java/src/org/chromium/url/Parsed.java)).
1682. Use a reference queue that is flushed every once in a while
169 ([example](https://source.chromium.org/search?q=symbol:TaskRunnerImpl.destroyGarbageCollectedTaskRunners)).