Android面试主题整理合集(三)

第三篇可分为:设计模式,算法,其他。

设计模式


1.装饰设计模式

  • 当不适合采用生成子类的方式对已有类进行扩充时,采用装饰设计模式可以扩展一个对象的功能,可以使一个对象变得越来越强大
  • 不适合采用生成子类的方式对已有类进行扩充原因:会使类更加臃肿。子类会继承父类所有非private的变量和方法,然后再进行扩充。而使用装饰设计模式扩充的类,只需要增加扩充那部分功能即可
  • 使用场景:RecyclerView本身是不支持添加底部和头部的,那么采用装饰设计模式可以对其进行功能扩展。装饰设计模式 RecyclerView添加头部和底部

2.MVC、MCP、MVVP 的区别

1.MVC Android传统就是用MVC模式,Modle(逻辑)和V(View)直接交互,耦合度太高,MVC中是允许Model和View进行交互的

2.MVP Model与View之间的交互由Presenter完成。还有一点就是Presenter与View之间的交互是通过接口的。当View 需要更新数据时,首先去找 Presenter,然后 Presenter 去找 Model 请求数据,Model 获取到数据之后通知 Presenter,Presenter 再通知 View 更新数据,这样 Model 和 View就不会直接交互了,所有的交互都由 Presenter 进行,Presenter 充当了桥梁的角色。很显然,Presenter 必须同时持有 View 和 Model 的对象的引用,才能在它们之间进行通信

Android面试主题整理合集(三)

 

存在问题:

  • 内存泄露:由于Presenter经常性的需要执行一些耗时操作那么当我们在操作未完成时候关闭了Activity,会导致Presenter一直持有Activity的对象,造成内存泄漏
  • 随着业务逻辑的增加,UI的改变多的情况下,这样就会造成View的接口会很庞大。而MVVM就解决了这个问题

解决办法: 在Presenter中使用弱引用,将view的引用加到弱引用中去 每个Activity都有BaseActivity,BaseActivity中

3.MVVM通过数据驱动来自动完成的,数据变化后会自动更新UI,UI的改变也能自动反馈到数据层 Mvvm和Mvp比较相似,不同的是Mvvm中可以用DataBinding或者LiveData动态绑定数据,进一步降低耦合

  • Activity负责UI操作,ViewModel负责数据逻辑。两者通过LiveData进行关联。ViewModel返回LiveData实例和Activity绑定,ViewModel有变化时可以自动更新UI,Activity也可以通过ViewModel发起数据请求
  • 问题:

看起来MVVM很好地解决了MVC和MVP的不足,但是由于数据和视图的双向绑定,导致出现问题时不太好定位来源,有可能数据问题导致,也有可能业务逻辑中对视图属性的修改导致

Android面试主题整理合集(三)

 

Android面试主题整理合集(三)

 

以登录为例,登录时只需要拿着用户名和密码请求服务器即可

  • MVC中,需要先找到输入框,再从输入框中拿到用户名和密码,再进行登录操作
  • MVP中,通过Presenter拿到用户名和密码,进行登录。相当于通过引入了Presenter将M层和V层分离,降低耦合
  • MVVM中,用户在输入框输入完用户名和密码后,这种UI的变化直接同步到数据,直接登录

3.策略设计模式

适用场景: 某些业务中,某一个行为,会有多个实现类,并且当前业务只会选择一种实现类

4.Double Check Lock 实现单例

public static TestInstance getInstance(){ //1
    if (mInstance == null){ //2
        synchronized (TestInstance.class){ //3
            if (mInstance == null){ //4
                mInstance = new TestInstance(); //5
            }
        }
    }
    return mInstance;
}

第一层判断主要是为了避免不必要的同步
第二层的判断则是为了在 null 的情况下创建实例。mInstance = new TestInstance(); 这个步骤,其实在jvm里面的执行分为三步:
1.在堆内存开辟内存空间;
2.初始化对象;
3.把对象指向堆内存空间;
由于在 JDK 1.5 以前 Java 编译器允许处理器乱序执行。不过在 JDK 1.5 之后,官方也发现了这个问题,故而具体化了 volatile ,即在 JDK 1.6 以后,只要定义为 private volatile static DaoManager3 sinstance ; 就可解决 DCL 失效问题

