面试官:你做过什么有亮点的项目吗?

Posted Hhua.

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了面试官:你做过什么有亮点的项目吗?相关的知识,希望对你有一定的参考价值。

前言

面试中除了问常见的算法网络基础,和一些八股文手写体之外,经常出现的一个问题就是,你做过什么项目吗?
面试官其实是想看看你做过什么有亮点的项目, 其实大家日常做的项目都差不多,增删改查,登录注册,弹窗等等,所谓有亮点,就是在这些实现功能的基础上,在以下几个方面做出了探索和优化, 个人能力有限,先聊这几个方面。

  1. 大数据量优化
  2. 研发效率的提高
  3. 研发质量的提高
  4. 性能优化
  5. 用户体验优化
  6. 复杂 & 新场景

我们以大家都做过的需求举例,通过优化,每个需求都可以做成有亮点的需求,也就是所谓企业级的项目。

1、大数据量

想做出亮点,首先做一些你周围同事做不到的需求,首先就是数据量变大,变得贼大,虽然大部分场景遇不到,但是挡不住骚包面试官喜欢问,我们只聊面试。

1.1、课程页面,增删改查

这种场景,我们可以让数据量变成1W行,大部分场景都是分页,只有极端场景(移动端无限滚动的商品页),如果直接渲染1W行列表,不出意外你的页面就要卡了,比较常见的优化方案就是虚拟滚动,就是只渲染你能看到的视窗中的几十行,然后通过监听滚动来更新这几十个dom,大致原理图如下 (网上找的)。

解决方案出来后,无论是Vue还是React解决方案都是类似的,这里用React+Typescript举栗子,首先我们就得完成下面的任务,为了简化场景,先假定每个元素高度都一样。

  1. 可视区的高度固定 viewHeight (clientHeight);
  2. 每个列表高度height (固定);
  3. 可视区域的数据索引start和end (scrollTop / height);
  4. 基于startIndex计算出offset偏移(scrollTop - (scrollTop % height);
  5. 渲染数据 & 监听滚动事件

代码大致如下


  // 列表容器的dom
  const container = useRef<htmlDivElement>(null)
  // 开始位置
  const [start, setStart] = useState(0)
  // 视图中的数据
  const [visibleData, setVisibleData] = useState<VirtualProps['list']>([])
  // 控制偏移量
  const [viewTransfrom, setViewTransfrom] = useState('translate3d(0,0,0)')
  useEffect(() => 
    const containerDom = container.current
    const viewHeight = containerDom?.clientHeight || 500 // 视窗高度
    const visibleCount = Math.ceil(viewHeight / HEIGHT) // 视窗内有几个元素
    const end = start + visibleCount
    setVisibleData(list.slice(start, end))
  , [])
  function handleScroll(e: React.UIEvent<HTMLDivElement, UIEvent>) 
    const scrollTop = e.currentTarget.scrollTop // 滚动的距离
    const containerDom = container.current
    const viewHeight = containerDom?.clientHeight || 500 // 视窗高度

    const start = Math.floor(scrollTop / HEIGHT)
    const end = start + Math.ceil(viewHeight / HEIGHT)
    setVisibleData(list.slice(start, end))
    setStart(start)
    setViewTransfrom(`translate3d(0,$start * HEIGHTpx,0)`)
  

稍微难一丢丢,或者更骚包的面试官还会问,如果每行都是一段文字,不知道有多高呢?其实解决方案也不复杂,可以预估一个大致的高度,然后渲染的时候获取实际dom的高度 + 缓存到数组
下面是伪代码,由于位置数组是一个累加的数组,其实还可以用二分算法继续优化,给博友们留个作业吧。

// 预估高度60
const PREDICT_HEIGHT = 60
  // 不定高数组,维护一个位置数据
const [positions, setPosition] = useState< top: number;height: number []>([])

// 渲染数组之后,更新positions数组
Array.from(listDom?.children).forEach((node, index) => 
  const  height  = node.getBoundingClientRect()
  // console.log(start+index, node.id)
  if (height !== positions[start + index].height) 
    setPosition((prev) => 
      const newPos = [...prev]
      newPos[start + index].height = height
      for (let k = index + 1; k < prev.length; k++)
        newPos[k].top = newPos[k - 1].top + newPos[k - 1].height
      return newPos
    )
  
)
, [visibleData])

