欢迎您访问365答案网,请分享给你的朋友!
生活常识 学习资料

冬困秋乏春无力-来一份全面的面试宝典洗洗脑,移动设备开发网易云音乐app开发说明

时间:2023-08-09

灰色保活: 利用系统的漏洞启动前台 Service

黑色保活

所谓黑色保活,就是利用不同的 app 进程使用广播来进行相互唤醒。举个 3 个比较常见的场景:
场景 1: 开机,网络切换、拍照、拍视频时候,利用系统产生的广播唤醒 app
场景 2: 接入第三方 SDK 也会唤醒相应的 app 进程,如微信 sdk 会唤醒微信,支付宝 sdk 会唤醒支付宝。由此发散开去,就会直接触发了下面的 场景 3
场景 3: 假如你手机里装了支付宝、淘宝、天猫、UC 等阿里系的 app,那么你打开任意一个阿里系的 app 后,有可能就顺便把其他阿里系的 app 给唤醒了。(只是拿阿里打个比方,其实 BAT 系都差不多)

白色保活

白色保活手段非常简单,就是调用系统 api 启动一个前台的 Service 进程,这样会在系统的通知栏生成一个 Notification,用来让用户知道有这样一个 app 在运行着,哪怕当前的 app退到了后台。如下方的 LBE 和 QQ 音乐这样:

灰色保活

灰色保活,这种保活手段是应用范围最广泛。它是利用系统的漏洞来启动一个前台的 Service进程,与普通的启动方式区别在于,它不会在系统通知栏处出现一个 Notification,看起来就如同运行着一个后台 Service 进程一样。这样做带来的好处就是,用户无法察觉到你运行着一个前台进程(因为看不到 Notification),但你的进程优先级又是高于普通后台进程的。那么如何利用系统的漏洞呢,大致的实现思路和代码如下:
思路一: API < 18,启动前台 Service 时直接传入 new Notification();
思路二: API >= 18,同时启动两个 id 相同的前台 Service,然后再将后启动的 Service 做 stop处理

熟悉 Android 系统的童鞋都知道,系统出于体验和性能上的考虑,app 在退到后台时系统并不会真正的 kill 掉这个进程,而是将其缓存起来。打开的应用越多,后台缓存的进程也越多。在系统内存不足的情况下,系统开始依据自身的一套进程回收机制来判断要 kill 掉哪些进程,以腾出内存来供给需要的 app。这套杀进程回收内存的机制就叫 Low Memory Killer ,它是基于 Linux 内核的 OOM Killer(Out-Of-Memory killer)机制诞生。

进程的重要性,划分 5 级:

前台进程 (Foreground process)
可见进程 (Visible process)
服务进程 (Service process)
后台进程 (Background process)
空进程 (Empty process)

了解完 Low Memory Killer,再科普一下 oom_adj。什么是 oom_adj?它是 linux 内核分配给每个系统进程的一个值,代表进程的优先级,进程回收机制就是根据这个优先级来决定是否进行回收。对于 oom_adj 的作用,你只需要记住以下几点即可:进程的 oom_adj 越大,表示此进程优先级越低,越容易被杀回收;越小,表示进程优先级越高,越不容易被杀回收

普通 app 进程的 oom_adj>=0,系统进程的 oom_adj 才可能<0

有些手机厂商把这些知名的 app 放入了自己的白名单中,保证了进程不死来提高用户体验(如微信、QQ、陌陌都在小米的白名单中)。如果从白名单中移除,他们终究还是和普通app 一样躲避不了被杀的命运,为了尽量避免被杀,还是老老实实去做好优化工作吧。所以,进程保活的根本方案终究还是回到了性能优化上,进程永生不死终究是个彻头彻尾的伪命题!

8 、讲解一下 Context

