Advertisement

2019-4-19小米面试总结

阅读量:

面试过程

小米在预约的时间内发送邮件时需要注意以下事项:首先除了预定的时间地点外还有一个确认环节如果未点击确认环节会导致你不得不多次前往前台查询却始终未能找到相关记录经历三个轮次共计三个小时的时间却没有一次休息机会最后也未能到达人力资源部门因此我认为这次面谈的结果可能会不尽如人意但整个过程相对来说还是比较轻松愉快

算法题目

生产者和消费者

刚开始的时候, 我一头雾水, 问什么是'生产者消费者'呢?结果发现原来是一个软件工程毕业生都搞不清楚的基本概念。幸运的是, 面试官很体谅人, 不厌其烦地教会了我关于'生产者消费者'的核心逻辑。回忆起来后就去试着实现了, 我就是这么做的

复制代码
     /** * 生产者队列(最后感觉用队列比较好)
     */
    Stack<String> strings = new Stack<>();
    /** * 需要生产的总数
     */
    int count = 100;
    /** * 允许缓存的最大数
     */
    int cache = 10;
    /** * 已经生产的个数
     */
    int value = 0;
    
    public synchronized void produce() throws InterruptedException {
        while (value < count) {
            if (strings.size() < cache) {
                strings.push("生产一个数据");
                value++;
                notifyAll();
            }else {
                wait();
            }
        }
    }
    
    public synchronized void consume() throws InterruptedException {
        while (value < count) {
            if (!strings.isEmpty()) {
                String pop = strings.pop();
                notifyAll();
            }else {
                wait();
            }
        } 
    }

结果:我认为大体思路是对的,并非完全没有问题;然而生产者与消费者通常不会以这种方式书写相关文档或协议书;这是我给出的一个参考答案;但这个答案也不算完全准确

二分查找

过于简单,就不上代码了,我使用递归写的,因为代码量少

其它题目

线程如何通讯

通过Handler机制实现了跨线程通信与数据共享,并进一步讨论了共享变量的相关内容。多线程安全性的挑战在于如何确保三条核心要素的有效实施及其相互之间的协调机制。重点阐述了实现三个关键要素的方法及其在实际应用中的具体保障措施。

View事件传递

我讲述是从Activity事件开始的。他猛地转过身来质问我:"Activity事件从哪里来的呀?"我满脸疑惑地张了张嘴:"真的没听说过吗?"他又说:"那就从这里开始吧?"随后他详细地解释起来:"咱们就按步骤操作好了……"

在这里插入图片描述

把上面的图片叙述了一遍,外加特殊的情况叙述了下

特殊的情况描述

同一时间序列事件代表以down事件起始,在其中可能包含任意数量的move事件后以up事件结束。
各个View的onTouchEvent方法对DOWN事件做出响应的方式体现了该View对于以DOWN开始的手势系列的整体处理意图:当onTouchEvent返回True时,则表示该View愿意处理此类开始的手势;反之则表示不感兴趣。
若ViewGroup决定拦截某一特定手势动作(即onInterceptTouchEvent返回True),那么随后在同一时间序列中的所有后续动作都将被默认拦截。
一旦某个Event被某个View所接收并处理,则该Event必须被消耗完毕;否则在后续的时间序列中将不再由该View负责处理此Event。
若ViewGroup接收到一半途完成的动作(例如MOVE),则系统会将其转换为CANCEL动作并传递给负责该手势(gesture)的具体子View进行处理;而不会继续传递到当前Group所在的onTouchEvent判断层。

自定义View完成一个柱状图
在这里插入图片描述

系统提供了某个集合该集合包含各个数据项创建一个具有柱状图布局的视图对象视图的高度比例设为100%视图的宽度比例设为100%

我认为这是一个可行的方法:首先获取屏幕宽度,并将其除以集合长度从而确定每个柱子可绘制的有效区域范围。接下来使用Path工具先绘制两个坐标轴线的基础框架。接着根据该宽度计算出每个柱子左右上下四边的具体位置坐标,并逐一绘制这些矩形框即可实现整个图表的基本构建过程。理论上这种方法是可行且可操作性的。不过后来发现,在绘图过程中反复计算各个矩形框的位置确实比较繁琐费时。如果采用整体平移的方式进行绘图,则可能会简化后续操作流程。目前我暂时没有想到更好的优化方法了

View的绘制流程

