Android高级进阶知识Android架构师

记一次今日头条Android资深工程师面试;谈下offer是60

2020-02-25  本文已影响0人  西柚9102

最近面试了一下今日头条的移动Android资深工程师,拿到了offer,不过暂时没考虑好跳槽的事,先记录下。 今日头条的面试主要分为三轮,首先是基础面试,基本面试10个题左右(我记得其中8个)

第一面是北京的开发进行视频面试,有理论和编程题组成。用的是在线编程工具,如下图;

第一面

1,请编程实现单例模式,懒汉和饱汉写法

//饱汉写法
public static Singleton getInstance() {  
        if (singleton == null) {    
            synchronized (Singleton.class) {    
               if (singleton == null) {    
                  singleton = new Singleton();   
               }    
            }    
        }    
        return singleton;   
    }

   //懒汉写法
    private static final Singleton1 single = new Singleton1();  
    //静态工厂方法   
    public static Singleton1 getInstance() {  
        return single;  
    } 

2,请编程实现Java的生产者-消费者模型

看到这个有点懵逼,要是大学毕业的时候写这个肯定没问题,这都工作多年,这也只能按照自己的思路写了。我使用synchronized锁以及wait notify实现一个比较简单的。这里就不作展示了

3,HashMap的内部结构? 内部原理?

关于HashMap的问题,不再详述,这方面的资料也挺多,不过需要注意的是Java1.7和1.8版本HashMap内部结构的区别。

4,请简述Android事件传递机制, ACTION_CANCEL事件何时触发?

第一个问题不做解释。。

关于ACTION_CANCEL何时被触发,系统文档有这么一种使用场景:

在设计设置页面的滑动开关时,如果不监听ACTION_CANCEL,在滑动到中间时,如果你手指上下移动,就是移动到开关控件之外,则此时会触发ACTION_CANCEL,而不是ACTION_UP,造成开关的按钮停顿在中间位置。
意思是当滑动的时候就会触发,不知道大家搞没搞过微信的长按录音,有一种状态是“松开手指,取消发送”,这时候就会触发ACTION_CANCEL。>

5,Android的进程间通信,Liunx操作系统的进程间通信。
关于这个问题也是被问的很多,不多啰嗦了解释了。

6,JVM虚拟机内存结构,以及它们的作用。
这个问题也比较基础,JVM的内存结构如下图所示。

可以通过下面的问题来学习: https://www.cnblogs.com/jiyukai/p/6665199.html https://www.zhihu.com/question/65336620

7,简述Android的View绘制流程,Android的wrap_content是如何计算的。

8,有一个整形数组,包含正数和负数,然后要求把数组内的所有负数移至正数的左边,且保证相对位置不变,要求时间复杂度为O(n), 空间复杂度为O(1)。例如,{10, -2, 5, 8, -4, 2, -3, 7, 12, -88, -23, 35}变化后是{-2, -4,-3, -88, -23,5, 8 ,10, 2, 7, 12, 35}。

要实现上面的效果有两种方式:

第一种:两个变量,一个用来记录当前的遍历点,一个用来记录最左边的负数在数组中的索引值。然后遍历整个数组,遇到负数将其与负数后面的数进行交换,遍历结束,即可实现负数在左,正数在右。

第二种:两个变量记录左右节点,两边分别开始遍历。左边的节点遇到负值继续前进,遇到正值停止。右边的节点正好相反。然后将左右节点的只进行交换,然后再开始遍历直至左右节点相遇。这种方式的时间复杂度是O(n).空间复杂度为O(1)

//方法1
 public void setParted(int[] a){  
        int temp=0;  
        int border=-1;  

        for(int i=0;i<a.length;i++){  
            if(a[i]<0){  
                temp=a[i];  
                a[i]=a[border+1];  
                a[border+1]=temp;  
                border++;  
            }  
        }  
        for(int j=0;j<a.length;j++){  
            System.out.println(a[j]);  
        }  
    }  