1.2、文件上传

其实就是数据量大了之后,想继续让用户有比较好的交互体验,就得不断地解决新问题
上传普通文件axios.post + 进度条就搞定了,如果想有亮点,可以把文件的体积想的大一些,比如2个G,直接上传容易断,我们需要断点续传,就诞生几个新的问题

  1. 文件切片 + 秒传 + 暂停
  2. 文件计算hash值,就像文件的身份证号,用来问后端有没有切片存在
  3. 计算hash的卡顿 可以使用web-worker,时间切片,抽样Hash三种解决方案
  4. 上传文件切片

2、研发效率的提高

程序员也是一种很贵的资源,能提高他们的研发效率,也算是给公司省钱了,当然是亮点了,但是每个人的开发能力不同,我们可以从团队协作多项目间复用两条路,来寻求研发效率的提高

团队效率

最常见的就是统一规范,js规范,git 分支规范,log规范,项目文件规范,并且用恰当的工具进行自动化校验和修正
然后是多项目间的复用率,也能极大地提高效率

  1. 代码初始化,可以封装成脚手架,类似create-vite, 可以内置上面说的各种规范,新项目直接启动
  2. 代码研发效率, 前端主要就是组件库和工具库utils封装
  3. 代码联调效率, 比如接口json自动生成Typescript接口类型等等,比如mock数据工具
  4. 代码上线效率,发布部署,部署结果同步到聊天群等等,把日常重复的行为自动化
    这部分也有大量的开源代码可以参考,比如React生态的AntDesign,Vue生态的Antd-vue和element-ui, vueuse, 通用工具库参考lodash等等,这里就不赘述了
    这也是大部分团队能有机会做开源的领域,由于需要多个项目之间的共用,所以对代码质量,版本管理,代码文档也会有更高的要求,无形中也提高了我们的段位和能力
    现在很火的rust生态,可以极大地提高前端编译的速度,其实也无形中提高了开发者写代码时候的心情和效率,比如webpack换成vite,babel换成swc,还有现在很火的rspack,都是努力让前端开发环境能有丝滑秒开的体验
    还有一些非代码层面的协作效率,比如敏捷看板,高效开会,代码review啥的,不在本篇文章讨论范围之内,先略过

3、研发质量的提高

质量的提高,也是程序员上限的提高
这一部分其实也是一个大话题,【重构】【整洁代码的艺术】【代码大全】等经典书籍数不胜数,不过在前端这个比较蛮荒的领域,能把自动化测试做好,就已经非常难得了
业务性的页面写测试成本过高,但是上面说的多项目之间共享的组件库工具库还是需要用测试来确保代码质量,学会jest或者vitest写测试,也是我们有机会参与热门开源项目的机会,代码测试覆盖率也是一个项目质量高低的重要指标,而且也是代码可维护性高的体现
你可以现在就尝试用vitest给你项目中写的工具函数 or组件库来点测试代码保驾护航把
除了代码层面的单元测试,还有流程层面的,比如code-review,

4、性能的提高

天下武功,唯快不破
如何让页面打开速度更快是一个永恒的话题,性能优化第一课首先你就得知道页面性能几个常见的指标,FCP,TTI,LCP,就像我们想提高游戏水平,就得了解攻击力防御力这些参数的含义

前端优化可以先从两个方向开始

4.1. 更快的加载文件

