深度了解Flutter APP的构建流程

this.project = project

// 配置maven仓库地址,环境变量有配置FLUTTER_STORAGE_BASE_URL就优先用,没就缺省值

String hostedRepository = System.env.FLUTTER_STORAGE_BASE_URL ?: DEFAULT_MAVEN_HOST

String repository = useLocalEngine()

? project.property(‘local-engine-repo’)
“$hostedRepository/download.flutter.io”

project.rootProject.allprojects {

repositories {

maven {

url repository

}

}

}

// 创建app模块中配置的flutter{ source: ‘…/…/’}闭包extensions

// source:用来配置当前Flutter工程的根路径,注意不是Android工程,如果没有配置抛出Must provide Flutter source directory异常

// target:用来指定Flutter代码的启动入口,如果没有配置默认为lib/main.dart

project.extensions.create(“flutter”, FlutterExtension)

//添加flutter构建相关的各种task

//文章最开始提到的构建流程最后执行阶段添加的几个flutter相关的task就是在这里添加:eg,

// app:compileFlutterBuildDebug

// app:packLibsflutterBuildDebug

// app:copyFlutterAssetsDebug

// 后面单独分析

this.addFlutterTasks(project)

// By default, assembling APKs generates fat APKs if multiple platforms are passed.

// Configuring split per ABI allows to generate separate APKs for each abi.

// This is a noop when building a bundle.

// 判断是否根据(abi)分包编译

if (shouldSplitPerAbi()) {

project.android {

splits {

abi {

// Enables building multiple APKs per ABI.

enable true

// Resets the list of ABIs that Gradle should create APKs for to none.

reset()

// Specifies that we do not want to also generate a universal APK that includes all ABIs.

universalApk false

}

}

}

}

//判断编译命令是否添加–target-platform=xxxABI参数,没有就用缺省,有就看这个ABI是否flutter支持的,支持就配置,否则抛出异常

getTargetPlatforms().each { targetArch ->

String abiValue = PLATFORM_ARCH_MAP[targetArch]

project.android {

if (shouldSplitPerAbi()) {

splits {

abi {

include abiValue

}

}

}

}

}

//通过属性配置获取flutter.sdk,或者通过环境变量FLUTTER_ROOT获取,都没有就抛出环境异常

String flutterRootPath = resolveProperty(“flutter.sdk”, System.env.FLUTTER_ROOT)

if (flutterRootPath == null) {

throw new GradleException(“Flutter SDK not found. Define location with flutter.sdk in the local.properties file or with a FLUTTER_ROOT environment variable.”)

}

flutterRoot = project.file(flutterRootPath)

if (!flutterRoot.isDirectory()) {

throw new GradleException(“flutter.sdk must point to the Flutter SDK directory”)

}

//获取Flutter Engine的版本号,如果通过local-engine-repo参数使用本地自己编译的Engine则版本为+,否则读取SDK目录下bin\internal\engine.version文件值,一串类似MD5的值

engineVersion = useLocalEngine()

? “+” // Match any version since there’s only one.
“1.0.0-” + Paths.get(flutterRoot.absolutePath, “bin”, “internal”, “engine.version”).toFile().text.trim()

//依据平台获取对应flutter命令脚本,都位于SDK目录下bin\中,名字为flutter

String flutterExecutableName = Os.isFamily(Os.FAMILY_WINDOWS) ? “flutter.bat” : “flutter”

flutterExecutable = Paths.get(flutterRoot.absolutePath, “bin”, flutterExecutableName).toFile();

//获取flutter混淆配置清单,位于SDK路径下packages\flutter_tools\gradle\flutter_proguard_rules.pro

//里面配置只有 -dontwarn io.flutter.plugin.** 和 -dontwarn android.**

String flutterProguardRules = Paths.get(flutterRoot.absolutePath, “packages”, “flutter_tools”,

“gradle”, “flutter_proguard_rules.pro”)

//新增profile构建类型,在当前project下的android.buildTypes中进行配置

project.android.buildTypes {

// Add profile build type.

profile {

initWith debug

if (it.hasProperty(“matchingFallbacks”)) {

matchingFallbacks = [“debug”, “release”]

}

}

release {

// Enables code shrinking, obfuscation, and optimization for only

// your project’s release build type.

minifyEnabled true

// Enables resource shrinking, which is performed by the

// Android Gradle plugin.

// NOTE: The resource shrinker can’t be used for libraries.

shrinkResources isBuiltAsApp(project)

// Fallback to android/app/proguard-rules.pro.

// This way, custom Proguard rules can be configured as needed.

proguardFiles project.android.getDefaultProguardFile(“proguard-android.txt”), flutterProguardRules, “proguard-rules.pro”

}

}