//方法2
public void setParted1(int[] a,int left,int right){  
        if(left>=right||left==a.length||right==0){  
            for(int i=0;i<a.length;i++){  
                System.out.println(a[i]);  
            }  
            return ;  
        }  
        while(a[left]<0){  
            left++;  
        }  
        while(a[right]>=0){  
            right--;  
        }  
        if(left>=right||left==a.length||right==0){  
            for(int i=0;i<a.length;i++){  
                System.out.println(a[i]);  
            }  
            return;  
        }  
        swap(a,left,right);  
        left++;  
        right--;  
        setParted1(a,left,right);  
    }  
     private void swap(int a[],int left,int right){  
        int temp=0;  
        temp=a[left];  
        a[left]=a[right];  
        a[right]=temp;  
    }  
    public static void main(String[] args) {  
        int a[]={1,2,-1,-5,-6,7,-7,-10};  
        new PartTest().setParted1(a,0,a.length-1);   
    }  

显然,第二种实现的难点比较高,不过只要此种满足条件。

第二面

1,bundle的数据结构,如何存储,既然有了Intent.putExtra,为啥还要用bundle。

bundle的内部结构其实是Map,传递的数据可以是boolean、byte、int、long、float、double、string等基本类型或它们对应的数组,也可以是对象或对象数组。当Bundle传递的是对象或对象数组时,必须实现Serializable 或Parcelable接口。

2,android的IPC通信方式,是否使用过

这方面的资料比较多,也不方便阐述

3,Android的多点触控如何传递 核心类

4,asynctask的原理 AsyncTask是对Thread和Handler的组合包装。

5,android 图片加载框架有哪些,对比下区别

6,图片框架的一些原理知识

7,其他的一些Android的模块化开发,热更新,组件化等知识。

我也针对一些面试常见的问题以及面试过程中面试官针对不同层次回答的想法整理了Android面试题集,希望能让大家了解到答题过程中对面的人是怎么想的,做到知己知彼,也好有的放矢的去突出自己的亮点为面试结果加分。


还有包括腾讯,以及字节跳动,华为,小米,等一线互联网公司主流架构技术教程。是19年最新的技术,有需要这方面学习提升的朋友可以免费分享,再放一两年可能就旧了。

完整Android进阶资料笔记整理在石墨文档可见;《Android架构视频+BAT面试专题PDF+学习笔记》

三面;主流框架

在Android面试的时候,经常会被问到一些Android开发中用到的一些开发框架,如常见的网络请求框架Retrofit/OkHttp,组件通信框架EventBus/Dagger2,异步编程RxJava/RxAndroid等。结合本次面试,整理下上面的几个框架,大家可以拿去,面试备用

1. EventBus

EventBus是一个Android发布/订阅事件总线,简化了组件间的通信,让代码更加简介,但是如果滥用EventBus,也会让代码变得更加辅助。

面试EventBus的时候一般会谈到如下几点

(1)EventBus是通过注解+反射来进行方法的获取的

(2)使用ConcurrentHashMap来保存映射关系 调用实体的构建:调用实体中对于Object,也就是实际执行方法的对象不应该使用强引用而是应该使用弱引用,因为Map的static的,生命周期有可能长于被调用的对象,如果使用强引用就会出现内存泄漏的问题。

(3)方法的执行 使用Dispatcher进行方法的分派,异步则使用线程池来处理,同步就直接执行,而UI线程则使用MainLooper创建一个Handler,投递到主线程中去执行。

2.Retrofit

要明确EventBus中最核心的,就是动态代理技术。

Java中的动态代理:

首先动态代理是区别于静态代理的,代理模式中需要代理类和实际执行类同时实现一个相同的接口,并且在每个接口定义的方法前后都要加入相同的代码,这样有可能很多方法代理类都需要重复。而动态代理就是将这个步骤放入运行时的过程,一个代理类只需要实现InvocationHandler接口中的invoke方法,当需要动态代理时只需要根据接口和一个实现了InvocationHandler的代理对象A生成一个最终的自动生成的代理对象A。这样最终的代理对象A无论调用什么方法,都会执行InvocationHandler的代理对象A的invoke函数,你就可以在这个invoke函数中实现真正的代理逻辑。

Retrofit中的动态代理

