美文网首页面试
Android面试指南二

Android面试指南二

作者: 为自己代颜_ | 来源:发表于2018-05-19 20:37 被阅读17次

    (一)Java虚拟机调用方法的过程;

    JVM结构.jpeg
    如上图:先介绍下JVM几大模块
    1.1 class文件生成模块:通过jdk自带的javac编译命令生成,详细可查看(六)JVM笔记:class文件
    1.2 类加载器子系统模块:JVM运行时自动创建,作用是将class字节码加载到jvm对应的内存中。类加载的核心就是classLoader详细可查看(八)java虚拟机结构详解(JVM)
    1.3 内存空间模块:分为四个部分,分别存储class字节码不同的部分。(九)JVM内存管理和垃圾回收
    1.4 垃圾回收模块
    1.4.1 强引用(必不可少) 垃圾回收器绝对不会回收它。如Object obj = new Object(); obj便    
    是内存不足时,java虚拟机宁愿抛出OutofMemorryError错误导致程序崩溃异常终止,也 不 
    会回收强引用的对象。
    1.4.2 软引用(可有可无)如果内存空间足够,垃圾回收就不会回收它,如果内存空间不足 
    了,就会回收这些对象的内存
    1.4.3 弱引用(可有可无)垃圾回收器一旦发现了只具有弱引用的对象,不管当前内存空间 
    足够与否,都会回收它的内存,当发生GC的时候,弱引用的对象总是被回收
    1.4.4虚引用 当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象 
    的内存之前把这个虚引用加入到与之前关联的引用队列中。与弱引用不同点就是虚引用必须 
    和引用队列联合使用。
    

    1.5 其它模块:如指令集、执行引擎、本地方法接口等等,都属于jvm底层与CPU打交道的
    基本结构介绍完下面介绍下执行过程:
    首先Java虚拟机在执行方法涉及三块区域,堆区,栈区,方法区

    public class Foo{
        public int add(int a,int b){
            return a + b;
        }
    }
    Foo foo = new Foo();
    foo.add(1,3);
    

    以上面的例子来说,Java虚拟机会首先将这个Foo类的字节码加载到方法区还会在栈区开辟一个栈帧,然后new关键字将在堆区实例化Foo的实例,类的实例方法存在一个专门的区叫方法区,事实上类刚装载的时候就被装载好了,不过它们在"睡眠",只是这些方法必须当有对象产生的时候才会"苏醒"。这个实例是方法区Foo类字节码的引用,然后调用add方法。在方法区里面有一个方法表,里面存放了该类的方法及方法的字节码。在调用方法时,查找方法表,将对应的方法加载到栈区中的对应的栈帧中(这里需要提一个概念,栈区里会为类生成对应的栈帧),至此完成了一次JVM调用方法的过程。
    需要注意的是,在堆区实例化的类(也就是创建的对象)对应的都是同一个方法区的类字节码,这是非常重要的一点!
    以上就是JVM方法执行的一个主要流程。

    (二)java中集合,set,List,Map,讲下ArrayList和HashMap的底层实现;
    1 List存储特点:有序可重复;Set存储特点:不可重复;Map 是一种把键对象和值对象映射的集合,它的每一个元素都包含一对键对象和值对象。 Map没有继承于Collection接口 从Map集合中检索元素时,只要给出键对象,就会返回对应的值对象。

    1.1 List中常见的ArrayList和LinkList和vector
    ArrayList:采用的是数组的形式来保存对象的,这种方式将对象放在连续的位置上,数组结构的优点是便于对集合进行快速的随机访问,所以最大的缺点就是插入删除时非常麻烦。原因是当向指定的索引位置插入对象时,会同时将指定索引位置及之后的所有对象相应的向后移动一位.

    LinkList:采用的是将对象放在独立的空间中,而且在每个空间中还保存下一个链接的索引,但缺点是查找非常麻烦,要从第一个索引开始。
    
    vector类称作向量类,它实现了动态数组,用于元素数量变化的对象数组。像数组一样,vector类也用从0开始的下标表示元素的位置;但和数组不同的是,当vector对象创建后,数组的元素个数会随着vector对象元素个数的增大和缩小而自动变化。
    

    1.2 Set存储 :
    hashSet 无序不可重复无下标;
    元素的哈希值是通过元素的hashcode方法 来获取的, HashSet首先判断两个元素的哈希值,如果哈希值一样,接着会比较equals方法 如果 equls结果为true ,HashSet就视为同一个元素。如果equals 为false就不是同一个元素。

    TreeSet 有序不可重复无下标,是用来排序的, 可以指定一个顺序, 对象存入之后会按照指定的顺序排列TreeSet底层是二叉树结构是用来排序的, 且每插入一个新元素(第一个除外)都会调用compareTo()方法去和上一个插入的元素作比较,并按二叉树的结构进行排列可以指定一个顺序, 对象存入之后会按照指定的顺序排列.比如贷款产品列表,按收益多少排序要实现Comparable接口

    1.3 Map存储
    Map 是一种把键对象和值对象映射的集合,它的每一个元素都包含一对键对象和值对象。 Map没有继承于Collection接口 从Map集合中检索元素时,只要给出键对象,就会返回对应的值对象。
    hashMap,hashTable 都实现了map接口,底层是哈希表数据结构; 继承的类不同
    hashMap中的方法是非同步的(多线程中需要同步处理),内部通过一个Entry数组来实现;
    HashMap不是线程安全的类,要想保证线程安全,可以使用

    Map m = Collections.synchronizedMap(new HashMap(...));

    hashTable的方法是同步的(多线程应用程序中不用专门的操作就可以安全的使用)
    hashMap允许null键和null值;
    比较:HashMap比HashTable功能更多,而且它不是基于一些陈旧的类所以一般情况下HashMap优于HashTable

    ConcurrentHashMap和Hashtable主要区别就是围绕着锁的粒度以及如何锁,可以简单理解成把一个大的HashTable分解成多个,形成了锁分离。如图:

    而Hashtable的实现方式是—锁整个hash表
    其实不止用于线程,当设计数据表的事务时(事务某种意义上也是同步机制的体现),可以把一个表看成一个需要同步的数组,如果操作的表数据太多时就可以考虑事务分离了(这也是为什么要避免大表的出现),比如把数据进行字段拆分,水平分表等.

    HashMap的工作原理
    HashMap基于hashing原理,我们通过put()和get()方法储存和获取对象。当我们将键值对传递给put()方法时,它调用键对象的hashCode()方法来计算hashcode,让后找到bucket位置来储存值对象。当获取对象时,通过键对象的equals()方法找到正确的键值对,然后返回值对象。HashMap使用链表来解决碰撞问题,当发生碰撞了,对象将会储存在链表的下一个节点中。 HashMap在每个链表节点中储存键值对对象。

    当两个不同的键对象的hashcode相同时会发生什么? 它们会储存在同一个bucket位置的链表中。键对象的equals()方法用来找到键值对。

    因为HashMap的好处非常多,我曾经在电子商务的应用中使用HashMap作为缓存。因为金融领域非常多的运用Java,也出于性能的考虑,我们会经常用到HashMap和ConcurrentHashMap。你可以查看更多的关于HashMap的文章:

    HashMap和Hashtable的区别
    HashMap和HashSet的区别

    详情可查看http://www.importnew.com/7099.html

    TreeMap是二叉树数据结构,线程不同步,可用于给Map集合中的键进行排序

    各自旗下的子类关系

    Collection
         --List:将以特定次序存储元素。
               --ArrayList / LinkedList / Vector
         --Set : 不能含有重复的元素,自行排序
               --HashSet / TreeSet
          Map
         --HashMap
         --HashTable
         --TreeMap
    

    hashmap和hashtable相比区别,hashmap为什么效率高

    hashmap和hashtable相比效率差不太多
    hashmap为什么效率高其实很简单hashmap底层维护了一个数组,当多线程的时候对这个数组操作是不安全的。 下面就说说数组和hashmap谁的速度快,这其实是个坑,我其实当时回答其实对了一半,我说hashmap比数组快,
    因为hashmap底层是使用一个链表实现的,所以插入删除速度快,

    (三)应用在远程端出现问题了,你该怎么解决?
    用户反馈或者是异常bug收集发现问题,之前可能会等下一个版本,但是这样就太影响用户体验。
    解决办法
    1.根据业务可能的话,添加开关。界面的显示或隐藏,通过后台返回的标志来判断。
    2.强制用户更新弹窗提示,用户体验不太好
    3.使用动态加载技术,比如热修复技术腾讯微信的Tinker或阿里的AndFix
    AndFix 修复一些方法级的一些bug,不可新增类和方法 原理比如方法A出现问题,Andfix会用patch文件生成器对比两个apk包生成一个patch文件,patch文件包含被修复的方法B,使用替换的选择,是bug代码永远执行不到。通过向用户推送或者是用户主动拉取的方式来修复线上bug
    Tinker 腾讯的,不仅可以修复bug还可以进行小版本的发布,可以用gradle依赖和Tinker的核心库来实现,支持类替换,so替换,资源替换等等。
    原理是1基于android原生的classloader
    2基于dex文件格式,使用DexDiff算法

    (四)项目中有什么难点?
    热修复,动态加载技术,kotlin
    (五)设计模式:单例,工厂设计模式
    单例模式有以下特点:
      1、单例类只能有一个实例。
      2、单例类必须自己创建自己的唯一实例。
      3、单例类必须给所有其他对象提供这一实例。
    5.1 饿汉式单例类

        //饿汉式单例类.在类初始化时,已经自行实例化
        public class Singleton1{
        //私有的默认构造子
        private Singleton1(){}
        //已经自行实例化
        private static final Singleton1 single=new Singleton1();
        //静态工厂方法
        public static Singleton1 getInstance(){
        return single;
        }
        }
    
    特点:此类加载时就初始化单例对象,较大时会影响系统加载速度
    
    5.2 懒汉式单例类
    
        //懒汉式单例类.在第一次调用的时候实例化
        public class Singleton2{
        //私有的默认构造子
        private Singleton2(){}
        //注意,这里没有final
        private static Singleton2 single=null;
        //静态工厂方法
        public synchronized static Singleton2 getInstance(){
        if(single==null){
        single=new Singleton2();
        }
        return single;
        }
        }
    
    特点:只有访问到单例对象的时候才去检查和实例化单例对象,多线程访问
    需要加同步锁影响访问效率
    

    5.3 使用静态内部类作为Singleton容器(登记式单例类)

    public class Singleton{
    private Singleton(){ }
    private static class SingletonHolder{
    // 私有内部类在Singleton加载时不初始化
    private static Singleton instance = new Singleton();
    }
    public static Singleton getInstance(){
    return SingletonHolder.instance;
    }
    }
    

    特点:能延迟加载,又能保证线程安全 原理是直接用classLoader(jvm类加载机制) 进行异步加锁的问题,并减少了内存消耗保证了初始化instance时只有一个线程,所以是线程安全的

    工厂设计模式
    1.介绍
    工厂方法模式(Factory Pattern),是创建型设计模式之一。工厂方法模式是一种结构简单的模式,其在我们平时开发中应用很广泛,也许你并不知道,但是你已经使用了无数次该模式了,如Android中的Activity里的各个生命周期方法,以onCreate方法为例,它就可以看作是一个工厂方法,我们在其中可以构造我们的View并通过setContentView返回给framework处理等,相关内容我们下面再讲,先看看工厂方法模式定义。
    2.定义
    定义一个用于创建对象的接口,让子类决定实例化哪个类。
    3.使用场景
    在任何需要生成复杂对象的地方,都可以使用工厂方法模式。复杂对象适合使用工厂模式,用new就可以完成创建的对象无需使用工厂模式。
    4.模式的简单实现
    https://www.cnblogs.com/ouyangduoduo/p/6652789.html

    (六)Dagger2使用详解
    https://www.jianshu.com/p/22c397354997
    (七)
    . Android activity四种启动模式以及应用场

    7.1 standard
    默认模式,可以不用写配置。在这个模式下,每次激活Activity都会默认
    创建一个新的Activity实例并放入栈中,可以有多个相同的实例,也允许
    多个 相同Activity叠加。
    应用场景:Activity可以多次入栈
    7.2 singleTop
    可以有多个实例,但是不允许多个相同Activity叠加。如果Activity在栈
    顶的时候,启动相同的Activity,不会创建新的实例,而会调用其onNewIntent方法,否则会创建新的实例并放入栈顶,即使栈中存在该
    Activity的实例,只要是不在栈顶都会创建新的实例
    应用场景:栈顶存在则复用,不存在则创建;浏览器的书签界面,
    接受通知启动的内容显示界面
    7.3 singleTask
    若栈中存在activity实例,就会重用该实例(调用其onNewIntent
    方法),并将其上的实例移出栈,如果栈中不存在该实例,将会创
    建新的实例放入栈中。
    应用场景:如果一个activity创建需要占用系统大量的资源(如CPU
    , 内存。。。) 就用singleTask;浏览器的Activity不管从多个浏览
    器只会启动主界面一次,其余情况都会走onNewIntent方法并会清
    空主界面上的其它界面
    7.4 singleInstance
    单一实例启动模式 Activity会运行在自己的任务栈中,并且这个任
    务栈中只有一个实例存在,不允许其它activity在本任务栈中。
    应用场景:如果activity在整个手机系统中只允许一个实例存在,
    如电话拨打界面。

    (八)service两种启动方法及区别,如果一个service同时被两种方法启动如何执行
    8.1 首先服务的特点:1 没有界面用户不可见 2 程序在后台运行做耗时操作
    8.2 按照服务的启动方式 将服务划分为两类
    启动服务 startService() 绑定服务 bindService()
    8.3.1 启动式服务:应用程序组件(Activity)调用startService()方法启动服务时称为启动时服务
    特点:
    1)调用startService()方法一旦启动服务就会一直运行在后台 直到服务被杀死(自杀、他杀、系统回收)
    2)启动服务的组件(Activity)被销毁后 启动的服务会一直运行 不受影响
    3)启动式服务在后台执行单一的操作 不会将服务的操作结果返回给启动它的组件
    8.3.2启动式服务的生命周期
    onCreate()—>onStartCommand()—>StopService()

    1. 当应用程序组件(Activity)调用startService()启动服务先回调onCreate()方法创建和初始化service
    2. 接着回调onStartCommand()方法接收Intent意图请求并且开启工作线程执行耗时操作—-
    3. 当service中的操作执行完毕后调用StopSelf()或者时其它组件调用 stopService()就会回调onDestory()释放资源
      context.startService() ->onCreate()- >onStartCommand()->Service running--调用context.stopService() ->onDestroy()
      8.3.3 代码实例
      Intent intent =new Intent(DemoActivity.this, MyService.class);
      startService(intent);
    
    public class MyService extends Service{
    
        @Nullable
        @Override
        public IBinder onBind(Intent intent) {
            return null;
        }
    
        /**
         * 表示当service第一次创建时回调的函数  初始化  注意:service只能被创建一次
         */
        @Override
        public void onCreate() {
            super.onCreate();
            Log.i("tag","--------onCreate-----");
        }
    
        /**
         * 表示当应用程序组件调用startService()方法启动服务时 服务就会启动并且回调该方法
         * @param intent  应用程序组件调用startService()启动服务时传递的Intent对象
         * @param flags   表示开启服务是够需要传递额外的数据
         * @param startId 用来唯一标示start请求
         * @return 表示当系统回调完onStartCommand()方法后 service被系统意外杀死时  是否能够重新启动服务以及
         * 是否可以继续执行请求操作
         *  根据返回值将service划分为粘性service和非粘性service
         *
         * START_STICKY(常量1) STICKY粘性  当应用程序执行完onStartCommand()方法后 service被异常kill
         * 系统会自动重启服务  但是在重启服务时传入的intent为null  车祸苏醒失忆
         *
         * START_NOT_STICKY(常量2) 非粘性  当应用程序执行完onStartCommand()方法后 service被异常kill
         * 系统不会自动重启服务       车祸死亡
         *
         * START_REDELIVER_INTENT(常量3) 当应用程序执行完onStartCommand()方法后 service被异常kill
         * 系统会自动的重启服务并且将Intent重新传入   车祸苏醒正常
         */
        @Override
        public int onStartCommand(Intent intent, int flags, int startId) {
            Log.i("tag","--------onStartCommand-----"+intent.getStringExtra("str"));
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    //        stopSelf();//自杀
            return START_REDELIVER_INTENT;
        }
    
        /**
         * 标示当service被销毁时回调的函数   资源释放
         * stopService();
         */
        @Override
        public void onDestroy() {
            super.onDestroy();
            Log.i("tag","--------onDestroy-----");
        }
    }
    

    8.4 绑定服务 bindService()
    1)特点:onBind()只有采用Context.bindService()方法启动服务时才会回调该方法。该方法在调用者与服务绑定时被调用,
      当调用者与服务已经绑定,多次调用Context.bindService()方法并不会导致该方法被多次调用。
      onUnbind()只有采用Context.unbindService()方法启动服务时才会回调该方法。该方法在调用者与服务解除绑定时被调用。
    2)调用过程:
    当我们bind到一个service的时候,回调用的onBind方法,这时会返回一个IBinder类,这个类个人觉得很想代理模式,通过它来调用service中的方法,在我们bindservice的时候,需要传入一个参数,ServiceConnection,在这个对象里面有两个回调方法,一个是ublic void onServiceConnected(ComponentName name, IBinder service)一个是public void onServiceDisconnected(ComponentName name),在onServiceConnected中的参数service就是我们在onBind方法中返回的IBinder,通过对它的转型,我们就可以调用相应的service中的方法了。
    3)生命周期:
    context.bindService()->onCreate()->onBind()->Service running--调用>onUnbind() -> onDestroy()

    1. Context.bindService()启动
      Intent intent =new Intent(DemoActivity.this, DemoService.class);
      bindService(intent, conn, Context.BIND_AUTO_CREATE);
      //unbindService(conn);//解除绑定
    

    startservice启动服务后,程序退出stopservice,服务依旧存在,而bindservice启动服务后程序退出unbindservice,服务就会销毁,而同时调用两种方法启动同一个方法,只会启动一个服务,先后启动。但是其生命周期有所不同,取决于两种方法启动服务的先后顺序详细可查看:https://blog.csdn.net/wuyouagd/article/details/53378589

    (九)Binder
    Binder是Android系统中进程间通讯(IPC)的一种方式,也是Android系统中最重要的特性之一。Android中的四大组件Activity,Service,Broadcast,ContentProvider,不同的App等都运行在不同的进程中,它是这些进程间通讯的桥梁。正如其名“粘合剂”一样,它把系统中各个组件粘合到了一起,是各个组件的桥梁。
    探究与 Binder 相关的 Java 类
    要说这些类,我们首先要用到它们,最简单的方式就是去创建一个 Service,让它运行在单独的进程中,然后编写 AIDL,实现一个接口,再到 Activity 中去使用这个接口。

    aidl跨进程通信 AIDL的英文全称是Android Interface Define Language 当B进程要去调用A进程中的service时,并实现通信,通过AIDL实现Service的跨进程通信(IPC),其实是通过Binder机制来实现的。我们通常都是通过AIDL来操作的之前整理过的一博客做了详细的介绍:http://blog.csdn.net/yshr1991/article/details/51568752

    (十)打包后文档结构
    https://www.baidu.com/from=844b/bd_page_type=1/ssid=0/uid=0/pu=usm%401%2Csz%401320_2001%2Cta%40iphone_1_11.3_3_605/baiduid=AB5A7A074C09C3CEB17B7467A0F706D1/w=0_10_/t=iphone/l=3/tc?ref=www_iphone&lid=8487901873809066870&order=9&fm=alop&tj=www_normal_9_0_10_title&vit=osres&m=8&srd=1&cltj=cloud_title&asres=1&title=Android%3Aapk文件结构及打包技巧-ThinkDifferent-...&dict=32&wd=&eqid=75cb15c88cc30c00100000035adcad3d&w_qd=IlPT2AEptyoA_yi9IkOvJCowOSwot9-HoyNvshO5dA6NGw694CgQpme3&tcplug=1&sec=29247&di=a3ebee3e494539a7&bdenc=1&tch=124.399.88.1973.3.4507&nsrc=IlPT2AEptyoA_yixCFOxXnANedT62v3IHBO1NilVLD3amFSyxP4kHREsRDHzL8zTUS3cdT0KtRsI&clk_info=%7B%22srcid%22%3A1599%2C%22tplname%22%3A%22www_normal%22%2C%22t%22%3A1524411749604%2C%22xpath%22%3A%22div-a-h3%22%7D&sfOpen=1

    你对网络了解吗,你们项目几个人做的,简单介绍你们项目。
    网络处理Volley和okHttp为什么结合使用

    volley重点在于request的队列的管理,其他的地方都很不强,一般
    都是volley+okhttp接合使用,不过如果没有特别多的网络性能要求(一般而言,比如你们用户量没有超百万)还是默认使用volley那套网络连接方式就够了。
    OkHttp引擎在Android 4.4上是基于HttpURLConnection的。 Twitter, Facebook 和 Snapch都采用了它。

    Volley的工作方式是创建不同的request,然后把它们添加到队列中(queue)。一个项目只需要一个queue就足够了,每次你想创建一个request的时候你都只需要获得这个唯一的queue来添加。

    Volley可以轻松设置OkHttp作为其传输层,Request参数中设置HttpStack

    volley 只是适合小数据的传输,上传文件下载文件这些都支持的不是很好

    Volley/Gson的解决方案比较成熟,因为这是谷歌的解决方案,同时也因为出现在安卓开发者网站上,因此在2013到2014年都非常流行。到目前为止,这仍然是一个很好的选择,它是简单有效的。不过需要考虑一下的是Volley和Gson现在不怎么更新了。

    okhttp还比volley有更多的选择。文中说的组合是说替换volley的HttpStack为okhttp的stack。并不是说okhttp只做了传输层的封装……volley的网络请求过程还就直接封装成异步的了……
    拓展性高的是okhttp,数据流还是用okio来优化的,请求过程有同步,也有异步方法,响应体保持同步阻塞,大大的方便了有进行连续多次网络请求的需求。

    相关链接:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0720/3209.html

    Retrofit 和rxjava的结合

    相关文章

      网友评论

        本文标题:Android面试指南二

        本文链接:https://www.haomeiwen.com/subject/lhlklftx.html