Flutter学习之视图体系(2)

}

确实可以看出,都会创建Element,只是StatelessWidgetStatefulWidget所创建的Element类型不一样,这里就先不深入了。上面可以知道widgetelement存在对应的关系,那下面看看element

2.Element

看看官方开发者文档中开篇看到:

An instantiation of a Widget at a particular location in the tree.

意思是:element是树中特定位置的widget实例。这里描述的很明显,也就是Widget是总监,部署技术规划,而element就是员工,真正干活。

widget描述如何配置子树,由于widgets是不可变的,所以可以用相同的widget来同时配置多个子树,Element表示widget配置树中的特定位置的实例,随着时间的推移,和给定的Element关联的Widget可能会随时变化,例如,如果父widget重建并为此位置创建新的widget

Elements构成一棵树,大多数elements都会有唯一的孩子,但是一些widgets(如RenderObjectElement)可以有多个孩子。

** Element具有以下生命周期:**

  • framework通过调用即将用来作element的初始化配置信息的WidgetWidget.createElement方法来创建一个element
  • framework通过调用mount方法以将新创建的Element添加到给定父级中给定槽点的树上。 mount方法负责将任何子Widget扩充到Widget并根据需要调用attachRenderObject,以将任何关联的渲染对象附加到渲染树上。
  • 此时,element被视为激活,可能出现在屏幕上。
  • 在某些情况下,父可能会更改用于配置此Element的Widget,例如因为父重新创建了新状态。发生这种情况时,framework将调用新的Widget的update方法。新Widget将始终具有与旧Widget相同的runtimeTypekey属性。如果父希望在树中的此位置更改WidgetruntimeTypekey,可以通过unmounting(卸载)此Element并在此位置扩充新Widget来实现。
  • 在某些时候,祖先(Element)可能会决定从树中移除该element(或中间祖先),祖先自己通过调用deactivateChild来完成该操作。停用中间祖先将从渲染树中移除该element的渲染对象,并将此element添加到所有者属性中的非活动元素列表中,从而framework调用deactivate方法作用在此element上。
  • 此时,该element被视为“无效”,不会出现在屏幕上。一个element直到动画帧结束前都可以保存“非活动”状态。动画帧结束时,将卸载仍处于非活动状态的所有element
  • 如果element被重写组合到树中(例如,因为它或其祖先之一有一个全局建(global key)被重用),framework将从所有者的非活动elements列表中移除该element,并调用该elementactivate方法,并重新附加到element的渲染对象到渲染树上。(此时,该元素再次被视为“活动”并可能出现在屏幕上)
  • 如果element在当前动画帧的末尾(最后一帧)没有被重新组合到树中,那么framework将会调用该元素的unmount方法

这里可以知道element的生命周期。并且平时开发没有接触到Element,都是直接操控widget,也就是说Flutter已经帮我们对widget的操作映射到element上,我这里想象到的有点事降低开发复杂。下面结合一个例子(绘制Text),看看element是不是最后渲染出来的view:

new Text(“hello flutter”);

下面看下new Text的源码:


@override
Widget build(BuildContext context) {
final DefaultTextStyle defaultTextStyle = DefaultTextStyle.of(context);
TextStyle effectiveTextStyle = style;
if (style == null || style.inherit)
effectiveTextStyle = defaultTextStyle.style.merge(style);
if (MediaQuery.boldTextOverride(context))
effectiveTextStyle = effectiveTextStyle.merge(const TextStyle(fontWeight: FontWeight.bold));
Widget result = RichText( ---->Text原来是通过RichText这个widget来构建树
textAlign: textAlign ?? defaultTextStyle.textAlign ?? TextAlign.start,
textDirection: textDirection, // RichText uses Directionality.of to obtain a default if this is null.
locale: locale, // RichText uses Localizations.localeOf to obtain a default if this is null
softWrap: softWrap ?? defaultTextStyle.softWrap,
overflow: overflow ?? defaultTextStyle.overflow,
textScaleFactor: textScaleFactor ?? MediaQuery.textScaleFactorOf(context),
maxLines: maxLines ?? defaultTextStyle.maxLines,
strutStyle: strutStyle,
text: TextSpan(
style: effectiveTextStyle,
text: data,
children: textSpan != null ? [textSpan] : null,
),
);

继续往RichText里面看:


@override
RenderParagraph createRenderObject(BuildContext context) {
assert(textDirection != null || debugCheckHasDirectionality(context));
//返回RenderParagraph widget
return RenderParagraph(text,
textAlign: textAlign,
textDirection: textDirection ?? Directionality.of(context),
softWrap: softWrap,
overflow: overflow,
textScaleFactor: textScaleFactor,
maxLines: maxLines,
strutStyle: strutStyle,
locale: locale ?? Localizations.localeOf(context, nullOk: true),
);
}

发现最终它会返回RenderParagraphwidget,继续往里看:

class RichText extends LeafRenderObjectWidget {
}

可以发现RichText继承LeafRenderObjectWidget,继续往下看:

//用于配置RenderObject子类的RenderObjectWidgets的超类,没有孩子,也就是没有字节点(child)
abstract class LeafRenderObjectWidget extends RenderObjectWidget {
const LeafRenderObjectWidget({ Key key }) : super(key: key);

//构建出类型是LeafRenderObjectElement
@override
LeafRenderObjectElement createElement() => LeafRenderObjectElement(this);
}

可以看到LeafRenderObjectWidget是抽象类,并且继承了RenderObjectWidget,继续往下看:

/// RenderObjectWidgets provide the configuration for [RenderObjectElement]s,
/// which wrap [RenderObject]s, which provide the actual rendering of the
/// application.
//上面注释是:RenderObjectWidgets向[RenderObjectElement]提供了配置信息,包装了[RenderObject],在应用程序了提供了实际的渲染
abstract class RenderObjectWidget extends Widget {

const RenderObjectWidget({ Key key }) : super(key: key);

//创建element
@override
RenderObjectElement createElement();
//创建RenderObject
@protected
RenderObject createRenderObject(BuildContext context);

//更新RenderObject
@protected
void updateRenderObject(BuildContext context, covariant RenderObject renderObject) { }

//卸载RenderObject
@protected
void didUnmountRenderObject(covariant RenderObject renderObject) { }
}

由注释可以知道RenderObject才是绘制UI背后的真正对象,那下面继续简单跟踪:

3.RenderObjectWidget

先看看RenderObjectWidget继承关系:

image.png

看看文档的一一介绍:

3.1.SingleChildRenderObjectWidget

image.png

官方文档写的很清楚: 是RenderObjectWidgets的超类,用于配置有单个孩子的RenderObject子类(为子类提供存储,实际不提供更新逻辑),并列了具体的实际widget,如常用的Align、ClipRect、DecoratedBox都是属于这类。

3.2.MultiChildRenderObjectWidget

image.png

同样也是RenderObjectWidgets的超类,用于配置有单个children(也就是多个child)的RenderObject子类,如Flex、Flow、Stack都属于这类。

3.3.LeafRenderObjectWidget

image.png

同样也是RenderObjectWidgets的超类,用于配置没有孩子的RenderObject子类,如:RichText(平时的Text)、RawImage、Texture等。 这里总结一下:

  • SingleChildRenderObjectWidget用作只有一个child的widget
  • MultiChildRenderObjectWidget用作有children(多个孩子)的widget
  • LeafRenderObjectWidget用作没有child的widget
  • RenderObjectWidget定义了创建,更新,删除RenderObject的方法,子类必须实现,RenderObject是最终布局、UI渲染的实际对象。

那么假如我现在界面上,假如布局如下:

image.png

那么渲染流程如下:image.png

这时候会想:为什么要加中间层Element呢,不直接由Widget直接转换成RendObject不是直接更好么?其实并不是这样的。首先知道Flutter是响应式框架,在某一个时刻,可能会受到不同的输入流影响,中间层Element对这一时刻的事件做了汇总,最后将需要修改的部分同步到RendObjecttree上,也就是:

  • 尽可能的降低RenderObjecttree的更改,提高页面渲染效率。
  • 没有直接操作UI,通过数据驱动视图,代码更容易理解和简洁。

上面得出UI树是由一个个element节点组成,但是最终的渲染是通过RenderObject来完成,一个widget从创建到显示到界面的流程是:widget生成element,然后通过createRenderObject方法创建对应的RenderObject关联到Element.renderObject上,最后通过RenderObject来完成绘制。

三、启动到显示

上面知道,widget并不是真正显示的对象,知道了一个widget是怎样渲染在屏幕上显示,那下面就从main()方法入口,看看一个app从点击启动到运行的流程:

1.WidgetsFlutterBinding.ensureInitialized

//app入口
void main() => runApp(MyApp());

app入口函数就是调用了runApp方法,看看runApp方法里面做了什么:

void runApp(Widget app) {
WidgetsFlutterBinding.ensureInitialized()
…attachRootWidget(app)
…scheduleWarmUpFrame();
}

首先参数(Widget app)是一个widget,下面一行一行分析,进入**WidgetsFlutterBinding.ensureInitialized()**方法:

/// A concrete binding for applications based on the Widgets framework.
/// This is the glue that binds the framework to the Flutter engine.
//意思:基于widget framework的应用程序的具体绑定
//这是将framework widget和Flutter engine绑定的桥梁
class WidgetsFlutterBinding extends BindingBase with GestureBinding, ServicesBinding, SchedulerBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding {

/// Returns an instance of the [WidgetsBinding], creating and
/// initializing it if necessary. If one is created, it will be a
/// [WidgetsFlutterBinding]. If one was previously initialized, then
/// it will at least implement [WidgetsBinding].
///
/// You only need to call this method if you need the binding to be
/// initialized before calling [runApp].
///
/// In the flutter_test framework, [testWidgets] initializes the
/// binding instance to a [TestWidgetsFlutterBinding], not a
/// [WidgetsFlutterBinding].
//上面注释意思是:返回[WidgetsBinding]的具体实例,必须创建和初始化,如果已//经创建了,那么它就是一个[WidgetsFlutterBinding],如果已经初始化了,那么//至少要实现[WidgetsBinding]
//如果你只想调用这个方法,那么你需要在调用runApp之前绑定并且初始化
static WidgetsBinding ensureInitialized() {
if (WidgetsBinding.instance == null)
WidgetsFlutterBinding();
return WidgetsBinding.instance;
}
}

看到这个WidgetsFlutterBinding混入(with)很多的Binding,下面先看父类BindingBase:

abstract class BindingBase {
BindingBase() {
developer.Timeline.startSync(‘Framework initialization’);

assert(!_debugInitialized);
initInstances();
assert(_debugInitialized);

assert(!_debugServiceExtensionsRegistered);
initServiceExtensions();
assert(_debugServiceExtensionsRegistered);

developer.postEvent(‘Flutter.FrameworkInitialization’, <String, String>{});

developer.Timeline.finishSync();
}

static bool _debugInitialized = false;
static bool _debugServiceExtensionsRegistered = false;
ui.Window get window => ui.window;//获取window实例
@protected
@mustCallSuper
void initInstances() {
assert(!_debugInitialized);
assert(() { _debugInitialized = true; return true; }());
}
}

看到有句代码ui.Window get window => ui.window;,在Android中所有的视图都是通过window来呈现的,那Flutter中也有window,那看看window在Flutter中的作用看看官方对它的定义:

image.png

意思是:链接宿主操作系统的接口,也就是Flutter framework 链接宿主操作系统的接口。系统中有一个Window实例,可以从window属性来获取,看看源码:

class Window {
Window._();

//返回DPI,DPI是每英寸的像素点数,是设备屏幕的固件属性
//获取可能不准确
double get devicePixelRatio => _devicePixelRatio;
double _devicePixelRatio = 1.0;

//绘制UI的区域大小
Size get physicalSize => _physicalSize;
Size _physicalSize = Size.zero;

//获取矩形的物理像素
WindowPadding get viewInsets => _viewInsets;
WindowPadding _viewInsets = WindowPadding.zero;

//获取内边距
WindowPadding get padding => _padding;
WindowPadding _padding = WindowPadding.zero;

//当绘制区域改变时触发
VoidCallback get onMetricsChanged => _onMetricsChanged;
VoidCallback _onMetricsChanged;
Zone _onMetricsChangedZone;
set onMetricsChanged(VoidCallback callback) {
_onMetricsChanged = callback;
_onMetricsChangedZone = Zone.current;
}

//当系统语言发生变化时触发回调
Locale get locale {
if (_locales != null && _locales.isNotEmpty) {
return _locales.first;
}
return null;
}

//获取系统语言
List get locales => _locales;
List _locales;

//当Local发生改变时触发回调
VoidCallback get onLocaleChanged => _onLocaleChanged;
VoidCallback _onLocaleChanged;
Zone _onLocaleChangedZone;
set onLocaleChanged(VoidCallback callback) {
_onLocaleChanged = callback;
_onLocaleChangedZone = Zone.current;
}

//当系统字体大小改变时触发回调
VoidCallback get onTextScaleFactorChanged => _onTextScaleFactorChanged;
VoidCallback _onTextScaleFactorChanged;
Zone _onTextScaleFactorChangedZone;
set onTextScaleFactorChanged(VoidCallback callback) {
_onTextScaleFactorChanged = callback;
_onTextScaleFactorChangedZone = Zone.current;
}

//屏幕亮度改变时触发回调
VoidCallback get onPlatformBrightnessChanged => _onPlatformBrightnessChanged;
VoidCallback _onPlatformBrightnessChanged;
Zone _onPlatformBrightnessChangedZone;
set onPlatformBrightnessChanged(VoidCallback callback) {
_onPlatformBrightnessChanged = callback;
_onPlatformBrightnessChangedZone = Zone.current;
}

//屏幕刷新时会回调
FrameCallback get onBeginFrame => _onBeginFrame;
FrameCallback _onBeginFrame;
Zone _onBeginFrameZone;
set onBeginFrame(FrameCallback callback) {
_onBeginFrame = callback;
_onBeginFrameZone = Zone.current;
}

//绘制屏幕时回调
VoidCallback get onDrawFrame => _onDrawFrame;
VoidCallback _onDrawFrame;
Zone _onDrawFrameZone;
set onDrawFrame(VoidCallback callback) {
_onDrawFrame = callback;
_onDrawFrameZone = Zone.current;
}

//点击或者指针事件触发回调
PointerDataPacketCallback get onPointerDataPacket => _onPointerDataPacket;
PointerDataPacketCallback _onPointerDataPacket;
Zone _onPointerDataPacketZone;
set onPointerDataPacket(PointerDataPacketCallback callback) {
_onPointerDataPacket = callback;
_onPointerDataPacketZone = Zone.current;
}

//获取请求的默认路由
String get defaultRouteName => _defaultRouteName();
String _defaultRouteName() native ‘Window_defaultRouteName’;

//该方法被调用后,onBeginFrame和onDrawFrame将紧连着会在合适时机调用
void scheduleFrame() native ‘Window_scheduleFrame’;

//更新应用在GPU上的渲染,这方法会直接调用Flutter engine的Window_render方法
void render(Scene scene) native ‘Window_render’;

//窗口的语义内容是否改变
bool get semanticsEnabled => _semanticsEnabled;
bool _semanticsEnabled = false;

//当窗口语言发生改变时回调
VoidCallback get onSemanticsEnabledChanged => _onSemanticsEnabledChanged;
VoidCallback _onSemanticsEnabledChanged;
Zone _onSemanticsEnabledChangedZone;
set onSemanticsEnabledChanged(VoidCallback callback) {
_onSemanticsEnabledChanged = callback;
_onSemanticsEnabledChangedZone = Zone.current;
}

//当用户表达写的动作时回调
SemanticsActionCallback get onSemanticsAction => _onSemanticsAction;
SemanticsActionCallback _onSemanticsAction;
Zone _onSemanticsActionZone;
set onSemanticsAction(SemanticsActionCallback callback) {
_onSemanticsAction = callback;
_onSemanticsActionZone = Zone.current;
}

//启用其他辅助功能回调
VoidCallback get onAccessibilityFeaturesChanged => _onAccessibilityFeaturesChanged;
VoidCallback _onAccessibilityFeaturesChanged;
Zone _onAccessibilityFlagsChangedZone;
set onAccessibilityFeaturesChanged(VoidCallback callback) {
_onAccessibilityFeaturesChanged = callback;
_onAccessibilityFlagsChangedZone = Zone.current;
}

//更新此窗口的语义数据
void updateSemantics(SemanticsUpdate update) native ‘Window_updateSemantics’;

//设置Isolate调试名称
void setIsolateDebugName(String name) native ‘Window_setIsolateDebugName’;

//向特定平台发送消息
void sendPlatformMessage(String name,
ByteData data,
PlatformMessageResponseCallback callback) {
final String error =
_sendPlatformMessage(name, _zonedPlatformMessageResponseCallback(callback), data);
if (error != null)
throw new Exception(error);
}
String _sendPlatformMessage(String name,
PlatformMessageResponseCallback callback,
ByteData data) native ‘Window_sendPlatformMessage’;

//获取平台消息的回调
PlatformMessageCallback get onPlatformMessage => _onPlatformMessage;
PlatformMessageCallback _onPlatformMessage;
Zone _onPlatformMessageZone;
set onPlatformMessage(PlatformMessageCallback callback) {
_onPlatformMessage = callback;
_onPlatformMessageZone = Zone.current;
}

//由_dispatchPlatformMessage调用
void _respondToPlatformMessage(int responseId, ByteData data)
native ‘Window_respondToPlatformMessage’;

//平台信息响应回调
static PlatformMessageResponseCallback _zonedPlatformMessageResponseCallback(PlatformMessageResponseCallback callback) {
if (callback == null)
return null;

// Store the zone in which the callback is being registered.
final Zone registrationZone = Zone.current;

return (ByteData data) {
registrationZone.runUnaryGuarded(callback, data);
};
}
}

可以知道window包含当前设备系统的一些信息和回调,那么现在看看混入的各种Binding:

  • GestureBinding:绑定手势子系统,提供onPointerDataPacket回调。
  • ServicesBinding:绑定平台消息通道,提供onPlatformMessage回调。
  • SchedulerBinding:绑定绘制调度子系统,提供onBeginFrameonDrawFrame回调。
  • PaintingBinding:绑定绘制库,处理图像缓存。
  • SemanticsBinding:语义层和flutter engine的桥梁,对辅助功能的底层支持。
  • RendererBinding:是渲染树和Flutter engine的桥梁,提供onMetricsChangedonTextScaleFactorChanged回调。
  • WidgetsBinding:是Flutter widget和engine的桥梁,提供onLocaleChangedonBuildSchedule回调。

也就是WidgetsFlutterBinding.ensureInitialized()这行代码看名字是将WidgetsFlutterBinding实例初始化。其实并非那么简单,另外获取一些系统基本信息和初始化监听window对象的一些事件,然后将这些事件按照上层Framework模型规则进行包装、抽象最后分发。简而言之WidgetsFlutterBindingFlutter engineFramework的桥梁。

2.attachRootWidget(app)

第二行是attachRootWidget(app),看名字意思是将根RootWidget挂载。点进去看:

//如果这个widget有必要创建,就将它附加到[renderViewElement]
void attachRootWidget(Widget rootWidget) {
_renderViewElement = RenderObjectToWidgetAdapter(
container: renderView,
debugShortDescription: ‘[root]’,
child: rootWidget,
).attachToRenderTree(buildOwner, renderViewElement);
}

renderView是UI渲染树的根节点:

// The render tree that’s attached to the output surface.
//挂载在渲染树 rootNode根节点
RenderView get renderView => _pipelineOwner.rootNode;

rootWidget是外面所传进来的根Widget,这里不用管它,下面看attachToRenderTree(buildOwner, renderViewElement)这个方法,根据意思,这个方法应该构建RenderTree,这个方法需要两个参数,首先看buildOwner这个参数:

/// The [BuildOwner] in charge of executing the build pipeline for the
/// widget tree rooted at this binding.
BuildOwner get buildOwner => _buildOwner;
final BuildOwner _buildOwner = BuildOwner();

看官网对它的定义:

image.png

意思是:是widget framework的管理类,用来跟踪哪些widget需要重建,并处理widget树的其他任务,例如管理树的非活动元素列表,并在调试时在热重载期间在必要时触发“重组”命令,下面看另外一个参数renderViewElement,代码注释如下:

/// The [Element] that is at the root of the hierarchy (and which wraps the
/// [RenderView] object at the root of the rendering hierarchy).
///
/// This is initialized the first time [runApp] is called.
Element get renderViewElement => _renderViewElement;
Element _renderViewElement;

renderViewElementrenderView对应的Element对象,因为renderView是树的根,所以renderViewElement位于层次结构的根部,那下面点击attachToRenderTree的源码看:

/// Inflate this widget and actually set the resulting [RenderObject] as the
/// child of [container].
///
/// If element is null, this function will create a new element. Otherwise,
/// the given element will have an update scheduled to switch to this widget.
///
/// Used by [runApp] to bootstrap applications.
RenderObjectToWidgetElement attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement element ]) {
if (element == null) {
owner.lockState(() {
element = createElement();
assert(element != null);
element.assignOwner(owner);
});
owner.buildScope(element, () {
element.mount(null, null);
});
} else {
element._newWidget = this;
element.markNeedsBuild();
}
return element;
}