问这个问题的时候确实有些困惑。虽然没有头绪知道答案是什么?是想问我关于View的那一两个关键方法吗?我反问他在探讨如何将View通过某种流程整合到手机窗口上吗?或者说是为了阐述App开发的基本步骤?最终我认为可以从测量入手进行分析。

如何自定义一个View

懒得说了

Activity启动流程

从startActivity开始讲起, AMS依次经过ApplicationThread再到ActivityThread等类似的过程完整地描述了一遍

在这里插入图片描述

子线程中能不能更新View

我当时的回答是说:一般来说是不允许在子线程中更新View的。这是因为所有的更新操作都需要在主线程下执行才能确保系统的稳定性与安全性。然而,在某些特殊情况下是可以实现子线程更新View功能的:当且仅当ViewRootImpl对象已经被成功创建时才有可能实现这一操作;因为所有的布局请求最终都会触发到ViewRootImpl类中的requestLayout方法;在此过程中会调用到checkThread方法来判断当前线程是否处于主线程环境中;如果确实在主线程外执行,则会在Activity(onCreate)方法启动后立即开启一个执行线程来完成布局请求;这种机制能够避免崩溃问题;因为只有当ViewRootImpl对象不存在时才允许开启子线程执行相关操作;至于具体的实现细节可以通过查看Activity(countentView)是如何加入窗体这一流程来进一步理解;有关详细的技术分析请参考此处

Toast子线程可以弹出吗?

我的回答是可以实现的;但前提是必须执行该子线程上的Looper.prepare()操作;否则会导致崩溃;因为在展示过程中会对当前looper对象进行检查;如果当前looper对象为空则会出现错误

复制代码
    // 我只是叙述了一点点的皮毛问题
       if (looper == null) {
                // Use Looper.myLooper() if looper is not specified.
                looper = Looper.myLooper();
                if (looper == null) {
                    throw new RuntimeException(
                            "Can't toast on a thread that has not called Looper.prepare()");
                }
            }

ViewGroup的onLayout方法是抽象的吗?

我说我没注意,说实话我从来没注意这个是不是抽象的

如何性能优化的?

借助SDK提供的工具, 我们可以清晰地了解视图的层次结构, 并识别出哪些地方可以进一步优化。同时建议采用约束布局来减少嵌套层次, 从而避免过度依赖Background属性导致的问题, 同时还可以通过开发者模式中的绘图功能来观察各层绘制的颜色梯度等特性。此外, 还可通过分析多层绘制的颜色分布情况, 更好地掌握图形渲染效果。对于 Android 开发者来说, 使用Android Studio提供的内存分析工具来检测内存泄漏情况是一个高效的方法。

怎么收集崩溃日志

在网络环境中,我们依赖于友盟崩溃日志系统;而在线下环境中,我们开发了UncaughtExceptionHandler来捕获并存储在指定位置的崩溃问题。

怎么分析Anr

我指出利用启动与终止的方法即可获取traces.txt文件。此文件可显示被调用的方法,并进而识别哪些主要线程长时间调用了特定的方法从而引发anr问题。

热修复做过吗?知道原理吗?

  1. 本节详细说明了通过Dex机制修复代码的具体过程:主要是通过反射机制对PathClassLoader中的DexPathList结构进行了调整以支持新增DexFile的操作,并对findClass方法进行了深入分析以了解其具体的运行逻辑。
  2. 本节重点介绍了4个核心组件的快速修复方法:一是针对内存泄漏问题设计了一套有效的排查策略;二是优化了进程间通信机制以提升性能;三是实现了对关键数据结构的有效缓存管理;四是改进了错误报告功能使其更加友好易用。
  3. 资源相关问题也得到了妥善解决:通过Reflection API实现了 AssetsManager对象的动态创建与管理从而确保系统能够快速响应各种资源需求的变化。

枚举类可以被反射创建吗?枚举类可以集成吗?枚举类可以实现接口吗?

枚举类本质上是一个类别;通过反编译查看smail代码;从而能够了解枚举类的本质;从而能够解答这道题的所有问题。

复制代码
    // 我定义的枚举类
    public enum MyEnum {
    NUMER1(1), NUME2(2);
    
    private int value;
    
    MyEnum(int value) {
        this.value = value;
    }
    
    public int getValue() {
        return value;
    }
    }

反编译获取smail代码

