面试八连问,你能接下几招?
1.横竖屏切换时Activity的生命周期
- 在没有配置activity的configChanges属性时 Activity的生命周期:onPause->onStop->onCreate->onStart->onResume
- 配置了activity的configChanges onConfigurationChanged
2..如何设置activity成窗口样式
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
>>//示例
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
//开启线程处理耗时操作
>>onCreate //创建单独的HandlerThread处理所有的intent请求
:1.HandlertThread thread = new HandlerThread("")
thread.start()
mServiceLooper =thread.getLooper()
mServiceHandler =new ServiceHandler(mServiceLooper)
onSTartCommand.
: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.
//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的区别 //从线程安全性、速度
>>线程方面
: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间添加分割线
//推荐用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零基础入门到精通,高手进阶之路
转载自:https://juejin.cn/post/7266998476134973474