Android面试常见题目总结

烈酒焚心 提交于 2019-11-29 09:38:31

1.a=1 b=2 怎么在不使用第三个变量的情况下 使ab的值互换

先看看使用第三变量的方法:

public class Swap{
    public static void main(String[] args){
        int a = 3;
        int b = 5;

        int c = a; //把a的值先存到c
        a = b; //把b存给a
        b = c; //把存在c的值倒赋给b

        System.out.println("a="+a);
        System.out.println("b="+b);
    }
}

总结一下不使用第三变量的方法:

public class Change {  
      
    public void changeMethodA(int a, int b){  
        System.out.println("交换之前\ta:"+a+"\tb:"+b);  
        a = a + b - (b = a);  
        System.out.println("交换之后\ta:"+a+"\tb:"+b);  
    }  
      
    public void changeMethodB(int a, int b){  
        System.out.println("交换之前\ta:"+a+"\tb:"+b);  
        b = a + (a = b)*0;  
        System.out.println("交换之后\ta:"+a+"\tb:"+b);  
    }  
      
    public void changeMethodC(int a, int b){  
        System.out.println("交换之前\ta:"+a+"\tb:"+b);  
        a = a + b;  
        b = a - b;  
        a = a - b;  
        System.out.println("交换之后\ta:"+a+"\tb:"+b);  
    }  
      
    public void changeMethodD(int a, int b){  
        System.out.println("交换之前\ta:"+a+"\tb:"+b);  
        a = a * b;  
        b = a / b;  
        a = a / b;  
        System.out.println("交换之后\ta:"+a+"\tb:"+b);  
    }  
      
    public void changeMethodE(int a, int b){  
        System.out.println("交换之前\ta:"+a+"\tb:"+b);  
        a = a^b;  
        b = a^b;  
        a = a^b;  
        System.out.println("交换之后\ta:"+a+"\tb:"+b);  
    }  
      
    public static void main(String[] args) {  
        Change change = new Change();  
        change.changeMethodA(1, 10);  
        System.out.println("-----------------------------------");  
        changeTest.changeMethodB(2, 9);  
        System.out.println("-----------------------------------");  
        changeTest.changeMethodC(3, 8);  
        System.out.println("-----------------------------------");  
        changeTest.changeMethodD(4, 7);  
        System.out.println("-----------------------------------");  
        changeTest.changeMethodE(5, 6);  
    }  
}  

1.1找出一篇文章中出现次数最多的字

public static Character findMaxValue(String str){
        if (str == null || str == ""){
            return null;
        }
        Character maxChar = null;
        int maxCount = 0;
        Map<Character,Integer> map = new HashMap<Character, Integer>();//将出现的字母和出现的次数放进一个map中
        for(int i=0; i < str.length();i++){
            if (map.containsKey(str.charAt(i)))
              map.put(str.charAt(i),map.get(str.charAt(i))+1);//如果map的key包含该字符,则+1
            else
                map.put(str.charAt(i),1);//如不map的key不包含该字符,则初始化为1
            if (maxCount < map.get(str.charAt(i))){
                maxCount = map.get(str.charAt(i));
                maxChar = str.charAt(i);
            }
        }
        System.out.println("字母"+ maxChar+"出现次数为"+maxCount);
        return maxChar;
    }

1.2统计一个ViewGroup中包含的子View的个数(递归和非递归实现)

/**
     * 递归统计一个View的子View数(包含自身)
     *
     * @param root
     * @return
     */
    public int count1(View root) {
        int viewCount = 0;

        if (null == root) {
            return 0;
        }

        if (root instanceof ViewGroup) {
            viewCount++;
            for (int i = 0; i < ((ViewGroup) root).getChildCount(); i++) {
                View view = ((ViewGroup) root).getChildAt(i);
                if (view instanceof ViewGroup) {
                    viewCount += count1(view);
                } else {
                    viewCount++;
                }
            }
        }

        return viewCount;
    }

/**
     * 非递归统计一个View的子View数(包含自身)
     *
     * @param root
     * @return
     */
    public int count(View root) {
        int viewCount = 0;

        if (null == root) {
            return 0;
        }

        if (root instanceof ViewGroup) {
            ViewGroup viewGroup = (ViewGroup) root;
            LinkedList<ViewGroup> queue = new LinkedList<ViewGroup>();
            queue.add(viewGroup);
            while (!queue.isEmpty()) {
                ViewGroup current = queue.removeFirst();
                viewCount++;
                for (int i = 0; i < current.getChildCount(); i++) {
                    if (current.getChildAt(i) instanceof ViewGroup) {
                        queue.addLast((ViewGroup) current.getChildAt(i));
                    } else {
                        viewCount++;
                    }
                }
            }
        } else {
            viewCount++;
        }

        return viewCount;
    }