首先前端工程化中的打包压缩,就是减少了文件的体积和数量,并且通过很好的文件缓存管理,最大限度的利用浏览器的缓存,达到更快加载文件的目的
文件体积里,其实图片的格式选择和优化是大头,jpg, png, webp的选择,还有打包中图片的压缩,都可以获得比较可观的体积收益,静态资源还可以使用cdn继续提高加载文件的速度
还可以使用懒加载的思想,减少首屏加载的文件数量,也可以很好的提高文件加载的速度,图片/路由懒加载现在在前端开发领域都是必备特性了,快去手写一个lazy-load把
rollup带来的tree-shaking能力(跟互联网公司裁员还挺像,囧),可以去除项目中的无用代码,现在也成了前端工程化的标配,这也是为什么我们要尽量向esm规范靠拢的原因之一,而且静态分析还可以给我们带来一些额外的收益,比如vite中的预打包等等
这些我们都可以在工程化环节使用工具or插件的形式存在,所以学会定制webpack/vite的插件也成为前端架构师的必备能力之一, 快去学起来把

4.2. 代码执行的更快

这里也很好理解,执行的速度也是鉴定代码好坏的一个指标,比如同样一个leftpad函数(前面补齐字符),如果这么写

function leftpad(str,length,ch)
  let len = length-str.length+1
  return Array(len).join(ch)+str

console.log(leftpad('hello',10,'0'))

相比于我们用二分法+位运算的优化思路的写法

function leftpad2(str,length,ch)
  let len = length-str.length
  total = ''
  while(true)
    // if(len%2==1)
    if(len & 1)
      total+=ch
    
    if(len==1)
      return total+str
    
    ch += ch
    len = len >> 1
    // len = parseInt(len/2)
  

console.log(leftpad2('hello',10,'0'))
console.time('leftpad')
for(let i=0;i<10000;i++)
  leftpad('hello',1000,'0')

console.timeEnd('leftpad')

console.time('leftpad2')
for(let i=0;i<10000;i++)
  leftpad2('hello',1000,'0')

console.timeEnd('leftpad2')

❯ node leftpad.js
00000hello
00000hello
leftpad: 51.97ms
leftpad2: 2.077ms

数据量越大,性能差距就越大,数据量是1W的时候,性能有25倍的差距,当然也可以看出来算法和数据结构对前端的必要性,对不同的场景选择合适的算法or数据结构也是高级前端的必备能力
不同的框架内部也有不同的性能优化方式,减少组件不必要的rerender,减少浏览器的重绘回流,减少页面内部的dom操作等等经典的优化方式 就不赘述了
还有按需执行代码的思想,比如vue3种的静态标记,只有dom种的动态部分需要参与计算diff,静态的dom会直接略过, 还有astro,nuxt3这种ssr框架中的岛屿架构,就是只对页面中的动态组件进行js激活,都是按需思想的表达

5、复杂场景

一些天生复杂的场景,或者是前端新兴的领域或者热门的领域,面试官也比较喜欢,这个方向就比较多了,以后有机会展开详细说说

  1. 很火的低代码(搭建平台)
  2. 文档技术 (在线office,notion笔记)
  3. 图形学(figma,白板,canvas)
  4. 3D (可视化,游戏,webgl)
  5. app、桌面端(flutter、rn、elecrton)
  6. 更多未来的可能性…

6、不要陷入成长陷阱

写了这么多,我们要无限进步,但是不要陷入低水平的成长陷阱中,也就是我们要努力学会通用技能,而不是单纯的招式。
比如以前浏览器混战的时候,我有花了很多时间研究ie6/7/8的兼容性问题,并且感觉自己进步很大,但是浏览器兼容是一个特定混乱时期的问题,现在回头看看,那些兼容性的写法对于现在的我,毫无积累
我们要花更多的时间学习能够对我们有积累效果的技能,现在webpack和vite是工程化领域混乱发展的时期,过于关注api的使用,以后有一个工具统一这个领域后,你现在努力学习的工程化工具技能,跟当年的ie6兼容性一样,都被历史埋了。
可以试着学习webpack和vite内部的原理,学习他们内部优化的思想,怎么收集文件的依赖关系,怎么实现模块化,怎么实现loader和plugin机制扩展自身,怎么实现高效的热更新等等,这些才是能够帮助我们对未来有积累的技能。
少学api,多研究点问题和本质。