if (useLocalEngine()) {

// This is required to pass the local engine to flutter build aot.

String engineOutPath = project.property(‘local-engine-out’)

File engineOut = project.file(engineOutPath)

if (!engineOut.isDirectory()) {

throw new GradleException(‘local-engine-out must point to a local engine build’)

}

localEngine = engineOut.name

localEngineSrcPath = engineOut.parentFile.parent

}

//给所有buildTypes添加依赖,addFlutterDependencies

project.android.buildTypes.all this.&addFlutterDependencies

}

/**

  • Adds the dependencies required by the Flutter project.

  • This includes:

    1. The embedding
    1. libflutter.so

*/

void addFlutterDependencies(buildType) {

//获取flutter build类型,值为debug、profile、release

String flutterBuildMode = buildModeFor(buildType)

if (!supportsBuildMode(flutterBuildMode)) {

return

}

// The embedding is set as an API dependency in a Flutter plugin.

// Therefore, don’t make the app project depend on the embedding if there are Flutter

// plugins.

// This prevents duplicated classes when using custom build types. That is, a custom build

// type like profile is used, and the plugin and app projects have API dependencies on the

// embedding.

// 如果插件不是applicationVariants类型,即android library,或者项目根目录下.flutter-plugins文件中安卓插件个数为空

if (!isFlutterAppProject() || getPluginList().size() == 0) {

// 给Flutter Plugin的android插件添加编译依赖

// 譬如io.flutter:flutter_embedding_debug:1.0.

addApiDependencies(project, buildType.name,

“io.flutter:flutter_embedding_ f l u t t e r B u i l d M o d e : flutterBuildMode: flutterBuildMode:engineVersion”)

}

// 给project添加对应编译依赖

// 譬如io.flutter:arm64_v8a_debug:1.0.0,来自maven仓库

List platforms = getTargetPlatforms().collect()

// Debug mode includes x86 and x64, which are commonly used in emulators.

if (flutterBuildMode == “debug” && !useLocalEngine()) {

platforms.add(“android-x86”)

platforms.add(“android-x64”)

}

platforms.each { platform ->

String arch = PLATFORM_ARCH_MAP[platform].replace(“-”, “_”)

// Add the libflutter.so dependency.

addApiDependencies(project, buildType.name,

“io.flutter:KaTeX parse error: Expected group after '_' at position 7: {arch}_̲flutterBuildMode:$engineVersion”)

}

}

…省略部分…

//接下来再单独分析核心部分:addFlutterTasks方法

6. 接下来我们再看看flutter.gradle的addFlutterTasks方法:

private void addFlutterTasks(Project project) {

if (project.state.failure) {

return

}

…此处省略一堆属性获取与赋值操作…

// 定义 addFlutterDeps 函数,参数variant为标准构建对应的构建类型

def addFlutterDeps = { variant ->

if (shouldSplitPerAbi()) {

//常规操作:如果是构建多个变体apk模式就处理vc问题

variant.outputs.each { output ->

//确保每个APK都有自己唯一的versionCode,这里就是做这个事情的

//具体可以看官方文档 https://developer.android.com/studio/build/configure-apk-splits

def abiVersionCode = ABI_VERSION.get(output.getFilter(OutputFile.ABI))

if (abiVersionCode != null) {

output.versionCodeOverride =

abiVersionCode * 1000 + variant.versionCode

}

}

}

//获取编译类型,variantBuildMode值为debug、profile、release之一

String variantBuildMode = buildModeFor(variant.buildType)

//依据参数生成一个task名字,譬如这里的compileFlutterBuildDebug、compileFlutterBuildProfile、compileFlutterBuildRelease

//参照文章开始最后执行阶段添加的flutter task名字就是在这里确认的

String taskName = toCammelCase([“compile”, FLUTTER_BUILD_PREFIX, variant.name])

// 给当前project创建compileFlutterBuildDebug、compileFlutterBuildProfile、compileFlutterBuildRelease Task

// 实现为FlutterTask,主要用来编译Flutter代码,这个task稍后单独分析

FlutterTask compileTask = project.tasks.create(name: taskName, type: FlutterTask) {

// 各种task属性赋值操作,基本都来自上面的属性获取或者匹配分析

flutterRoot this.flutterRoot

flutterExecutable this.flutterExecutable

buildMode variantBuildMode

localEngine this.localEngine

localEngineSrcPath this.localEngineSrcPath

// 默认dart入口lib/main.dart、可以通过target属性自定义指向

targetPath getFlutterTarget()

verbose isVerbose()

fastStart isFastStart()

fileSystemRoots fileSystemRootsValue

fileSystemScheme fileSystemSchemeValue

trackWidgetCreation trackWidgetCreationValue

targetPlatformValues = targetPlatforms

sourceDir getFlutterSourceDirectory()

// flutter中间产物目录

intermediateDir project.file(“ p r o j e c t . b u i l d D i r / {project.buildDir}/ project.buildDir/{AndroidProject.FD_INTERMEDIATES}/flutter/${variant.name}/”)

extraFrontEndOptions extraFrontEndOptionsValue

extraGenSnapshotOptions extraGenSnapshotOptionsValue

splitDebugInfo splitDebugInfoValue

treeShakeIcons treeShakeIconsOptionsValue

dartObfuscation dartObfuscationValue

dartDefines dartDefinesValue

bundleSkSLPath bundleSkSLPathValue

performanceMeasurementFile performanceMeasurementFileValue

codeSizeDirectory codeSizeDirectoryValue

// 权限相关处理

doLast {

project.exec {

if (Os.isFamily(Os.FAMILY_WINDOWS)) {

commandLine(‘cmd’, ‘/c’, “attrib -r ${assetsDirectory}/* /s”)

} else {

commandLine(‘chmod’, ‘-R’, ‘u+w’, assetsDirectory)

}

}

}

}

// 项目构建中间产物的文件,也就是根目录下build/intermediates/flutter/debug/libs.jar文件

File libJar = project.file(“ p r o j e c t . b u i l d D i r / {project.buildDir}/ project.buildDir/{AndroidProject.FD_INTERMEDIATES}/flutter/${variant.name}/libs.jar”)

// 创建packLibsFlutterBuildProfile、packLibsFlutterBuildDebug、packLibsFlutterBuildRelease任务,主要是产物的复制挪位置操作,作用就是把build/intermediates/flutter/debug/下依据abi生成的app.so通过jar命令打包成build/intermediates/flutter/debug/libs.jar

Task packFlutterAppAotTask = project.tasks.create(name: “packLibs F L U T T E R B U I L D P R E F I X {FLUTTER_BUILD_PREFIX} FLUTTERBUILDPREFIX{variant.name.capitalize()}”, type: Jar) {

// 目标路径为build/intermediates/flutter/debug目录

destinationDir libJar.parentFile

// 文件名为libs.jar

archiveName libJar.name

// 依赖前面定义的compileTask,也就是说,这个task基本作用是产物处理

dependsOn compileTask

// targetPlatforms取值为android-arm、android-arm64、android-x86、android-x64

targetPlatforms.each { targetPlatform ->

// abi取值为armeabi-v7a、arm64-v8a、x86、x86_64

String abi = PLATFORM_ARCH_MAP[targetPlatform]

// 数据来源来自compileTask任务中间产物目录

// 即把build/intermediates/flutter/debug/下依据abi生成的app.so通过jar命令打包成一个build/intermediates/flutter/debug/libs.jar文件

from(“ c o m p i l e T a s k . i n t e r m e d i a t e D i r / {compileTask.intermediateDir}/ compileTask.intermediateDir/{abi}”) {

include “*.so”

// Move app.so to lib/<abi>/libapp.so

rename { String filename ->

return “lib/ a b i / l i b {abi}/lib abi/lib{filename}”

}

}

}

}

// 前面有介绍过addApiDependencies作用,把 packFlutterAppAotTask 产物加到依赖项里面参与编译

// 类似implementation files(‘libs.jar’),然后里面的so会在项目执行标准mergeDebugNativeLibs task时打包进标准lib目录

addApiDependencies(project, variant.name, project.files {

packFlutterAppAotTask

})

// We build an AAR when this property is defined.

// 当构建有is-plugin属性时则编译aar

boolean isBuildingAar = project.hasProperty(‘is-plugin’)

// In add to app scenarios, a Gradle project contains a :flutter and :app project.

// We know that :flutter is used as a subproject when these tasks exists and we aren’t building an AAR.

// 当是Flutter Module方式,即Flutter以aar作为已存在native安卓项目依赖时才有这些:flutter:模块依赖,否则没有这些task

// 可以参见新建的FlutterModule中.android/include_flutter.groovy中gradle.project(“:flutter”).projectDir实现

Task packageAssets = project.tasks.findByPath(“:flutter:package${variant.name.capitalize()}Assets”)

Task cleanPackageAssets = project.tasks.findByPath(“:flutter:cleanPackage${variant.name.capitalize()}Assets”)

// 判断是否为FlutterModule依赖

boolean isUsedAsSubproject = packageAssets && cleanPackageAssets && !isBuildingAar

// 新建copyFlutterAssetsDebug task,目的就是copy产物,也就是assets归档

// 常规merge中间产物类似,compileTask产物的assets目录在mergeAssets时复制到主包中间产物目录

Task copyFlutterAssetsTask = project.tasks.create(

name: “copyFlutterAssets${variant.name.capitalize()}”,

type: Copy,

) {

dependsOn compileTask

with compileTask.assets

if (isUsedAsSubproject) {

dependsOn packageAssets

dependsOn cleanPackageAssets

into packageAssets.outputDir

return

}

// variant.mergeAssets will be removed at the end of 2019.

def mergeAssets = variant.hasProperty(“mergeAssetsProvider”) ?

variant.mergeAssetsProvider.get() : variant.mergeAssets

dependsOn mergeAssets

dependsOn “clean${mergeAssets.name.capitalize()}”

mergeAssets.mustRunAfter(“clean${mergeAssets.name.capitalize()}”)

into mergeAssets.outputDir

}

if (!isUsedAsSubproject) {

def variantOutput = variant.outputs.first()

def processResources = variantOutput.hasProperty(“processResourcesProvider”) ?

variantOutput.processResourcesProvider.get() : variantOutput.processResources

processResources.dependsOn(copyFlutterAssetsTask)

}

return copyFlutterAssetsTask

}

// 判断是否是applicationVariants

if (isFlutterAppProject()) {

project.android.applicationVariants.all { variant ->

// 获取是assemble task咯

Task assembleTask = getAssembleTask(variant)

if (!shouldConfigureFlutterTask(assembleTask)) {

return

}

//把前面定义的addFlutterDeps函数调用返回的copyFlutterAssetsTask任务拿到作为依赖项

Task copyFlutterAssetsTask = addFlutterDeps(variant)

def variantOutput = variant.outputs.first()

def processResources = variantOutput.hasProperty(“processResourcesProvider”) ?

variantOutput.processResourcesProvider.get() : variantOutput.processResources

processResources.dependsOn(copyFlutterAssetsTask)

// Copy the output APKs into a known location, so flutter run or flutter build apk

// can discover them. By default, this is <app-dir>/build/app/outputs/flutter-apk/<filename>.apk.

//

// The filename consists of app<-abi>?<-flavor-name>?-<build-mode>.apk.

// Where:

// * abi can be armeabi-v7a|arm64-v8a|x86|x86_64 only if the flag split-per-abi is set.

// * flavor-name is the flavor used to build the app in lower case if the assemble task is called.

// * build-mode can be release|debug|profile.

// 执行flutter run或者flutter build apk的产物apk归档处理

variant.outputs.all { output ->

assembleTask.doLast {

// packageApplication became packageApplicationProvider in AGP 3.3.0.

def outputDirectory = variant.hasProperty(“packageApplicationProvider”)

? variant.packageApplicationProvider.get().outputDirectory
variant.packageApplication.outputDirectory

// outputDirectory is a DirectoryProperty in AGP 4.1.

String outputDirectoryStr = outputDirectory.metaClass.respondsTo(outputDirectory, “get”)

? outputDirectory.get()
outputDirectory

String filename = “app”

String abi = output.getFilter(OutputFile.ABI)

if (abi != null && !abi.isEmpty()) {

filename += “-${abi}”

}

if (variant.flavorName != null && !variant.flavorName.isEmpty()) {

filename += “-${variant.flavorName.toLowerCase()}”

}

filename += “-${buildModeFor(variant.buildType)}”

project.copy {

from new File(“ o u t p u t D i r e c t o r y S t r / outputDirectoryStr/ outputDirectoryStr/{output.outputFileName}”)

into new File(“${project.buildDir}/outputs/flutter-apk”);

rename {

return “${filename}.apk”

}

}

}

}

}

configurePlugins()

return

}

// Flutter host module project (Add-to-app).

// 是不是模块源码依赖方式集成到现有项目,参见 https://flutter.cn/docs/development/add-to-app/android/project-setup

// 是的话对模块也做类似一堆处理即可,不再重复分析了,也是 assets 合并

String hostAppProjectName = project.rootProject.hasProperty(‘flutter.hostAppProjectName’) ? project.rootProject.property(‘flutter.hostAppProjectName’) : “app”

Project appProject = project.rootProject.findProject(“😒{hostAppProjectName}”)

assert appProject != null : “Project 😒{hostAppProjectName} doesn’t exist. To custom the host app project name, set org.gradle.project.flutter.hostAppProjectName=<project-name> in gradle.properties.”

// Wait for the host app project configuration.

appProject.afterEvaluate {

assert appProject.android != null

project.android.libraryVariants.all { libraryVariant ->

Task copyFlutterAssetsTask

appProject.android.applicationVariants.all { appProjectVariant ->

Task appAssembleTask = getAssembleTask(appProjectVariant)

if (!shouldConfigureFlutterTask(appAssembleTask)) {

return

}

// Find a compatible application variant in the host app.

//

// For example, consider a host app that defines the following variants:

// | ----------------- | ----------------------------- |

// | Build Variant | Flutter Equivalent Variant |

// | ----------------- | ----------------------------- |

// | freeRelease | relese |

// | freeDebug | debug |

// | freeDevelop | debug |

// | profile | profile |

// | ----------------- | ----------------------------- |

//

// This mapping is based on the following rules:

// 1. If the host app build variant name is profile then the equivalent

// Flutter variant is profile.

// 2. If the host app build variant is debuggable

// (e.g. buildType.debuggable = true), then the equivalent Flutter

// variant is debug.

// 3. Otherwise, the equivalent Flutter variant is release.

String variantBuildMode = buildModeFor(libraryVariant.buildType)

if (buildModeFor(appProjectVariant.buildType) != variantBuildMode) {

return

}

if (copyFlutterAssetsTask == null) {

copyFlutterAssetsTask = addFlutterDeps(libraryVariant)

}

Task mergeAssets = project

.tasks

.findByPath(“: h o s t A p p P r o j e c t N a m e : m e r g e {hostAppProjectName}:merge hostAppProjectName:merge{appProjectVariant.name.capitalize()}Assets”)

assert mergeAssets

mergeAssets.dependsOn(copyFlutterAssetsTask)

}

}

}

configurePlugins()

}

了解configurePlugins源码:

/**

  • flutter的依赖都添加在pubspec.yaml中

  • 接着都会执行flutter pub get,然后工具会生成跟目录下.flutter-plugins等文件

  • 这里做的事情就是帮忙给module自动添加上这些插件dependencies依赖模块

*/

private void configurePlugins() {

if (!buildPluginAsAar()) {

getPluginList().each this.&configurePluginProject

getPluginDependencies().each this.&configurePluginDependencies

return

}

project.repositories {

maven {

url “${getPluginBuildDir()}/outputs/repo”

}

}

getPluginList().each { pluginName, pluginPath ->

configurePluginAar(pluginName, pluginPath, project)

}

}

到这里我们的addFlutterTasks方法以及走完了

7. 接下来我们再看看上文提到的FlutterTask compileTask,这个FlutterTask作甚的

看入口方法:

void buildBundle() {

if (!sourceDir.isDirectory()) {

throw new GradleException(“Invalid Flutter source directory: ${sourceDir}”)

}

//默认以app为例创建build/app/intermediates/flutter目录

intermediateDir.mkdirs()

// Compute the rule name for flutter assemble. To speed up builds that contain

// multiple ABIs, the target name is used to communicate which ones are required

// rather than the TargetPlatform. This allows multiple builds to share the same

// cache.

// 计算flutter assemble的规则名称列表

String[] ruleNames;

if (buildMode == “debug”) {

ruleNames = [“debug_android_application”]

} else {

ruleNames = targetPlatformValues.collect { “android_aot_bundle_KaTeX parse error: Expected group after '_' at position 12: {buildMode}_̲it” }

}

// 重点执行命令

project.exec {

logging.captureStandardError LogLevel.ERROR

// windows的话就是flutter SDK路径下 bin/flutter.bat文件,unix就是bin/flutter

executable flutterExecutable.absolutePath

// 我们app的build.gradle中配置的flutter { source ‘…/…/’ }闭包,路径,也就是项目根目录下

workingDir sourceDir

// 使用本地自己编译的flutter engine才需要的参数

if (localEngine != null) {

args “–local-engine”, localEngine

args “–local-engine-src-path”, localEngineSrcPath

}

// 类似标准gradle构建参数打印控制

if (verbose) {

args “–verbose”

} else {

args “–quiet”

}

// 追加一堆编译参数

args “assemble”

args “–depfile”, “${intermediateDir}/flutter_build.d”

// flutter 编译产物输出路径

args “–output”, “${intermediateDir}”

if (performanceMeasurementFile != null) {

args “–performance-measurement-file=${performanceMeasurementFile}”

}

// Flutter dart程序入口,默认为lib/main.dart

if (!fastStart || buildMode != “debug”) {

args “-dTargetFile=${targetPath}”

} else {

args “-dTargetFile=${Paths.get(flutterRoot.absolutePath, “examples”, “splash”, “lib”, “main.dart”)}”

}

args “-dTargetPlatform=android”

args “-dBuildMode=${buildMode}”

if (trackWidgetCreation != null) {

args “-dTrackWidgetCreation=${trackWidgetCreation}”

}

if (splitDebugInfo != null) {

args “-dSplitDebugInfo=${splitDebugInfo}”

}

if (treeShakeIcons == true) {

args “-dTreeShakeIcons=true”

}

if (dartObfuscation == true) {

args “-dDartObfuscation=true”

}

if (dartDefines != null) {

args “–DartDefines=${dartDefines}”

}

if (bundleSkSLPath != null) {

args “-iBundleSkSLPath=${bundleSkSLPath}”

}

if (codeSizeDirectory != null) {

args “-dCodeSizeDirectory=${codeSizeDirectory}”

}

if (extraGenSnapshotOptions != null) {

args “–ExtraGenSnapshotOptions=${extraGenSnapshotOptions}”

}

最后

希望大家能有一个好心态,想进什么样的公司要想清楚,并不一定是大公司,我选的也不是特大厂。当然如果你不知道选或是没有规划,那就选大公司!希望我们能先选好想去的公司再投或内推,而不是有一个公司要我我就去!还有就是不要害怕,也不要有压力,平常心对待就行,但准备要充足。最后希望大家都能拿到一份满意的 offer !如果目前有一份工作也请好好珍惜好好努力,找工作其实挺累挺辛苦的。

这里附上上述的面试题相关的几十套字节跳动,京东,小米,腾讯、头条、阿里、美团等公司19年的面试题。把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节。

由于篇幅有限,这里以图片的形式给大家展示一小部分。


《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!
etCreation}"

}

if (splitDebugInfo != null) {

args “-dSplitDebugInfo=${splitDebugInfo}”

}

if (treeShakeIcons == true) {

args “-dTreeShakeIcons=true”

}

if (dartObfuscation == true) {

args “-dDartObfuscation=true”

}

if (dartDefines != null) {

args “–DartDefines=${dartDefines}”

}

if (bundleSkSLPath != null) {

args “-iBundleSkSLPath=${bundleSkSLPath}”

}

if (codeSizeDirectory != null) {

args “-dCodeSizeDirectory=${codeSizeDirectory}”

}

if (extraGenSnapshotOptions != null) {

args “–ExtraGenSnapshotOptions=${extraGenSnapshotOptions}”

}

最后

希望大家能有一个好心态,想进什么样的公司要想清楚,并不一定是大公司,我选的也不是特大厂。当然如果你不知道选或是没有规划,那就选大公司!希望我们能先选好想去的公司再投或内推,而不是有一个公司要我我就去!还有就是不要害怕,也不要有压力,平常心对待就行,但准备要充足。最后希望大家都能拿到一份满意的 offer !如果目前有一份工作也请好好珍惜好好努力,找工作其实挺累挺辛苦的。

这里附上上述的面试题相关的几十套字节跳动,京东,小米,腾讯、头条、阿里、美团等公司19年的面试题。把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节。

由于篇幅有限,这里以图片的形式给大家展示一小部分。

[外链图片转存中…(img-UnzSqVUW-1715338454875)]
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值