2.ScrollView嵌套ListView显示不全的问题的解决方法及原理

原因:ScorllView在测量子布局的时候回用到MeasureSpec.UNSPECIFIED这个模式

解决方法1.写一个方法重新测量高度,并设置给listview

3.synchronized和Lock异同?

1.相同点:Lock能完成synchronized所实现的所有功能

2.Lock有比synchronized更精确的线程语义和更好的性能。synchronized会自动释放锁,而Lock一定要求程序员手工释放,并且必须在finally从句中释放。Lock还有更强大的功能,例如,它的tryLock方法可以非阻塞方式去拿锁。

4.HashMap和Hashtable的区别?

一.历史原因:Hashtable是基于陈旧的Dictionary类的,HashMap是Java 1.2引进的Map接口的一个实现

二.同步性:Hashtable是线程安全的,也就是说是同步的,而HashMap是线程序不安全的,不是同步的

三.值:只有HashMap可以让你将空值作为一个表的条目的key或value

5.ArrayList和Vector的区别

(1)同步性:

Vector是线程安全的,也就是说是它的方法之间是线程同步的,而ArrayList是线程序不安全的,它的方法之间是线程不同步的。如果只有一个线程会访问到集合,那最好是使用ArrayList,因为它不考虑线程安全,效率会高些;如果有多个线程会访问到集合,那最好是使用Vector,因为不需要我们自己再去考虑和编写线程安全的代码。

备注:对于Vector&ArrayList、Hashtable&HashMap,要记住线程安全的问题,记住Vector与Hashtable是旧的,是java一诞生就提供了的,它们是线程安全的,ArrayList与HashMap是java2时才提供的,它们是线程不安全的。所以,我们讲课时先讲老的。

(2)数据增长:

ArrayList与Vector都有一个初始的容量大小,当存储进它们里面的元素的个数超过了容量时,就需要增加ArrayList与Vector的存储空间,每次要增加存储空间时,不是只增加一个存储单元,而是增加多个存储单元,每次增加的存储单元的个数在内存空间利用与程序效率之间要取得一定的平衡。Vector默认增长为原来两倍,而ArrayList的增长策略在文档中没有明确规定(从源代码看到的是增长为原来的1.5倍)。ArrayList与Vector都可以设置初始的空间大小,Vector还可以设置增长的空间大小,而ArrayList没有提供设置增长空间的方法。

总结:即Vector增长原来的一倍,ArrayList增加原来的0.5倍。

5.1单例模式

1、懒汉
public class Singleton {
    private static Singleton instance;
    private Singleton (){}

    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}
