1
    2
    3
    4
    5
    6
    7
    8
    9
   10
   11
   12
   13
   14
   15
   16
   17
   18
   19
   20
   21
   22
   23
   24
   25
   26
   27
   28
   29
   30
   31
   32
   33
   34
   35
   36
   37
   38
   39
   40
   41
   42
   43
   44
   45
   46
   47
   48
   49
   50
   51
   52
   53
   54
   55
   56
   57
   58
   59
   60
   61
   62
   63
   64
   65
   66
   67
   68
   69
   70
   71
   72
   73
   74
   75
   76
   77
   78
   79
   80
   81
   82
   83
   84
   85
   86
   87
   88
   89
   90
   91
   92
   93
   94
   95
   96
   97
   98
   99
  100
  101
  102
  103
  104
  105
  106
  107
  108
  109
  110
  111
  112
  113
  114
  115
  116
  117
  118
  119
  120
  121
  122
  123
  124
  125
  126
  127
  128
  129
  130
  131
  132
  133
  134
  135
  136
  137
  138
  139
  140
  141
  142
  143
  144
  145
  146
  147
  148
  149
  150
  151
  152
  153
  154
  155
  156
  157
  158
  159
  160
  161
  162
  163
  164
  165
  166
  167
  168
  169

docs / android_jni_ownership_best_practices.md [blame]

# Android Java <-> C++ Ownership Best Practices

Aims to provide best practices for maintaining the ownership and lifecycle of
logically paired Java / C++ objects (e.g. if you have a Foo object in Java and
one in C++ that are meant to represent the same concept).

[TOC]

## Establish clear ownership
Either the Java or C++ object should own the lifecycle of the corresponding
object in the other language. The initially created object (either in C++ or
Java) should be the object that creates and owns the corresponding other
object.

It is the responsibility of the initial object to handle destruction / teardown
of the corresponding other object.

### [Option #1] Java owns the C++ counterpart
Because Java objects are garbage collected and finalizers are prohibited in
Chromium ([link](/styleguide/java/java.md#finalizers)), an explicit
destroy / teardown method on the Java object is required to prevent leaking the
corresponding C++ object. The destroy / teardown method on the Java object
would call an appropriate function on the C++ object (via JNI) to trigger the
deletion of the C++ object. At this point, the Java object should reset its
pointer reference to the C++ object to prevent any calls to the now destroyed
C++ instance.

### [Option #2] C++ owns the Java counterpart
For C++ objects, utilizing the appropriate smart java references
([link](/third_party/jni_zero/README.md#java-objects-and-garbage-collection),
[code ref](/base/android/scoped_java_ref.h)) will ensure corresponding Java
objects can be garbage collected. But if the Java object requires cleaning up
dependencies, the C++ object should call a corresponding teardown method on the
Java object in its destructor.

Even in cases where the Java object does not have dependencies requiring clean
up, the C++ object should notify the Java object that is has gone away. Then the
Java object can reset its pointer reference to the C++ object and prevent any
calls to the already destroyed object.

## Enforce relationship cardinality
There should be one Java object per native object (and vice versa) to keep the
lifecycle simple and easily understood.

For example, there is one BookmarkModel per Chrome profile in C++, and
therefore, there should only be one BookmarkModel instance per Profile in Java.

## Pick a side for your business logic
Where possible, keep the business logic in either C++ or Java, and have the
other object simply act as a shim to the other.

To facilitate cross-platform development, C++ is the preferred place for
business logic that could be shared in the future.

## Prefer colocation
The code of the Java and C++ object should be colocated to ensure consistent
layering and dependencies..

If the C++ object is in //components/[foo], then the corresponding Java object
should also reside in //components/[foo].

## Keep your C++ code close and your Java code closer
The C++ code shared across platforms and the corresponding Java class should be
as close as possible in the code.

For cases where there are just a few Java <-> C++ calls, try to simply inline
those into the same C++ file to minimize indirection.

**Example:**

//components/[foo]/foo_factory.cc
```c++
<...> cross platform includes

#if BUILDFLAG(IS_ANDROID)
#include “base/android/scoped_java_ref.h”
#include “components/[foo]/android/jni_headers/FooFactory_jni.h”
#endif  // BUILDFLAG(IS_ANDROID)

<...> shared functions

#if BUILDFLAG(IS_ANDROID)
static ScopedJavaLocalRef<jobject> JNI_FooFactory_Get(JNIEnv* env) {
    return FooFactory::Get()->GetJavaObject();
}
#endif  // BUILDFLAG(IS_ANDROID)
```

For cases where the Java <-> C++ API surface is substantial (e.g. if you have a
C++ object with a large public API and you want to expose all those functions to
Java), you can split out a JNI methods to a separate class that is owned by the
primary C++ object. This approach is suitable when we want to minimize the JNI
boilerplate in the C++ class.

**Example:**

//components/[foo]/foo.h
```c++
class Foo {
 public:
  <...>

#if BUILDFLAG(IS_ANDROID)
  void DoSomething();
#endif  // BUILDFLAG(IS_ANDROID)

 private:
#if BUILDFLAG(IS_ANDROID)
  std::unique_ptr<FooAndroid> foo_android_;
#endif  // BUILDFLAG(IS_ANDROID)
}
```

//components/[foo]/foo.cc
```c++
<...>

#if BUILDFLAG(IS_ANDROID)
void Foo::DoSomething() {
  if (!foo_android_) {
    foo_android_ = std::make_unique<FooAndroid>(this);
  }
  foo_android_->DoSomething();
}
#endif  // BUILDFLAG(IS_ANDROID)
```

//components/[foo]/android/foo_android.h
```c++
class FooAndroid {
 public:
  void DoAThing();

  // JNI methods called from Java.
  void SomethingElse(JNIEnv* env);
  jboolean AndABooleanToo(JNIEnv* env);
  <...>

 private:
  const raw_ptr<Foo> foo_;
  base::android::ScopedJavaGlobalRef<jobject> java_ref_;
}
```

//components/[foo]/android/foo_android.cc
```c++
FooAndroid::FooAndroid(Foo* foo) : foo_(foo) {}

FooAndroid::DoAThing() {
  Java_Foo_DoAThing(base::android::AttachCurrentThread(), java_ref_);
}

void FooAndroid::SomethingElse(JNIEnv* env) {
  foo_->SomethingElse();
}

jboolean FooAndroid::AndABooleanToo(JNIEnv* env) {
  return foo->AndABooleanToo();
}
```

## When Lifetime is Hard
We do not allow the [use of finalizers](/styleguide/java/java.md#Finalizers),
but there are a couple of other tricks that have been used to clean up objects
besides explicit lifetimes:
1. Destroy and re-create the native object every time you need it
   ([GURL does this](/url/android/java/src/org/chromium/url/Parsed.java)).
2. Use a reference queue that is flushed every once in a while
   ([example](https://source.chromium.org/search?q=symbol:TaskRunnerImpl.destroyGarbageCollectedTaskRunners)).