Android 面试官: “你做过那些性能优化?“

前言

如果你已经有 2 - 3 年以上开发经验还不懂的怎么去优化自己的项目,那就有点说不过去了,下面是我自己总结的一套通用级别的 Android 性能优化。

1、 你对 APP 的启动有过研究吗? 有做过相关的启动优化吗?

程序员:

之前做热修复的时候研究过 Application 的启动原理。项目中也做过一些启动优化。

面试官:

哦,你之前研究过热修复? (这个时候有可能就会深入的问问热修复的原理,这里咱们就不讨论热修复原理) 那你说说对启动方面都做了哪些优化?

程序员:

  1. 我发现程序在冷启动的时候,会有 1s 左右的白屏闪现,低版本是黑屏的现象,在这期间我通过翻阅系统主题源码,发现了系统 AppTheme 设置了一个 windowBackground ,由此推断就是这个属性捣的鬼,开始我是通过设置 windowIsTranslucent 透明属性,发现虽然没有了白屏,但是中间还是有一小段不可见,这个用户体验还是不好的。最后我观察了市面上大部分的 Android 软件在冷启动的时候都会有一个 Splash 的广告页,同时在增加一个倒数的计时器,最后才进入到登录页面或者主页面。我最后也是这样做的,原因是这样做的好处可以让用户先基于广告对本 APP 有一个基本认识,而且在倒数的时候也预留给咱们一些对插件和一些必须或者耗时的初始化做一些准备。

    Ps:这里会让面试官感觉你是一个注重用户体验的

  2. 通过翻阅 Application 启动的源码,当我们点击桌面图标进入我们软件应用的时候,会由 AMS 通过 Socket 给 Zygote 发送一个 fork 子进程的消息,当 Zygote fork 子进程完成之后会通过反射启动 ActivityThread##main 函数,最后又由 AMS 通过 aidl 告诉 ActivityThread##H 来反射启动创建Application 实例,并且依次执行 attachBaseContextonCreate 生命周期,由此可见我们不能在这 2 个生命周期里做主线程耗时操作。

    Ps: 这里会让面试官感觉你对 App 应用的启动流程研究的比较深,有过真实的翻阅底层源码,而并不是背诵答案。

  3. 知道了 attachBaseContextonCreate 在应用中最先启动,那么我们就可以通过 TreceView 等性能检测工具,来检测具体函数耗时时间,然后来对其做具体的优化。

    • 项目不及时需要的代码通过异步加载。

    • 将对一些使用率不高的初始化,做懒加载。

    • 将对一些耗时任务通过开启一个 IntentService来处理。

    • 还通过 redex 重排列 class 文件,将启动阶段需要用到的文件在 APK 文件中排布在一起,尽可能的利用 Linux 文件系统的 pagecache 机制,用最少的磁盘 IO 次数,读取尽可能多的启动阶段需要的文件,减少 IO 开销,从而达到提升启动性能的目的。

    • 通过抖音发布的文章知晓在 5.0 低版本可以做 MultiDex 优化,在第一次启动的时候,直接加载没有经过 OPT 优化的原始 DEX,先使得 APP 能够正常启动。然后在后台启动一个单独进程,慢慢地做完 DEX 的 OPT 工作,尽可能避免影响到前台 APP 的正常使用。