跟着代码往下走如果根element没有创建,那么就调用createElement创建根elementelementwidget进行关联。接着调用element.assignOwner(owner),这个方法其实就是设置这个element的跟踪,最后调用owner.buildScope这个方法,这个方法是确定更新widget的范围。如果element已经创建了,将根element和关联的widget设为新的,并且重新构建这个element,为了后面的复用。

3.scheduleWarmUpFrame

runApp()方法最后一行执行scheduleWarmUpFrame方法:

/// Schedule a frame to run as soon as possible, rather than waiting for
/// the engine to request a frame in response to a system “Vsync” signal.
///
/// This is used during application startup so that the first frame (which is
/// likely to be quite expensive) gets a few extra milliseconds to run.
///
/// Locks events dispatching until the scheduled frame has completed.
///
/// If a frame has already been scheduled with [scheduleFrame] or
/// [scheduleForcedFrame], this call may delay that frame.
///
/// If any scheduled frame has already begun or if another
/// [scheduleWarmUpFrame] was already called, this call will be ignored.
///
/// Prefer [scheduleFrame] to update the display in normal operation.
void scheduleWarmUpFrame() {
if (_warmUpFrame || schedulerPhase != SchedulerPhase.idle)
return;

_warmUpFrame = true;
Timeline.startSync(‘Warm-up frame’);
final bool hadScheduledFrame = _hasScheduledFrame;
// We use timers here to ensure that microtasks flush in between.
Timer.run(() {
assert(_warmUpFrame);
handleBeginFrame(null);—>1
});
Timer.run(() {
assert(_warmUpFrame);
handleDrawFrame(); ---->2
// We call resetEpoch after this frame so that, in the hot reload case,
// the very next frame pretends to have occurred immediately after this
// warm-up frame. The warm-up frame’s timestamp will typically be far in
// the past (the time of the last real frame), so if we didn’t reset the
// epoch we would see a sudden jump from the old time in the warm-up frame
// to the new time in the “real” frame. The biggest problem with this is
// that implicit animations end up being triggered at the old time and
// then skipping every frame and finishing in the new time.
resetEpoch();
_warmUpFrame = false;
if (hadScheduledFrame)
scheduleFrame();
});

// Lock events so touch events etc don’t insert themselves until the
// scheduled frame has finished.
lockEvents(() async {
await endOfFrame;
Timeline.finishSync();
});
}