Context 是一个抽象基类。在翻译为上下文,也可以理解为环境,是提供一些程序的运行环境基础信息。Context 下有两个子类,ContextWrapper 是上下文功能的封装类,而 ContextImpl则是上下文功能的实现类。而ContextWrapper又有三个直接的子类,ContextThemeWrapper、Service 和 Application。其中,ContextThemeWrapper 是一个带主题的封装类,而它有一个直接子类就是 Activity,所以 Activity 和 Service 以及 Application 的 Context 是不一样的,只有Activity 需要主题,Service 不需要主题。Context 一共有三种类型,分别是 Application、Activity和 Service。这三个类虽然分别各种承担着不同的作用,但它们都属于 Context 的一种,而它们具体 Context 的功能则是由 ContextImpl 类去实现的,因此在绝大多数场景下,Activity、Service 和 Application 这三种类型的 Context 都是可以通用的。不过有几种场景比较特殊,比如启动 Activity,还有弹出 Dialog。出于安全原因的考虑,Android 是不允许 Activity 或 Dialog凭空出现的,一个 Activity 的启动必须要建立在另一个 Activity 的基础之上,也就是以此形成的返回栈。而 Dialog 则必须在一个 Activity 上面弹出(除非是 System alert 类型的 Dialog),因此在这种场景下,我们只能使用 Activity 类型的 Context,否则将会出错。

getApplicationContext()和 getApplication()方法得到的对象都是同一个 application 对象,只是对象的类型不一样。

Context 数量 = Activity 数量 + Service 数量 + 1 (1 为 Application)

9 、理解 Activity ,View,Window 三者关系

这个问题真的很不好回答。所以这里先来个算是比较恰当的比喻来形容下它们的关系吧。Activity 像一个工匠(控制单元),Window 像窗户(承载模型),View 像窗花(显示视图)LayoutInflater 像剪刀,Xml 配置像窗花图纸。

1:Activity 构造的时候会初始化一个 Window,准确的说是PhoneWindow。
2:这个 PhoneWindow 有一个“ViewRoot”,这个“ViewRoot”是一个 View 或者说 ViewGroup,是最初始的根视图。
3:“ViewRoot”通过 addView 方法来一个个的添加 View。比如 TextView,Button 等
4:这些 View 的事件监听,是由WindowManagerService 来接受消息,并且回调 Activity 函数。比如 onClickListener,onKeyDown 等。

10 、四种 LaunchMode 及其使用场景

此处延伸: 栈(First In Last Out)与队列(First In First Out)的区别

栈与队列的区别:
队列先进先出,栈先进后出对插入和删除操作的"限定"。 栈是限定只能在表的一端进行插入和删除操作的线性表。队列是限定只能在表的一端进行插入和在另一端进行删除操作的线性表。遍历数据速度不同
standard 模式

这是默认模式,每次激活 Activity 时都会创建 Activity 实例,并放入任务栈中。
使用场景: 大多数 Activity。

singleTop 模式

如 果 在 任 务 的 栈 顶 正 好 存 在 该 Activity 的 实 例 , 就 重 用 该 实 例 ( 会 调 用 实 例 的onNewIntent() ),否则就会创建新的实例并放入栈顶,即使栈中已经存在该 Activity 的实例,只要不在栈顶,都会创建新的实例。使用场景如新闻类或者阅读类 App 的内容页面。

singleTask 模式

如果在栈中已经有该 Activity 的实例,就重用该实例(会调用实例的 onNewIntent() )。重用时,会让该实例回到栈顶,因此在它上面的实例将会被移出栈。如果栈中不存在该实例,将会创建新的实例放入栈中。使用场景如浏览器的主界面。不管从多少个应用启动浏览器,只会启动主界面一次,其余情况都会走 onNewIntent,并且会清空主界面上面的其他页面。

singleInstance 模式

在一个新栈中创建该 Activity 的实例,并让多个应用共享该栈中的该 Activity 实例。一旦该模式的 Activity 实例已经存在于某个栈中,任何应用再激活该 Activity 时都会重用该栈中的实例( 会调用实例的 onNewIntent() )。其效果相当于多个应用共享一个应用,不管谁激活该Activity 都会进入同一个应用中。使用场景如闹铃提醒,将闹铃提醒与闹铃设置分离。singleInstance 不要用于中间页面,如果用于中间页面,跳转会有问题,比如:A -> B(singleInstance) -> C,完全退出后,在此启动,首先打开的是 B

11 、View 的绘制流程 自定义控件:

1、组合控件。这种自定义控件不需要我们自己绘制,而是使用原生控件组合成的新控件。如标题栏。
2、继承原有的控件。这种自定义控件在原生控件提供的方法外,可以自己添加一些方法。如制作圆角,圆形图片。
3、完全自定义控件:这个 View 上所展现的内容全部都是我们自己绘制出来的。比如说制作水波纹进度条。