2、饿汉
public class Singleton {  
    private static Singleton instance = new Singleton();  
    private Singleton (){}  
    public static Singleton getInstance() {  
        return instance;  
    }  
}
3、双重校验锁
public class Singleton {
    private volatile static Singleton singleton;
    private Singleton (){}
    public static Singleton getSingleton() {
        if (singleton == null) {
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}
4、枚举
public enum Singleton {
    INSTANCE;
    public void whateverMethod() {
    }
}
5、静态内部类
public class Singleton {  
    private static class SingletonHolder {  
        private static final Singleton INSTANCE = new Singleton();  
    }  
    private Singleton (){}  
    public static final Singleton getInstance() {  
        return SingletonHolder.INSTANCE;  
    }  
}

6.常用的排序

冒泡:

    public static void bubbleSort(int[] array) {
        int temp = 0;
        for (int i = 0; i < array.length - 1; i++) {
            for (int j = 0; j < array.length - 1 - i; j++) {
                if (array[j] > array[j + 1]) {
                    temp = array[j + 1];
                    array[j + 1] = array[j];
                    array[j] = temp;
                }

            }
        }
    }

选择排序:

public static void selectSort(int[] array) {
        int temp = 0;

        for (int i = 0; i < array.length; i++) {
            for (int j = array.length - 1; j > i; j--) {

                if (array[i] > array[j]) {
                    temp = array[i];
                    array[i] = array[j];
                    array[j] = temp;

                }
            }
        }

    }

二分查找:

    public static int binarySearch(int[] array, int searchKey){

        int begin = 0;
        int end = array.length - 1;
        while (begin <= end){
            int middle = (begin + end) / 2;
            if (searchKey == array[middle]){
                return middle;
            }else if(searchKey <= array[middle]){
                begin = middle + 1;
            }else {
                end = middle -1;
            }
        }

        return -1;
    }

7.Android系统启动流程

  • 1.启动电源以及系统启动
    当电源按下时引导芯片代码开始从预定义的地方(固化在ROM)开始执行。加载引导程序Bootloader到RAM,然后执行。

  • 2.引导程序BootLoader
    引导程序BootLoader是在Android操作系统开始运行前的一个小程序,它的主要作用是把系统OS拉起来并运行。

  • 3.Linux内核启动
    内核启动时,设置缓存、被保护存储器、计划列表、加载驱动。当内核完成系统设置,它首先在系统文件中寻找init.rc文件,并启动init进程。

  • 4.init进程启动
    初始化和启动属性服务,并且启动Zygote进程。

  • 5.Zygote进程启动
    创建JavaVM并为JavaVM注册JNI,创建服务端Socket,启动SystemServer进程。

  • 6.SystemServer进程启动
    启动Binder线程池和SystemServiceManager,并且启动各种系统服务。
    7.Launcher启动
    被SystemServer进程启动的ActivityManagerService会启动Launcher,Launcher启动后会将已安装应用的快捷图标显示到界面上。

8.Activity的启动流程

  • 1.Activity的启动流程一般是通过调用startActivity或者是startActivityForResult来开始的

  • 2.startActivity内部也是通过调用startActivityForResult来启动Activity,只不过传递的requestCode小于0

  • 3.Activity的启动流程涉及到多个进程之间的通讯这里主要是ActivityThread与ActivityManagerService之间的通讯

  • 4.ActivityThread向ActivityManagerService传递进程间消息通过ActivityManagerNative,ActivityManagerService向ActivityThread进程间传递消息通过IApplicationThread。

  • 5.ActivityManagerService接收到应用进程创建Activity的请求之后会执行初始化操作,解析启动模式,保存请求信息等一系列操作。

  • 6.ActivityManagerService保存完请求信息之后会将当前系统栈顶的Activity执行onPause操作,并且IApplication进程间通讯告诉应用程序继承执行当前栈顶的Activity的onPause方法;

  • 7.ActivityThread接收到SystemServer的消息之后会统一交个自身定义的Handler对象处理分发;

  • 8.ActivityThread执行完栈顶的Activity的onPause方法之后会通过ActivityManagerNative执行进程间通讯告诉ActivityManagerService,栈顶

  • 9.Actiity已经执行完成onPause方法,继续执行后续操作;

  • 10.ActivityManagerService会继续执行启动Activity的逻辑,这时候会判断需要启动的Activity所属的应用进程是否已经启动,若没有启动则首先会启动这个Activity的应用程序进程;

  • 11.ActivityManagerService会通过socket与Zygote继承通讯,并告知Zygote进程fork出一个新的应用程序进程,然后执行ActivityThread的mani方法;

  • 12.在ActivityThead.main方法中执行初始化操作,初始化主线程异步消息,然后通知ActivityManagerService执行进程初始化操作;

  • 13.ActivityManagerService会在执行初始化操作的同时检测当前进程是否有需要创建的Activity对象,若有的话,则执行创建操作;

  • 14.ActivityManagerService将执行创建Activity的通知告知ActivityThread,然后通过反射机制创建出Activity对象,并执行Activity的onCreate方法,onStart方法,onResume方法;

  • 15.ActivityThread执行完成onResume方法之后告知ActivityManagerService onResume执行完成,开始执行栈顶Activity的onStop方法;

  • 16.ActivityManagerService开始执行栈顶的onStop方法并告知ActivityThread;

  • 17.ActivityThread执行真正的onStop方法;

9、JNI

  • (1)安装和下载Cygwin,下载 Android NDK

  • (2)在ndk项目中JNI接口的设计

  • (3)使用C/C++实现本地方法

  • (4)JNI生成动态链接库.so文件

  • (5)将动态链接库复制到java工程,在java工程中调用,运行java工程即可

10.Universal-ImageLoader,Picasso,Fresco,Glide对比

Fresco 是 Facebook 推出的开源图片缓存工具,主要特点包括:两个内存缓存加上 Native 缓存构成了三级缓存,

优点

  • 1. 图片存储在安卓系统的匿名共享内存, 而不是虚拟机的堆内存中, 图片的中间缓冲数据也存放在本地堆内存, 所以, 应用程序有更多的内存使用, 不会因为图片加载而导致oom, 同时也减少垃圾回收器频繁调用回收 Bitmap 导致的界面卡顿, 性能更高。

  • 2. 渐进式加载 JPEG 图片, 支持图片从模糊到清晰加载。

  • 3. 图片可以以任意的中心点显示在 ImageView, 而不仅仅是图片的中心。

  • 4. JPEG 图片改变大小也是在 native 进行的, 不是在虚拟机的堆内存, 同样减少 OOM。

  • 5. 很好的支持 GIF 图片的显示。

缺点:

  • 1. 框架较大, 影响 Apk 体积

  • 2. 使用较繁琐

Universal-ImageLoader:(估计由于HttpClient被Google放弃,作者就放弃维护这个框架)

优点:

  • 1.支持下载进度监听

  • 2.可以在 View 滚动中暂停图片加载,通过 PauseOnScrollListener 接口可以在 View 滚动中暂停图片加载。

  • 3.默认实现多种内存缓存算法 这几个图片缓存都可以配置缓存算法,不过 ImageLoader 默认实现了较多缓存算法,如 Size 最大先删除、使用最少先删除、最近最少使用、先进先删除、时间最长先删除等。

  • 4.支持本地缓存文件名规则定义

Picasso 优点

  • 1. 自带统计监控功能。支持图片缓存使用的监控,包括缓存命中率、已使用内存大小、节省的流量等。

  • 2.支持优先级处理。每次任务调度前会选择优先级高的任务,比如 App 页面中 Banner 的优先级高于 Icon 时就很适用。

  • 3.支持延迟到图片尺寸计算完成加载

  • 4.支持飞行模式、并发线程数根据网络类型而变。 手机切换到飞行模式或网络类型变换时会自动调整线程池最大并发数,比如 wifi 最大并发为 4,4g 为 3,3g 为 2。 这里 Picasso 根据网络类型来决定最大并发数,而不是 CPU 核数。

  • 5.“无”本地缓存。无”本地缓存,不是说没有本地缓存,而是 Picasso 自己没有实现,交给了 Square 的另外一个网络库 okhttp 去实现,这样的好处是可以通过请求 Response Header 中的 Cache-Control 及 Expired 控制图片的过期时间。

Glide 优点

  • 1. 不仅仅可以进行图片缓存还可以缓存媒体文件。Glide 不仅是一个图片缓存,它支持 Gif、WebP、缩略图。甚至是 Video,所以更该当做一个媒体缓存。

  • 2. 支持优先级处理。

  • 3. 与 Activity/Fragment 生命周期一致,支持 trimMemory。Glide 对每个 context 都保持一个 RequestManager,通过 FragmentTransaction 保持与 Activity/Fragment 生命周期一致,并且有对应的 trimMemory 接口实现可供调用。

  • 4. 支持 okhttp、Volley。Glide 默认通过 UrlConnection 获取数据,可以配合 okhttp 或是 Volley 使用。实际 ImageLoader、Picasso 也都支持 okhttp、Volley。

  • 5. 内存友好。Glide 的内存缓存有个 active 的设计,从内存缓存中取数据时,不像一般的实现用 get,而是用 remove,再将这个缓存数据放到一个 value 为软引用的 activeResources map 中,并计数引用数,在图片加载完成后进行判断,如果引用计数为空则回收掉。内存缓存更小图片,Glide 以 url、view_width、view_height、屏幕的分辨率等做为联合 key,将处理后的图片缓存在内存缓存中,而不是原始图片以节省大小与 Activity/Fragment 生命周期一致,支持 trimMemory。图片默认使用默认 RGB_565 而不是 ARGB_888,虽然清晰度差些,但图片更小,也可配置到 ARGB_888。

  • 6.Glide 可以通过 signature 或不使用本地缓存支持 url 过期

11、Xutils, OKhttp, Volley, Retrofit对比

Xutils这个框架非常全面,可以进行网络请求,可以进行图片加载处理,可以数据储存,还可以对view进行注解,使用这个框架非常方便,但是缺点也是非常明显的,使用这个项目,会导致项目对这个框架依赖非常的严重,一旦这个框架出现问题,那么对项目来说影响非常大的。、

OKhttp:Android开发中是可以直接使用现成的api进行网络请求的。就是使用HttpClient,HttpUrlConnection进行操作。okhttp针对Java和Android程序,封装的一个高性能的http请求库,支持同步,异步,而且okhttp又封装了线程池,封装了数据转换,封装了参数的使用,错误处理等。API使用起来更加的方便。但是我们在项目中使用的时候仍然需要自己在做一层封装,这样才能使用的更加的顺手。

Volley:Volley是Google官方出的一套小而巧的异步请求库,该框架封装的扩展性很强,支持HttpClient、HttpUrlConnection, 甚至支持OkHttp,而且Volley里面也封装了ImageLoader,所以如果你愿意你甚至不需要使用图片加载框架,不过这块功能没有一些专门的图片加载框架强大,对于简单的需求可以使用,稍复杂点的需求还是需要用到专门的图片加载框架。Volley也有缺陷,比如不支持post大数据,所以不适合上传文件。不过Volley设计的初衷本身也就是为频繁的、数据量小的网络请求而生。

Retrofit:Retrofit是Square公司出品的默认基于OkHttp封装的一套RESTful网络请求框架,RESTful是目前流行的一套api设计的风格, 并不是标准。Retrofit的封装可以说是很强大,里面涉及到一堆的设计模式,可以通过注解直接配置请求,可以使用不同的http客户端,虽然默认是用http ,可以使用不同Json Converter 来序列化数据,同时提供对RxJava的支持,使用Retrofit + OkHttp + RxJava + Dagger2 可以说是目前比较潮的一套框架,但是需要有比较高的门槛。

Volley VS OkHttp

Volley的优势在于封装的更好,而使用OkHttp你需要有足够的能力再进行一次封装。而OkHttp的优势在于性能更高,因为 OkHttp基于NIO和Okio ,所以性能上要比 Volley更快。IO 和 NIO这两个都是Java中的概念,如果我从硬盘读取数据,第一种方式就是程序一直等,数据读完后才能继续操作这种是最简单的也叫阻塞式IO,还有一种是你读你的,程序接着往下执行,等数据处理完你再来通知我,然后再处理回调。而第二种就是 NIO 的方式,非阻塞式, 所以NIO当然要比IO的性能要好了,而 Okio是 Square 公司基于IO和NIO基础上做的一个更简单、高效处理数据流的一个库。理论上如果Volley和OkHttp对比的话,更倾向于使用 Volley,因为Volley内部同样支持使用OkHttp,这点OkHttp的性能优势就没了, 而且 Volley 本身封装的也更易用,扩展性更好些。

OkHttp VS Retrofit

毫无疑问,Retrofit 默认是基于 OkHttp 而做的封装,这点来说没有可比性,肯定首选 Retrofit。

Volley VS Retrofit

这两个库都做了不错的封装,但Retrofit解耦的更彻底,尤其Retrofit2.0出来,Jake对之前1.0设计不合理的地方做了大量重构, 职责更细分,而且Retrofit默认使用OkHttp,性能上也要比Volley占优势,再有如果你的项目如果采用了RxJava ,那更该使用 Retrofit 。所以这两个库相比,Retrofit更有优势,在能掌握两个框架的前提下该优先使用 Retrofit。但是Retrofit门槛要比Volley稍高些,要理解他的原理,各种用法,想彻底搞明白还是需要花些功夫的,如果你对它一知半解,那还是建议在商业项目使用Volley吧。

12、Socket建立网络连接的步骤

建立Socket连接至少需要一对套接字,其中一个运行与客户端—ClientSocket,一个运行于服务端—ServiceSocket

  • 1、服务器监听:服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态,等待客户端的连接请求。

  • 2、客户端请求:指客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。注意:客户端的套接字必须描述他要连接的服务器的套接字,

        指出服务器套接字的地址和端口号,然后就像服务器端套接字提出连接请求。

  • 3、连接确认:当服务器端套接字监听到客户端套接字的连接请求时,就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述

发给客户端,一旦客户端确认了此描述,双方就正式建立连接。而服务端套接字则继续处于监听状态,继续接收其他客户端套接字的连接请求。

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!