Tommy C. Li | 68f0ba5 | 2019-03-18 17:24:10 | [diff] [blame] | 1 | # How to Write Cross-Platform UI (Native components) |
| 2 | |
| 3 | [TOC] |
| 4 | |
| 5 | Chrome runs on many platforms such as Desktop (Windows, Mac, Linux, and |
| 6 | ChromeOS), Android, iOS, and VR. These platforms have different UIs, written in |
| 7 | different languages. Chrome also has WebUI, which is browser UI implemented in |
| 8 | the HTML content area. This can be considered another UI toolkit. |
| 9 | |
| 10 | ## Separating out platform-independent business logic |
| 11 | |
| 12 | For UI that appears on multiple platforms, it's best to share as much of the |
| 13 | business logic (model behavior backing the UI) as possible. The |
| 14 | platform-specific views should be as minimal and "dumb" as possible. One caveat: |
| 15 | sharing everything may not be feasible if there are performance or latency |
| 16 | concerns. |
| 17 | |
| 18 | The cross-platform model will be in native C++ code, likely in the |
| 19 | //components/foobar directory. Model layer code should handle the business |
| 20 | logic, and be richly observable. Platform-specific views/controllers should |
| 21 | observe the model, update the UI, and mutate the model as needed. This model |
| 22 | should have tests. |
| 23 | |
| 24 | Note that there can be model state that is applicable to only a subset of |
| 25 | platforms. For instance, mobile devices can change device rotation or layout. On |
| 26 | Mobile, the platform itself may provide extended text editing capabilities that |
| 27 | don't exist on Desktop. |
| 28 | |
| 29 | ## Lifetime Issues |
| 30 | |
| 31 | Different platforms may have different UI lifetime semantics, so it's helpful to |
| 32 | keep the business logic stateless or with a flexible lifetime model. Android |
| 33 | Java object lifetimes are well-defined, but has gotchas for cross-platform code. |
| 34 | For instance: the Java side of Android is available immediately on startup, but |
| 35 | the C++ side needs to wait for the native library to be loaded. Having a native |
| 36 | object "own" a garbage-collected Java object or vice versa can also be tricky. |
| 37 | |
| 38 | 1. Pure static functions are the easiest, if this works for your use case. In |
| 39 | addition to being easy to bridge to Java, these are also easy to unit test, |
| 40 | since they don't require setting up state. |
| 41 | |
| 42 | You can usually use pure static functions for simpler cases that don't |
| 43 | involve state. These are easy to bridge to Java. See |
| 44 | [`UrlFormatter`](https://cs.chromium.org/chromium/src/components/url_formatter/android/java/src/org/chromium/components/url_formatter/UrlFormatter.java) |
| 45 | for an example of this. |
| 46 | |
| 47 | 2. For objects that are associated with a specific `Profile`, use a |
| 48 | `KeyedService` instance with |
| 49 | [`BrowserContextKeyedServiceFactory`](https://cs.chromium.org/chromium/src/components/keyed_service/content/browser_context_keyed_service_factory.h). |
| 50 | If your object depends on other `KeyedService` instances, there's a strong |
| 51 | chance your object should also be a `KeyedService`. |
| 52 | |
| 53 | These are also pretty easy to bridge to Java. In most cases, the Java code |
| 54 | will need to pass a `Profile` object, so that the native `KeyedService` can |
| 55 | be fetched. |
| 56 | |
| 57 | A neat trick is that the Java code can be all-static, even if the native |
| 58 | code isn't, if every method also includes a `Profile` parameter. |
| 59 | |
| 60 | 3. Objects tied to WebContents lifetimes are also commonly used, and can be |
| 61 | implemented using a WebContentsObserver and WebContentsUserData. |
| 62 | |
| 63 | 4. For lifetimes tied to UI instances, or other custom lifetimes, you will need |
| 64 | to do custom lifetime management. For Java bridging advice, see the JNI |
| 65 | documentation for details. |
| 66 | |
| 67 | ## More Advice on Bridging to Java |
| 68 | |
| 69 | Bridging a code module to Java will have three parts: |
| 70 | |
| 71 | 1. components/omnibox/browser/foobar.h/cc - Cross-platform code you already |
| 72 | wrote and have unit tests for. |
| 73 | |
| 74 | 2. chrome/browser/android/omnibox/foo_feature_android.h/cc - C++ side of the |
| 75 | bridge. You might only need the cc file. |
| 76 | |
| 77 | 3. chrome/android/java/src/org/chromium/chrome/browser/omnibox/FooFeature.java |
| 78 | - Java side of the bridge. |
| 79 | |
| 80 | See the [JNI README](../base/android/jni_generator/README.md) for more details. |