View 的绘制流程: onMeasure()——>onLayout()——>onDraw()
第一步: onMeasure():测量视图大小。从顶层父 View 到子 View 递归调用 measure 方法,measure 方法又回调 OnMeasure。
第二步: onLayout():确定 View 位置,进行页面布局。从顶层父 View 向子 View 的递归调用view.layout 方法的过程,即父 View 根据上一步 measure 子 View 所得到的布局大小和布局参数,将子 View 放在合适的位置上。
第三步: onDraw():绘制视图。ViewRoot 创建一个 Canvas 对象,然后调用 onDraw()。六个
步骤:

①、绘制视图的背景;
②、保存画布的图层(Layer);
③、绘制 View 的内容;
④、绘制 View 子视图,如果没有就不用;
⑤、还原图层(Layer);
⑥、绘制滚动条。

12 、View ,ViewGroup 事件分发 Touch事件分发中只有两个主角:ViewGroup和View。ViewGroup包含onInterceptTouchEvent、dispatchTouchEvent、onTouchEvent三个相关事件。View包含dispatchTouchEvent、onTouchEvent两个相关事件。其中 ViewGroup 又继承于 View。

2.ViewGroup 和 View 组成了一个树状结构,根节点为 Activity 内部包含的一个 ViwGroup。

3.触摸事件由 Action_Down、Action_Move、Aciton_UP 组成,其中一次完整的触摸事件中,Down 和 Up 都只有一个,Move 有若干个,可以为 0 个。

4.当 Acitivty 接收到 Touch 事件时,将遍历子 View 进行 Down 事件的分发。ViewGroup 的遍历可以看成是递归的。分发的目的是为了找到真正要处理本次完整触摸事件的 View,这个View 会在 onTouchuEvent 结果返回 true。

5.当某个子View返回true时,会中止Down事件的分发,同时在ViewGroup中记录该子View。接下去的 Move 和 Up 事件将由该子 View 直接进行处理。由于子 View 是保存在 ViewGroup中的,多层 ViewGroup 的节点结构时,上级 ViewGroup 保存的会是真实处理事件的 View 所在的 ViewGroup 对象:如 ViewGroup0-ViewGroup1-TextView的结构中,TextView 返回了 true,它将被保存在 ViewGroup1 中,而 ViewGroup1 也会返回 true,被保存在 ViewGroup0 中。当Move 和 UP 事件来时,会先从 ViewGroup0 传递至 ViewGroup1,再由 ViewGroup1 传递至
TextView。

6.当 ViewGroup 中所有子 View 都不捕获 Down 事件时,将触发 ViewGroup 自身的 onTouch事件。触发的方式是调用 super.dispatchTouchEvent 函数,即父类 View 的 dispatchTouchEvent
方法。在所有子 View 都不处理的情况下,触发 Acitivity 的 onTouchEvent 方法。

7.onInterceptTouchEvent有两个作用:

1.拦截 Down 事件的分发。
2.中止 Up 和 Move 事件向目标 View 传递,使得目标 View 所在的 ViewGroup 捕获 Up 和 Move 事件。

13 、保存 Activity 状态

onSaveInstanceState(Bundle)会在 activity 转入后台状态之前被调用,也就是 onStop()方法之前,onPause 方法之后被调用;

14 、Android 中的几种动画

帧动画:指通过指定每一帧的图片和播放时间,有序的进行播放而形成动画效果,比如想听的律动条。

补间动画: 指通过指定 View 的初始状态、变化时间、方式,通过一系列的算法去进行图形变换,从而形成动画效果,主要有 Alpha、Scale、Translate、Rotate 四种效果。
注意: 只是在视图层实现了动画效果,并没有真正改变 View 的属性,比如滑动列表,改变标题栏的透明度。
属性动画: 在 Android3.0 的时候才支持,通过不断的改变 View 的属性,不断的重绘而形成动画效果。相比于视图动画,View 的属性是真正改变了。比如 view 的旋转,放大,缩小。

15 、Android 中跨进程通讯的几种方式

Android 跨进程通信,像 intent,contentProvider,广播,service 都可以跨进程通信。
intent:这种跨进程方式并不是访问内存的形式,它需要传递一个 uri,比如说打电话。
contentProvider:这种形式,是使用数据共享的形式进行数据共享。
service:远程服务,aidl,广播

16 、AIDL 理解