Ps:

  1. 面试官这里会觉得你对启动优化确实了解的不错,有一定的启动优化经验。
  2. 在第五点面试官会觉得你比较关注该圈子的动态,发现好的解决方案,并能用在自己项目上。这一点是加分项!
  1. Application 启动完之后,AMS 会找出前台栈顶待启动的 Activity , 最后也是通过 AIDL 通知 ActivityThread#H 来进行对 Activity 的实例化并依次执行生命周期 onCreateonStartonRemuse 函数,那么这里由于 onCreate 生命周期中如果调用了 setContentView 函数,底层就会通过将 XML2View 那么这个过程肯定是耗时的。所以要精简 XML 布局代码,尽可能的使用 ViewStubincludemerge 标签来优化布局。接着在 onResume 声明周期中会请求 JNI 接收 Vsync (垂直同步刷新的信号) 请求,16ms 之后如果接收到了刷新的消息,那么就会对 DecorView 进行 onMeasure->onLayout->onDraw 绘制。最后才是将 Activity 的根布局 DecorView 添加到 Window 并交于 SurfaceFlinger 显示。

    所以这一步除了要精简 XML 布局,还有对自定义 View 的测量,布局,绘制等函数不能有耗时和导致 GC 的操作。最后也可以通过 TreaceView 工具来检测这三个声明周期耗时时间,从而进一步优化,达到极限。

这一步给面试官的感觉你对整个 Activity 的启动和 View 的绘制还有刷新机制都有深入的研究,那么此刻你肯定给面试官留了一个好印象,说明你平时对这些源码级别的研究比较广泛,透彻。

总结:

最后我基于以上的优化减少了 50% 启动时间。

面试官:

嗯,研究的挺深的,源码平时不少看吧。

程序员:

到这里,我知道这一关算是过了!

2、有做过相关的内存优化吗?

程序员:

有做过,目前的项目内存优化还是挺多的,要不我先说一下优化内存有什么好处吧?咱们不能盲目的去优化!

有的时候对于自己熟悉的领域,一定要主动出击,自己主导这场面试。

面试官:

可以。

Ps:这里大多数面试官会同意你的请求,除非遇见装B的。

程序员:

好处:

  1. 减少 OOM ,可以提高程序的稳定性。

  2. 减少卡顿,提高应用流畅性。

  3. 减少内存占用,提高应用后台存活性。

  4. 减少程序异常,降低应用 Crash 率, 提高稳定性。

那么我基于这四点,我的程序做了如下优化:

  • 1.减少 OOM

    在应用开发阶段我比较喜欢用 LeakCanary 这款性能检测工具,好处是它能实时的告诉我具体哪个类发现了内存泄漏(如果你对 LeakCanary 的原理了解的话,可以说一说它是怎么检测的)。

    还有我们要明白为什么应用程序会发送 OOM ,又该怎么去避免它?

    发生 OOM 的场景是当申请 1M 的内存空间时,如果你要往该内存空间存入 2M 的数据,那么此时就会发生 OOM。

    在应用程序中我们不仅要避免直接导致 OOM 的场景还要避免间接导致 OOM 的场景。间接的话也就是要避免内存泄漏的场景。

    内存泄漏的场景是这个对象不再使用时,应用完整的执行最后的生命周期,但是由于某些原因,对象虽然已经不再使用,仍然会在内存中存在而导致 GC 不会去回收它,这就意味着发生了内存泄漏。(这里可以介绍下 GC 回收机制,回收算法,知识点尽量往外扩展而不脱离本题)

    最后在说一下在实际开发中避免内存泄漏的场景:

    1. 资源型对象未关闭: Cursor,File

    2. 注册对象未销毁: 广播,回调监听

    3. 类的静态变量持有大数据对象

    4. 非静态内部类的静态实例

    5. Handler 临时性内存泄漏: 使用静态 + 弱引用,退出即销毁

    6. 容器中的对象没清理造成的内存泄漏

    7. WebView: 使用单独进程

    其实这些都是基础,把它记下就行了。记得多了在实际开发中就有印象了。

  • 2.减少卡顿

    怎么减少卡顿? 那么我们可以从 2 个原理方面来探讨卡顿的根本原因,第一个原理方面是绘制原理,另一个就是刷新原理。

  1. 绘制原理:

  1. 刷新原理:

View 的 requestLayout 和 ViewRootImpl##setView 最终都会调用 ViewRootImpl 的 requestLayout 方法,然后通过 scheduleTraversals 方法向 Choreographer 提交一个绘制任务,然后再通过 DisplayEventReceiver 向底层请求 vsync 垂直同步信号,当 vsync 信号来的时候,会通过 JNI 回调回来,在通过 Handler 往消息队列 post 一个异步任务,最终是 ViewRootImpl 去执行绘制任务,最后调用 performTraversals 方法,完成绘制。

详细流程可以参考下面流程图:

卡顿的根本原因:

  • 从刷新原理来看卡顿的根本原理是有两个地方会造成掉帧:

一个是主线程有其它耗时操作,导致doFrame 没有机会在 vsync 信号发出之后 16 毫秒内调用;

还有一个就是当前doFrame方法耗时,绘制太久,下一个 vsync 信号来的时候这一帧还没画完,造成掉帧。

既然我们知道了卡顿的根本原因,那么我们就可以监控卡顿,从而可以对卡顿优化做到极致。我们可以从下面四个方面来监控应用程序卡顿:

  1. 基于 Looper 的 Printer 分发消息的时间差值来判断是否卡顿。
        //1\\. 开启监听
          Looper.myLooper().setMessageLogging(new
                            LogPrinter(Log.DEBUG, "ActivityThread"));

        //2\\. 只要分发消息那么就会在之前和之后分别打印消息
        public static void loop() 
           final Looper me = myLooper();
           if (me == null) 
               throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
                
            final MessageQueue queue = me.mQueue;
        		...

            for (;;) 
            	Message msg = queue.next(); // might block
        		...
              //分发之前打印
              final Printer logging = me.mLogging;
             if (logging != null) 
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                                msg.callback + ": " + msg.what);
              

        			...
              try 
               //分发消息
               msg.target.dispatchMessage(msg);
        			...
              //分发之后打印
        			if (logging != null) 
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
                    
                
            
  1. 基于 Choreographer 回调函数 postFrameCallback 来监控

  1. 基于开源框架 BlockCanary 来监控

  2. 基于开源框架 rabbit-client 来监控

怎么避免卡顿:

一定要避免在主线程中做耗时任务,总结一下 Android 中主线程的场景:

  1. UI 生命周期的控制
  2. 系统事件的处理
  3. 消息处理
  4. 界面布局
  5. 界面绘制
  6. 界面刷新

还有一个最重要的就是避免内存抖动,不要在短时间内频繁的内存分配和释放。

基于这几点去说卡顿肯定是没有问题的。

  • 3.减少内存占用

    可以从如下几个方面去展开说明:

    1. AutoBoxing(自动装箱): 能用小的坚决不用大的。

    2. 内存复用

    3. 使用最优的数据类型

    4. 枚举类型: 使用注解枚举限制替换 Enum

    5. 图片内存优化(这里可以从 Glide 等开源框架去说下它们是怎么设计的)

      • 选择合适的位图格式

      • bitmap 内存复用,压缩

      • 图片的多级缓存

    6. 基本数据类型如果不用修改的建议全部写成 static final,因为 它不需要进行初始化工作,直接打包到 dex 就可以直接使用,并不会在 类 中进行申请内存

    7. 字符串拼接别用 +=,使用 StringBuffer 或 StringBuilder

    8. 不要在 onMeause, onLayout, onDraw 中去刷新 UI

    9. 尽量使用 C++ 代码转换 YUV 格式,别用 Java 代码转换 RGB 等格式,真的很占用内存

  • 4.减少程序异常

    减少程序异常那么我们可以从稳定性和 Crash 来分别说明。

    这个我们将在第四点会详细的介绍程序的稳定性和 Crash 。

如果说出这些,再实际开发中举例说明一下怎么解决的应该是没有问题的。

3、你在项目中有没有遇见卡顿问题?是怎么排查卡顿?又是怎么优化的?

程序员:

有遇见, 比如在主线程中做耗时操作、频繁的创建对象和销毁对象导致 GC 回收频繁、布局的层级多等。