首先这个方法在SchedulerBinding里,这两个方法主要执行了handleBeginFramehandleDrawFrame方法:

3.1.handleBeginFrame

结语

网上高级工程师面试相关文章鱼龙混杂,要么一堆内容,要么内容质量太浅, 鉴于此我整理了上述安卓开发高级工程师面试题以及答案。希望帮助大家顺利进阶为高级工程师。
目前我就职于某大厂安卓高级工程师职位,在当下大环境下也想为安卓工程师出一份力,通过我的技术经验整理了面试经常问的题,答案部分是一篇文章或者几篇文章,都是我认真看过并且觉得不错才整理出来。

大家知道高级工程师不会像刚入门那样被问的问题一句话两句话就能表述清楚,所以我通过过滤好文章来帮助大家理解。

1307页字节跳动Android面试真题解析火爆全网,完整版开放下载

现在都说互联网寒冬,其实只要自身技术能力够强,咱们就不怕!我这边专门针对Android开发工程师整理了一套【Android进阶学习视频】、【全套Android面试秘籍】、【Android知识点PDF】。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

网上高级工程师面试相关文章鱼龙混杂,要么一堆内容,要么内容质量太浅, 鉴于此我整理了上述安卓开发高级工程师面试题以及答案。希望帮助大家顺利进阶为高级工程师。
目前我就职于某大厂安卓高级工程师职位,在当下大环境下也想为安卓工程师出一份力,通过我的技术经验整理了面试经常问的题,答案部分是一篇文章或者几篇文章,都是我认真看过并且觉得不错才整理出来。

大家知道高级工程师不会像刚入门那样被问的问题一句话两句话就能表述清楚,所以我通过过滤好文章来帮助大家理解。

[外链图片转存中…(img-3720Trku-1714489927064)]

现在都说互联网寒冬,其实只要自身技术能力够强,咱们就不怕!我这边专门针对Android开发工程师整理了一套【Android进阶学习视频】、【全套Android面试秘籍】、【Android知识点PDF】。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 10
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值