Disable recycling, refactoring & fixes (#4705)

* Added tests for native view recycling
Disabled android native view recycling
Move toString from view-common to view-base
Fix crash on application restore and navigation back on API26
Added setAsRootView method
Added missing logo into perf-tests/recycling app

* additional fix for image-source-tests. ios is case sensitive.

* Add @private to some internal properties
Fix where padding is not respected when background is reset.
This commit is contained in:
Hristo Hristov
2017-08-17 09:15:35 +03:00
committed by GitHub
parent 2701ea3c1e
commit bba7a82bdf
18 changed files with 532 additions and 251 deletions

View File

@@ -144,11 +144,9 @@ export abstract class ViewBase extends Observable {
* read-only. If you want to set out-of-band the nativeView use the setNativeView method.
*/
public nativeViewProtected: any;
public recycleNativeView: "always" | "never" | "auto";
public nativeView: any;
public bindingContext: any;
/**
* Gets the name of the constructor function for this instance. E.g. for a Button class this will return "Button".
*/
@@ -306,6 +304,14 @@ export abstract class ViewBase extends Observable {
public ensureDomNode();
//@private
/**
* @private
*/
public recycleNativeView: "always" | "never" | "auto";
/**
* @private
*/
public _isPaddingRelative: boolean;
public _styleScope: any;
/**
@@ -321,6 +327,10 @@ export abstract class ViewBase extends Observable {
* Allow multiple updates to be performed on the instance at once.
*/
public _batchUpdate<T>(callback: () => T): T;
/**
* @private
*/
_setupAsRootView(context: any): void;
//@endprivate
}

View File

@@ -6,6 +6,7 @@ import { Order, FlexGrow, FlexShrink, FlexWrapBefore, AlignSelf } from "../../la
import { KeyframeAnimation } from "../../animation/keyframe-animation";
// Types.
import { Source } from "../../../utils/debug";
import { Property, CssProperty, CssAnimationProperty, InheritedProperty, Style, clearInheritedProperties, propagateInheritableProperties, propagateInheritableCssProperties, resetCSSProperties, initNativeView, resetNativeView } from "../properties";
import { Binding, BindingOptions, Observable, WrappedValue, PropertyChangeData, traceEnabled, traceWrite, traceCategories, traceNotifyEvent } from "../bindable";
import { isIOS, isAndroid } from "../../../platform";
@@ -94,40 +95,40 @@ export function eachDescendant(view: ViewBaseDefinition, callback: (child: ViewB
let viewIdCounter = 1;
const contextMap = new WeakMap<Object, Map<string, WeakRef<Object>[]>>();
// const contextMap = new WeakMap<Object, Map<string, WeakRef<Object>[]>>();
function getNativeView(context: Object, typeName: string): Object {
let typeMap = contextMap.get(context);
if (!typeMap) {
typeMap = new Map<string, WeakRef<Object>[]>();
contextMap.set(context, typeMap);
return undefined;
}
// function getNativeView(context: Object, typeName: string): Object {
// let typeMap = contextMap.get(context);
// if (!typeMap) {
// typeMap = new Map<string, WeakRef<Object>[]>();
// contextMap.set(context, typeMap);
// return undefined;
// }
const array = typeMap.get(typeName);
if (array) {
let nativeView;
while (array.length > 0) {
const weakRef = array.pop();
nativeView = weakRef.get();
if (nativeView) {
return nativeView;
}
}
}
return undefined;
}
// const array = typeMap.get(typeName);
// if (array) {
// let nativeView;
// while (array.length > 0) {
// const weakRef = array.pop();
// nativeView = weakRef.get();
// if (nativeView) {
// return nativeView;
// }
// }
// }
// return undefined;
// }
function putNativeView(context: Object, view: ViewBase): void {
const typeMap = contextMap.get(context);
const typeName = view.typeName;
let list = typeMap.get(typeName);
if (!list) {
list = [];
typeMap.set(typeName, list);
}
list.push(new WeakRef(view.nativeViewProtected));
}
// function putNativeView(context: Object, view: ViewBase): void {
// const typeMap = contextMap.get(context);
// const typeName = view.typeName;
// let list = typeMap.get(typeName);
// if (!list) {
// list = [];
// typeMap.set(typeName, list);
// }
// list.push(new WeakRef(view.nativeViewProtected));
// }
export abstract class ViewBase extends Observable implements ViewBaseDefinition {
public static loadedEvent = "loaded";
@@ -141,7 +142,7 @@ export abstract class ViewBase extends Observable implements ViewBaseDefinition
private _visualState: string;
private _inlineStyleSelector: SelectorCore;
private __nativeView: any;
private _disableNativeViewRecycling: boolean;
// private _disableNativeViewRecycling: boolean;
public domNode: DOMNode;
public recycleNativeView: "always" | "never" | "auto";
@@ -204,7 +205,7 @@ export abstract class ViewBase extends Observable implements ViewBaseDefinition
public _defaultPaddingRight: number;
public _defaultPaddingBottom: number;
public _defaultPaddingLeft: number;
private _isPaddingRelative: boolean;
public _isPaddingRelative: boolean;
constructor() {
super();
@@ -213,7 +214,7 @@ export abstract class ViewBase extends Observable implements ViewBaseDefinition
}
get nativeView(): any {
this._disableNativeViewRecycling = true;
// this._disableNativeViewRecycling = true;
return this.nativeViewProtected;
}
@@ -230,7 +231,7 @@ export abstract class ViewBase extends Observable implements ViewBaseDefinition
}
get android(): any {
this._disableNativeViewRecycling = true;
// this._disableNativeViewRecycling = true;
return this._androidView;
}
@@ -670,25 +671,29 @@ export abstract class ViewBase extends Observable implements ViewBaseDefinition
}
private resetNativeViewInternal(): void {
const nativeView = this.nativeViewProtected;
if (nativeView && isAndroid) {
const recycle = this.recycleNativeView;
if (recycle === "always" || (recycle === "auto" && !this._disableNativeViewRecycling)) {
resetNativeView(this);
if (this._isPaddingRelative) {
nativeView.setPaddingRelative(this._defaultPaddingLeft, this._defaultPaddingTop, this._defaultPaddingRight, this._defaultPaddingBottom);
} else {
nativeView.setPadding(this._defaultPaddingLeft, this._defaultPaddingTop, this._defaultPaddingRight, this._defaultPaddingBottom);
}
this.resetNativeView();
}
}
// const nativeView = this.nativeViewProtected;
// if (nativeView && isAndroid) {
// const recycle = this.recycleNativeView;
// if (recycle === "always" || (recycle === "auto" && !this._disableNativeViewRecycling)) {
// resetNativeView(this);
// if (this._isPaddingRelative) {
// nativeView.setPaddingRelative(this._defaultPaddingLeft, this._defaultPaddingTop, this._defaultPaddingRight, this._defaultPaddingBottom);
// } else {
// nativeView.setPadding(this._defaultPaddingLeft, this._defaultPaddingTop, this._defaultPaddingRight, this._defaultPaddingBottom);
// }
// this.resetNativeView();
// }
// }
if (this._cssState) {
this._cancelAllAnimations();
}
}
_setupAsRootView(context: any): void {
this._setupUI(context);
}
@profile
public _setupUI(context: android.content.Context, atIndex?: number, parentIsLoaded?: boolean) {
@@ -706,10 +711,10 @@ export abstract class ViewBase extends Observable implements ViewBaseDefinition
let nativeView;
if (isAndroid) {
const recycle = this.recycleNativeView;
if (recycle === "always" || (recycle === "auto" && !this._disableNativeViewRecycling)) {
nativeView = <android.view.View>getNativeView(context, this.typeName);
}
// const recycle = this.recycleNativeView;
// if (recycle === "always" || (recycle === "auto" && !this._disableNativeViewRecycling)) {
// nativeView = <android.view.View>getNativeView(context, this.typeName);
// }
if (!nativeView) {
nativeView = <android.view.View>this.createNativeView();
@@ -790,15 +795,15 @@ export abstract class ViewBase extends Observable implements ViewBaseDefinition
@profile
public _tearDownUI(force?: boolean) {
if (traceEnabled()) {
traceWrite(`${this}._tearDownUI(${force})`, traceCategories.VisualTreeEvents);
}
// No context means we are already teared down.
if (!this._context) {
return;
}
if (traceEnabled()) {
traceWrite(`${this}._tearDownUI(${force})`, traceCategories.VisualTreeEvents);
}
this.resetNativeViewInternal();
this.eachChild((child) => {
@@ -810,18 +815,24 @@ export abstract class ViewBase extends Observable implements ViewBaseDefinition
this.parent._removeViewFromNativeVisualTree(this);
}
const nativeView = this.nativeViewProtected;
if (nativeView && isAndroid) {
const recycle = this.recycleNativeView;
if (recycle === "always" || (recycle === "auto" && !this._disableNativeViewRecycling)) {
// const nativeParent = isAndroid ? (<android.view.View>nativeView).getParent() : (<UIView>nativeView).superview;
const nativeParent = (<android.view.View>nativeView).getParent();
const animation = (<android.view.View>nativeView).getAnimation();
if (!nativeParent && !animation) {
putNativeView(this._context, this);
}
}
}
// const nativeView = this.nativeViewProtected;
// if (nativeView && isAndroid) {
// const recycle = this.recycleNativeView;
// let shouldRecycle = false;
// if (recycle === "always") {
// shouldRecycle = true;
// } else if (recycle === "auto" && !this._disableNativeViewRecycling) {
// const propertiesSet = Object.getOwnPropertySymbols(this).length + Object.getOwnPropertySymbols(this.style).length / 2;
// shouldRecycle = propertiesSet <= this.recyclePropertyCounter;
// }
// // const nativeParent = isAndroid ? (<android.view.View>nativeView).getParent() : (<UIView>nativeView).superview;
// const nativeParent = (<android.view.View>nativeView).getParent();
// const animation = (<android.view.View>nativeView).getAnimation();
// if (shouldRecycle && !nativeParent && !animation) {
// putNativeView(this._context, this);
// }
// }
this.disposeNativeView();
@@ -941,6 +952,21 @@ export abstract class ViewBase extends Observable implements ViewBaseDefinition
}
}
}
public toString(): string {
let str = this.typeName;
if (this.id) {
str += `<${this.id}>`;
} else {
str += `(${this._domId})`;
}
let source = Source.get(this);
if (source) {
str += `@${source};`;
}
return str;
}
}
ViewBase.prototype.isCollapsed = false;

View File

@@ -2,9 +2,6 @@
import { View as ViewDefinition, Point, Size, Color, dip } from ".";
import { HorizontalAlignment, VerticalAlignment, Visibility, Length, PercentLength } from "../../styling/style-properties";
// Types.
import { Source } from "../../../utils/debug";
import {
ViewBase, Property, booleanConverter, EventData, layout,
getEventOrGestureName, traceEnabled, traceWrite, traceCategories
@@ -747,21 +744,6 @@ export abstract class ViewCommon extends ViewBase implements ViewDefinition {
super.resetNativeView();
}
public toString(): string {
let str = this.typeName;
if (this.id) {
str += `<${this.id}>`;
} else {
str += `(${this._domId})`;
}
let source = Source.get(this);
if (source) {
str += `@${source};`;
}
return str;
}
public _setNativeViewFrame(nativeView: any, frame: any) {
//
}

View File

@@ -7,7 +7,8 @@ import { CacheLayerType } from "../../../utils/utils";
import { Background, ad as androidBackground } from "../../styling/background";
import {
ViewCommon, layout, isEnabledProperty, originXProperty, originYProperty, automationTextProperty, isUserInteractionEnabledProperty,
traceEnabled, traceWrite, traceCategories, traceNotifyEvent
traceEnabled, traceWrite, traceCategories, traceNotifyEvent,
paddingLeftProperty, paddingTopProperty, paddingRightProperty, paddingBottomProperty
} from "./view-common";
import {
@@ -486,7 +487,18 @@ export class View extends ViewCommon {
} else {
const nativeView = this.nativeViewProtected;
org.nativescript.widgets.ViewHelper.setBackground(nativeView, value);
nativeView.setPadding(this._defaultPaddingLeft, this._defaultPaddingTop, this._defaultPaddingRight, this._defaultPaddingBottom);
const style = this.style;
const paddingTop = paddingTopProperty.isSet(style) ? this.effectivePaddingTop : this._defaultPaddingTop;
const paddingRight = paddingRightProperty.isSet(style) ? this.effectivePaddingRight : this._defaultPaddingRight;
const paddingBottom = paddingBottomProperty.isSet(style) ? this.effectivePaddingBottom : this._defaultPaddingBottom;
const paddingLeft = paddingLeftProperty.isSet(style) ? this.effectivePaddingLeft : this._defaultPaddingLeft;
if (this._isPaddingRelative) {
nativeView.setPaddingRelative(paddingLeft, paddingTop, paddingRight, paddingBottom);
} else {
nativeView.setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom);
}
(<any>nativeView).background = undefined;
const cache = <CacheLayerType><any>nativeView;

View File

@@ -334,6 +334,22 @@ export class FrameBase extends CustomLayoutView implements FrameDefinition {
this._updateActionBar();
}
public _findEntryForTag(fragmentTag: string): BackstackEntry {
let entry: BackstackEntry;
if (this._currentEntry && this._currentEntry.fragmentTag === fragmentTag) {
entry = this._currentEntry;
} else {
entry = this._backStack.find((value) => value.fragmentTag === fragmentTag);
// on API 26 fragments are recreated lazily after activity is destroyed.
if (!entry) {
const navigationItem = this._navigationQueue.find((value) => value.entry.fragmentTag === fragmentTag);
entry = navigationItem ? navigationItem.entry : undefined;
}
}
return entry;
}
public navigationQueueIsEmpty(): boolean {
return this._navigationQueue.length === 0;
}

View File

@@ -419,13 +419,7 @@ function findPageForFragment(fragment: android.app.Fragment, frame: Frame) {
return;
}
let entry: BackstackEntry;
if (frame._currentEntry && frame._currentEntry.fragmentTag === fragmentTag) {
entry = frame._currentEntry;
} else {
entry = frame.backStack.find((value) => value.fragmentTag === fragmentTag);
}
const entry = frame._findEntryForTag(fragmentTag);
const page = entry ? entry.resolvedPage : undefined;
if (page) {
const callbacks: FragmentCallbacksImplementation = fragment[CALLBACKS];
@@ -668,7 +662,7 @@ class ActivityCallbacksImplementation implements AndroidActivityCallbacks {
this._rootView = rootView;
// Initialize native visual tree;
rootView._setupUI(activity);
rootView._setupAsRootView(activity);
activity.setContentView(rootView.nativeViewProtected, new org.nativescript.widgets.CommonLayoutParams());
// frameId is negative w
if (frame) {

View File

@@ -132,6 +132,10 @@ export class Frame extends View {
* @private
*/
_getNavBarVisible(page: Page): boolean;
/**
* @private
*/
_findEntryForTag(fragmentTag: string): BackstackEntry;
//@endprivate
/**

View File

@@ -20,8 +20,6 @@ const DELEGATE = "_delegate";
let navDepth = -1;
const FRAME_CONTEXT = {};
export class Frame extends FrameBase {
private _ios: iOSFrame;
private _paramToNavigate: any;
@@ -35,13 +33,6 @@ export class Frame extends FrameBase {
public _bottom: number;
public _isInitialNavigation: boolean = true;
public get _context(): any {
return FRAME_CONTEXT;
}
public set _context(value: any) {
throw new Error("Frame _context is readonly");
}
constructor() {
super();
this._ios = new iOSFrame(this);
@@ -255,10 +246,6 @@ export class Frame extends FrameBase {
return this._ios;
}
// get nativeView(): any {
// return this._ios.controller.view;
// }
public static get defaultAnimatedNavigation(): boolean {
return FrameBase.defaultAnimatedNavigation;
}
@@ -495,10 +482,10 @@ class UINavigationControllerImpl extends UINavigationController {
}
@profile
public viewDidLoad(): void {
super.viewDidLoad();
public viewWillAppear(animated: boolean): void {
super.viewWillAppear(animated);
let owner = this._owner.get();
if (owner) {
if (owner && (!owner.isLoaded && !owner.parent)) {
owner.onLoaded();
}
}