面试八连问,你能接下几招?

1.横竖屏切换时Activity的生命周期

  • 在没有配置activity的configChanges属性时 Activity的生命周期:onPause->onStop->onCreate->onStart->onResume
  • 配置了activity的configChanges onConfigurationChanged

2..如何设置activity成窗口样式

ruby 复制代码
android:theme="@android:style/Theme.Dialog"

3..Activity的启动方式

  • standard 不管有没有已存在的实例,都生成新实例
  • singleTop 如果发现有对应的Activity实例位于栈顶,则重复利用,否则创建实例
  • singleTask a)栈内复用,复用时具有clearTop机制 b)single taskAffinity in task
  • singleInstance a)启用一个新的栈结构,将Activity放置于栈结构中,并保证不会有其它Activity实例进入 b)方便多个应用共享全局唯一的实例

4.Service的生命周期

通过startService调用 onCreate->onStartCommand->onDestroy 通过bindService调用 onCreate->onBind->onUnbind->onDestroy

Service Binder

scala 复制代码
>>//示例
  a).public class LocalService extends Service{
    private static LBinder binder=new LBinder();            //必须声明为static
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
      super.onCreate(savedInstanceState);
    }
        @Override
    public IBinder onBind(Intent intent)
    {
      return binder;
    }
    
    static class LBinder extends Binder{
      public int getCount(){
        return count;
      }
    }
    }
​

5..IntentService

//开启线程处理耗时操作

arduino 复制代码
>>onCreate              //创建单独的HandlerThread处理所有的intent请求
  :1.HandlertThread thread = new HandlerThread("") 
     thread.start()
     mServiceLooper =thread.getLooper()
     mServiceHandler =new ServiceHandler(mServiceLooper) 
​

onSTartCommand.

arduino 复制代码
  :1.public int onStartCommand(Intent intent,int startId){
    onStart(intent,startId)
    return mRedelivery?START_REDELIVER_INTENT:START_NOT_STICKY;//在于service挂掉了intent是否会回传
     }
  :2.public void onStart(Intent intent,int startId)
     {
    Message msg=mServiceHandler.obtainMessage()
        msg.obj=intent
    msg.arg1=startId
    mServiceHandler.sendMessage(msg)
     }
​

ServiceHandler.

scala 复制代码
//onHandleIntent,为自定义的类所实现的耗时操作
  :1.private final class ServiceHandler extends Handler{
    public ServiceHandler(Looper looper){super(looper);}
    public void handleMessage(Message msg)
       {
      onHandleIntent((Intent)msg.obj)
      stopSelf(msg.arg1)//当id为最后一个startService的startId时,进行关闭
    }
     }

总结

a).通过HandlerThread创建线程,并调用getLooper获取looper变量,以新建Handler

b).IntentService通过onStartCommand接收新的intent,并放在message.obj发送过handler

c).handler调用实现的onHandleIntent(intent)

d).Message msg=mServiceHandler.obtainMessage() //obtainMessage和Looper调用msg.relycleUnchecked是以插入链表头部和拆除的形式分配、释放Message资源

6.Android有哪几种布局

  • LinearyLayout 按照水平或垂直的顺序排列子元素,android:layout_weight生效
  • FrameLayout 后来的子元素覆盖在之前子元素的上层,以左上角为基准点
  • AbsoluteLayout android:layout_x和android:layout_y会生效
  • RelativeLayout android:layout_below和android:layout_above

TableLayout //表格布局由TableRow构成,layout_column指定开始列,layout_span制定范围 a).全局属性 android:shrinkColumns="0,1" //可收缩的列 android:stretchColumns="0,1" //可拉伸的列 b).单元格属性 android:layout_span="2" //该TableRow中的子View所横跨的列 android:layout_column="1" //制定子view在哪列开始排列

7.HashMap、HashTable的区别 //从线程安全性、速度

scss 复制代码
>>线程方面
  :1.HashTable是线程安全的
>>HashTable的key、value不允许设置null,而hashmap可以
>>是否可在遍历时进行操作
  :1.HashMap:fail-fast  
     a).既在遍历HashMap时,如果通过实例对象进行修改则报ConcurrentModificationException, 
      expectedModCount==modCount
>>解决冲突
  :1.HashMap的装载因子是3/4,链接法解决冲突,当链表过长时转化为红黑树