复制代码
    .class public final enum Lcom/openmmweb/demo/MyEnum;
    .super Ljava/lang/Enum;
    .source "MyEnum.java"
    
    
    # annotations
    .annotation system Ldalvik/annotation/Signature;
    value = {
        "Ljava/lang/Enum",
        "<",
        "Lcom/openmmweb/demo/MyEnum;",
        ">;"
    }
    .end annotation
    
    
    # static fields
    .field private static final synthetic $VALUES:[Lcom/openmmweb/demo/MyEnum;
    
    .field public static final enum NUME2:Lcom/openmmweb/demo/MyEnum;
    
    .field public static final enum NUMER1:Lcom/openmmweb/demo/MyEnum;
    
    
    # instance fields
    .field private value:I
    
    
    # direct methods
    .method static constructor <clinit>()V
    .registers 5
    
    .prologue
    const/4 v4, 0x2
    
    const/4 v3, 0x0
    
    const/4 v2, 0x1
    
    .line 7
    new-instance v0, Lcom/openmmweb/demo/MyEnum;
    
    const-string v1, "NUMER1"
    
    invoke-direct {v0, v1, v3, v2}, Lcom/openmmweb/demo/MyEnum;-><init>(Ljava/lang/String;II)V
    
    sput-object v0, Lcom/openmmweb/demo/MyEnum;->NUMER1:Lcom/openmmweb/demo/MyEnum;
    
    new-instance v0, Lcom/openmmweb/demo/MyEnum;
    
    const-string v1, "NUME2"
    
    invoke-direct {v0, v1, v2, v4}, Lcom/openmmweb/demo/MyEnum;-><init>(Ljava/lang/String;II)V
    
    sput-object v0, Lcom/openmmweb/demo/MyEnum;->NUME2:Lcom/openmmweb/demo/MyEnum;
    
    .line 6
    new-array v0, v4, [Lcom/openmmweb/demo/MyEnum;
    
    sget-object v1, Lcom/openmmweb/demo/MyEnum;->NUMER1:Lcom/openmmweb/demo/MyEnum;
    
    aput-object v1, v0, v3
    
    sget-object v1, Lcom/openmmweb/demo/MyEnum;->NUME2:Lcom/openmmweb/demo/MyEnum;
    
    aput-object v1, v0, v2
    
    sput-object v0, Lcom/openmmweb/demo/MyEnum;->$VALUES:[Lcom/openmmweb/demo/MyEnum;
    
    return-void
    .end method
    
    .method private constructor <init>(Ljava/lang/String;II)V
    .registers 4
    .param p3, "value"    # I
    .annotation system Ldalvik/annotation/Signature;
        value = {
            "(I)V"
        }
    .end annotation
    
    .prologue
    .line 11
    invoke-direct {p0, p1, p2}, Ljava/lang/Enum;-><init>(Ljava/lang/String;I)V
    
    .line 12
    iput p3, p0, Lcom/openmmweb/demo/MyEnum;->value:I
    
    .line 13
    return-void
    .end method
    
    .method public static valueOf(Ljava/lang/String;)Lcom/openmmweb/demo/MyEnum;
    .registers 2
    .param p0, "name"    # Ljava/lang/String;
    
    .prologue
    .line 6
    const-class v0, Lcom/openmmweb/demo/MyEnum;
    
    invoke-static {v0, p0}, Ljava/lang/Enum;->valueOf(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
    
    move-result-object v0
    
    check-cast v0, Lcom/openmmweb/demo/MyEnum;
    
    return-object v0
    .end method
    
    .method public static values()[Lcom/openmmweb/demo/MyEnum;
    .registers 1
    
    .prologue
    .line 6
    sget-object v0, Lcom/openmmweb/demo/MyEnum;->$VALUES:[Lcom/openmmweb/demo/MyEnum;
    
    invoke-virtual {v0}, [Lcom/openmmweb/demo/MyEnum;->clone()Ljava/lang/Object;
    
    move-result-object v0
    
    check-cast v0, [Lcom/openmmweb/demo/MyEnum;
    
    return-object v0
    .end method
    ...

通过smail代码获取到的所有定义的枚举类均会自动生成相应的接口实现;基于java语言只能进行单层 inheritance的规定,在这种情况下,枚举类无法直接继承自其他外部 class;然而作为 basic class类型 自然能够实现相关的 interface。

在讨论过程中,我们遇到了一个问题:通过反射机制是否可以创建枚举类的对象?经过一番探讨后发现:尽管理论上认为枚举类不会直接提供显式的构造函数以生成实例(即无法像普通类那样通过new关键字直接创建),但实际操作中可以通过反编译邮件代码来获取相关的信息并进行模拟构造。具体来说,在反编译后的邮件代码中可以看到:枚举类会生成一个新的静态初始化方法(即CreateInstance()),而这个方法正是用于隐式地为实例赋值的。因此,在某些特殊情况下是可以实现类似功能的

复制代码
    new-instance v0, Lcom/openmmweb/demo/MyEnum;
    
    const-string v1, "NUMER1"
    
    invoke-direct {v0, v1, v3, v2}, Lcom/openmmweb/demo/MyEnum;-><init>
复制代码
    .method private constructor <init>(Ljava/lang/String;II)V
    .registers 4
    .param p3, "value"    # I
    .annotation system Ldalvik/annotation/Signature;
        value = {
            "(I)V"
        }
    .end annotation
    
    .prologue
    .line 11
    invoke-direct {p0, p1, p2}, Ljava/lang/Enum;-><init>(Ljava/lang/String;I)V

反射代码,成功创建了枚举对象,并且成功运行

复制代码
      try {
            Constructor<MyEnum> constructor = MyEnum.class.getDeclaredConstructor(String.class, int.class, int.class);
            constructor.setAccessible(true);
            MyEnum number3 = constructor.newInstance("NUMBER3", 4, 4);
            Log.d("wyz", "构造的对象3:" + number3 + "  " + number3.getValue());
        } catch (Exception e) {
            e.printStackTrace();
            Log.d("wyz", "崩溃出错:" + e.getMessage());
        }

最后经过一番探索我发现问题的核心在于:
在Android平台下枚举类是可以借助反射机制来实现创建功能的
然而在Java语言中由于其特有的设计原则无法通过反射机制来实现枚举类型的一一对应关系

Java中直接创建会毫不客气的和你说

复制代码
    java.lang.IllegalArgumentException: Cannot reflectively create enum objects
    	at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
    	at solution.testFunc(solution.java:20)
    	at solution.main(solution.java:7)

为什么会出现这种情况?关键区别在于Android中使用的是Android's newInstance方法而Java中使用的是Java.lang.reflectClass.loadClass()调用newInstance的方法。

复制代码
    public T newInstance(Object ... initargs)
        throws InstantiationException, IllegalAccessException,
               IllegalArgumentException, InvocationTargetException
    {
    。。。
    //可以看到这里,java的会判断当前的类Modifier会通过与运算,判断类的类型是否是枚举,如果是枚举直接崩溃哦
        if ((clazz.getModifiers() & Modifier.ENUM) != 0)
            throw new IllegalArgumentException("Cannot reflectively create enum objects");
            。。。
        }
        。。。
        return inst;
    }

anddorid的newInstance方法

复制代码
    public T newInstance(Object... args) throws InstantiationException,
            IllegalAccessException, IllegalArgumentException, InvocationTargetException {
        if (serializationClass == null) {
        // 不会检测是否是枚举,这个方法执行了一个Native方法了
            return newInstance0(args);
        } else {
            return (T) newInstanceFromSerialization(serializationCtor, serializationClass);
        }
    }

总结下:

  1. 如果枚举类不特意指定父类,则默认继承Enum类。
  2. 枚举作为一类特殊的对象也是合法定义的。
  3. 那么能否通过反射机制来创建枚举实例呢?Android系统支持此类操作而Java却不允许。
  4. 主要原因在于Android与Java在创建实例时所采用的方法有所不同。
  5. Java会检查实例化的类型是否为枚举类型。
Rxjava源码分析,如何切换线程的

我详细说明了.subscribeOn如何切换场景的过程以及AndroidSchedulers是如何实现功能的。

Retrofit源码分析

我对相关技术了解不深;主要介绍了容器工厂和addCallAdapter的具体功能;借助动态代理机制实现了部分自动化操作;说实话我对源码的理解有限

你们项目用了什么第三方框架

巴拉巴拉

你经常使用什么设计模式

巴拉巴拉

说下你一般怎么写单利

我阐述了非线程安全对象(如懒汉)与线性可选对象(如饿汉)的区别,并指出非线性可选对象(即静态内部类)必须通过volatile关键字进行修饰。进一步阐述了静态内部类为何具备线程安全特性,并解释原因可能包括内存池机制等细节。

总结下

进行了三个小时的面试。
感到非常疲惫和口渴,并且还有一些难以记住的问题。
感觉提到了许多问题,并且可能让对方感到厌烦;
不过整个过程相当轻松,并不觉得有多紧张。

全部评论 (0)

还没有任何评论哟~