Android 面试札记第 4 篇

  1. 字符串反转

写出一个程序,接受一个字符串,然后输出该字符串反转后的字符串。例如:输入 abcd,输出:dcba

    public String reverseString(String s) {
        char[] chars = s.toCharArray();
        char temp;
        int length = chars.length;
        for (int i = 0 ,j = length -1; i < length / 2 ; i++,j--) {
            temp = chars[i];
            chars[i] = chars[j];
            chars[j] = temp;
        }
        return new String(chars);
    }

我们只需要遍历char数组中一半的元素就可以了,数组长度为奇数时中间的一个元素不需要管,而int类型除以2正好没有带上中间的元素。
参考文章https://blog.csdn.net/zhwyj1019/article/details/81876505

  1. 字符串匹配问题

对于字符串str,其中绝对不含有字符’.’和‘’。再给定字符串exp,其中可以含有’.’或’‘’,’’字符不能是exp的首字符,并且任意两个’‘字符不相邻。exp中的’.’代表任何一个字符,exp中的’’表示’‘的前一个字符可以有0个或者多个。请写一个函数,判断str是否能被exp匹配(注意:输入的数据不保证合法,但只含小写字母和‘.’和‘*’)。

递归版本:

class Solution {
    public boolean isMatch(String s, String p) {
        if(s == null || p == null)
            return false;
        char[] str = s.toCharArray();
        char[] pat = p.toCharArray();
        return isValid(str, pat) ? process(str, pat, 0, 0):false;
    }
    
    public boolean isValid(char[] s, char[] p){//判断输入是否有效
        for(int i = 0; i < s.length; i++){
            if(s[i] == '*' || s[i] == '.')
                return false;
        }
        for(int i = 0; i < p.length; i++){
            if(p[i] == '*' &&(i == 0 || p[i-1] == '*'))
                return false;
        }
        return true;
    }
    
    public boolean process(char[] s, char[] p, int si, int pi){//判断是否匹配
        if(pi == p.length)
            return si == s.length;
        if(pi +1  == p.length || p[pi+1] != '*'){
            return si != s.length && (p[pi] == s[si] || p[pi] == '.') && process(s,p,si+1,pi+1);
        }
        while(si != s.length && (p[pi] == s[si] || p[pi] == '.')){
            if(process(s,p,si,pi+2))
                return true;
            si++;
        }
        return process(s,p,si,pi+2);
    }
}

首先,isValid函数判断输入是否有效,即字符串s不能包含’‘和’.’,字符串p中’‘字符前不能为空或者字符’*’

判断函数process用递归判断,pi,si分别标识字符串p和s的所在位置,即判断字符串p的pi位置之后的字符串是否能模式识别字符串s的si位置之后的字符串。

如果p的pi+1位置不是’*'的话,只需要判断现在所处的字符是否匹配,并且再继续往下判断si+1和pi+1位置后的字符串。

如果p的pi+1位置是’‘的话,两种情况:一是’‘代表0次,我们判断p的pi+2位置之后和s的si位置之后即可;二是’*'代表非0次,我们保持pi不动,si向后移直到跳出循环即可。

原文链接:https://blog.csdn.net/wannuoge4766/article/details/89211379

  1. 选择题
    Service中如何实现更改Activity界面元素(B)
    A . 通过把当前activity对象传递给service对象
    B . 通过向Activity发送广播
    C . 通过Context对象更改Activity界面元素
    D . 可以在Service中,调用Activity的方法实现更改界面元素

  2. 选择题
    下列哪些语句关于内存回收的说明是正确的? (B)
    A.程序员必须创建一个线程来释放内存
    B.内存回收程序负责释放无用内存
    C.内存回收程序允许程序员直接释放内存
    D.内存回收程序可以在指定的时间释放内存对象

  3. 选择题
    下面关于Android dvm的进程和Linux的进程,应用程序的进程说法正确的是(D)
    A. DVM指dalvik的虚拟机.每一个Android应用程序都在它自己的进程中运行,不一定拥有一个独立 的Dalvik虚拟机实例.而每一个DVM都是在Linux中的一个进程,所以说可以认为是同一个概念.
    B. DVM指dalvik的虚拟机.每一个Android应用程序都在它自己的进程中运行,不一定拥有一个独立的Dalvik虚拟机实例.而每一个DVM不一定都是在Linux 中的一个进程,所以说不是一个概念.
    C. DVM指dalvik的虚拟机.每一个Android应用程序都在它自己的进程中运行,都拥有一个独立的Dalvik虚拟机实例.而每一个DVM不一定都是在Linux 中的一个进程,所以说不是一个概念
    D. DVM指dalvik的虚拟机.每一个Android应用程序都在它自己的进程中运行,都拥有一个独立的 Dalvik虚拟机实例.而每一个DVM都是在Linux 中的一个进程,所以说可以认为是同一个概念.

  4. 有序广播和无序广播的区别