5.OkHttp中的责任链

Android面试主题整理合集(三)

 

public interface Interceptor {
    //每一层的拦截器接口,需要进行实现 Chain:串连拦截器的链
    Response intercept(Chain chain) throws IOException;

    //链主要有两个方法:拿到Request;通过Request拿到Response
    interface Chain {
        Request request();

        Response proceed(Request request):Response throws IOException;//负责往下执行
    }
}

public class RealInterceptorChain implements Interceptor.Chain {
    final List<Interceptor> interceptors;//节点的列表
    final int index;//当前节点的index,通过index和interceptors就可以拿到所有节点
    final Request request;//请求

    public RealInterceptorChain(List<Interceptor> interceptors, int index, Request request){
        this.interceptors = interceptors;
        this.index = index;
        this.request = request;
    }
    @Override
    public Request request() {
        return request;
    }

    @Override
    public Response proceed(Request request) throws IOException {
        RealInterceptorChain next = new RealInterceptorChain(interceptors,  index + 1, request);

        //next传到当前节点,当前节点处理好request后就可以通过next执行proceed方法,将request传递到下一节点
        Interceptor interceptor = interceptors.get(index);
        Response response = interceptor.intercept(next);
        return response;
    }
}

拦截器

public class BridgeInterceptor implements Interceptor{

    @Override
    public Response intercept(Chain chain) throws IOException {
        Log.e("TAG","BridgeInterceptor");
        Request request = chain.request();
        // 添加一些请求头
        request.header("Connection","keep-alive");
        // 做一些其他处理
        if(request.requestBody()!=null){
            RequestBody requestBody = request.requestBody();
            request.header("Content-Type",requestBody.getContentType());
            request.header("Content-Length",Long.toString(requestBody.getContentLength()));
        }
        Response response = chain.proceed(request);//这里的chain就是传进来的next,next的index已经加1

        return response;
    }
}

RealCall中excute()方法

protected void execute() {
            final Request request = orignalRequest;
            try {
                List<Interceptor> interceptors = new ArrayList<>();
                interceptors.add(new BridgeInterceptor());
                interceptors.add(new CacheInterceptor());
                interceptors.add(new CallServerInterceptor());

                Interceptor.Chain chain = new RealInterceptorChain(interceptors,0,orignalRequest);
                Response response = chain.proceed(request);

                callback.onResponse(RealCall.this,response);
            } catch (IOException e) {
                callback.onFailure(RealCall.this,e);
            }
        }

算法


1.反转单链表

class Node{
    private int data;
    private Node next;
    public Node(int data,Node next){
        this.data=data;
        this.next=next;
    }

}
Node node4 = new Node(4, null);
Node node3 = new Node(3, node4);
Node node2 = new Node(2, node3);
Node node1 = new Node(1, node2);
Node pHead = node1;//头结点

这组链表从1到4排序,要求反转后4到1

public static Node reverseList(Node pHead) {
        Node pReversedHead = null; //反转过后的单链表存储头结点
        Node pNode = pHead; //当前节点
        Node pPrev = null; //前一结点
        while (pNode != null) {
            //1.记录next,下一步:更新当前节点的上一节点和本身。最后移动一位
            Node pNext = pNode.next;
            if (pNext == null) {
                //到了尾节点
                pReversedHead = pNode;
            }
            pNode.next = pPrev;
            pPrev = pNode;
            pNode = pNext;
        }

        return pReversedHead;
}

 //递归方式反转(node1->node2->node3->node4->node5)
 public static ListNode reverseR(Node head){ 
    //空链表和一个结点的链表无需反转 
    if(head == null || head.next == null){ 
        return head; 
    } 
    //递归到node4(head)时回溯时,reverseR(node4.next)直接返回node5
    Node res = reverseR(head.next); 
    //递归回溯时,此时head指向node4,将node4的next(node5)的next指向node4(head)
    head.next.next = head; 
    //将node4.next指向null
    head.next = null; 
    return res; 
 }