面试官:

嗯,那具体说说是怎么优化的。

程序员:

这里我们还是可以从显示原理和优化建议来展开说明,参考如下:

  1. 显示原理:
  • 绘制原理:

  • 刷新原理:
    View 的 requestLayout 和 ViewRootImpl##setView 最终都会调用 ViewRootImpl 的 requestLayout 方法,然后通过 scheduleTraversals 方法向 Choreographer 提交一个绘制任务,然后再通过 DisplayEventReceiver 向底层请求 vsync 垂直同步信号,当 vsync 信号来的时候,会通过 JNI 回调回来,在通过 Handler 往消息队列 post 一个异步任务,最终是 ViewRootImpl 去执行绘制任务,最后调用 performTraversals 方法,完成绘制。

    详细流程可以参考下面流程图:

  1. 卡顿的根本原因:
    从刷新原理来看卡顿的根本原理是有两个地方会造成掉帧:

    一个是主线程有其它耗时操作,导致doFrame 没有机会在 vsync 信号发出之后 16 毫秒内调用;

    还有一个就是当前 doFrame 方法耗时,绘制太久,下一个 vsync 信号来的时候这一帧还没画完,造成掉帧。

    既然我们知道了卡顿的根本原因,那么我们就可以监控卡顿,从而可以对卡顿优化做到极致。我们可以从下面四个方面来监控应用程序卡顿:

    • 基于 Looper 的 Printer 分发消息的时间差值来判断是否卡顿。
       //1\\. 开启监听
         Looper.myLooper().setMessageLogging(new
                           LogPrinter(Log.DEBUG, "ActivityThread"));

       //2\\. 只要分发消息那么就会在之前和之后分别打印消息
       public static void loop() 
          final Looper me = myLooper();
          if (me == null) 
              throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
               
           final MessageQueue queue = me.mQueue;
       		...

           for (;;) 
           	Message msg = queue.next(); // might block
       		...
             //分发之前打印
             final Printer logging = me.mLogging;
            if (logging != null) 
               logging.println(">>>>> Dispatching to " + msg.target + " " +
                               msg.callback + ": " + msg.what);
             

       			...
             try 
              //分发消息
              msg.target.dispatchMessage(msg);
       			...
             //分发之后打印
       			if (logging != null) 
               logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
                   
               
           
  • 基于 Choreographer 回调函数 postFrameCallback 来监控

  • 基于开源框架 BlockCanary 来监控

  • 基于开源框架 rabbit-client 来监控

  1. 怎么可以提高程序运行流畅
    1.布局优化:
    1.1 布局优化分析工具:

    1.2 优化方案:

  2. 提升动画性能

    • 尽量别用补间动画,改为属性动画,因为通过性能监控发现补间动画重绘非常频繁

    • 使用硬件加速提高渲染速度,实现平滑的动画效果。

  3. 怎么避免卡顿:

    一定要避免在主线程中做耗时任务,总结一下 Android 中主线程的场景:

    • UI 生命周期的控制

    • 系统事件的处理

    • 消息处理

    • 界面布局

    • 界面绘制

    • 界面刷新

基于这几点去说卡顿肯定是没有问题的。

4、怎么保证 APP 的稳定运行?

程序员:

保证程序的稳定我们可以从内存、代码质量、Crash、ANR、后台存活等知识点来展开优化。

面试官:

那你具体说说你是怎么做的?

程序员:

1.内存

可以从第二点内存优化来说明

2.代码质量

  1. 团队之前相互代码审查,保证了代码的质量,也可以学习到了其它同事码代码的思想。

  2. 使用 Link 扫描代码,查看是否有缺陷性。

3. Crash

  1. 通过实现 Thread.UncaughtExceptionHandler 接口来全局监控异常状态,发生 Crash 及时上传日志给后台,并且及时通过插件包修复。

  2. Native 线上通过 Bugly 框架实时监控程序异常状况,线下局域网使用 Google 开源的 breakpad 框架。发生异常就搜集日志上传服务器(这里要注意的是日志上传的性能问题,后面省电模块会说明)