1.无序广播
通过Context.sendBroadcast()方法来发送,它是完全异步的。
所有的receivers(接收器)的执行顺序不确定,因此所有的receivers(接收器)接收broadcast的顺序不确定。
这种方式效率更高,但是BroadcastReceiver无法使用setResult系列、getResult系列及abortbroadcast(中止)系列API。
广播不能被终止,数据不能被修改。
2.有序广播
有序广播,即从优先级别最高的广播接收器开始接收,接收完了如果没有丢弃,就下传给下一个次高优先级别的广播接收器进行处理,依次类推,直到最后。如果多个应用程序设置的优先级别相同,则谁先注册的广播,谁就可以优先接收到广播。通过Context.sendorderBroadCast()方法来发送,sendOrderedBroadcast(intent, receiverPermission, resultReceiver, scheduler, initialCode, initialData, initialExtras);,其中的参数resultReceiver,可以自己重写一个类,作为一个最终的receive 最后都能够接收到广播,最终的receiver 不需要再清单文件里面配置,initialData可以作为传输的数据
广播可以被终止,数据传输过程中可以被修改。
参考 https://blog.csdn.net/ldc_123/article/details/52801218

  1. 全局广播和本地广播的区别(自我拓展:本地广播和全局广播的实现原理)

1、本地广播:发送的广播事件不被其他应用程序获取,也不能响应其他应用程序发送的广播事件。本地广播只能被动态注册,不能静态注册。动态注册或方法时需要用到LocalBroadcastManager。
2、全局广播:发送的广播事件可被其他应用程序获取,也能响应其他应用程序发送的广播事件(可以通过 exported–是否监听其他应用程序发送的广播 在清单文件中控制) 全局广播既可以动态注册,也可以静态注册。
参考https://blog.csdn.net/look_future/article/details/79672760

  1. 静态广播和动态广播的区别

动态注册和静态注册的区别:

  1. 动态注册的广播会受Activity的生命周期的影响, 当Activity销毁的时候,广播就失效了。
  2. 而静态注册的广播,即使Activity销毁了,仍然可以收到广播。更牛掰的是即使杀死进程,仍然可以收到广播。

参考 https://www.cnblogs.com/lang-yu/p/6170325.html

  1. 冷启动和热启动,关于启动时间的日子打印应该在哪个方法里面

这个题目其实是考察冷启动和热启动的不同流程:
1.冷启动和热启动的概念:
冷启动:
在启动应用时,系统中没有该应用的进程,这时系统会创建一个新的进程分配给该应用;
热启动:
在启动应用时,系统中已有该应用的进程(例:按back键、home键,应用虽然会退出,但是该应用的进程还是保留在后台);

2.冷启动、热启动的区别
冷启动:系统没有该应用的进程,需要创建一个新的进程分配给应用,所以会先创建和初始化Application类,再创建和初始化MainActivity类(包括一系列的测量、布局、绘制),最后显示在界面上。
热启动: 从已有的进程中来启动,不会创建和初始化Application类,直接创建和初始化MainActivity类(包括一系列的测量、布局、绘制),最后显示在界面上。
3.冷启动时间的计算
API19 之后,系统会出打印日志输出启动的时间;
冷启动时间 = 应用启动(创建进程) —> 完成视图的第一次绘制(Activity内容对用户可见);
4.冷启动流程
Zygote进程中fork创建出一个新的进程;
创建和初始化Application类、创建MainActivity;
inflate布局、当onCreate/onStart/onResume方法都走完;
contentView的measure/layout/draw显示在界面上;
总结:
Application构造方法 –> attachBaseContext() –> onCreate() –> Activity构造方法 –> onCreate() –> 配置主题中背景等属性 –> onStart() –> onResume() –> 测量布局绘制显示在界面上。

  1. 冷启动和热启动的时间是因为 app 业务影响,还是因为系统影响。(各自的启动时间受什么影响?)