Retrofit中使用了动态代理是不错,但是并不是为了真正的代理才使用的,它只是为了动态代理一个非常重要的功能,就是“拦截”功能。我们知道动态代理中自动生成的A对象的所有方法执行都会调用实际代理类A中的invoke方法,再由我们在invoke中实现真正代理的逻辑,实际上也就是A的所有方法都被A对象给拦截了。 而Retrofit的功能就是将代理变成像方法调用那么简单。

Retrofit作用

Retrofit实际上是为了更方便的使用Okhttp,因为Okhttp的使用就是构建一个Call,而构建Call的大部分过程都是相似的,而Retrofit正是利用了代理机制带我们动态的创建Call,而Call的创建信息就来自于你的注解。

3.OkHttp

请求任务队列

Okhttp使用了一个线程池来进行异步网络任务的真正执行,而对于任务的管理采用了任务队列的模型来对任务执行进行相应的管理,有点类似服务器的反向代理模型。Okhttp使用分发器Dispatcher来维护一个正在运行任务队列和一个等待队列。如果当前并发任务数量小于64,就放入执行队列中并且放入线程池中执行。而如果当前并发数量大于64就放入等待队列中,在每次有任务执行完成之后就在finally块中调用分发器的finish函数,在等待队列中查看是否有空余任务,如果有就进行入队执行。Okhttp就是使用任务队列的模型来进行任务的执行和调度的。

复用连接池

Http使用的TCP连接有长连接和短连接之分,对于访问某个服务器的频繁通信,使用短连接势必会造成在建立连接上大量的时间消耗;而长连接的长时间无用保持又会造成资源你的浪费。Okhttp底层是采用Socket建立流连接,而连接如果不手动close掉,就会造成内存泄漏,那我们使用Okhttp时也没有做close操作,其实是Okhttp自己来进行连接池的维护的。在Okhttp中,它使用类似引用计数的方式来进行连接的管理,这里的计数对象是StreamAllocation,它被反复执行aquire与release操作,这两个函数其实是在改变Connection中的List<WeakReference<StreamAllocation>>大小。List中Allocation的数量也就是物理socket被引用的计数(Refference Count),如果计数为0的话,说明此连接没有被使用,是空闲的,需要通过淘汰算法实现回收。

4. RxJava

从15年开始,前端掀起了一股异步编程的热潮,在移动Android编程过程中,经常会听到观察者与被观察者等概念。

观察者与被观察者通信

Observable的通过create函数创建一个观察者对象。

public final static <T> Observable<T> create(OnSubscribe<T> f) {
  return new Observable<T>(hook.onCreate(f));
}

Observable的构造函数如下:

protected Observable(OnSubscribe<T> f) {
  this.onSubscribe = f;
}

创建了一个Observable我们记为Observable1,保存了传入的OnSubscribe对象为onSubscribe,这个很重要,后面会说到。

onSubscribe方法

public final Subscription subscribe(Subscriber<? super T> subscriber) {
  return Observable.subscribe(subscriber, this);
}
private static <T> Subscription subscribe(Subscriber<? super T> subscriber, Observable<T> observable) {
  ...
  subscriber.onStart();
  onSubscribe.call(subscriber);
  return hook.onSubscribeReturn(subscriber);
}

线程切换过程(Scheduler)

RxJava最好用的特点就是提供了方便的线程切换,但它的原理归根结底还是lift,使用subscribeOn()的原理就是创建一个新的Observable,把它的call过程开始的执行投递到需要的线程中;而 observeOn() 则是把线程切换的逻辑放在自己创建的Subscriber中来执行。把对于最终的Subscriber1的执行过程投递到需要的线程中来进行。

后话

我有时候经常会思考,以后Android开发该往什么方向走?现在我们所掌握的技术以后会不会被替代淘汰?

哪些方向会成为绝对主流方向呢?
像如今前景比较好的音视频来说,并不是用Android去开发,而属于NDK开发,用的C++语言。
Flutter跨平台开发现在只是一种选择,面试加分项,实际用的公司不多。
人工智能方向,门槛对于大多Android开发来说很高,没有相关专业的名校硕士学历很难入场,对数据结构算法等要求很高。

未来有点难以掌控的感觉。但是还好,可以掌控现在。技术无止境,不能停止学习。如果不能引领潮流,那就做一个跟随者吧!

上一篇下一篇

猜你喜欢

热点阅读