当前位置:   article > 正文

2024安卓岗位面试题总结_2024android面试题

2024android面试题

文章目录

数据结构

在这里插入图片描述

ArrayList

自动扩容:

Collection - ArrayList 源码解析

LinkedList

  • 栈操作
LinkedList<String> queue = new LinkedList<>();
queue.push("1");
queue.push("2");
queue.push("3");
System.out.println(queue.pop());
System.out.println(queue.pop());
System.out.println(queue.pop());
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 双端队列
LinkedList<String> deque = new LinkedList<>();
deque.offerFirst("1");
deque.offerFirst("2");
deque.offerFirst("3");
System.out.println(deque.pollLast());
System.out.println(deque.pollLast());
System.out.println(deque.pollLast());
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

Collection - LinkedList源码解析

HashMap

Map - HashSet & HashMap 源码解析

Java7 HashMap

在这里插入图片描述

Java8 HashMap

在这里插入图片描述

ThreadLocal

ThreadLocal 是一个关于创建线程局部变量的类。

public T get() {
	Thread t = Thread.currentThread(); //code 1
	ThreadLocalMap map = getMap(t);
	if (map != null) {
		ThreadLocalMap.Entry e = map.getEntry(this);
		if (e != null) {
			@SuppressWarnings("unchecked")
			T result = (T)e.value;
			return result;
		}
	}
	return setInitialValue();
}

public void set(T value) {
	Thread t = Thread.currentThread(); //code 2
	ThreadLocalMap map = getMap(t);
	if (map != null)
		map.set(this, value);
	else
		createMap(t, value);
}
	
ThreadLocalMap getMap(Thread t) {
	return t.threadLocals;
}