这个题目承接上面的问题,考察的是冷启动的优化问题:冷启动受业务 app 的影响,我们应该:
1.减少在Application和第一个Activity的onCreate()方法的工作量;
2.不要让Application参与业务的操作;
3.不要在Application进行耗时操作;
4.不要以静态变量的方式在Application中保存数据;
5.减少布局的复杂性和深度;
参考 >https://www.jianshu.com/p/eb3b410cce49

  1. MVVM 中是如何进行数据绑定的

MVVM 中的数据绑定依赖 DataBinding,分为单向绑定和双向绑定。
MVVM是更节省的设计模式,能实现双向的数据绑定。
1.单向绑定是指View层(如EditText)上的数据改变会实时更新到Model层JavaBean中对应的属性值(如username)上。或者,Model层的数据改变会实时更新到View层上的显示,这样我们称之为单向的数据绑定。
2.而双向绑定呢,是指Model层和View层,无论那层数据改变都会实时更新到对方,Model层数据改变会更新View,同样,View层数据改变会更新Model,这样就称之为双向的数据绑定。

  1. 消息传递机制 (Handler)

1.线程间通讯 ——— Handler,HandlerThread等。
2.组件间通信 ——— BroadcastReceiver,接口回调等。
3. 第三方通信 ——— EventBus,rxBus
4.进程间通信 ——— Content Provider ,Broadcast ,AIDL等。
5.长连接推送 ——— WebSocket,XMPP等。
参考https://blog.csdn.net/haoxuhong/article/details/80103030
Handler
一个Android应用程序被创建的时候都会创建一个UI主线程,但是有时我们会有一些比较耗时的操作,为了防止阻塞UI主线程,我们会将耗时的操作放到子线程中进行处理,处理完之后操作UI,但是Android不允许子线程操作UI,违背了Android单线程模型的原则(即 Android UI操作并不是线程安全的并且这些操作必须在UI线程中执行),所以Android通过Handler消息机制来实现线程之间的通讯。
Handler 机制的主要角色:
1.Message:消息,其中包含了消息ID,消息处理对象以及处理的数据等,由MessageQueue统一列队,终由Handler处理。
2.Handler:处理者,负责Message的发送及处理。使用Handler时,需要实现handleMessage(Message msg)方法来对特定的Message进行处理,例如更新UI等。
3.MessageQueue:消息队列,用来存放Handler发送过来的消息,并按照FIFO规则执行。当然,存放Message并非实际意义的保存,而是将Message以链表的方式串联起来的,等待Looper的抽取
4.Looper:消息泵,不断地从MessageQueue中抽取Message执行。因此,一个MessageQueue需要一个Looper。
5.Thread:线程,负责调度整个消息循环,即消息循环的执行场所。

Handler 机制的主要运用:
sendEmptyMessage(int);//发送一个空的消息
sendMessage(Message);//发送消息,消息中可以携带参数
sendMessageAtTime(Message, long);//未来某一时间点发送消息
sendMessageDelayed(Message, long);//延时Nms发送消息

post(Runnable);//提交计划任务马上执行
postAtTime(Runnable, long);//提交计划任务在未来的时间点执行
postDelayed(Runnable, long);//提交计划任务延时Nms执行
Handler机制扩展:

Activity.runOnUiThread(Runnable)
View.post(Runnable)
以上也可以从子线程切换到主线程。

HandlerThread:

HandlerThread本质上就是一个普通Thread,只不过内部建立了Looper.

HandlerThread 的用法
 //创建一个线程,线程名字:handler-thread
        myHandlerThread = new HandlerThread( "handler-thread") ;
        //开启一个线程
        myHandlerThread.start();
        //在这个线程中创建一个handler对象 主要这个handler是在子线程中循环接受消息的
        handler = new Handler( myHandlerThread.getLooper() ){
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                //这个方法是运行在 handler-thread 线程中的 ,可以执行耗时操作
                Log.d( "handler " , "消息: " + msg.what + "  线程: " +                Thread.currentThread().getName()  ) ;
 
            }
        };
 
        //在主线程给handler发送消息
        handler.sendEmptyMessage( 1 ) ;
 
        new Thread(new Runnable() {
            @Override
            public void run() {
             //在子线程给handler发送数据
             handler.sendEmptyMessage( 2 ) ;
            }
        }).start() ;
 
    }
 
    @Override
    protected void onDestroy() {
        super.onDestroy();
 
        //释放资源
        myHandlerThread.quit() ;
    }