此处延伸: 简述 Binder
AIDL: 每一个进程都有自己的 Dalvik VM 实例,都有自己的一块独立的内存,都在自己的内存上存储自己的数据,执行着自己的操作,都在自己的那片狭小的空间里过完自己的一生。而 aidl 就类似与两个进程之间的桥梁,使得两个进程之间可以进行数据的传输,跨进程通信有多种选择,比如BroadcastReceiver , Messenger 等,但是 BroadcastReceiver 占用的系统资源比较多,如果是频繁的跨进程通信的话显然是不可取的;Messenger 进行跨进程通信时请求队列是同步进行的,无法并发执行。

Binde 机制简单理解:

在 Android 系统的 Binder 机制中,是有Client,Service,ServiceManager,Binder 驱动程序组成的,其中 Client,service,Service Manager 运行在用户空间,Binder 驱动程序是运行在内核空间的。而 Binder 就是把这 4 种组件粘合在一块的粘合剂,其中核心的组件就是 Binder 驱动程序,Service Manager 提供辅助管理的功能,而 Client 和 Service 正是在 Binder 驱动程序和Service Manager 提供的基础设施上实现 C/S 之间的通信。其中 Binder 驱动程序提供设备文件/dev/binder 与用户控件进行交互,Client、Service,Service Manager 通过 open 和 ioctl 文件操作相应的方法与 Binder 驱动程序进行通信。而Client和Service之间的进程间通信是通过Binder驱动程序间接实现的。而BinderManager 是一个守护进程,用来管理 Service,并向 Client 提供查询 Service 接口的能力。

17 、Handler 的原理

Android 中主线程是不能进行耗时操作的,子线程是不能进行更新 UI的。所以就有了 handler,它的作用就是实现线程之间的通信。

handler 整个流程中,主要有四个对象,handlerMessage,MessageQueue,Looper。当应用创建的时候,就会在主线程中创建 handler 对象,

我们通过要传送的消息保存到 Message 中,handler 通过调用 sendMessage 方法将 Message发送到 MessageQueue 中,Looper 对象就会不断的调用 loop()方法

不断的从 MessageQueue 中取出 Message 交给 handler 进行处理。从而实现线程之间的通信。

18 、Binder 机制原理

在 Android 系统的 Binder 机制中,是有Client,Service,ServiceManager,Binder 驱动程序组成的,其中 Client,service,Service Manager 运行在用户空间,Binder 驱动程序是运行在内核空间的。而 Binder 就是把这 4 种组件粘合在一块的粘合剂,其中核心的组件就是 Binder 驱动程序,Service Manager 提供辅助管理的功能,而 Client 和 Service 正是在 Binder 驱动程序和Service Manager 提供的基础设施上实现 C/S 之间的通信。其中 Binder 驱动程序提供设备文件/dev/binder 与用户控件进行交互,Client、Service,Service Manager 通过 open 和 ioctl 文件操作相应的方法与 Binder 驱动程序进行通信。而 Client 和 Service 之间的进程间通信是通过 Binder 驱动程序间接实现的。而 Binder Manager 是一个守护进程,用来管理 Service,并向 Client 提供查询 Service 接口的能力。

19 、热修复的原理

我们知道 Java 虚拟机 —— JVM 是加载类的 class 文件的,而 Android 虚拟机——Dalvik/ARTVM 是加载类的 dex 文件,

而他们加载类的时候都需要ClassLoader,ClassLoader 有一个子类 baseDexClassLoader,而baseDexClassLoader下有一个数组——DexPathList,是用来存放 dex 文件,当 baseDexClassLoader通过调用 findClass 方法时,实际上就是遍历数组,找到相应的 dex 文件,找到,则直接将它 return。

而热修复的解决方法就是将新的 dex 添加到该集合中,并且是在旧的 dex 的前面,所以就会优先被取出来并且 return 返回。

20 、Android 内存泄露及管理

(1)内存溢出(OOM)和内存泄露(对象无法被回收)的区别。
(2)引起内存泄露的原因
( 3 ) 内存泄露检测工具 ------>LeakCanary

内存溢出 out of memory: 是指程序在申请内存时,没有足够的内存空间供其使用,出现 out of memory;比如申请了一个 integer,但给它存了 long 才能存下的数,那就是内存溢出。内存溢出通俗的讲就是内存不够用。
内存泄露 memory leak: 是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光

内存泄露原因:

①Handler 引起的内存泄漏。
解决:将 Handler 声明为静态内部类,就不会持有外部类 SecondActivity 的引用,其生命周期就和外部类无关,如果 Handler 里面需要 context 的话,可以通过弱引用方式引用外部类
②单例模式引起的内存泄漏。
解决:Context 是 ApplicationContext,由于 ApplicationContext 的生命周期是和 app 一致的,不会导致内存泄漏
③非静态内部类创建静态实例引起的内存泄漏。
解决: 把内部类修改为静态的就可以避免内存泄漏了
④非静态匿名内部类引起的内存泄漏。
解决:将匿名内部类设置为静态的。
⑤注册/反注册未成对使用引起的内存泄漏。
注册广播接受器、EventBus 等,记得解绑。
⑥资源对象没有关闭引起的内存泄漏。
在这些资源不使用的时候,记得调用相应的类似 close()、destroy()、recycler()、release()等方法释放。
⑦集合对象没有及时清理引起的内存泄漏。
通常会把一些对象装入到集合中,当不使用的时候一定要记得及时清理集合,让相关对象不再被引用。

21 、Fragment 与 与 Fragment 、Activity 通信的方式

1.直接在一个 Fragment 中调用另外一个 Fragment 中的方法
2.使用接口回调
3.使用广播
4.Fragment 直接调用 Activity 中的 public 方法

22 、Android UI 适配

字体使用 sp,使用 dp,多使用 match_parent,wrap_content,weight
图片资源,不同图片的的分辨率,放在相应的文件夹下可使用百分比代替。

23 、app 优化

app 优化: (工具:Hierarchy Viewer 分析布局 工具:TraceView 测试分析耗时的)
App 启动优化.布局优化响应优化.内存优化.电池使用优化.网络优化.App 启动优化(针对冷启动)

App 启动的方式有三种:

冷启动: App 没有启动过或 App 进程被 killed, 系统中不存在该 App 进程, 此时启动 App 即为冷启动。
热启动: 热启动意味着你的 App 进程只是处于后台, 系统只是将其从后台带到前台, 展示给用户。

介于冷启动和热启动之间, 一般来说在以下两种情况下发生:

(1)用户 back 退出了 App, 然后又启动、App 进程可能还在运行, 但是 activity 需要重建。
(2)用户退出 App 后, 系统可能由于内存原因将 App 杀死, 进程和 activity 都需要重启, 但是可以在 onCreate 中将被动杀死锁保存的状态(saved instance state)恢复。

优化:

Application 的 onCreate(特别是第三方 SDK 初始化),首屏 Activity 的渲染都不要进行耗时操作,如果有,就可以放到子线程或者 IntentService 中

布局优化

尽量不要过于复杂的嵌套。可以使用,,

响应优化

Android 系统每隔 16ms 会发出 VSYNC 信号重绘我们的界面(Activity)。

页面卡顿的原因:

①过于复杂的布局.
②UI 线程的复杂运算
③频繁的 GC,导致频繁 GC 有两个原因:

内存抖动, 即大量的对象被创建又在短时间内马上被释放.瞬间产生大量的对象会严重占用内存区域。

内存优化: 参考内存泄露和内存溢出部分
电池使用优化(使用工具: Batterystats & bugreport)

(1)优化网络请求
(2)定位中使用 GPS, 请记得及时关闭

网络优化(网络连接对用户的影响:流量,电量,用户等待)可在Android studio下方logcat旁边那个工具 Network Monitor 检测
API 设计: App 与 Server 之间的 API 设计要考虑网络请求的频次, 资源的状态等、以便 App可以以较少的请求来完成业务需求和界面的展示.
Gzip 压缩: 使用 Gzip 来压缩 request 和 response, 减少传输数据量, 从而减少流量消耗.
**图片的 Size:**可以在获取图片时告知服务器需要的图片的宽高, 以便服务器给出合适的图片,避免浪费.
网络缓存: 适当的缓存, 既可以让我们的应用看起来更快, 也能避免一些不必要的流量消耗.

24 、图片优化

(1) 对 图 片 本 身 进 行 操 作 。 尽 量 不 要 使 用 setImageBitmap 、 setImageResource 、BitmapFactory.decodeResource 来设置一张大图,因为这些方法在完成 decode 后,最终都是通过 java 层的 createBitmap 来完成的,需要消耗更多内存.
(2)图片进行缩放的比例,SDK 中建议其值是 2 的指数值,值越大会导致图片不清晰。
(3)不用的图片记得调用图片的 recycle()方法