/**
* Variant of set() to establish initialValue. Used instead
* of set() in case user has overridden the set() method.
*
* @return the initial value
*/
private T setInitialValue() {
	T value = initialValue();
	Thread t = Thread.currentThread();
	ThreadLocalMap map = getMap(t);
	if (map != null)
		map.set(this, value);
	else
		createMap(t, value);
	return value;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

ThreadLocalMap是什么

static class ThreadLocalMap {
	static class Entry extends WeakReference<ThreadLocal<?>> {
		/** The value associated with this ThreadLocal. */
		Object value;
		Entry(ThreadLocal<?> k, Object v) {
			super(k);
			value = v;
		}
	}
	private Entry[] table;
	//省略部分代码
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

ThreadLocal在Looper中的应用

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private static void prepare(boolean quitAllowed) {
	if (sThreadLocal.get() != null) {//code 1
		throw new RuntimeException("Only one Looper may be create per thread");
	}
	sThreadLocal.set(new Looper(quitAllowed));// code 2
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

SparseArray

1.通过二分法查找数据。
2.不适合存储大数据。
3.比起HashMap,执行效率慢,查找通过二分法,添加和删除需要插入和删除数组中的条目。
为了提高性能,SparseArray在删除时做了一个优化,不会实际删除项,而是将需要删除的对象做一个标记。

SparseArray的使用

Java 中单例模式

public class SingleTon {
    //需要注意的是volatile
    private static volatile SingleTon mInstance;
    private SingleTon() {
    }
    public static SingleTon getInstance() {
        if (mInstance == null) { 
            synchronized (SingleTon.class) {
                if (mInstance == null) {
                    mInstance=new SingleTon();
                }
            }
        }
        return mInstance;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

Java中生产者与消费者模式

​三种种实现方式:wait 和 notify、await 和 signal、BlockingQueue。​

  • wait 和 notify
public class StorageWithWaitAndNotify {
    private final int MAX_SIZE = 10;
    private LinkedList<Object> list = new LinkedList<Object>();
    public void produce() {
        synchronized (list) {
            while (list.size() == MAX_SIZE) {
                System.out.println("仓库已满:生产暂停");
                try {
                    list.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            list.add(new Object());
            System.out.println("生产了一个新产品,现库存为:" + list.size());
            list.notifyAll();
        }
    }
    public void consume() {
        synchronized (list) {
            while (list.size() == 0) {
                System.out.println("库存为0:消费暂停");
                try {
                    list.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            list.remove();
            System.out.println("消费了一个产品,现库存为:" + list.size());
            list.notifyAll();
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • await 和 signal
import java.util.LinkedList;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
class StorageWithAwaitAndSignal {
    private final int MAX_SIZE = 10;
    private ReentrantLock mLock = new ReentrantLock();
    private Condition mEmpty = mLock.newCondition();
    private Condition mFull = mLock.newCondition();
    private LinkedList<Object> mList = new LinkedList<Object>();
    public void produce() {
        mLock.lock();
        while (mList.size() == MAX_SIZE) {
            System.out.println("缓冲区满,暂停生产");
            try {
                mFull.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        mList.add(new Object());
        System.out.println("生产了一个新产品,现容量为:" + mList.size());
        mEmpty.signalAll();
        mLock.unlock();
    }
    public void consume() {
        mLock.lock();
        while (mList.size() == 0) {
            System.out.println("缓冲区为空,暂停消费");
            try {
                mEmpty.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        mList.remove();
        System.out.println("消费了一个产品,现容量为:" + mList.size());
        mFull.signalAll();
        mLock.unlock();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • BlockingQueue
public class StorageWithBlockingQueue {
    private final int MAX_SIZE = 10;
    private LinkedBlockingQueue<Object> list = new LinkedBlockingQueue<Object>(MAX_SIZE);

    public void produce() {
        if (list.size() == MAX_SIZE) {
            System.out.println("缓冲区已满,暂停生产");
        }
        try {
            list.put(new Object());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("生产了一个产品,现容量为:" + list.size());
    }

    public void consume() {
        if (list.size() == 0) {
            System.out.println("缓冲区为空,暂停消费");
        }
        try {
            list.take();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("消费了一个产品,现容量为:" + list.size());
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

高级UI面试题汇总

1. View的绘制原理

  1. 调用生命周期,通过handleResumeActivity调用addView方法
  2. 调用WindowManagerGlobal的addView
  3. 调用ViewRootImpl中的requestLayout
  4. 调用scheduleTraversals()方法,这里设计一个同步栅栏概念
  5. 调用onMeasure测量方法
  6. layout布局
  7. draw绘制流程

ViewRootImpl.java
WindowManagerGlobal.java
WindowManagerService.java
PhoneWindow.java

2. View,WindowManagerGlobal,WindowManagerService之间的关系

在这里插入图片描述
在这里插入图片描述
Window, WindowManager和WindowManagerService之间的关系

3. requestLayout和invalidate区别

在这里插入图片描述

比较一下requestLayout和invalidate方法
requestLayout和invalidate区别

4. View的绘制流程是从Activity的哪个生命周期方法开始执行的

View的绘制流程是从Activity的 onResume 方法开始执行的。 首先我们找到 onResume 在哪儿执行的,

// ActivityThread.java
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward, String reason) {
	// 1 执行 onResume 流程
	final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
	// 2 执行 View 的流程
	wm.addView(decor, l);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

ActivityThread.java

5. Activity, Window, View三者的联系和区别

  1. Activity,Window, View分别是什么
  • Activity是安卓四大组件之一,负责界面、交互和业务逻辑处理;
  • Window对安卓而言,是窗体的一种抽象,是顶级Window的外观与行为策略。目前仅有的实现类是PhoneWindow;
  • View是放在Window容器的元素,Window是View的载体,View是Window的具体展示;
  1. Window和View的关系
  • Window是一个界面的窗口,适用于存放View的容器,即Window是View的管理者。安卓中所有的视图都是通过Window来呈现的,比如Activity、Dialog、Toast;
  • Window的添加、删除和更改是通过WindowManager来实现的,而WindowManager又是继承ViewManager。
  • WindowManager也是一个接口,它的唯一实现类是WindowManagerImpl,所以往往在管理view的时候,系统是通过WindowManagerImpl来管理view。
  • WindowManagerImpl内部的实现极其简单,它会将所有的方法全部转交给WindowManagerGlobal中的方法来进行。
    windowManagerGlobal 通过调用 ViewRootImpl 来完成对view的操作。
  • ViewRootImpl 是视图层次结构的顶部,它实现了View与WindowManager之间所需要的协议,并且大部分的
    WindowManagerGlobal的内部实现是通过ViewRootImpl来进行的。
  1. Activity与Window的关系
  • Activity是向用户展示一个界面,并可以与用户进行交互。我们通过分析原理发现Activity内部持有一个Window对象,用户管理View。
  • 从Activity的attch函数中是可以发现,新建了一个PhoneWindow对象并赋值给了mWindow。Window相当于Activity的管家,用于管理View的相关事宜。事件的分发,其实也是先交予Window再向下分发。
  • 从上面的代码中可以发现,事件被传递到了Window的superDispatchTouchEvent方法,再接着会传递给我们的DecorView。

Activity.java

6. 在onResume中是否可以测量宽高

不一定
如果通过恢复onResume, 则可以; 若不是恢复,则不可以;
在 onResume() 中 handler.post(Runnable) 是无法正确的获取不到 View 的真实宽高

7. 如何更新UI, 为什么子线程不能更新UI?

在requestLayout中会检查线程

ViewRootImpl.java

8. DecorView, ViewRootImpl,View之间的关系

在这里插入图片描述

9. 事件分发机制是什么过程?

  1. 事件分发涉及的方法
  • dispatchTouchEvent : 事件分发逻辑
    事件分发逻辑,返回值boolean类型. true表示该View或ViewGroup处理了事件, 反之返回false.
    dispatchTouchEvent与onTouchEvent的区别在于,默认情况下前者的返回值依赖于后者的返回值, 而且前者的侧重点在于制定事件分发的流程,后者侧重点在于View或者ViewGroup是否处理该事件.

  • onInterceptTouchEvent: 是否拦截事件(ViewGroup)
    是ViewGroup专属的方法.当返回值为true表示ViewGroup需要拦截该事件. 这里有两种情况:

    • 处理Down事件时,如果返回true,那么时间会直接交给当前ViewGroup处理, 如果返回false,交给ViewGroup最后一个View处理.
    • 处理MOVE,UP事件时, 当target为空, 不会调用该方法; 当target不为空, 而且返回值为true, 那么将生成CANCEL事件交给view分发,先置空view,接下来会调用viewGroup的onTouchEvent
  • onTouchEvent:是否处理事件

    • DOWN事件返回true. 表示处理该事件
    • DOWN事件返回false. 交由兄弟分发.

View.java
ViewGroup.java

10. 事件冲突出发时机以及分发流程

  • 内部拦截法
    getParent().requestDisallowInterceptTouchEvent(true);
  • 外部拦截法
    外部拦截法直接在父布局中判断是否需要自己处理该事件, onInterceptTouchEvent返回为true

11. View中onTouch,onTouchEvent和onClick的执行顺序

  1. onTouch的执行
    首先会判断用户是否调用了setOnTouchListener.
  2. onTouchEvent的执行
    onTouchEvent 是否执行,由 onTouch 的返回值影响。如果 onTouch 返回 true,则 result 为true,这个onTouchEvent 方法不会执行。反之 onTouchEvent 方法才会执行。
  3. onClick的执行
    view内置诸如click事件的实现等等都基于onTouchEvent的performClick方法,假如onTouch返回true,这些事件将不会被触发。

12. 怎么拦截事件?如果 onTouchEvent返回false, onClick还会执行么?

父容器通过重写 onInterceptTouchEvent 方法并返回true 达到拦截事件的目的。

onTouchEvent如果返回false onClick还会执行么?
onClick是在 onTouchEvent 方法内部执行的,所以onClick是否执行,与 onTouchEvent 方法的返回值显然是没关系的。

13. 事件的分发机制,责任链模式的优缺点

责任链模式的缺点:

  1. 不能保证每个请求一定被处理。由于一个请求没有明确的接收者,所以不能保证它一定会被处理,该请求可能一直
    传到链的末端都得不到处理。
  2. 对比较长的职责链,请求的处理可能涉及多个处理对象,系统性能将受到一定影响。
  3. 职责链建立的合理性要靠客户端来保证,增加了客户端的复杂性,可能会由于职责链的错误设置而导致系统出错,如可能会造成循环调用。

设计模式之职责链模式

14. ScrollView下嵌套一个RecycleView通常会出现什么问题

滑动卡顿解决方案

  • 方式1:利用RecyclerView内部方法
recyclerView.setHasFixedSize(true);
recyclerView.setNestedScrollingEnabled(false);
  • 1
  • 2
  • 方式2:重写LayoutManager
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this) {
	@Override
	public boolean canScrollVertically() {
		return false;
	}
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

综合解决方案

  • 方式1:插入LinearLayout/RelativeLayout 在原有布局中插入一层LinearLayout/RelativeLayout,形成如下布局
ScrollView
	LinearLayout/RelativeLayout
		RecyclerView
  • 1
  • 2
  • 3
  • 方式2:重写LayoutManager 该方法的核心点在于通过重写LayoutManager中的onMeasure()方法,即
@Override
public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state, int widthSpec, int heightSpec) {
	super.onMeasure(recycler, state, widthSpec, heightSpec);
	// 重新实现RecyclerView高度的计算,使得其能够在ScrollView中表现出正确的高度.
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 方式3:重写ScrollView 该方法的核心点在于通过重写ScrollView的onInterceptTouchEvent(MotionEvent ev)方法,拦截滑动事件,使得滑动事件能够直接传递给RecyclerView,具体重写方式可参考如下
public class RecyclerScrollView extends ScrollView {
    private int slop;
    private int touch;
    public RecyclerScrollView(Context context) {
        super(context);
        setSlop(context);
    }
    public RecyclerScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
        setSlop(context);
    }
    public RecyclerScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        setSlop(context);
    }
    /**
     * 是否intercept当前的触摸事件
     *
     * @param ev 触摸事件
     * @return true:调用onMotionEvent()方法,并完成滑动操作
     */
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                // 保存当前touch的纵坐标值
                touch = (int) ev.getRawY();
                break;
            case MotionEvent.ACTION_MOVE:
                // 滑动距离大于slop值时,返回true
                if (Math.abs((int) ev.getRawY() - touch) > slop) return true;
                break;
        }
        return super.onInterceptTouchEvent(ev);
    }
    /**
     * 获取相应context的touch slop值(即在用户滑动之前,能够滑动的以像素为单位的距离)
     *
     * @param context ScrollView对应的context
     */
    private void setSlop(Context context) {
        slop = ViewConfiguration.get(context).getScaledTouchSlop();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44

15. View.inflater过程与异步inflater

  • View.inflater
    整个Inflater.inflate的过程,其实就是就是先进行xml解析,拿到xml中相关的tag的情况下,然后通过反射创建tag对
    应的View对象的一个流程。
  • 异步inflate
    AsyncLayoutInflater

16. inflater创建view效率为什么比new View慢?

Inflate 是通过反射xml标签中的类开创建view的,这个反射过程相对比较耗时,因此它比直接在java中创建View的效率要高。

17. 动画的分类以及区别

  • 帧动画:由数张图片连续播放产生的动画效果。
  • 补间动画:对View的平移,旋转,缩放,透明产生效果;
  • 属性动画:动态的改变属性产生动画效果;

18. WebView如何做资源缓存?

一般H5的加载流程:

  • 加载开始前做初始化工作,包括Runtime初始化,创建WebView等;
  • 完成初始化之后,WebView开始去请求资源加载H5页面;
  • 页面发起CGI请求对应的数据(或者通过本地缓存获取),拿到数据后对DOM进行操作更新。

从流程上看存在的直观的问题:

  • 终端耗时:终端初始化阶段的时间里(网络资料显示耗时在1s以上),网络完全是处于空闲等待状态的,浪费时间;
  • 资源和数据动态拉取,获取的速度受制于网络速度;

主流的解决方案有:

  • WebView自带的H5缓存机制
  • 预加载
  • 离线包

19. WebView和JS交互的几种方式。

交互方式总结

  • 对于Android调用JS代码的方法有2种:
    • (1)通过WebView的loadUrl()
    • (2)通过WebView的evaluateJavascript()
  • 对于JS调用Android代码的方法有3种:
    • (1)通过WebView的addJavascriptInterface()进行对象映射
    • (2)通过WebViewClient 的shouldOverrideUrlLoading()方法回调拦截 url
    • (3)通过 WebChromeClient 的onJsAlert()、onJsConfirm()、onJsPrompt()方法回调拦截JS, 对话框alert()、confirm()、prompt()消息

20. Android WebView拦截请求的方式

mWebview.setWebViewClient(new WebViewClient() {
	@Override
	public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
		return false;
	}
});
mWebview.loadUrl("https://www.baidu.com/");
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

21. 如何实现Activity窗口快速变暗

private void dimBackground(final float from, final float to) {
	final Window window = getWindow();
	ValueAnimator valueAnimator = ValueAnimator.ofFloat(from, to);
	valueAnimator.setDuration(500);
	valueAnimator.addUpdateListener(new AnimatorUpdateListener() {
		@Override
		public void onAnimationUpdate(ValueAnimator animation) {
			WindowManager.LayoutParams params = window.getAttributes();
			params.alpha = (Float) animation.getAnimatedValue();
			window.setAttributes(params);
		}
	});
	valueAnimator.start();
}

变暗: dimBackground(1.0f, 0.5f);
变亮: dimBackground(0.5f, 1.0f);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

22. RecyclerView与ListView的对比,缓存策略,优缺点。

ListView缓存
在这里插入图片描述
在整个滑动的过程中,离开屏幕的Item立即被回收至缓存ScrapView,滑入屏幕的Item则会优先从缓存ScrapView中获取,只是ListView与RecyclerView的实现细节有差异。

ListView与RecyclerView缓存级别的对比

是否需要回调createView是否需要回调bindView生命周期备注
mActiveViewsonLayout函数周期内用于屏幕itemView快速复用
mScrapViews与mAdapter一致,当mAdapter被更换时,mScrapViews即被清除

ListView获取缓存的流程:
在这里插入图片描述
RecyclerView(四级缓存):

是否需要createview是否需要调用bindView生命周期备注
mAttachedScraponLayout函数周期中用于屏幕内itemview快速复用
mCachedViews与mAdapter一致,当mAdapter被更换时,mCachedViews即被缓存至mRecyclerPool默认上限为2,即缓存屏幕外2个itemview
mViewCachedExtension不直接使用,需要用户再制定,默认不实现
mRecyclerPool与自身生命周期一致,不再被引用时即被释放默认上限为5, 技术上可以实现所有RecyclerPool共用同一个

23.RecyclerView的回收复用机制

在这里插入图片描述

真正带你搞懂 RecyclerView 的缓存机制,再也不怕面试被虐了
【Android】RecyclerView 缓存机制,真的很难理解?到底是几级缓存?
让你彻底掌握RecyclerView的缓存机制
RecyclerView.java

24.ViewPager2中的Fragment懒加载实现方式

  1. 在onResume中调用
  2. 充分利用setUserVisibleHint

Android学习小计:ViewPager2中的Fragment懒加载实现方式

25. 自定义ViewPager

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        for (int i = 0; i < image_id.length; i++) {
            this.getChildAt(i).layout(i * getWidth(), t, (i + 1) * getWidth(), b);
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

Android自定义View——自定义ViewPager

26. SurfaceView和TextureView对比

https://blog.csdn.net/zhangyadong_QQ/article/details/84000192

Jetpack

livedata

  1. Lifecycle的使用与实现原理
  2. jetpack中的状态机制如何管理生命周期
  3. livedata使用与实现原理分析
  4. livedata粘性事件源码分析
  5. hook实现livedata非粘性功能
  6. livedata递归调用源码中如何做容错
  7. 企业级livedatabus封装实现
    Lifecycle 使用及原理解析 一文搞懂
    Jetpack之生命周期感知组件-Lifecycle使用篇
    LiveData-原理全解析
    LiveData 使用与源码分析——完全搞懂LiveData原理
    Livedata粘性事件实现源码解析,让你彻底掌握数据更新机制
    LiveData 非粘性消息的探索和尝试
    Android 消息总线的演进之路:用 LiveDataBus 替代 RxBus、EventBus

databinding核心原理

databinding双向绑定原理

room

ORM映射关系设计与详解
如何自己去设计一套数据库框架
Room框架的设计策略
Room框架的基本使用

Android Jetpack 学习之 Room 框架
【Jetpack】Room 中的销毁重建策略 ( 创建临时数据库表 | 拷贝数据库表数据 | 删除旧表 | 临时数据库表重命名 )
Jetpack Room 使用及原理解析
【Android Jetpack】Room数据库的使用及原理详解
Jetpack之Room
【Jetpack】Room + ViewModel + LiveData 综合使用 ( 核心要点说明 | 组合方式 | 代码示例 )

navigator

什么是Navigation
Navigation三大件
Navigation与Fragment
Navigation的高级用法
导航图数据如何加载?如何管理?
NavController如何进行跳转控制
Navigation如何管理回退栈
Navigation对于Fragment/show-hide的处理探索

viewmodel

  1. viewmodel出现所解决的问题
  2. viewmodel对于数据处理的生命周期选择
  3. viewmodel核心源码分析
  4. viewmodel结合livedata的应用
  5. viewmodel结合databinding完成ui双向绑定

hilt

框架设计中的高级反射技术
手写实现隔离层完成框架切换
hilt使用与实现原理分析
使用jetpack新技术hilt实现深度解耦

dagger2

  1. dagger2基本使用
  2. dagger2注入基本原理
  3. 局部单例使用与实现原理
  4. 全局单例的实现
  5. scope与dependencies详解与原理分析
  6. SubComponent使用与原理分析
  7. Named使用与module参数处理
  8. Lazy与Provider实战意义与原理分析

paging

paging选择的及思想方案及处理(paging的业务分析)
paging的基础使用
paging的设计原理

WorkManager

WorkManager后台服务管理神器分析
WorkManager与保活分析
WorkManager源码解析
对于WorkManager在项目中应用下的思考

DataStore

JVM篇

Android-Flutter面试真经 原创

1. GC算法

  • 引用计数法
  • 可达性分析法
  • 标记清除算法
  • 复制算法
  • 标记整理算法
  • 分代算法

2. JVM内存区域的划分,哪些区域会发生OOM

区域:

  • 程序计数器
  • JVM虚拟机栈
  • 本地方法栈
  • 方法区
  • 运行时常量池

除了程序计数器,其他的部分都会发生OOM

  • 堆:
  • JVM虚拟机和本地方法栈: StackOverflow
  • 方法区:

3. java的内存模型

Java内存模型规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存,线程的工作内存中保存了该线程中是用到的变量的主内存副本拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存。不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量的传递均需要自己的工作内存和主存之间进行数据同步进行。
原子性:
可见性:
有序性:

4. 类加载过程

  • 加载
  • 验证
  • 准备: 创建静态变量,并未静态变量开辟内存空间
  • 解析: 将符号引用替换为直接引用
  • 初始化: 给静态变量赋值,并执行静态代码中的逻辑

5. volatile和synchronize的区别

volatile
它所修饰的​变量​不保留拷贝,直接访问主内存中的。
synchronize
当它用来修饰​一个方法或者一个代码块​的时候,能够保证在同一时刻最多只有一个线程执行该段代码。

6. Java中引用类型的区别,具体的使用场景

  • ​强引用:​ 强引用指的是通过 new 对象创建的引用,垃圾回收器即使是内存不足也不会回收强引用指向的对象。
  • 软引用:​ 软引用是通过 SoftRefrence 实现的,它的生命周期比强引用短,在内存不足,抛出 OOM 之前,垃圾回收器会回收软引用引用的对象。软引用常见的使用场景是存储一些内存敏感的缓存,当内存不足时会被回收。
  • ​弱引用:​ 弱引用是通过 WeakRefrence 实现的,它的生命周期比软引用还短,GC 只要扫描到弱引用的对象就会回收。弱引用常见的使用场景也是存储一些内存敏感的缓存。
  • ​虚引用:​ 虚引用是通过 FanttomRefrence 实现的,它的生命周期最短,随时可能被回收。如果一个对象只被虚引用引用,我们无法通过虚引用来访问这个对象的任何属性和方法。它的作用仅仅是保证对象在 finalize 后,做某些事情。虚引用常见的使用场景是跟踪对象被垃圾回收的活动,当一个虚引用关联的对象被垃圾回收器回收之前会收到一条系统通知。

7. JVM内存结构

在这里插入图片描述
JVM 基础 - JVM 内存结构

Framework篇

Handler面试题

Android Handler面试题总结

1. Handler、Looper、MessageQueue、线程的关系

  • 一个线程只会有一个Looper对象,所以线程和Looper是一一对应的
  • MessageQueue对象是在new Looper的时候创建的,所以Looper和MessageQueue是一一对应的
  • Handler的作用只是将消息加到MessageQueue中,并后续取出消息的target字段分发给当初的那个Handler, 所以Handler对于Looper是可以多对一的,也就是多个Handler对象都可以用同一个线程, 同一个Looper, 同一个MessageQueue.

Looper.java
Handler.java
MessageQueue.java

2. 主线程为什么不用初始化Looper

因为应用在启动的过程中就已经初始化了一个主线程Looper。每个java应用程序都是有一个main方法入口,Android是基于Java的程序也不例外,Android程序的入口在ActivityThread的main方法中,代码如下:

// 初始化主线程Looper
 Looper.prepareMainLooper();
 ...
 // 新建一个ActivityThread对象
 ActivityThread thread = new ActivityThread();
 thread.attach(false, startSeq);
 // 获取ActivityThread的Handler,也是他的内部类H
 if (sMainThreadHandler == null) {
 	sMainThreadHandler = thread.getHandler();
 }
 ...
 Looper.loop();
 // 如果loop方法结束则抛出异常,程序结束
 throw new RuntimeException("Main thread loop unexpectedly exited");
} 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

可以看到,main方法中会先初始化主线程Looper,新建ActivityThread对象,然后再启动Looper,这样主线程的Looper在程序启动的时候就跑起来了。并且,我们通常认为 ActivityThread 就是主线程,事实上它并不是一个线程,而是主线程操作的管理者。
ActivityThread.java

3. 为什么主线程的Looper是一个死循环,但是却不会ANR

因为当Looper处理完所有消息的时候会进入阻塞状态,当有新的Message进来的时候会打破阻塞继续执行。

4. Message是怎么找到它所属的Handler然后进行分发的

在loop方法中,找到要处理的Message需要调用下面的一段代码来处理消息:

msg.target.dispatchMessage(msg);
  • 1

所以是将消息交给了msg.target来处理,那么这个target是什么呢,通常查看target的源头可以发现:

private boolean enqueueMessage(MessageQueue queue,Message msg,long uptimeMillis) {
    msg.target = this;
	return queue.enqueueMessage(msg, uptimeMillis);
}
  • 1
  • 2
  • 3
  • 4

在使用Hanlder发送消息的时候,会设置msg.target = this,所以target就是当初把消息加到消息队列的那个Handler。

5. Handler是如何切换线程的

使用不同线程的Looper处理消息。我们知道,代码的执行线程,并不是代码本身决定,而是执行这段代码的逻辑是在哪个线程,或者说是哪个线程的逻辑调用的。每个Looper都运行在对应的线程,所以不同的Looper调用的dispatchMessage方法就运行在其所在的线程了。

6. post(Runnable) 与 sendMessage 有什么区别

	public final boolean post(@NonNull Runnable r) {
		return  sendMessageDelayed(getPostMessage(r), 0);
	}
	private static Message getPostMessage(Runnable r) {
		Message m = Message.obtain();
	    m.callback = r;
	    return m;
	}  
	public final boolean sendMessage(@NonNull Message msg) {
	     return sendMessageDelayed(msg, 0);
	}

	public void dispatchMessage(@NonNull Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

    private static void handleCallback(Message message) {
        message.callback.run();
    }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

7. Handler如何保证MessageQueue并发访问安全的

循环加锁,配合阻塞唤醒机制。我们发现,MessageQueue其实是【生产者-消费者】模型,Handler不断地放入消息,Looper不断地取出,这就涉及到死锁问题。如果Looper拿到锁,但是队列中没有消息,就会一直等待,而Handler需要把消息放进去,锁却被Looper拿着无法入队,这就造成了死锁,Handler机制的解决方法是循环加锁,代码在MessageQueue的next方法中:

Message next() {
	 ...
	 for (;;) {
		 ...
		 nativePollOnce(ptr, nextPollTimeoutMillis);
		 synchronized (this) {
		 	...
		}
      }
} 

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

8. Handler的阻塞唤醒机制是怎么实现的

epoll
Handler的阻塞唤醒机制是基于Linux的阻塞唤醒机制。这个机制也是类似于handler机制的模式。在本地创建一个文件描述符,然后需要等待的一方则监听这个文件描述符,唤醒的一方只需要修改这个文件,那么等待的一方就会收到文件从而打破唤醒。

9. 什么是Handler的同步屏障

所谓同步屏障,其实就是一个Message,只不过它是插入在MessageQueue的链表头,且其target==null。 而Message加急消息就是使用同步屏障实现的。同步屏障用到了postSyncBarrier()方法。

public int postSyncBarrier() {
 return postSyncBarrier(SystemClock.uptimeMillis());
}
private int postSyncBarrier(long when) {
	 synchronized (this) {
		 final int token = mNextBarrierToken++;
		 final Message msg = Message.obtain();
		 msg.markInUse();
		 msg.when = when;
		 msg.arg1 = token;
		 Message prev = null;
		 Message p = mMessages;
		 // 把当前需要执行的Message全部执行
		 if (when != 0) {
			 while (p != null && p.when <= when) {
				 prev = p;
				 p = p.next;
			 }
		 }
		 // 插入同步屏障
		 if (prev != null) { // invariant: p == prev.next
			 msg.next = p;
			 prev.next = msg;
		 } else {
			 msg.next = p;
			 mMessages = msg;
		 }
		 return token;
	 }
} 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

可以看到,同步屏障就是一个特殊的target,即target==null,我们可以看到他并没有给target属性赋值,那这个target有什么用呢?

if (msg != null && msg.target == null) {
    // Stalled by a barrier.  Find the next asynchronous message in the queue.
    do {
       prevMsg = msg;
       msg = msg.next;
    } while (msg != null && !msg.isAsynchronous());
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

如果遇到同步屏障,那么会循环遍历整个链表找到标记为异步消息的Message,即isAsynchronous返回true,其他的消息会直接忽视,那么这样异步消息,就会提前被执行了。同时,,同步屏障不会自动移除,使用完成之后需要手动进行移除,不然会造成同步消息无法被处理。

10. IdleHandler的使用场景

前面说过,当MessageQueue没有消息的时候,就会阻塞在next方法中,其实在阻塞之前,MessageQueue还会做一件事,就是检查是否存在IdleHandler,如果有,就会去执行它的queueIdle方法。

11. HandlerThread使用场景

HandlerThread源码

public class HandlerThread extends Thread {
    @Override
    public void run() {
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
    }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

可以看到,HandlerThread是一个封装了Looper的Thread类,就是为了让我们在子线程里面更方便的使用Handler。这里的加锁就是为了保证线程安全,获取当前线程的Looper对象,获取成功之后再通过notifyAll方法唤醒其他线程,那哪里调用了wait方法呢?答案是getLooper方法。

public Looper getLooper() {
        if (!isAlive()) {
            return null;
        }

        // If the thread has been started, wait until the looper has been created.
        synchronized (this) {
            while (isAlive() && mLooper == null) {
                try {
                    wait();
                } catch (InterruptedException e) {
                }
            }
        }
        return mLooper;
    }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

Binder面试题

1.为什么Android要采用Binder做进程通信

常用的跨进程通信有:共享内存、管道、消息队列、Socket。虽然都能实现跨进程通信,但需要拷贝两次。Binder通过mmap实现,通过映射内存实现一次拷贝,大大提高了跨进程通信效率。

为什么 Android 要采用 Binder 作为 IPC 机制?
面试官:为什么 Android 要采用 Binder 作为 IPC 机制?
在这里插入图片描述

2.用户空间和内核空间区别

用户空间是分配给用户进程的内存区域。内核空间是操作系统内核运行的区域。

3.简单说说物理地址和虚拟地址

物理地址是指计算机中实际硬件内存芯片上的地址。虚拟地址通过使用页表或段表等机制,将虚拟地址映射到物理地址。

4.Binder如何做到一次拷贝

使用mmap

5.简单讲讲mmap原理

mmap是一种内存映射文件的方法

6.内存中的一页是什么,你怎么理解的

“页”(Page)是操作系统中用于管理内存的基本单位之一。它是一块连续的内存区域,具有固定的大小。操作系统使用页作为内存管理的基本单元,来管理进程的虚拟内存和物理内存之间的映射关系。

7.Binder传输数据的大小限制是多少

回答:1M-8K

Intent传输数据的大小受Binder的限制,上限是1M。不过这个1M并不是安全的上限,Binder可能在处理别的工作,安全上限是多少这个在不同的机型上也不一样。

一次Binder通信最大可以传输多大的数据?
intent传递数据大小限制

8.AIDL生成java类的细节

会生成一个Stub内部类,实现跨进程的通信。

9.Android App有多少Binder线程,是固定的么

在Android系统中,每个进程的Binder线程数默认最大是16。但每个版本可能会有不同的结果。

10. bindService启动Service与Binder服务实体的流程

回答:

  1. 客户端通过Context.bindService()向系统发送启动服务的请求;
  2. 接着,系统会创建一个新的Service实例;
  3. 然后,系统会调用服务的onCreate()方法来初始化服务;
  4. 在这之后,系统会调用服务的onBind()方法获取到Binder对象,用于建立客户端与服务端的通信链接;
  5. 当Binder对象被返回给客户端后,服务便处于运行状态;
  6. 当解绑后,系统会调用服务的onUnbind()方法;
  7. 最后,系统会触发销毁服务,这会触发服务的onDestroy()方法.

11. java层Binder实体与BinderProxy是如何实例化及使用的,与Native层的关系是怎样的

回答: BinderProxy是Native层创建的,Java层并没有新建入口. BinderProxy的关键函数是transact, Java层通过该函数进行Binder通信.

首先,BinderProxy是Native层新建的,Java层并没有BinderProxy的新建入口。
其次,BinderProxy的关键函数是transact,java层的Binder客户端通过该函数进行Binder通信。
而该函数本质上也是调用的native层接口,具体来说,BinderProxy通过调用native层的transactNative方法来进行Binder通信。
此外,值得一提的是,“BpBinder” 和 BinderProxy 其实是一个东西:远程 binder 实体,只不过一个在Native层、一个在Java层,“BpBinder” 内部持有了一个 binder 句柄值 handle。
因此,可以说Java层的Binder实体与BinderProxy的实例化及使用都是依赖于Native层的,两者之间存在着紧密的联系和交互。

12.Binder如何找到目标进程

当客户端发起一个Binder请求时,系统会为该请求分配一个唯一的标识符(即Binder Token),并将该标识符和请求数据一起传递给Binder驱动。

Zygote面试

在这里插入图片描述

1. init进程是什么

init进程是用户空间的第一个进程,其进程号为1.

在Android系统中,init进程是用户空间的第一个进程,其进程号为1。它在系统启动流程中扮演着重要的角色,该流程大致为:BootRom > Bootloader > Kernel > Init > Zygote > SystemServer > Launcher。
当bootloader启动后,会启动kernel,kernel启动完后,在用户空间启动init进程。然后,init进程通过读取和解析init.rc中的相关配置来启动其他相关进程以及其他操作。例如,我们熟悉的Zygote孵化器进程就是由init进程启动的。
总的来说,init进程在初始化过程中会启动很多重要的守护进程,因此,了解 Init 进程的启动过程将有助于我们更好地理解 Android系统。

2. 小米华为这种系统服务怎么做

init进程启动后,会读取init.rc文件. 将需要启动的系统服务放到该文件中,会随着init的启动而启动

3. Zygote进程最原始的进程是什么进程(或者Zygote进程由来)

Zygote进程是一种非常重要的进程,它是由Linux的初始进程(init)启动的。Zygote进程是所有Android应用进程的父进程,包括SystemServer和各种应用程序进程都是通过Zygote进程fork出来的。

4.Zygote为什么需要用到Socket通信而不是Binder

虽然Socket和Binder有不同的优缺点,而在Zygote进程中使用Socket可以更好地满足Zygote进程的需求。

  1. binder在Zygote之后才初始化完成
  2. Socket具有良好的跨平台性,能够在不同的设备和架构上运行,并且更加灵活和可扩展
  3. Socket具有简单的API和易于使用的特点。
  4. Socket在数据传输时具有更低的延迟和更高的吞吐量,这对于Zygote进程来说非常重要。

Zygote通信为什么用Socket,而不是Binder?

5.每个App都会将系统的资源,系统的类都加载一遍吗

每个应用在运行时都会加载自己所需的系统资源和类,而不会重复加载整个系统的资源和类。

6.PMS是干什么的,你是怎么理解PMS

PMS是包管理器服务, 负责管理应用程序的安装、卸载、权限授予和查询等相关任务

Activity Manager Service

在这里插入图片描述

1.为什么会有AMS AMS的作用

承担了管理应用程序生命周期、任务栈、进程、Activity启动等重要职责。

  1. Activity 生命周期管理
  2. 应用程序任务栈管理
  3. Activity启动管理
  4. 进程管理
  5. 权限检查
  6. 系统广播的分发
  7. 应用程序的状态保存与恢复

2.为什么一个Activity需要声明在AndroidManfest.xml中

通过注册,系统可以正确地管理和调度应用程序的各个组件,确保正确地时机被启动.

通过在AndroidManifest.xml文件中声明Activity,系统可以正确地管理和调度应用程序的各个组件,确保它们能够在正确的时机被启动、与其他组件正确地交互,并能够被系统和其他应用程序正确地识别和使用。这也是Android应用程序架构的一部分,确保应用程序的组件能够协同工作。

3.AMS如何管理Activity,谈谈AMS的执行原理

主要涉及4个类:

主要类说明
ActivityRecord存在历史栈的一个实例,代表一个Activity。Activity在AMS中的存在形式,ActivityRecord保存了Activity的信息。
TaskRecordActivity栈,内部维护一个ArrayList
ActivityStack并不是一个Activity栈,真正意义上的Activity栈是TaskRecord,这个类是负责管理各个Activity栈,内部维护一个ArrayList
ActivityStackSupervisor内部持有一个ActivityStack,而ActivityStack内部也持有ActivityStackSupervisor,相当于ActivityStack的辅助管理类

ActivityStack 管理 TaskRecord ,TaskRecord 管理 ActivityRecord。

在这里插入图片描述
知识点梳理 - AMS 的介绍及知识点
AMS之Activity栈管理(上篇)
Android ActivityManagerService(AMS)的Activity管理
AMS (3): AMS 如何管理Activity?
Android AMS 原理解读
“一文读懂”系列:AMS是如何动态管理进程的?
PMS/AMS/WMS原理解析

4. 从launcher程序启动App流程分析

  1. 点击app
  2. system_server进程fork出一个子进程
  3. App进程在attach到AMS上
  4. AMS发送指令到App进程启动主Activity
    在这里插入图片描述
    ActivityThread源码
    手把手带你搞懂AMS启动原理

5. startActivity跳转流程分析

回答:

  1. 调用startAdtivity时候,通过Instrument类调用AMS中的startActivity,

  2. 然后通过一系列的检查后, 就会创建一个ActivityRecord,用来保存Activity信息,

  3. 检查是否已经创建了该进程,

  4. 然后调用生命周期

  5. 调用 Activity 的 startActivity 方法来启动目标 Activity

  6. 接着就会调用到 Instrunmentation 的 execStartActivity 方法,通过获取 ATMS 的 binder 代理对象,然后调用到 ATMS 的 startActivity 中去

  7. 调用到 ATMS 中后,会执行到ActivityStarter 的 execute 方法,内部最终执行到了 executeRequest ,接着就会进行一些校验和判断权限,包括进程检查,intent检查,权限检查等,后面就会创建 ActivityRecord ,用来保存 Activity 的相关信息,

  8. 然后就会根据启动模式计算 flag ,设置启动 Activity 的 Task 栈。

  9. 在 ActivityTaskSupervisor 中检查要启动的 Activity 进程是否存在,存在则向客户端进程 ApplicationThread 回调启动 Activity,否则就创建进程。

  10. 会调到 ActivityThread 后在 TransactionExecute 中开始执行system_server回调回来的事务,处理各种回调,切换到对应的生命周期

  11. 最后又回调到 ActivityThread 的 handleLaunchActivity 来启动 Activity。在其中调用了 performLaunchActivity 方法。

  12. 在 performLaunchActivity 中通过反射创建 Activity 实例,如果没有 Application 则先进行创建,然后再调用Activity 的 attach 方法进行初始化,最后回调 activity 的 onCreate 方法。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

Android 深入研究之 ✨ Activity启动流程+Activity生命周期✨
从一个APP启动另一个APP的Activity的方法
Android | Activity 启动流程分析

性能优化

Android 性能优化必知必会(2020-4-27日更新)

1. 冷启动流程

  • ①点击桌面App图标,Launcher进程采用Binder IPC向system_server进程发起startActivity请求;
  • ②system_server进程接收到请求后,向zygote进程发送创建进程的请求;
  • ③Zygote进程fork出新的子进程,即App进程;
  • ④App进程,通过Binder IPC向sytem_server进程发起attachApplication请求;
  • ⑤system_server进程在收到请求后,进行一系列准备工作后,再通过binder IPC向App进程发送scheduleLaunchActivity请求;
  • ⑥App进程的binder线程(ApplicationThread)在收到请求后,通过handler向主线程发送LAUNCH_ACTIVITY消息;
  • ⑦主线程在收到Message后,通过发射机制创建目标Activity,并回调Activity.onCreate()等方法。
  • ⑧到此,App便正式启动,开始进入Activity生命周期,执行完onCreate/onStart/onResume方法,UI渲染结束后便可以看到App的主界面。

2. Activity四种启动模式

  • ​standard​:标准模式:如果在mainfest中不设置就默认standard;standard就是新建一个Activity就在栈中新建一个activity实例;
    • 邮件、mainfest中没有配置就默认标准模式
  • ​singleTop​:栈顶复用模式:与standard相比栈顶复用可以有效减少activity重复创建对资源的消耗,但是这要根据具体情况而定,不能一概而论;
    • 登录页面、WXPayEntryActivity、WXEntryActivity 、推送通知栏
  • ​singleTask​:栈内单例模式,栈内只有一个activity实例,栈内已存activity实例,在其他activity中start这个activity,Android直接把这个实例上面其他activity实例踢出栈GC掉;
    • 程序模块逻辑入口:主页面(Fragment的containerActivity)、WebView页面、扫一扫页面、电商中:购物界面,确认订单界面,付款界面
  • ​singleInstance​ :堆内单例:整个手机操作系统里面只有一个实例存在就是内存单例;
    • 系统Launcher、锁屏键、来电显示等系统应用

3. startService和bindService的区别,生命周期以及使用场景

在这里插入图片描述
startService 和bindService 区别​

  • startService: onCreate -> onStartCommand -> onDestory ,在多次调用startService的时候,onCreate不重复执行,但是onStartCommand会执行。startService调用了这后,会一直存在,直到其调用了stopService。
  • bindService : onCreate -> onBind -> onUnbind -> onDestory,多次调用bindService,onCreate及onBind都只执行一次。它生命周期跟随其调用者,调用者释放的时候,必须对该Service解绑,当所有绑定全部取消后,系统即会销毁该服务。 bindService 的方式通过onServiceConnected方法,获取到Service对象,通过该对象可以直接操作到Service内部的方法,从而实现的Service 与调用者之间的交互。

4. Android中的动画有哪几类,它们的特点和区别是什么

Android中动画大致分为3类:帧动画、补间动画(Tween Animation)、属性动画(Property Animation)。

  • 帧动画:通过xml配置一组图片,动态播放。很少会使用。
  • 补间动画(Tween Animation):大致分为旋转、透明、缩放、位移四类操作。很少会使用。
  • 属性动画(Property Animation):属性动画是现在使用的最多的一种动画,它比补间动画更加强大。属性动画大致分为两种使用类型,分别是 ViewPropertyAnimator 和 ObjectAnimator。前者适合一些通用的动画,比如旋转、位移、缩放和透明,使用方式也很简单通过 ​​View.animate()​​​ 即可得到 ViewPropertyAnimator,之后进行相应的动画操作即可。后者适合用于为我们的自定义控件添加动画,当然首先我们应该在自定义 View 中添加相应的 ​​getXXX()​​​ 和 ​​setXXX()​​​ 相应属性的 getter 和 setter 方法,这里需要注意的是在 setter 方法内改变了自定义 View 中的属性后要调用 ​​invalidate()​​​ 来刷新View的绘制。之后调用 ​​ObjectAnimator.of​​​ 属性类型()返回一个 ObjectAnimator,调用 ​​start()​​ 方法启动动画即可。

补间动画与属性动画的区别:

  • 补间动画是父容器不断的绘制 view,看起来像移动了效果,其实 view 没有变化,还在原地。
  • 属性动画是通过不断改变 view 内部的属性值,真正的改变 view。

5. 启动优化

测量时间:

  1. adb测量: adb shell am start -W packagename/首屏Activity
  2. 手动打点

两个工具:systrace&Traceview

  • systrace: python systrace.py -t 10 [other-options][categories]
  • TraceView: Debug.startMethodTracing(“”); & Debug.stopMethodTracing();

使用AspectJ测量时间

优化启动技巧:

  1. 设置theme, 启动Activity后恢复theme
  2. 异步加载布局
  3. 利用IdleHandler进行延迟优化

6. App内存优化

线下监控:

  • Memory Profiler
  • Memory Analyzer
  • LeakCanary

线上监控:

  • 内存超标, dump内存
  • KOOM方案(fork子进程进行dump)

7.App布局优化

  • systrace工具测量
  • vsync信号
    Choreographer.getInstance().postFrameCallback(new Choreographer.FrameCallback() {
        @Override
        public void doFrame(long frameTimeNanos) {
            if (mStartFrameTime == 0) {
                mStartFrameTime = frameTimeNanos;
            }
            long interval = frameTimeNanos - mStartFrameTime;
            if (interval > MONITOR_INTERVAL_NANOS) {
                double fps = (((double) (mFrameCount * 1000L * 1000L)) / interval) * MAX_INTERVAL;
                mFrameCount = 0;
                mStartFrameTime = 0;
            } else {
                ++mFrameCount;
            }

            Choreographer.getInstance().postFrameCallback(this);
        }
    });
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 异步加载布局
  • GPU检测

8.App卡顿优化

  • CPU Profiler
  • Systrace
  • StrictMode
  • blockcanaray-android
  • Lancet框架

9. App线程优化

检测:

  1. dump内存,可以分析线程
  2. 有个文件,存储着当前的线程
    native线程可以通过hook进行监测内存的创建

10. App电量优化

setho

11. App瘦身优化

12. App专项技术优化

13. 微信APM Matrix解析

微信APM Matrix解析

14. App保活

Android进程保活招数概览
进程保活
【腾讯Bugly干货分享】Android 进程保活招式大全
2018年Android的保活方案效果统计
Android 10 低内存应用白名单和应用保活
android10 保活 安卓10保活 转载
android10进程保活
android 10 应用保活 android应用保活方案 转载
Android App保活的方式

1、账号同步;

public class AccountHelper {
    //authenticator.xml 中配置 的accountType值
    private static final String ACCOUNT_TYPE = "com.cby.processkeeplive";
    /**
     * 添加Account,需要"android.permission.GET_ACCOUNTS"权限
     *
     * @param context
     */
    @SuppressLint("MissingPermission")
    public static void addAccount(Context context) {
        AccountManager accountManager = (AccountManager) context.getSystemService(Context.ACCOUNT_SERVICE);
        Account[] accountsType = accountManager.getAccountsByType(ACCOUNT_TYPE);
        if (accountsType.length > 0) {
            Log.e("cby", "账户已经存在");
            return;
        }
        //给这个账户类型添加账户 cby cby007
        Account account = new Account("cby", ACCOUNT_TYPE);
        //需要"android.permission.AUTHENTICATE_ACCOUNTS"权限
        accountManager.addAccountExplicitly(account, "cby007", new Bundle());
    }
    /**
     * 设置账户同步,即告知系统我们需要系统为我们来进行账户同步,只有设置了之后系统才会自动去
     * 触发SyncAdapter#onPerformSync方法
     */
    public static void autoSyncAccount() {
        Account account = new Account("cby", ACCOUNT_TYPE);
        //设置可同步
        ContentResolver.setIsSyncable(account, AppUtils.getDefault().getPackageName() + ".provider", 2);
        //设置自动同步
        ContentResolver.setSyncAutomatically(account, AppUtils.getDefault().getPackageName() + ".provider", true);
        //设置同步周期参考值,不受开发者控制完全由系统决定何时同步,测试下来最长等了差不多几十分钟才同步一次,不同系统表现不同
        ContentResolver.addPeriodicSync(account, AppUtils.getDefault().getPackageName() + ".provider", new Bundle(), 1);
    }
}
public class AuthenticationService extends Service {
    private AccountAuthenticator mAuthenticator;
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mAuthenticator.getIBinder(); // 返回操作数据的Binder
    }
    @Override
    public void onCreate() {
        super.onCreate();
        mAuthenticator = new AccountAuthenticator(this);
    }
    /**
     * AbstractAccountAuthenticator 是用于实现对手机系统设置里“账号与同步”中Account的添加、删除和验证等一些基本功能。
     */
    class AccountAuthenticator extends AbstractAccountAuthenticator {
        public AccountAuthenticator(Context context) {
            super(context);
        }
        @Override
        public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) {
            return null;
        }
        @Override
        public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options) throws NetworkErrorException {
            return null;
        }
        @Override
        public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account, Bundle options) throws NetworkErrorException {
            return null;
        }
        @Override
        public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException {
            return null;
        }
        @Override
        public String getAuthTokenLabel(String authTokenType) {
            return null;
        }
        @Override
        public Bundle updateCredentials(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException {
            return null;
        }
        @Override
        public Bundle hasFeatures(AccountAuthenticatorResponse response, Account account, String[] features) throws NetworkErrorException {
            return null;
        }
    }
}
public class SyncContentProvider extends ContentProvider {
    @Override
    public boolean onCreate() {
        return false;
    }
    @Nullable
    @Override
    public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
        return null;
    }
    @Nullable
    @Override
    public String getType(@NonNull Uri uri) {
        return null;
    }
    @Nullable
    @Override
    public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
        return null;
    }
    @Override
    public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
        return 0;
    }
    @Override
    public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
        return 0;
    }
}
public class SyncService extends Service {
    private SyncAdapter mSyncAdapter;
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mSyncAdapter.getSyncAdapterBinder();
    }
    @Override
    public void onCreate() {
        super.onCreate();
        mSyncAdapter = new SyncAdapter(getApplicationContext(), true);
    }
    static class SyncAdapter extends AbstractThreadedSyncAdapter {
        public SyncAdapter(Context context, boolean autoInitialize) {
            super(context, autoInitialize);
        }
        @Override
        public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) {
            //todo 账户同步 工作
            Log.e("cby","同步账户");
            //与互联网 或者 本地数据库同步账户
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137

2、JobService 拉活;

public class GuardJobService extends JobService {
    @Override
    public boolean onStartJob(JobParameters params) {
        Log.e(Constant.TAG, "开启job");
        //如果7.0以上 轮询
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            startGuardJob(this);
        }
        boolean isLocalRun = isServiceWork(this, LocalService.class.getName());
        boolean isRemoteRun = isServiceWork(this, RemoteService.class.getName());
        if (!isLocalRun || !isRemoteRun) {
            LocalService.start(this);
            RemoteService.start(this);
        }
        return false;
    }
    @Override
    public boolean onStopJob(JobParameters params) {
        return false;
    }
    @SuppressLint("MissingPermission")
    public static void startGuardJob(Context context) {
        if (context != null) {
            JobScheduler jobScheduler = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
            // setPersisted 在设备重启依然执行
            JobInfo.Builder builder = new JobInfo.Builder(10, new ComponentName(context
                    .getPackageName(), GuardJobService.class
                    .getName())).setPersisted(true);
            //小于7.0
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
                // 每隔1s 执行一次 job
                builder.setPeriodic(1_000);
            } else {
                // 延迟执行任务
                builder.setMinimumLatency(1_000);
            }
            jobScheduler.schedule(builder.build());
        }
    }
    // 判断服务是否正在运行
    public boolean isServiceWork(Context mContext, String serviceName) {
        boolean isWork = false;
        ActivityManager manager = (ActivityManager) mContext
                .getSystemService(Context.ACTIVITY_SERVICE);
        List<ActivityManager.RunningServiceInfo> serviceInfoList = manager.getRunningServices(100);
        if (serviceInfoList.size() <= 0) {
            return false;
        }
        for (int i = 0; i < serviceInfoList.size(); i++) {
            String serviceClassName = serviceInfoList.get(i).service.getClassName();
            if (serviceClassName.equals(serviceName)) {
                isWork = true;
                break;
            }
        }
        return isWork;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58

3、前台进程、双进程拉活;

public class ForegroundService extends Service {
    private static final int SERVICE_ID = 1;
    public static void start(Context context) {
        context.startService(new Intent(context, ForegroundService.class));
    }
    @Override
    public IBinder onBind(Intent intent) {
        return new KeepLiveService();
    }
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        if (Build.VERSION.SDK_INT < 18) {
            // 开启前台服务
            startForeground(SERVICE_ID, new Notification());
        } else if (Build.VERSION.SDK_INT < 26) {
            // 开启前台服务
            startForeground(SERVICE_ID, new Notification());
            // 移除通知栏消息
            startService(new Intent(this, InnerService.class));
        } else {
            // 8.0 以上
            @SuppressLint("WrongConstant")
            NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
            if (manager != null) {
                NotificationChannel channel = new NotificationChannel("channel", "cby", NotificationManager.IMPORTANCE_MIN);
                manager.createNotificationChannel(channel);
                NotificationCompat.Builder builder = new NotificationCompat.Builder(this, channel.getId());
                startForeground(SERVICE_ID, builder.build());
            }
        }
        return super.onStartCommand(intent, flags, startId);
    }
    public static class InnerService extends Service {
        @Override
        public IBinder onBind(Intent intent) {
            return null;
        }
        @Override
        public int onStartCommand(Intent intent, int flags, int startId) {
            startForeground(SERVICE_ID, new Notification());
            stopForeground(true);
            stopSelf();
            return super.onStartCommand(intent, flags, startId);
        }
    }
    private class KeepLiveService extends IKeepLiveService.Stub {
        @Override
        public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {
        }
    }
}
public class LocalService extends ForegroundService {
    public static void start(Context context) {
        Intent intent = new Intent(context, LocalService.class);
        context.startService(intent);
    }
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        bindService(new Intent(this, RemoteService.class), connection, Context.BIND_IMPORTANT);
        return super.onStartCommand(intent, flags, startId);
    }
    ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
        }
        @Override
        public void onServiceDisconnected(ComponentName name) {
            // 远程进程被 kill ,拉活
            Log.d(Constant.TAG, "远程进程被 kill ,拉活");
            startService(new Intent(LocalService.this, RemoteService.class));
            bindService(new Intent(LocalService.this, RemoteService.class), this, Context.BIND_IMPORTANT);
        }
    };
}
public class RemoteService extends ForegroundService {
    public static void start(Context context) {
        Intent intent = new Intent(context, RemoteService.class);
        context.startService(intent);
    }
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        bindService(new Intent(this, LocalService.class), connection, Context.BIND_IMPORTANT);
        return super.onStartCommand(intent, flags, startId);
    }
    ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
        }
        @Override
        public void onServiceDisconnected(ComponentName name) {
            // 远程进程被 kill ,拉活
            Log.d(Constant.TAG, "主进程被 kill ,拉活");
            startService(new Intent(RemoteService.this, LocalService.class));
            bindService(new Intent(RemoteService.this, LocalService.class), this, Context.BIND_IMPORTANT);
        }
    };
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98

4、单像素保活

public class KeepLiveManager {
    private static final KeepLiveManager ourInstance = new KeepLiveManager();
    private WeakReference<KeepLiveActivity> reference;
    private KeepLiveReceiver receiver;
    public static KeepLiveManager getInstance() {
        return ourInstance;
    }
    private KeepLiveManager() {
    }
    public void register(Context context) {
        if (receiver == null) {
            receiver = new KeepLiveReceiver();
            IntentFilter filter = new IntentFilter();
            filter.addAction(Intent.ACTION_SCREEN_OFF);
            filter.addAction(Intent.ACTION_SCREEN_ON);
            context.registerReceiver(receiver, filter);
        }
    }
    public void unRegister(Context context) {
        if (receiver != null) {
            context.unregisterReceiver(receiver);
        }
    }
    public void setKeepLiveActivity(KeepLiveActivity activity) {
        reference = new WeakReference<>(activity);
    }
    public void start(Context context) {
        KeepLiveActivity.launch(context);
    }
    public void stop(Context context) {
        if (reference != null && reference.get() != null) {
            reference.get().finish();
        }
    }
}
public class KeepLiveActivity extends AppCompatActivity {
    public static void launch(Context context) {
        Intent intent = new Intent(context, KeepLiveActivity.class);
        context.startActivity(intent);
    }
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Window window = getWindow();
        // 设置左上角
        window.setGravity(Gravity.TOP | Gravity.LEFT);
        WindowManager.LayoutParams params = window.getAttributes();
        // 坐标
        params.x = 0;
        params.y = 0;
        // 设置 1 像素
        params.width = 1;
        params.height = 1;
        window.setAttributes(params);
        KeepLiveManager.getInstance().setKeepLiveActivity(this);
    }
}
public class KeepLiveReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
            // 屏幕熄灭,开启 KeepLiveActivity
            KeepLiveManager.getInstance().start(context);
        } else {
            KeepLiveManager.getInstance().stop(context);
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68

享学面试题集锦

1. 一张图片100x100在内存中的大小?(字节跳动)

  1. Java创建Bitmap
  2. Native创建Bitmap: Luban, 逐一解析.
    分辨率 * 每个像素点的大小。
    转换后高度 = 原图高度 * (设备的 dpi / 目录对应的 dpi )
    转换后宽度 = 原图宽度 * (设备的 dpi / 目录对应的 dpi )

2. 内存优化,内存抖动和内存泄漏。(东方头条)

内存抖动:
内存不断分配对象, GC不断回收. 短时间大量的循环,导致内存抖动.
导致内存的卡顿.
甚至会引起OOM.

内存泄露:
无法回收对象

可作为GC Roots的对象包括:

  1. 方法区:类静态属性的对象;
  2. 方法区:常量的对象;
  3. 虚拟机栈(本地变量表)中的对象;
  4. 本地方法栈JNI(Native方法)中的对象;

3. 什么时候会发生内存泄漏?举几个例子(美团)

  1. 持有Activity的引用:Activity生命周期已经结束,但仍保留引用,导致无法回收
  2. 未释放资源:数据库连接,文件流等
  3. 静态集合:static HashMap
  4. 监听器和回调:
  5. 未销毁的线程
  6. 单例模式
  7. WebView的使用:如果WebView的引用没有被正确释放,可能导致内存泄漏。
  8. Bitmap的使用:需要及时地释放不再需要的Bitmap对象。

4. TraceView的使用,查找CPU占用(东方头条)

AS自带工具

5.内存泄漏查找 (酷我音乐)

使用第三方工具:
LeakCanary
KOOM
创建Activity之前计算内存占用, 释放后Activity的内存占用,进行比较

6.LeakCanary监测的原理

原理:
弱引用对象在其包装对象被回收后(弱引用对象创建时,传入了引用队列),该弱引用对象会被加到引用队列中(ReferenceQueue)。
通过在ReferenceQueue中检测是否有目标对象的弱引用对象存在,即可判断目标对象是否被回收。

......
Activity mActivity;
......
ReferenceQueue<Activity> mQueue = new ReferenceQueue<>();
WeakReference<Activity> mWeakReference = new WeakReference<>(mActivity, mQueue);
......
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

LeakCanary检测原理和源码解析

gradle

开源框架篇

RecyclerView

RecyclerView与ListView 对比:缓存机制
RecyclerView比ListView多两级缓存,支持多个离屏ItemView缓存,支持开发者自定义缓存处理逻辑,支持所有RecyclerView共用同一个RecyclerViewPool(缓存池)。
具体来说:
ListView(两级缓存):
在这里插入图片描述
RecyclerView(四级缓存):
在这里插入图片描述
ListView和RecyclerView缓存机制基本一致:

  • 1). mActiveViews和mAttachedScrap功能相似,意义在于快速重用屏幕上可见的列表项ItemView,而不需要重新createView和bindView;
  • 2). mScrapView和mCachedViews + mReyclerViewPool功能相似,意义在于缓存离开屏幕的ItemView,目的是让即将进入屏幕的ItemView重用.
  • 3). RecyclerView的优势在于a.mCacheViews的使用,可以做到屏幕外的列表项ItemView进入屏幕内时也无须bindView快速重用;b.mRecyclerPool可以供多个RecyclerView共同使用,在特定场景下,如viewpaper+多个列表页下有优势.客观来说,RecyclerView在特定场景下对ListView的缓存机制做了补强和完善。
    ​2. 缓存不同:​
  • 1). RecyclerView缓存RecyclerView.ViewHolder,抽象可理解为:View + ViewHolder(避免每次createView时调用findViewById) + flag(标识状态);
  • 2). ListView缓存View。

OKHttp

分发器

Dispatcher ,分发器就是来调配请求任务的,内部会包含一个线程池。可以在创建 OkHttpClient 时,传递我们自己定义的线程池来创建分发器。
这个Dispatcher中的成员有:

//异步请求同时存在的最大请求
private int maxRequests = 64;
//异步请求同一域名同时存在的最大请求
private int maxRequestsPerHost = 5;
//闲置任务(没有请求时可执行一些任务,由使用者设置)
private @Nullable Runnable idleCallback;
//异步请求使用的线程池
private @Nullable ExecutorService executorService;
//异步请求等待执行队列
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
//异步请求正在执行队列
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
//同步请求正在执行队列
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

同步请求

synchronized void executed(RealCall call) {
	runningSyncCalls.add(call);
}
  • 1
  • 2
  • 3

因为同步请求不需要线程池,也不存在任何限制。所以分发器仅做一下记录

异步请求

synchronized void enqueue(AsyncCall call) {
	if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
		runningAsyncCalls.add(call);
		executorService().execute(call);
	} else {
		readyAsyncCalls.add(call);
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

当正在执行的任务未超过最大限制64,同时 runningCallsForHost(call) < maxRequestsPerHost 同一Host的请求不超过5个,则会添加到正在执行队列,同时提交给线程池。否则先加入等待队列。

加入线程池直接执行没啥好说的,但是如果加入等待队列后,就需要等待有空闲名额才开始执行。因此每次执行完一个请求后,都会调用分发器的 finished 方法

//异步请求调用
void finished(AsyncCall call) {
	finished(runningAsyncCalls, call, true);
}
//同步请求调用
void finished(RealCall call) {
	finished(runningSyncCalls, call, false);
}
private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
	int runningCallsCount;
	Runnable idleCallback;
	synchronized (this) {
		//不管异步还是同步,执行完后都要从队列移除(runningSyncCalls/runningAsyncCalls)
		if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
		if (promoteCalls) promoteCalls();
		//异步任务和同步任务正在执行的和
		runningCallsCount = runningCallsCount();
		idleCallback = this.idleCallback;
	}
	// 没有任务执行执行闲置任务
	if (runningCallsCount == 0 && idleCallback != null) {
		idleCallback.run();
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

需要注意的是 只有异步任务才会存在限制与等待,所以在执行完了移除正在执行队列中的元素后,异步任务结束会执行 promoteCalls() 。很显然这个方法肯定会重新调配请求。

private void promoteCalls() {
	//如果任务满了直接返回
	if (runningAsyncCalls.size() >= maxRequests) return;
	//没有等待执行的任务,返回
	if (readyAsyncCalls.isEmpty()) return;
	//遍历等待执行队列
	for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
		AsyncCall call = i.next();
		//等待任务想要执行,还需要满足:这个等待任务请求的Host不能已经存在5个了
		if (runningCallsForHost(call) < maxRequestsPerHost) {
			i.remove();
			runningAsyncCalls.add(call);
			executorService().execute(call);
		}
		if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

在满足条件下,会把等待队列中的任务移动到 runningAsyncCalls 并交给线程池执行。所以分发器到这里就完了。逻辑上还是非常简单的。

请求流程

用户是不需要直接操作任务分发器的,获得的 RealCall 中就分别提供了 execute 与 enqueue 来开始同步请求或异步请求。

@Override public Response execute() throws IOException {
	synchronized (this) {
		if (executed) throw new IllegalStateException("Already Executed");
		executed = true;
	}
	captureCallStackTrace();
	eventListener.callStart(this);
	try {
		//调用分发器
		client.dispatcher().executed(this);
		//执行请求
		Response result = getResponseWithInterceptorChain();
		if (result == null) throw new IOException("Canceled");
		return result;
	} catch (IOException e) {
		eventListener.callFailed(this, e);
		throw e;
	} finally {
		//请求完成
		client.dispatcher().finished(this);
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

异步请求的后续同时是调用 getResponseWithInterceptorChain() 来执行请求

@Override
public void enqueue(Callback responseCallback) {
	synchronized (this) {
		if (executed) throw new IllegalStateException("Already Executed");
		executed = true;
	}
	captureCallStackTrace();
	eventListener.callStart(this);
	//调用分发器
	client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

如果该 RealCall 已经执行过了,再次执行是不允许的。异步请求会把一个 AsyncCall 提交给分发器。
AsyncCall 实际上是一个 Runnable 的子类,使用线程启动一个 Runnable 时会执行 run 方法,在 AsyncCall 中被重定向到 execute 方法:

final class AsyncCall extends NamedRunnable {
	private final Callback responseCallback;
	AsyncCall(Callback responseCallback) {
		super("OkHttp %s", redactedUrl());
		this.responseCallback = responseCallback;
	}
	//线程池执行
	@Override
	protected void execute() {
		boolean signalledCallback = false;
		try {
			Response response = getResponseWithInterceptorChain();
			//.......
		} catch (IOException e) {
			//......
		} finally {
			//请求完成
			client.dispatcher().finished(this);
		}
	}
}
public abstract class NamedRunnable implements Runnable {
	protected final String name;
	public NamedRunnable(String format, Object... args) {
		this.name = Util.format(format, args);
	}
	@Override
	public final void run() {
		String oldName = Thread.currentThread().getName();
		Thread.currentThread().setName(name);
		try {
			execute();
		} finally {
			Thread.currentThread().setName(oldName);
		}
	}
	protected abstract void execute();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

同时 AsyncCall 也是 RealCall 的普通内部类,这意味着它是持有外部类 RealCall 的引用,可以获得直接调用外部类的方法。可以看到无论是同步还是异步请求实际上真正执行请求的工作都在getResponseWithInterceptorChain() 中。这个方法就是整个OkHttp的核心:拦截器责任链。但是在介绍责任链之前,我们再来回顾一下线程池的基础知识

分发器线程池

前面我们提过,分发器就是来调配请求任务的,内部会包含一个线程池。当异步请求时,会将请求任务交给线程池来执行。那分发器中默认的线程池是如何定义的呢?为什么要这么定义?

public synchronized ExecutorService executorService() {
	if (executorService == null) {
		executorService = new ThreadPoolExecutor(
						0, //核心线程
						Integer.MAX_VALUE, //最大线程
						60, //空闲线程闲置时间
						TimeUnit.SECONDS, //闲置时间单位
						new SynchronousQueue<Runnable>(), //线程等待队列
						Util.threadFactory("OkHttp Dispatcher", false) //线程创建工厂
		);
	}
	return executorService;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

拦截器责任链

  • 责任链模式
  • 拦截器流程
    在这里插入图片描述
    默认情况下有五大拦截器:
  1. RetryAndFollowUpInterceptor: 重试及重定向拦截器
    第一个接触到请求,最后接触到响应;负责判断是否需要重新发起整个请求
  2. BridgeInterceptor: 桥接拦截器
    补全请求,并对响应进行额外处理
  3. CacheInterceptor: 缓存拦截器
    请求前查询缓存,获得响应并判断是否需要缓存
  4. ConnectInterceptor: 连接拦截器
    与服务器完成TCP连接
  5. CallServerInterceptor: 请求服务器拦截器
    与服务器通信;封装请求数据与解析响应数据(如:HTTP报文)

Android如何发起网络请求,你有用过相关框架码?OkHttp框架解决了你什么问题?(美团)

一般而言,我们都会借助一些框架来完成这个网络交互,OkHttp就是一个能够帮助我们完成网络通信的框架之一。借助OkHttp能否完成Http1.x、Http2.0以及WebSocket的通信。

Retrofit

Rxjava

你真的了解RxJava的线程切换吗?
Android:随笔——RxJava的线程切换
高频面试–RxJava线程切换的原理
RxJava的线程切换
RxJava3.0线程切换原理

线程切换

observable.subscribeOn(Schedulers.io())
	.observeOn(AndroidSchedulers.mainThread())
	.subscribe(observer);
  • 1
  • 2
  • 3
  • Scheduler: 通过Scheduler让操作符跑在指定线程,从而实现多线程调度
  • observerOn: 指定一个观察者在哪个调度器上观察这个Observable.
    • 在指定线程中向上游订阅
    • 影响的是跟在后面的操作(指定观察者运行的线程)。所以如果想要多次改变线程,可以多次使用 observeOn;
  • subscribeOn: 指定Observable自身在哪个调度器上执行.
    • 收到数据后在指定线程中调用下游的回调方法
    • 影响的是最开始的被观察者所在的线程。当使用多个 subscribeOn() 的时候,只有第一个subscribeOn() 起作用;

在这里插入图片描述

Glide

引入缓存的目的

  • 1、减少流量消耗,加快响应速度;
  • 2、Bitmap 的创建/销毁比较耗内存,可能会导致频繁GC;使用缓存可以更加高效地加载 Bitmap,减少卡
    顿。

Glide缓存流程

  • Glide缓存分为内存缓存和磁盘缓存,其中内存缓存是由弱引用+LruCache组成。
  • 取的顺序是:弱引用、LruCache、磁盘 存的顺序是:磁盘、弱引用、LruCache
    在这里插入图片描述
    内存缓存原理
  • 1.弱引用是由这样一个HashMap维护,key是缓存的key,这个key由图片url、width、height等10来个参数组成;value是图片资源对象的弱引用形式。
Map<Key, ResourceWeakReference> activeEngineResources = new HashMap<>();
  • 1
  • 2.LruCache是由一个LinkedHashMap维护,根据Lru算法来管理图片。
#LruCache
Map<T, Y> cache = new LinkedHashMap<>(100, 0.75f, true);
  • 1
  • 2
  • 3.磁盘缓存原理(DiskLruCache)

谈谈你对Glide生命周期的理解

Glide中通过RequestManagerFragment,内部创建的无UI的Fargment完成生命周期监听。它在RequestManagerRetriever类的getRequestManagerFragment()被调用。相关源码

final Map<android.app.FragmentManager, RequestManagerFragment> pendingRequestManagerFragments = new HashMap<>();
private RequestManagerFragment getRequestManagerFragment(@NonNull final android.app.FragmentManager fm, @Nullable android.app.Fragment parentHint, boolean isParentVisible) {
	RequestManagerFragment current = (RequestManagerFragment) fm.findFragmentByTag(FRAGMENT_TAG);//获取Fragment
	if (current == null) {
		current = pendingRequestManagerFragments.get(fm);
		if (current == null) {
			current = new RequestManagerFragment();
			current.setParentFragmentHint(parentHint);
			if (isParentVisible) {
				current.getGlideLifecycle().onStart();
			}
			pendingRequestManagerFragments.put(fm, current);//保存到map集合
			fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateLoss();//添加到Actvity中
			handler.obtainMessage(ID_REMOVE_FRAGMENT_MANAGER, fm).sendToTarget();
		}
	}
	return current;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

项目中使用Glide框架出现内存溢出,应该是什么原因?(美团)

  1. 项目中使用Glide框架出现内存溢出,请问大概是什么原因?
    答:尽量在with的时候,传入有生命周期的作用域(非Application作用域),尽量避免使用了Application作用域,因为Application作用域不会对页面绑定生命周期机制,就回收不及时释放操作等…
  2. 刚刚提到 作用域,请描述下 Glide中的作用域?
    答:对下图的总结:
    一共分为两种:
    第一种是作用域Application,它的生命周期是全局的,不搞空白Fragment就绑定Activity/Fragment
    第二种是作用域非Application,它的生命周期是,专门搞空白Fragment就绑定Activity/Fragment

ARouter

在这里插入图片描述

EventBus

在这里插入图片描述

一文彻底搞懂EventBus 3.0原理

备用的图

在这里插入图片描述

音视频

音视频开发成长之路与音视频知识总结
音视频开发流程(音视频开发教程)
音视频开发系列1:音视频开发基本概念
一文搞懂音视频开发技术点及职业发展方向
69 篇文章带你系统性的学习音视频开发(收藏起来假期看)

我的面试纲要

熟悉常用的设计模式及反射原理,自定义注解及泛型,多次采用设计模式重构app代码

1. 常用的设计模式

  1. 单例模式(Singleton Pattern): 保证一个类只有一个实例,并提供一个全局访问点。
  2. 工厂模式(Factory Pattern): 定义一个接口用于创建对象,但让子类决定实例化哪个类。工厂方法使一个类的实例化延迟到其子类。
  3. 抽象工厂模式(Abstract Factory Pattern): 提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
  4. 建造者模式(Builder Pattern): 将一个复杂对象的构建与其表示分离,使得同样的构建过程可以创建不同的表示。
  5. 原型模式(Prototype Pattern): 通过复制现有的对象来创建新的对象,而不是通过实例化。
  6. 适配器模式(Adapter Pattern): 允许接口不兼容的类能够一起工作,将一个类的接口转换成客户希望的另一个接口。
  7. 装饰者模式(Decorator Pattern): 动态地给一个对象添加额外的职责。装饰者模式提供了一种灵活的方式,可以扩展一个类的功能。
  8. 代理模式(Proxy Pattern): 为其他对象提供一种代理以控制对这个对象的访问。
  9. 观察者模式(Observer Pattern): 定义一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
  10. 策略模式(Strategy Pattern): 定义一系列算法,将每个算法封装起来,并使它们可以互换。
  11. 模板方法模式(Template Method Pattern): 定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。
  12. 状态模式(State Pattern): 允许对象在其内部状态改变时改变它的行为。
  13. 命令模式(Command Pattern): 将请求封装成一个对象,从而使你可以用不同的请求对客户进行参数化。
  14. 迭代器模式(Iterator Pattern): 提供一种方法顺序访问一个聚合对象中的各个元素,而不暴露其内部的表示。
  15. 责任链模式(Chain of Responsibility Pattern): 使多个对象都有机会处理请求,从而避免请求的发送者与接收者之间的耦合关系。

3. 装饰者模式和代理模式的区别

总结区别:

  1. 装饰者模式用于动态地为对象添加额外的职责,通常通过组合的方式。它关注的是对象的功能扩展。
  2. 代理模式用于控制对对象的访问,通常在访问真实对象前后进行一些控制或附加操作。它关注的是控制访问行为。

2. java的反射原理

原理:在程序的运行状态中,动态获取程序信息及动态调用对象的功能成为Java的反射机制

如何获取一个Class对象

  • 对象.getClass()
    Class c = obj.getClass();
  • 类型.class字面量
    MyTestClass.class;
  • Class类的forName方法
    Class c = Class.forName(“com.xxxx.MyTestClass”);
  • 类加载器的loadClass方法
    Class c = this.getClass().getClassLoader().loadClass(“com.xxx.MyTestClass”);

如何操作一个Class对象

  • Class类的常用方法
    • forName方法可以根据类的完全限定名称获取Class对象。会加载和连接,根据initialize参数决定是否初始化。(我们常用的不指定initialize就是默认初始化)
    • newInstance 创建此Class对象表示的类的新实例 内部实现是调用的Constructor.newInstance方法
    • getClasses 获取在此Class对象对应的类型中声明的public类或接口成员的Class对象数组,包括从超类继承的public类和接口成员
    • getDeclaredClasses 获取在此Class对象对应的类型中声明的类或接口成员的Class对象数组,包括public, protected, default (package) access,private类和接口,但不包括继承的类和接口
    • getFields 获取此Class对象表示的类或接口的所有可访问public字段Field数组 包括继承的
    • getDeclaredFields 获取此Class对象表示的类或接口的所有 public、protected、default(package)access和private字段Field数组 不包括继承的
    • getField 获取此Class对象表示的类或接口的指定的可访问public字段Field对象 会递归向父类、父接口查
    • getDeclaredField 获取此Class对象表示的类或接口的指定的public、protected、default(package)access和private字段Field对象 不包括继承的
    • getMethods 获取该class对象表示的类或接口的所有public方法Method数组 包括继承的
    • getDeclaredMethods 获取该class对象表示的类或接口的public, protected, default (package) access,private方法Method数组 不包括继承的
    • getMethod 获取该class对象表示的类或接口的指定的public方法Method数组 会递归向父类、父接口查
    • getDeclaredMethod 获取该class对象表示的类或接口的指定的public方法Method数组 不包括继承的
    • getConstructors 获取这个class对象表示的类的所有public构造函数Constructor数组
    • getDeclaredConstructors 获取这个class对象表示的类的所有public、protected、default(package)access和private 构造函数Constructor数组
    • getConstructor 获取这个class对象表示的类的指定的public构造函数Constructor对象
    • getDeclaredConstructor 获取这个class对象表示的类的指定的public、protected、default(package)access和private 构造函数Constructor对象
  • Constructor类的常用方法
    public T newInstance(Object … initargs)
  • Method的常用方法
    public Object invoke(Object obj, Object… args)
  • Field的常用方法
    Filed里主要是一些get set方法

谈谈Java反射:从入门到实践,再到原理
Java反射原理
Java反射机制原理探究
深入理解java反射原理

3. java的双亲机制

双亲委派机制是指当一个类加载器收到类加载请求时,它会先将该请求委派给它的父类加载器去尝试加载。只有当父类加载器无法加载该类时,子类加载器才会尝试加载。

优缺点:
优点: 1. 避免重复加载; 2. 安全性; 3.扩展性;
缺点: 1. 灵活性受限; 2. 破坏隔离性 3.不适合动态更新;

一文搞懂“双亲委派机制”

4. java反射运行效率慢

反射涉及动态解析的类型,因此无法执行某些Java虚拟机优化
反射提高了Java程序的灵活性和扩展性,降低耦合性,提高自适应能力。

Java反射到底慢在哪里

5.自定义注解

import java.lang.annotation.*;

// 1.CONSTRUCTOR:用于描述构造器
// 2.FIELD:用于描述域
// 3.LOCAL_VARIABLE:用于描述局部变量
// 4.METHOD:用于描述方法
// 5.PACKAGE:用于描述包
// 6.PARAMETER:用于描述参数
// 7.TYPE:用于描述类、接口(包括注解类型) 或enum声明
//  @Target说明了Annotation所修饰的对象范围
@Target(ElementType.TYPE)
public @interface Table {}

@Target(ElementType.FIELD)
@interface NoDBColumn {}

// 取值(RetentionPoicy)有:
//    1.SOURCE:在源文件中有效(即源文件保留)
//    2.CLASS:在class文件中有效(即class保留)
//    3.RUNTIME:在运行时有效(即运行时保留)
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented // 用于描述其它类型的annotation应该被作为被标注的程序成员的公共API,
@interface Column {}

// @Inherited 元注解是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。
@Inherited
@interface Greeting {}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

深入理解Java:注解(Annotation)自定义注解入门

6. java泛型

  • 泛型类
public class Generic<T> {
    private T key;
    public Generic(T key) {this.key = key;}
    public T getKey() {return key;}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 泛型接口
//定义一个泛型接口
public interface Generator<T> {
    public T next();
}
class FruitGenerator<T> implements Generator<T>{
    @Override
    public T next() {
        return null;
    }
}
public class FruitGenerator implements Generator<String> { /*TODO*/ }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 泛型方法
public <T> T genericMethod(Class<T> tClass)throws InstantiationException ,
  IllegalAccessException{
        T instance = tClass.newInstance();
        return instance;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 泛型方法与可变参数
public <T> void printMsg( T... args){
    for(T t : args){
        Log.d("泛型测试","t is " + t);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 静态方法与泛型
public class StaticGenerator<T> {
    ....
    ....
    /**
     * 如果在类中定义使用泛型的静态方法,需要添加额外的泛型声明(将这个方法定义成泛型方法)
     * 即使静态方法要使用泛型类中已经声明过的泛型也不可以。
     * 如:public static void show(T t){..},此时编译器会提示错误信息:
          "StaticGenerator cannot be refrenced from static context"
     */
    public static <T> void show(T t){/*TODO*/}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 泛型上下边界
    • 上界: extends关键字, 只可接收,不可设置;
    • 下界: super关键字,只可设置,不可接收;

java 泛型详解-绝对是对泛型方法讲解最详细的,没有之一
Java 泛型上下界(上下限)

7.kotlin形变

形变分为三个区域:不变, 协变, 逆变

  • 不变: 普通泛型
class StudentSetGets<IO> {
	private var item: IO? = null
	fun set(value: IO) { /*TODO*/ }
	fun get() = item
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 协变: out 只能取,不能设置
class MyStudentGet<out T>(_item: T) {
	private val item = _item
	fun get(): T = item
}
  • 1
  • 2
  • 3
  • 4
  • 逆变:int 只能这是不能读取
class MyStudentSet<in T>() {
	fun set(value: T) = /*TODO*/
}
  • 1
  • 2
  • 3

熟悉掌握多线程的原理,对ThreadPoolExecutor有深入的分析

1. 多线程原理

原理就是存放线程的池子
一文读懂Java多线程原理

2. 多线程的创建

  1. 继承Thread类创建线程类
class SomeThread extends Thread {
	public void run();
}
SomeThread().start()
  • 1
  • 2
  • 3
  • 4
  1. 实现Runnable接口创建线程类
class SomeRunnable implements Runnable { 
	public void run() { //do something here } 
} 
Runnable oneRunnable = new SomeRunnable(); 
Thread oneThread = new Thread(oneRunnable); o
neThread.start();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  1. 通过Callable和Future创建线程
//1.创建Callable接口的实现类,并实现call()方法 
public class SomeCallable01 implements Callable { 
	@Override 
	public Integer call() throws Exception { 
		return 0; 
	}
}
public static void main(String[] args) {
    //2.创建Callable实现类的实例
    SomeCallable01 ctt = new SomeCallable01();
    //3.使用FutrueTask类进行包装Callable对象,FutureTask对象封装了Callable对象的call()方法的返回值
    FutureTask<Integer> ft = new FutureTask<>(ctt);

    //开启ft线程
    for(int i = 0;i < 21;i++)
    {
        System.out.println(Thread.currentThread().getName()+" 的循环变量i的值"+i);
        if(i==20)//i为20的时候创建ft线程
        {
            //4.使用FutureTask对象作为Thread对象的target创建并启动新线程
            new Thread(ft,"有返回值的线程FutureTask").start();
        }
    }

    //ft线程结束时,获取返回值
    try
    {    
        //5.调用FutureTask对象的get()方法获取子线程执行结束后的返回值。
        System.out.println("子线程的返回值:"+ft.get());//get()方法会阻塞,直到子线程执行结束才返回
    } catch (InterruptedException e)
    {
        e.printStackTrace();
    } catch (ExecutionException e)
    {
        e.printStackTrace();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

JUC线程池: FutureTask详解

3. 线程的生命周期

在这里插入图片描述

4. 创建线程池的五种方法

JAVA中创建线程池的五种方法及比较

newCachedThreadPool 创建一个可缓存的线程池,若线程数超过处理所需,缓存一段时间后会回收,若线程数不够,则新建线程。
private static void createCachedThreadPool() {
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            final int index = i;
            executorService.execute(() -> {
                // 获取线程名称,默认格式:pool-1-thread-1
                System.out.println(DateUtil.now() + " " + Thread.currentThread().getName() + " " + index);
                // 等待2秒
                sleep(2000);
            });
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

在这里插入图片描述

newFixedThreadPool 创建一个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待。
private static void createFixedThreadPool() {
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        for (int i = 0; i < 10; i++) {
            final int index = i;
            executorService.execute(() -> {
                // 获取线程名称,默认格式:pool-1-thread-1
                System.out.println(DateUtil.now() + " " + Thread.currentThread().getName() + " " + index);
                // 等待2秒
                sleep(2000);
            });
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

在这里插入图片描述
因为线程池大小是固定的,这里设置的是3个线程,所以线程名只有3个。因为线程不足会进入队列等待线程空闲,所以日志间隔2秒输出。

newScheduledThreadPool 创建一个周期性的线程池,支持定时及周期性执行任务。
private static void createScheduledThreadPool() {
        ScheduledExecutorService executorService = Executors.newScheduledThreadPool(3);
        System.out.println(DateUtil.now() + " 提交任务");
        for (int i = 0; i < 10; i++) {
            final int index = i;
            executorService.schedule(() -> {
                // 获取线程名称,默认格式:pool-1-thread-1
                System.out.println(DateUtil.now() + " " + Thread.currentThread().getName() + " " + index);
                // 等待2秒
                sleep(2000);
            }, 3, TimeUnit.SECONDS);
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

在这里插入图片描述
 因为设置了延迟3秒,所以提交后3秒才开始执行任务。因为这里设置核心线程数为3个,而线程不足会进入队列等待线程空闲,所以日志间隔2秒输出。

newSingleThreadExecutor 创建一个单线程的线程池,可保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
private static void createSingleThreadPool() {
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 10; i++) {
            final int index = i;
            executorService.execute(() -> {
                // 获取线程名称,默认格式:pool-1-thread-1
                System.out.println(DateUtil.now() + " " + Thread.currentThread().getName() + " " + index);
                // 等待2秒
                sleep(2000);
            });
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

在这里插入图片描述
因为只有一个线程,所以线程名均相同,且是每隔2秒按顺序输出的。

通过ThreadPoolExecutor类自定义
public ThreadPoolExecutor(
	int corePoolSize,  // 核心线程数,线程池中始终存活的线程数。
	int maximumPoolSize, // 最大线程数,线程池中允许的最大线程数。
	long keepAliveTime, // 存活时间,线程没有任务执行时最多保持多久时间会终止。
	TimeUnit unit, // 单位,参数keepAliveTime的时间单位,7种可选。
	BlockingQueue<Runnable> workQueue, // 一个阻塞队列,用来存储等待执行的任务,均为线程安全,7种可选。
	ThreadFactory threadFactory, // 线程工厂,主要用来创建线程,默及正常优先级、非守护线程。
	RejectedExecutionHandler handler // 拒绝策略,拒绝处理任务时的策略,4种可选,默认为AbortPolicy。
) { // 省略...}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

workQueue: 7种选择

  • ArrayBlockingQueue 一个由数组结构组成的有界阻塞队列。
  • LinkedBlockingQueue 一个由链表结构组成的有界阻塞队列。
  • SynchronousQueue 一个不存储元素的阻塞队列,即直接提交给线程不保持它们。
  • PriorityBlockingQueue 一个支持优先级排序的无界阻塞队列。
  • DelayQueue 一个使用优先级队列实现的无界阻塞队列,只有在延迟期满时才能从中提取元素。
  • LinkedTransferQueue 一个由链表结构组成的无界阻塞队列。与SynchronousQueue类似,还含有非阻塞方法。
  • LinkedBlockingDeque 一个由链表结构组成的双向阻塞队列。

handler: 4种拒绝策略

  • AbortPolicy 拒绝并抛出异常。
  • CallerRunsPolicy 重试提交当前的任务,即再次调用运行该任务的execute()方法。
  • DiscardOldestPolicy 抛弃队列头部(最旧)的一个任务,并执行当前任务。
  • DiscardPolicy 抛弃当前任务。

执行策略:
(1)当线程数小于核心线程数时,创建线程。
(2)当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列。
(3)当线程数大于等于核心线程数,且任务队列已满:
若线程数小于最大线程数,创建线程。
若线程数等于最大线程数,抛出异常,拒绝任务。

5.线程池的几种提交方式

  1. submit(Runnable task):
    Future<?> future = executorService.submit(new MyRunnable());
    提交一个 Runnable 任务给 ExecutorService,返回一个 Future 对象。由于 Runnable 不返回结果,Future 的 get() 方法将返回 null。
  2. submit(Callable task):
    Future future = executorService.submit(new MyCallable());
    提交一个 Callable 任务给 ExecutorService,返回一个 Future 对象。Future 的 get() 方法将返回任务执行的结果。
  3. submit(Runnable task, T result):
    Future future = executorService.submit(new MyRunnable(), someResult);
    提交一个 Runnable 任务给 ExecutorService,同时传递一个结果用于 Future 的 get() 方法。该方法通常用于在任务执行完成后,将结果设置到 Future 对象中。
  4. invokeAll(Collection<? extends Callable> tasks):
    List<Future> futures = executorService.invokeAll(taskList);
    执行给定的 Callable 任务集合,返回一个包含 Future 对象的列表。invokeAll 方法将阻塞直到所有任务完成。
  5. invokeAll(Collection<? extends Callable> tasks, long timeout, TimeUnit unit):
    List<Future> futures = executorService.invokeAll(taskList, 1, TimeUnit.MINUTES);
    执行给定的 Callable 任务集合,返回一个包含 Future 对象的列表。与上一个方法不同的是,invokeAll 方法在给定的时间内未完成的任务将被取消。
  6. invokeAny(Collection<? extends Callable> tasks):
    T result = executorService.invokeAny(taskList);
    执行给定的 Callable 任务集合,返回其中一个已完成的任务的结果。如果其中任何一个任务抛出异常或被取消,invokeAny 方法将抛出 ExecutionException。
  7. invokeAny(Collection<? extends Callable> tasks, long timeout, TimeUnit unit):
    T result = executorService.invokeAny(taskList, 1, TimeUnit.MINUTES);
    执行给定的 Callable 任务集合,返回其中一个已完成的任务的结果。与上一个方法不同的是,invokeAny 方法在给定的时间内未完成的任务将被取消。

6. ThreadPoolExecutor是如何运行,如何同时维护线程和执行任务的呢?

在这里插入图片描述
线程池在内部实际上构建了一个生产者消费者模型,将线程和任务两者解耦,并不直接关联,从而良好的缓冲任务,复用线程。线程池的运行主要分成两部分:任务管理、线程管理。
任务管理部分充当生产者的角色,当任务提交后,线程池会判断该任务后续的流转:
(1)直接申请线程执行该任务;
(2)缓冲到队列中等待线程执行;
(3)拒绝该任务。线程管理部分是消费者,它们被统一维护在线程池内,根据任务请求进行线程的分配,当线程执行完任务后则会继续获取新的任务去执行,最终当线程获取不到任务的时候,线程就会被回收。
Java线程池实现原理及其在美团业务中的实践

7. 现成的几个常见方法

  • Thread.sleep(long millis)​,一定是​当前线程​调用此方法,当前线程进入TIMED_WAITING状态,但不释放对象锁,millis后线程自动苏醒进入就绪状态。作用:给其它线程执行机会的最佳方式。
  • ​Thread.yield()​,一定是​当前线程​调用此方法,当前线程放弃获取的CPU时间片,但不释放锁资源,由运行状态变为就绪状态,让OS再次选择线程。作用:让相同优先级的线程轮流执行,但并不保证一定会轮流执行。实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。Thread.yield()不会导致阻塞。该方法与sleep()类似,只是不能由用户指定暂停多长时间。
    ​* thread.join()/thread.join(long millis)​,​当前线程里调用其它线程thread的join方法​,当前线程进入WAITING/TIMED_WAITING状态,当前线程不会释放已经持有的对象锁。线程thread执行完毕或者millis时间到,当前线程进入就绪状态。
  • ​thread.interrupt()​,​当前线程里调用其它线程thread的interrupt()方法,中断指定的线程。 如果指定线程调用了wait()方法组或者join方法组在阻塞状态,那么指定线程会抛出InterruptedException
    ​* Thread.interrupted​,一定是​当前线程​调用此方法,检查当前线程是否被设置了中断,​该方法会重置当前线程的中断标志​,返回当前线程是否被设置了中断。
    ​* thread.isInterrupted()​,​当前线程里调用其它线程thread的isInterrupted()方法,返回指定线程是否被中断​
  • ​object.wait()​,​当前线程​调用**对象的wait()**方法,当前线程释放对象锁,进入等待队列。依靠notify()/notifyAll()唤醒或者wait(long timeout) timeout时间到自动唤醒。

8. CAS

CAS的全称为Compare-And-Swap,直译就是对比交换。

9. AQS

  1. 什么是AQS
    AQS核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。

CLH(Craig,Landin,and Hagersten)队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)。AQS是将每条请求共享资源的线程封装成一个CLH锁队列的一个结点(Node)来实现锁的分配。

  1. AQS 对资源的共享方式
    AQS定义两种资源共享方式
  • Exclusive(独占):只有一个线程能执行,如ReentrantLock。又可分为公平锁和非公平锁:
    • 公平锁:按照线程在队列中的排队顺序,先到者先拿到锁
    • 非公平锁:当线程要获取锁时,无视队列顺序直接去抢锁,谁抢到就是谁的
  • Share(共享):多个线程可同时执行,如Semaphore/CountDownLatch。Semaphore、CountDownLatCh、 CyclicBarrier、ReadWriteLock 我们都会在后面讲到。
  1. AQS底层使用了模板方法模式
  • isHeldExclusively()//该线程是否正在独占资源。只有用到condition才需要去实现它。
  • tryAcquire(int)//独占方式。尝试获取资源,成功则返回true,失败则返回false。
  • tryRelease(int)//独占方式。尝试释放资源,成功则返回true,失败则返回false。
  • tryAcquireShared(int)//共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
  • tryReleaseShared(int)//共享方式。尝试释放资源,成功则返回true,失败则返回false。
  1. AbstractQueuedSynchronizer数据结构
    在这里插入图片描述
  2. 类的核心方法 - acquire方法
    在这里插入图片描述

IOS多线程

  1. Grand Central Dispatch (GCD)
// 在后台队列中执行任务
DispatchQueue.global().async {
    // 执行任务的代码
    DispatchQueue.main.async {
        // 在主队列中更新UI等
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  1. NSOperation和NSOperationQueue
// 创建NSOperation对象
let operation = BlockOperation {
    // 执行任务的代码
}
// 创建NSOperationQueue并添加任务
let operationQueue = OperationQueue()
operationQueue.addOperation(operation)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  1. Thread线程
// 创建并启动线程
let thread = Thread {
    // 执行任务的代码
}
thread.start()
  • 1
  • 2
  • 3
  • 4
  • 5
  1. Dispatch Semaphores
let semaphore = DispatchSemaphore(value: 1)
// 等待信号
semaphore.wait()
// 信号通知
semaphore.signal()
  • 1
  • 2
  • 3
  • 4
  • 5
  1. Dispatch Groups
let group = DispatchGroup()
// 将任务添加到组中
DispatchQueue.global().async(group: group) {
    // 任务1
}
DispatchQueue.global().async(group: group) {
    // 任务2
}
// 在所有任务完成时执行操作
group.notify(queue: .main) {
    // 所有任务完成后执行的代码
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

拥有开发SDK的能力,对外SDK接口文档的能力

反作弊sdk
打点sdk

熟练使用chatgpt等AI工具查询并解决实际的问题,提升工作效率

Android架构浅谈

MVC

在这里插入图片描述

MVP

在这里插入图片描述

MVC和MVP区别

在这里插入图片描述

MVVM

在这里插入图片描述
在这里插入图片描述

Flutter篇

逆向安全

资料

Java 全栈知识体系

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/酷酷是懒虫/article/detail/864266
推荐阅读
相关标签
  

闽ICP备14008679号