Looper的 quit 方法或 quitSafely 方法
相同点:

调用后,将不再接受新的事件加入消息队列。

不同点

当我们调用Looper的quit方法时,实际上执行了MessageQueue中的removeAllMessagesLocked方法,该方法的作用是把MessageQueue消息池中所有的消息全部清空,无论是延迟消息(延迟消息是指通过sendMessageDelayed或通过postDelayed等方法发送的需要延迟执行的消息)还是非延迟消息。

当我们调用Looper的quitSafely方法时,实际上执行了MessageQueue中的removeAllFutureMessagesLocked方法,通过名字就可以看出,该方法只会清空MessageQueue消息池中所有的延迟消息,并将消息池中所有的非延迟消息派发出去让Handler去处理,quitSafely相比于quit方法安全之处在于清空消息之前会派发所有的非延迟消息。

无论是调用了quit方法还是quitSafely方法只会,Looper就不再接收新的消息。即在调用了Looper的quit或quitSafely方法之后,消息循环就终结了,这时候再通过Handler调用sendMessage或post等方法发送消息时均返回false,表示消息没有成功放入消息队列MessageQueue中,因为消息队列已经退出了。

需要注意的是Looper的quit方法从API Level 1就存在了,但是Looper的quitSafely方法从API Level 18才添加进来。

  1. 如何在子线程中刷新视频流

  2. 怎样开启一个多进程

在Android中说多进程一般是指一个应用中存在多个进程,在Android中使用多进程只有一种方法:给四大组件在AndroidMenifest中指定android:process属性,除此之外别无他法(通过JNI在native层去fork一个进程也可以,不常用,不做介绍),所以我们不能给一个线程或者实体类指定其运行时所在的进程。

开启多进程

<activity
    android:name="com.zhong.ActivityA" />
<activity
    android:name="com.zhong.ActivityB"
    android:process=":remote" />
<activity
    android:name="com.zhong.ActivityC"
    android:process="com.zhong.remote" />

上面三个Activity中:

ActivityA:未指明android:process属性,它允许在默认进程中,进程名为包名,即com.zhong;
ActivityB:设置android:process=":remote",系统会为它创建一个单独的进程,进程名为包名+:remote,即com.zhong:remote,相当于android:process=“com.zhong:remote”;
ActivityC:设置android:process=“com.zhong.remote”,系统会为它创建一个单独的进程,进程名为process设置的值,即com.zhong.remote

":remote"和"com.zhong.remote"区别:

“:remote"中”:"是指要在当前进程名前面附近包名的简写,“com.zhong.remote"是一种完整的写法,进程名不会再附加包名;其次,”:"是属于当前应用的私有进程,"com.zhong.remote"是全局进程,全局进程其他应用可以通过ShareUID方式跑在同一进程中(前提是两个应用ShareUID相同并且签名相同),这两个应用可以相互访问对方私有数据。

多进程运行机制

由于Android为每个进程分配独立的虚拟机,所以不同虚拟机访问同一个类会产生不同的副本,因此运行在不同进程中的组件无法通过内存共享数据,所以一个应用使用多进程会出现一下几个问题:

1.静态成员和单例模式完全失效(不是同一块内存,会产生不同的副本)
2.线程同步机制完全失效(不是同一块内存,所以对象也不是同一个,因此类锁、对象锁也不是同一个,不能保证线程同步)
3.SharedPreferences 可靠性下降(SharedPreferences不支持多个进程同时写,会有一定的几率丢失数据)
4.Application 多次创建(Android为每个进程分配独立的虚拟机,这个过程其实就是启动一个应用,所以Application会被创建多次)

多进程小结:

Android中使用多进程一般是来分担主进程的内存压力,应用越做越大,需要的内存也越来越多,讲一些独立的组件放在不同的进程中,这样可以减轻主进程的内存负担;还有就是启动一个Service,做一些守护或者耗时的操作。
参考 Android多进程模式

  1. Service与 Activity 怎样交互