>>实现
  :1.HashMap 通过数组+链表的结构
    >transient Node<K,V>[] table;
    >static class Node<K,V> implements Map.Entry<K,V>{
    final int hash;
    final K key;
    final V value;
    Node<K,V> next;
    public final int hashCode(){
      return Objects.hashCode(key)^Objects.hashCode(v)
    }
     }
  :2.hashCode 通过Objects.hashCode(key)^Objects.hashCode(value)计算
  :3.扩容  哈希桶的容量形若100...0,扩容容量为2倍,阀值也为2倍,需要rehash
    >final Node<K,V>[] resize(){
    //oldTab 为当前表的哈希桶
        Node<K,V>[] oldTab = table;
    int oldCap=oldTab.length
    //当前的阈值
        int oldThr = threshold;
    if (oldCap > 0) {
      if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
      {
         newThr=oldThr<<1;
      }
    }   
    //更新阈值 
        threshold = newThr
    table= newTab
        if(oldTab!=null){
       for (int j = 0; j < oldCap; ++j) {
         Node<K,V> e;
         if((e=oldTab[j])!=null){
        oldTab[j] = null//将原哈希桶置空以便GC
        if (e.next == null)
          newTab[e.hash & (newCap - 1)] = e; //直接将这个元素放置在新的哈希桶里
        else
        {
           //因为扩容是容量翻倍,所以原链表上的每个节点,现在可能存放在原来的下标,即low位, 或者扩容后的下标,
           //即high位。 high位=  low位+原哈希桶容量
                   //低位链表的头结点、尾节点
                   Node<K,V> loHead = null, loTail = null;
           //高位链表的头节点、尾节点
                   Node<K,V> hiHead = null, hiTail = null;
                   Node<K,V> next;//临时节点 存放e的下一个节点
           do {
                      next = e.next;
                      //这里又是一个利用位运算 代替常规运算的高效点: 利用哈希值 与 旧的容量 可以得到哈希值去模后,
              //是大于等于oldCap还是小于oldCap,等于0代表小于oldCap,应该存放在低位,否则存放在高位
                      if ((e.hash & oldCap) == 0) {
                         //给头尾节点指针赋值
                         if (loTail == null)
                            loHead = e;
                         else
                            loTail.next = e;
                         loTail = e;
                       }//高位也是相同的逻辑
                          else {
                            if (hiTail == null)
                               hiHead = e;
                            else
                               hiTail.next = e;
                            hiTail = e;
                          }//循环直到链表结束
                        } while ((e = next) != null)
            //将低位链表存放在原index处,
                        if (loTail != null) {
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        //将高位链表存放在新index处
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
        }
         }
​
    }
     }
​
  :4.插入节点
     >public V put(K key,V value){return putVal(hash(key),key,value)}
     >final V putVal(int hash,K key,V value,...)
      {
    Node<K,V>[] tab=table 
    int n=tab.length
    Node<K,V> p;
    //如果当前哈希表是空的,代表是初始化
        if ((tab = table) == null || (n = tab.length) == 0)
            //那么直接去扩容哈希表,并且将扩容后的哈希桶长度赋值给n
            n = (tab = resize()).length;
    //如果当前index的节点是空的,表示没有发生哈希碰撞。 直接构建一个新节点Node,挂载在index处即可。
        //这里再啰嗦一下,index 是利用 哈希值 & 哈希桶的长度-1,替代模运算
        if(p=tab[I=?(n-1)&hash)==null)      
      tab[I]=newNode(hash,key,value,null)
    else//发生了冲突
    {
      Node<K,V> e;K k;
      if(p.hash==hash && ((k=p.key)==key || key.equals(k))//键值相同,寻找成功
         e=p;
      else if(p instance TreeNode)          //红黑树
         e=TreeNode<K,V)p.putTreeVal(this, tab,hash,key,value)
      else{
         //遍历链表
         for(int binCount=0;;++binCount)
         {
        if((e=p.next)==null)            //遍历到尾部,追加新节点到尾部
        {  
           p.next=newNode(hash, key, value)     //生成结点并插入
           if(binCount>=TREEIFY_THRESHOLD-1)    //将链表转化为树结构
              treeifyBin(tab,hash)
           break
        }
        //如果找到了要覆盖的节点
        if(e.hash==hash&&((k=e.key)==key)
           break
        p=e                 //迭代
         }
      }
      if(e!=null)
         e.value=value              //设置新值
    }
      }
  :5.删除结点
    >final Node<K,V> removeNode(int hash, Object key, Object value,
                               boolean matchValue, boolean movable) {
    // p 是待删除节点的前置节点
        Node<K,V>[] tab; Node<K,V> p; int n, index;
    if ((tab = table) != null && (n = tab.length) > 0 &&
            (p = tab[index = (n - 1) & hash]) != null) {
      Node<K,V> node = null, e; K k; V v
      //如果链表头的就是需要删除的节点
          if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                node = p;//将待删除节点引用赋给node
      else if ((e = p.next) != null) {//否则循环遍历 找到待删除节点,赋值给node
         if (p instanceof TreeNode)
                    node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
              else {
                do {
                    if (e.hash == hash &&
                        ((k = e.key) == key ||
                          (key != null && key.equals(k)))) {
                        node = e;
                        break;
                    }
                    p = e;
                } while ((e = e.next) != null);
              }
      }
      //如果有待删除节点node,  且 matchValue为false,或者值也相等
          if (node != null && (!matchValue || (v = node.value) == value ||
                                 (value != null && value.equals(v)))) {
             if (node instanceof TreeNode)
                    ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
             else if (node == p)//如果node ==  p,说明是链表头是待删除节点
                tab[index] = node.next;
             else//否则待删除节点在表中间
                p.next = node.next;
             ++modCount;//修改modCount
             --size;//修改size
             return node;
          }
       }
     }
  :6.查找
   >final Node<K,V> getNode(int hash, Object key) {
        Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
        //查找过程和删除基本差不多, 找到返回节点,否则返回null
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (first = tab[(n - 1) & hash]) != null) {
            if (first.hash == hash && // always check first node
                ((k = first.key) == key || (key != null && key.equals(k))))
                return first;
            if ((e = first.next) != null) {
                if (first instanceof TreeNode)
                    return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                do {
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        return null;
    }
   :7.iterator遍历
    > final Node<K,V> nextNode() {
         Node<K,V>[] t;
         Node<K,V> e = next;
         //fail-fast策略
         if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
         if (e == null)
            throw new NoSuchElementException();
         //依次取链表下一个节点,
         if ((next = (current = e).next) == null && (t = table) != null) {
           //如果当前链表节点遍历完了,则取哈希桶下一个不为null的链表头
           do {} while (index < t.length && (next = t[index++]) == null);
          }
          return e;
        }
40.LinkedHashMap 
>>成员 
  :1.LinkedHashMapEntry         //增加了before和after域,成为双向链表
     static class LinkedHashMapEntry<K,V> extends HashMap.Node<K,V> {
        LinkedHashMapEntry<K,V> before, after;//双向链表
        LinkedHashMapEntry(int hash, K key, V value, Node<K,V> next) {
            super(hash, key, value, next);
        }
     }
  :2.head tail
   >transient LinkedHashMap.Entry<K,V> head //双向链表的头结点
   >transient LinkedHashMap.Entry<K,V> tail //双向链表的尾节点
  :3.accessOrder    //默认为false,是按照插入结点的顺序,若为true,则可构造LruCache
>>重写newNode,在HashMap调用putVal()方法被调用
    >//在构建新节点时,构建的是`LinkedHashMap.Entry` 不再是`Node`.
     Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
       LinkedHashMap.Entry<K,V> p =
            new LinkedHashMap.Entry<K,V>(hash, key, value, e);
       linkNodeLast(p);  //!!!
       return p;
     }
     //将新增的节点,连接在链表的尾部
     private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
        LinkedHashMap.Entry<K,V> last = tail;
        tail = p;
        //集合之前是空的
        if (last == null)
            head = p;
        else {//将新节点连接在链表的尾部
            p.before = last;
            last.after = p;
        }
     }
>>删除结点
    >//在删除节点e时,同步将e从双向链表上删除
     void afterNodeRemoval(Node<K,V> e) { // unlink
        LinkedHashMap.Entry<K,V> p =
            (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
        //待删除节点 p 的前置后置节点都置空
        p.before = p.after = null;
        //如果前置节点是null,则现在的头结点应该是后置节点a
        if (b == null)
            head = a;
        else//否则将前置节点b的后置节点指向a
            b.after = a;
        //同理如果后置节点时null ,则尾节点应是b
        if (a == null)
            tail = b;
        else//否则更新后置节点a的前置节点为b
            a.before = b;
     }
>>get  //获取结点
   >public V get(Object key) {
        Node<K,V> e;
        if ((e = getNode(hash(key), key)) == null)
            return null;
        if (accessOrder)
            afterNodeAccess(e);//因为访问了该结点,所以动态调整其位置
        return e.value;
    }  
   >若accessOrder==true          //按访问顺序
    void afterNodeAccess(Node<K,V> e) { // move node to last
       LinkedHashMapEntry<K,V> last;//原尾节点
       //如果accessOrder 是true ,且原尾节点不等于e
       if (accessOrder && (last = tail) != e) {
            //节点e强转成双向链表节点p
            LinkedHashMapEntry <K,V> p =
                (LinkedHashMapEntry <K,V>)e, b = p.before, a = p.after;
            //p现在是尾节点, 后置节点一定是null
            p.after = null;
            //如果p的前置节点是null,则p以前是头结点,所以更新现在的头结点是p的后置节点a
            if (b == null)
                head = a;
            else//否则更新p的前直接点b的后置节点为 a
                b.after = a;
            //如果p的后置节点不是null,则更新后置节点a的前置节点为b
            if (a != null)
                a.before = b;
            else//如果原本p的后置节点是null,则p就是尾节点。 此时 更新last的引用为 p的前置节点b
                last = b;
            if (last == null) //原本尾节点是null  则,链表中就一个节点
                head = p;
            else {//否则 更新 当前节点p的前置节点为 原尾节点last, last的后置节点是p
                p.before = last;
                last.after = p;
            }
            //尾节点的引用赋值成p
            tail = p;
            //修改modCount。
            ++modCount;
        }
    }
​

8.如何在ListView间添加分割线

ini 复制代码
//推荐用divider设置drawable的分割线
>>.设置全局属性			
   a).android:divider="#FFF"   	        //设置为null可无视间距     
   b).android:dividerHeight="1px"    
   c).默认的list view不支持设置footerDevider,采取在每个item布局中添加
      > <View
          android:layout_width="match_parent"
          android:layout_height="1dp"
          android:background="#999"
          />

今日分享到此结束,对你有帮助的话,点个赞再走呗,每日一个面试小技巧
关注公众号:Android老皮

解锁 《Android十大板块文档》 ,让学习更贴近未来实战。已形成PDF版

内容如下

1.Android车载应用开发系统学习指南(附项目实战)
2.Android Framework学习指南,助力成为系统级开发高手
3.2023最新Android中高级面试题汇总+解析,告别零offer
4.企业级Android音视频开发学习路线+项目实战(附源码)
5.Android Jetpack从入门到精通,构建高质量UI界面
6.Flutter技术解析与实战,跨平台首要之选
7.Kotlin从入门到实战,全方面提升架构基础
8.高级Android插件化与组件化(含实战教程和源码)
9.Android 性能优化实战+360°全方面性能调优
10.Android零基础入门到精通,高手进阶之路

相关推荐
Lee川16 小时前
优雅进化的JavaScript:从ES6+新特性看现代前端开发范式
javascript·面试
Lee川19 小时前
从异步迷雾到优雅流程:JavaScript异步编程与内存管理的现代化之旅
javascript·面试
晴殇i21 小时前
揭秘JavaScript中那些“不冒泡”的DOM事件
前端·javascript·面试
绝无仅有21 小时前
Redis过期删除与内存淘汰策略详解
后端·面试·架构
绝无仅有1 天前
Redis大Key问题排查与解决方案全解析
后端·面试·架构
AAA梅狸猫1 天前
Looper.loop() 循环机制
面试
AAA梅狸猫1 天前
Handler基本概念
面试
Wect1 天前
浏览器缓存机制
前端·面试·浏览器
掘金安东尼1 天前
Fun with TypeScript Generics:玩转 TS 泛型
前端·javascript·面试
掘金安东尼1 天前
Next.js 企业级落地
前端·javascript·面试