As we know it today, in terms of memory management, NativeScript has been using a special routine in its Android runtime: MarkReachableObjects
. Its main purpose is to make sure no Java objects are collected by the garbage collector as long as they are needed by their JavaScript representations (in V8), and vice versa. However, while this mechanism ensures stability, it comes at a price - in some cases the time for a V8 GC pass can get as high as a second, which leads to blocking the UI thread and hurint the overall performance of the app.
Starting with NativeScript 3.2, a new and experimental (at the time) mode for garbage collection was added to the Android runtime - the markingMode: none
flag. What it does is turn off the previously mentioned routine and use alternative memory management in the Android runtime. The result is much better app performance. Drawbacks? Yes, though avoidable, some unpredictable errors/crashes may occur during runtime due to premature collection of Java/Javascript objects.
To ensure correct execution using this mode, the code needs to be written in such a way that no Java object is ever released while its JavaScript counterpart is still alive, and vice versa. In fact the tns-core-modules
are written with markingMode: none
in mind since launch, and should not be the reason for app crashes due to memory issues (of course bugs are possible, so please, log an issue in the NativeScript Android runtime or in a suspected plugin's repo).
Later, with NativeScript 5.1, the {N} team announced that all core plugins (the ones provided by the NativeScript Team) support markingMode:none
as well.
Recently, we went on and tested some of the NativeScript Marketplace's most popular plugins using their own demo apps (with some tweaks to raise the probability of failure). Here is a list of some:
...and found no issues running them with markingMode:none
. If you find any issues with any of these, we'd be happy to assist.
Already having been around for a while, and reaching a stable state, we are announcing that markingMode: none
is now officially supported by the team! Feel free to report any issues that come up in your app using that mode. The NativeScript team will make its best effort to address them.
As for future plans we are going to:
markingMode: none
- enabled apps.markingMode: none
option will become the default mode.Why should we be cautious using markingMode: none
today? Let's dig into an example:
Consider a NativeScript page with the following layout:
<StackLayout id="root">
<Label class="t-20" text="{{ fileName }}"></Label>
<Button text="add button with click listener" tap="{{ onAddClickListener }}"></Button>
</StackLayout>
As viewed, this page is bound to several members of its bindingContext
. Let's focus on the onAddClickListener
event handler:
public onAddClickListener() {
let root = <StackLayout>currentPage.getViewById('root');
let btn = new android.widget.Button(root._context);
btn.setText("ta-daa, now click!");
root.android.addView(btn);
let file = new java.io.File('real file'); // create Android native instance of a File
// create native click listener implementation
btn.setOnClickListener(new android.view.View.OnClickListener(
{
onClick: () => {
// call some method on the Android native instance
this.fileName = `${file.getName()} exists at ${new Date().toTimeString()}`;
}
}
));
}
What this handler does is:
android.widget.Button
and adds it to the page.java.io.File
.OnClickListener
interface implementation to the button, and inside calls file.getName()
on the java.io.File
instance.In terms of TypeScript syntax and logic, all this looks fine, and without markingMode: none
enabled it really behaves as so. However, let's set the flag (in app/package.json
):
"android": {
"markingMode": "none",
}
...and run the app. Then:
ADD BUTTON WITH CLICK LISTENER
button - onAddClickListener
is called and an additional button is added (as expected).Error: com.tns.NativeScriptException: Attempt to use cleared object reference id=<some-object-id-number>
The JavaScript instance no longer has available Java instance counterpart
.
So, if we look back to the onAddClickListener
method, the java.io.File
instance is enclosed by the native button's onClick
callback implementation, but with markingMode: none
enabled the framework no longer takes care of finding that connection. When GC happens in V8 (JavaScript) or in Android (Java) the java.io.File
instance (or its native representation) is GC'ed. This can result in either the Java or JavaScript instance missing. Thus, when calling onClick
and in an effort to use the already collected object, the app crashes with any of the errors.
Logically, we should make sure the java.io.File
instance is alive as long as we need it in the app execution (it was the JavaScript or Java representation that was collected and caused the crash, right?). In our case, we need it as long as the page is alive, as we don't expect to handle clicks when the page is dead 😀. So, in our case, it would be enough to store the instance in a property of the ViewModel
, to which the page is bound:
export class ViewModel extends Observable {
...
private myFile: java.io.File;
...
...and use it in the callback implementation like:
btn.setOnClickListener(new android.view.View.OnClickListener( { onClick: () => { this.fileName =${this.myFile.getName()} exists at ${new Date().toTimeString()}
; } } ));
This will ensure that the GC will not collect the java.io.File
instance unless the object that holds it (the ViewModel
instance) is collected.
NOTE: Because in a real world app such error may appear unpredictably, a handy way to test apps for issues is to use the adb's "monkey" to simulate random clicks and gestures. Read more on that in the
markingMode: none
documentation.
markingMode: none
and Android memory management in {N}