1.使用接口回调方式,activity实现相应的接口,service通过接口进行回调,比较灵活
2.使用广播
参考 Android—Service与Activity的交互

  1. 直播视频流是在哪个线程更新的,怎么做到的,为什么?

是在子线程中更新的,使用 SurfaceView 来实现的。为何SurfaceView能够在非UI线程中刷新界面?

SurfaceView与View的刷新方法都是一样的,通过lockCanvas和unlockCanvasAndPost方法来进行画的,但SurfaceView能在UI线程中刷新,也能在其它线程中刷新,而View只能在UI线程中刷新,View的刷新有一个checkThread(在ViewRootImp.java中)的判断,如果不是在UI线程中就会抛异常, 这是google人为这样设计的,不让其它线程刷新View,SurfaceView就不会进行判断,这样它就可以在其它线程中进行刷新。

  1. 当前 Activity 持有哪些窗体

其实这个问题想问的是 Android - Activity 与 Window 与 View 之间的关系。

Window 是什么?

Window 是 Android 中窗口的宏观定义,主要是管理 View 的创建,以及与 ViewRootImpl 的交互,将 Activity 与 View 解耦。

Activity 与 PhoneWindow 与 DecorView 之间什么关系?

一个 Activity 对应一个 Window 也就是 PhoneWindow,一个 PhoneWindow 持有一个 DecorView 的实例,DecorView 本身是一个 FrameLayout。
参考一篇文章看明白 Activity 与 Window 与 View 之间的关系

  1. 页面间数据通信有哪些方式

1.如果页面之间有直接关系,如Activity和在它之内的Fragment,可以直接通过接口的调用来传递数据。
优势:直接,方便。 劣势:代码耦合性较高

2.如果是两个Activity之间传递数据,有界面切换的过程的话,可以用startActivity
或startActivityForResult。用其中的intent参数携带数据。
优势:一般用于初始化Activity和调用系统功能

3.如果页面之间传递数据没有页面切换的过程,可以通过广播的方式,sendBroadcast(intent);
要接受数据的页面注册这个广播就行了。
优势:代码耦合性低,易重构,适用范围广。缺点:数据需要序列化和反序列化,代码较多

4.通过存储介质来分享数据,如页面A将数据存入数据库,SharedPreferences文件,Internet。页面B通过读取它们来得到数据。
优势:数据保存时间长,不受到界面生命周期的影响 缺点:读取速度较慢,需要异步操作

5.采用事件总线的方式,注册和接收事件(数据),其中的代表者是EventBus,页面需要指定和注册接收事件的类型
优势:不用序列化数据,适用范围大 缺点:需要学习使用。

  1. recylcerView 相比 listview 有哪些优点

优缺点
1.ListView相比RecycleView的优点
a.ListView实现添加HeaderView和FooderView有直接的方法
b.分割线可以直接设置
c.ListView实现onItemClickListence和onItemLongClickListence有直接的方法

2.RecyclerView相比ListView的优点
a.封装了ViewHodler,效率更高
b.可以添加增删Item动画、侧滑功能等
c.支持局部更新,可见才更新,不可见不更新
d.插件式实现,各个功能模块化,解耦性强,使用起来更方便

3.但RecyclerView不会替代ListView,因为使用场景不同;但是会替代很多开元框架:横向ListView、瀑布流等

Android RecyclerView的使用及和ListView比较的优缺点
ListView 与 RecyclerView 简单对比
RecyclerView与ListView的异同
RecyclerView 和 ListView 性能和效果区别

  1. 平时碰到过哪些内存内存优化
    OOM、ANR、ML
    Android - 性能优化-内存优化

  2. 比较下 MVP 和 mvvm
    MVP 缺点:
    1.会增加大量的模板代码
    2.更新一个很小的 UI 元素,也需要层层调用,流程比较绕
    MVVM 缺点:
    1.在布局文件xml中加入了很多逻辑代码,违背了展示和逻辑分离的原则,增加了复杂度,难以阅读和维护
    2.单纯的MVVM模式只能实现简单的UI更新,无法实现诸如列表更新的功能,以及加载完成网络数据后弹一个toast之类的功能
    3.不好定位 bug,很多问题无法直接定位到出问题的那一行,增加了工作量。
    Android开发模式:MVP Vs MVVM
    三种框架的比较—教你认清MVC,MVP和MVVM

  3. 工作中碰到的比较有价值的工作(或者碰到了哪些问题)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值