25 、HybridApp WebView 和 和 JS 交互

Android 与 JS 通过 WebView 互相调用方法,实际上是:

Android 去调用 JS 的代码 通过 WebView 的 loadUrl(),使用该方法比较简洁,方便。但是效率比较低,获取返回值比较困难。通过 WebView 的 evaluateJavascript(),该方法效率高,但是 4.4 以上的版本才支持,4.4 以下版本不支持。所以建议两者混合使用。 JS 去调用 Android 的代码 通过 WebView 的 addJavascriptInterface()进行对象映射 ,该方法使用简单,仅将 Android对象和 JS 对象映射即可,但是存在比较大的漏洞。
漏洞产生原因是: 当 JS 拿到 Android 这个对象后,就可以调用这个 Android 对象中所有的方法,包括系统类(java.lang.Runtime 类),从而进行任意代码执行。 解决方式:

(1)Google 在 Android 4.2 版本中规定对被调用的函数以@JavascriptInterface 进行注解从而避免漏洞攻击。
(2)在 Android 4.2 版本之前采用拦截 prompt()进行漏洞修复。

通过 WebViewClient 的 shouldOverrideUrlLoading ()方法回调拦截 url 。 这种方式的优点:

不存在方式 1 的漏洞;缺点: JS 获取 Android 方法的返回值复杂。(ios 主要用的是这个方式)

(1)Android 通过 WebViewClient 的回调方法 shouldOverrideUrlLoading ()拦截 url
(2)解析该 url 的协议
(3)如果检测到是预先约定好的协议,就调用相应方法

通过 WebChromeClient 的 onJsalert()、onJs/confirm/i()、onJsprompt()方法回调拦截 JS对话框 alert()、confirm()、prompt() 消息
这种方式的优点: 不存在方式 1 的漏洞;缺点:JS 获取 Android 方法的返回值复杂。 26 、JAVA GC 原理

垃圾收集算法的核心思想是:对虚拟机可用内存空间,即堆空间中的对象进行识别,如果对象正在被引用,那么称其为存活对象,反之,如果对象不再被引用,则为垃圾对象,可以回收其占据的空间,用于再分配。垃圾收集算法的选择和垃圾收集系统参数的合理调节直接影响着系统性能。

27 、ANR

ANR 全名 Application Not Responding, 也就是"应用无响应"、当操作在一段时间内系统无法处理时, 系统层面会弹出上图那样的 ANR 对话框.

产生原因:

(1)5s 内无法响应用户输入事件(例如键盘输入, 触摸屏幕等).
(2)BroadcastReceiver 在 10s 内无法结束
(3)Service 20s 内无法结束(低概率)

解决方式:

(1)不要在主线程中做耗时的操作,而应放在子线程中来实现。如 onCreate()和 onResume()里尽可能少的去做创建操作。
(2)应用程序应该避免在 BroadcastReceiver 里做耗时的操作或计算。
(3)避免在 Intent Receiver 里启动一个 Activity,因为它会创建一个新的画面,并从当前用户正在运行的程序上抢夺焦点。
(4)service 是运行在主线程的,所以在 service 中做耗时操作,必须要放在子线程中。

28 、设计模式

此处延伸: Double Check 的写法被要求写出来。
单例模式: 分为恶汉式和懒汉式
恶汉式:

最后

我见过很多技术leader在面试的时候,遇到处于迷茫期的大龄程序员,比面试官年龄都大。这些人有一些共同特征:可能工作了7、8年,还是每天重复给业务部门写代码,工作内容的重复性比较高,没有什么技术含量的工作。问到这些人的职业规划时,他们也没有太多想法。

其实30岁到40岁是一个人职业发展的黄金阶段,一定要在业务范围内的扩张,技术广度和深度提升上有自己的计划,才有助于在职业发展上有持续的发展路径,而不至于停滞不前。

不断奔跑,你就知道学习的意义所在!

以上进阶BATJ大厂学习资料可以免费分享给大家,需要完整版的朋友,【点这里可以看到全部内容】。

Copyright © 2016-2020 www.365daan.com All Rights Reserved. 365答案网 版权所有 备案号:

部分内容来自互联网,版权归原作者所有,如有冒犯请联系我们,我们将在三个工作时内妥善处理。