4. ANR

5. 后台存活

面试官:

嗯,你对知识点掌握的挺好。

说完这些,这一关也算是过了。

5、说说你在项目中网络优化?

程序员:

有,这一点其实可以通过 OKHTTP 连接池和 Http 缓存来说一下(当然这里不会再展开分析 OKHTTP 源码了)

面试官:

那你具体说一下吧

程序员

说了这些之后,再说一下你当前使用网络框架它们做了哪些优化比如 OKHTTP(Socket 连接池、Http缓存、责任链)、Retrofit(动态代理)。说了这些一般这关也算是过了。

6、你在项目中有用过哪些存储方式? 对它们的性能有过优化吗?

程序员:

主要用过 sp,File,SQLite 存储方式。其中对 sp 和 sqlite 做了优化。

面试官:

那你说说都做了哪些优化?

程序员:


这一块如果你使用过其它第三方的数据库,可以说说它们的原理和它们存取的方式。

7、你在项目中有做过自定义 View 吗?有对它做过什么优化?

程序员:

有做过。比如重复绘制,还有大图长图有过优化。

面试官:

那具体说一说

程序员:

最后也是结合真实场景具体说一个。

8、你们项目的耗电量怎么样? 有做过优化吗?

程序员:

在没有优化之前持续工作 30 分钟的耗电量是 8%, 优化后是 4%。

面试官:

那你说一说你是怎么优化的。

程序员:

因为我们产品是一款社交通信的软件,有音视频通话、GPS 定位上报、长连接的场景,所以优化起来确实有点困难。不过最后也还是优化了一半的电量下去。主要做了如下优化:

说出这些之后,在结合项目一个真实的优化点来说明一下。

9、有做过日志优化吗?

程序员:

有优化,在之前没有考虑任何性能的情况下,我是直接有 log 就写入文件,尽管我开了线程池去写文件,只要软件在运行那么就会频繁的使 CPU 进行工作。这也间接的导致了耗电。
面试官:

那你具体说一下,最后怎么解决这个问题的?

程序员:

展开上面这些点说明之后,面试官一般不会为难你。

10、你们 APK 有多大?有做过 APK 体积相关的优化吗?

程序员:

有过优化,在没有优化之前项目的包体积大小是 80M,优化之后是 50M.

面试官:

说一说是怎么优化的

程序员:

基于这几点优化方案,一般都能解决 APK 体积问题。最后再把自己项目 APK 体积优化步骤结合上面点说一下就行。

总结

其实性能优化点都是息息相关的,比如卡顿会涉及内存、显示,启动也会涉及 APK dex 的影响。所以说性能优化不仅仅是单方面的优化,一定要掌握最基本的优化方案,才能更加深入探讨性能原理问题。

在这里也建立大家多看流行开源框架源码,比如 Glide (内存方面), OKhttp (网络连接方面) 优化的真的很极致。到这里性能优化方面的知识也就说完了,下来一定好好去消化。下面有小编自己整理的一些性能优化学习文档和Android学习文档等。

如需要参考完整版请去我GitHub进行访问查阅

GitHub地址: 
https://github.com/733gh/Android-T3

Android性能优化学习路线

如需要参考高清版请去我GitHub进行访问查阅

GitHub地址: 
https://github.com/733gh/Android-T3

以上是关于面试官:你做过什么有亮点的项目吗?的主要内容,如果未能解决你的问题,请参考以下文章

面试官问:你做过什么Java线程池实践,我写了一篇博客给他看~

面试官问:你做过什么Java线程池实践,我写了一篇博客给他看~

什么是项目亮点

「面试题」介绍你做过最复杂的系统

阿里一面:你做过哪些代码优化?来一个人人可以用的极品案例

你问我答,准备面试需要做哪些技术储备,面试官更加关心什么方面的技术点?