输出

pHead = reverseList(pHead);//反转之后头结点
while (pHead != null) {
    System.out.println(pHead.key);
    pHead = pHead.next;
}

2.LRU算法(最近最少使用算法)

  • 可以在存储不足时移除掉最近最少使用的数据
  • 使用哈希链表(LinkedHashMap)把数据按照最后使用时间来排序。最新使用的数据插入(移到)链表最前端

其他


1.Https

  • 使用对称密钥:加密和解密使用的是同一个密钥。弊端:最开始的时候怎么将这个对称密钥发送出去呢?如果对称密钥在发送的时候就已经被拦截,那么发送的信息还是会被篡改和窥视
  • 使用非对称密钥:双方必须协商一对密钥,一个私钥一个公钥。用私钥加密的数据,只有对应的公钥才能解密,用公钥加密的数据, 只有对应的私钥才能解密。A将自己的公钥发给B,B以后给A发消息时候用公钥加密后发送,A收到消息后用自己的私钥解密。弊端:非对称密钥(RSA)加密和解密速度慢
  • 非对称密钥+对称密钥:A将自己的公钥发给B,B用公钥将对称密钥加密发给B,这样双方就安全地传递了对称加密的密钥,既解决了密钥的传递问题, 又解决了RSA速度慢的问题。弊端:第一次传递公钥时有风险
  • 数字证书。假如一开始A将自己的公钥发给B时,中间被拦截,拦截者替换成自己的公钥,这样还是会有安全问题。解决办法:数字证书

2.三次握手

其实就是指建立一个TCP连接时,需要客户端和服务器总共发送3个包。进行三次握手的主要作用就是为了确认双方的接收能力和发送能力是否正常

  • 第一次握手:客户端发送网络包,服务端收到了。 这样服务端就能得出结论:客户端的发送能力、服务端的接收能力是正常的。
  • 第二次握手:服务端发包,客户端收到了。 这样客户端就能得出结论:服务端的接收、发送能力,客户端的接收、发送能力是正常的。不过此时服务器并不能确认客户端的接收能力是否正常。
  • 第三次握手:客户端发包,服务端收到了。 这样服务端就能得出结论:客户端的接收、发送能力正常,服务器自己的发送、接收能力也正常。

因此,需要三次握手才能确认双方的接收与发送能力是否正常。

3.OAuth 2.0

用一个短期的令牌(token)允许用户让第三方应用访问他在某一网站上存储的私密的资源,而不需要用户名和密码

app登录成功后服务器返回一个token,下次app就通过这个token来直接登录,token如果过期就需要跳到登录页面重新登录
令牌(token)与密码(password)的作用是一样的,都可以进入系统,但是有三点差异

  • 令牌是有时间限制,到期会自动失效。密码一般长期有效,除非用户修改。
  • 令牌可以被数据所有者撤销,会立即失效。以上例而言,屋主可以随时取消快递员的令牌。密码一般不允许被他人撤销。
  • 令牌有权限范围(scope),比如只能进小区的二号门。对于网络服务来说,只读令牌就比读写令牌更安全。密码一般是完整权限。

4.别人面试题分享

Android面试主题整理合集(三)

 

Android面试主题整理合集(三)

 

Android面试主题整理合集(三)

 

最后

在这里我也分享一份由几位大佬一起收录整理的 Flutter进阶资料以及Android学习PDF+架构视频+面试文档+源码笔记 ,并且还有 高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料……

这些都是我闲暇时还会反复翻阅的精品资料。可以有效的帮助大家掌握知识、理解原理。当然你也可以拿去查漏补缺,提升自身的竞争力。
如果你有需要的话,可以前往 GitHub 自行查阅。

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值