百度阿里腾讯头条面试Android高级岗必问!Handler源码解析!

public final boolean post(Runnable r){
return sendMessageDelayed(getPostMessage®, 0);
}

public final boolean postAtTime(Runnable r, long uptimeMillis){
return sendMessageAtTime(getPostMessage®, uptimeMillis);
}

几个 post 方法都是调用了相应的sendXXX 方法,然后用getPostMessage(Runnable r) 构建 Message 对象:

private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}

这里获取到消息后,将 Runnable赋值给 Message.callback ,那这个 callback 有什么用呢?上面的整体流程分析中,我们知道 Looper.loop()会调用 msg.target.dispatchMessage(msg),这个target 就是 Handler 了,那么看一下这个方法的具体实现:

// Handler.java
public void dispatchMessage(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();
}

到这一步终于水落石出了,如果是用 postXXX 方法发送的消息,就会调用 handleCallback(msg) 方法,即调用我们post方法里传递的 Runnable 对象的run()方法。

也就是说,Runnable 跟线程没有半毛钱关系,他只是一个回调方法而已,只不过我们平时创建线程的时候使用多了,误以为他跟线程有什么py交易。


3.3 Handler(Callback) 跟 Handler() 这两个构造方法的区别在哪?

接着看3.2讲到的 dispatchMessage() 方法剩下的逻辑。

如果 msg 没有 callback 的话,那么将会判断 mCallback 是否为空,这个 mCallback 就是构造方法种传递的那个 Callback ,如果 mCallback为空,那么就调用 Handler 的 handleMessage(msg) 方法,否则就调用 mCallback.handleMessage(msg) 方法,然后根据 mCallback.handleMessage(msg)的返回值判断是否拦截消息,如果拦截(返回 true),则结束,否则还会调用 Handler#handleMessage(msg)方法。

也就是说:Callback.handleMessage() 的优先级比 Handler.handleMessage()要高 。如果存在Callback,并且Callback#handleMessage() 返回了 true ,那么Handler#handleMessage()将不会调用。

除了这点,还有什么区别吗?暂时真没发现。


3.4 子线程可以创建 Handler 吗?

问题可能有些模糊,意思是可以在子线程回调 handleMessage()吗。

上面理清了 Handler 的运行流程,但是创建流程好像还没怎么说,先看看 Handler 是怎么创建的:

public Handler() {
this(null, false);
}

public Handler(Callback callback) {
this(callback, false);
}

public Handler(boolean async) {
this(null, async);
}

public Handler(Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
}
}
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can’t create handler inside thread " + Thread.currentThread()

  • " that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
    }

先看上面这部分不传 Looper 的构造方法,这些方法最终都是调用了Handler(Callback callback, boolean async) 方法,所以直接看这个方法就行,一开始会在方法体内检测是否有潜在的内存泄漏风险,相信大家都有过被这东西烦过,看图:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这种被黄色支配的感觉不太舒服,可以在实例上面添加注解@SuppressLint("HandlerLeak")来去掉提示,但是这只是去掉提示而已,别忘了处理潜在的内存泄漏。

接着看下面,首先会调用 Looper.myLooper()方法拿到当前线程的 Looper 实例,如果为空,则抛异常,看看myLooper()具体是怎样的:

//Looper.java
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}

直接就是调用了 sThreadLocal的 get 方法,这个sThreadLocal是一个静态的 ThreadLocal 常量,看名字就能猜到与线程相关,具体的就不深究了。可以先把他看成一个线程id 与 Looper 的 map 键值对。既然有 get() ,那么就应该有 set() ,那么 Looper 是在哪里被存进去的呢?

//Looper.java
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException(“Only one Looper may be created per thread”);
}
sThreadLocal.set(new Looper(quitAllowed));
}

原来是在 Looper.prepare() 方法中被传进去的,并且 sThreadLocal 中每个线程都只能有一个 Looper 实例。需要注意的是,prepare()方法并没有调用 Looper#loop()方法,经过上面的流程分析也知道,这个 loop() 方法启动才能处理发送的消息,所以子线程创建 Handler 除了需要调用 Looper.prepare()外,还需要调用 Looper.loop()启动。

也就说明,任何线程都可以创建 Handler,只要当前线程调用了 Looper.prepare()方法,那么就可以使用 Handler 了,而且同一线程内就算创建 n 个 Handler 实例,也只对应一个 Looper,即对应一个消息队列。

理一理逻辑:Handler 机制要求创建 Handler 的线程必须先调用 Looper.prepare() 方法来初始化,初始化过程中会将当前线程的 Looper 存起来,如果没有进行 Looper 的初始化,将会抛异常,要启动 Looper ,还需要调用 loop() 方法。


3.5 为什么主线程不用调用 Looper.prepare() ?

上面说了,每个线程要创建 Handler 就必须要调用 Looper.prepare进行初始化,那么为什么我们平时在主线程创建 Handler 则不需要调用?

通过3.1 中的 debug 调用链就可以知道,主线程的 loop()方法是在 ActivityThread#main()方法中被调用的,那么看看 main() 方法:

//ActivityThread.java 删减部分代码
public static void main(String[] args) {
Looper.prepareMainLooper();
Looper.loop();
}

到这里就能明白了,在App启动的时候系统默认启动了一个主线程的 Looper,prepareMainLooper()也是调用了 prepare()方法,里面会创建一个不可退出的 Looper,并 set 到 sThreadLocal对象当中。


3.6 为什么创建 Message 对象推荐使用 Message.obtain()获取?

Message 对象有两种方式可以获得,一种是直接 new 一个实例,另一种就是调用 Message.obtain()方法了,Handler.obtainMessage() 也是调用Message.obtain()实现的,看看这个方法:

//Message.java

private static Message sPool;

public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize–;
return m;
}
}
return new Message();
}

可以看到,如果消息池不为空,obtain() 方法会将头结点 sPool取出,并置为非使用状态,然后返回,如果消息池为空,则新建一个消息。

知道有消息池这个东西了,那么这个消息池的消息是怎么来的呢?

使用 AS 搜索一下,发现只有两个方法对 sPool 这个节点进行了赋值,一个是上面的 obtain(),另一个是下面这个:

//Message.java

private static final int MAX_POOL_SIZE = 50;

void recycleUnchecked() {
// Mark the message as in use while it remains in the recycled object pool.
// Clear out all other details.
flags = FLAG_IN_USE;
what = 0;
arg1 = 0;
arg2 = 0;
obj = null;
replyTo = null;
sendingUid = -1;
when = 0;
target = null;
callback = null;
data = null;
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}

看方法名也可以知道,这是一个回收的方法,方法体内将 Message 对象的各种参数清空,如果消息池的数量小于最大数量(50)的话,就当前消息插入缓存池的头结点中。

已经知道 Message 是会被回收的了,那么什么情况才会被回收呢?

继续查看调用链:

// Looper.java ,省略部分代码
loop(){
final MessageQueue queue = me.mQueue;
for (;😉 {
Message msg = queue.next(); // might block , 从队列取出一个msg
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
msg.target.dispatchMessage(msg); //Handler处理消息

msg.recycleUnchecked(); //回收msg
}
}

其中的一个调用是在 Looper.loop()方法中,调用时机是在 Handler 处理事件之后,既然是 Handler 处理后就会回收,那么如果在 Handler.handleMessage() 中用新的线程使用这个 msg 会怎样呢?

//MainActivity.java
@SuppressLint(“HandlerLeak”)
static Handler innerHandler = new Handler() {
@Override
public void handleMessage(final Message msg) {
new Thread(new Runnable() {
@Override
public void run() {
boolean isRecycle = msg.obj == null;
Log.e(“==是否已经回收=”, “” + isRecycle);
}
}).start();
}
};

private void send(){
Message msg = Message.obtain();
msg.what = 0; //标识
msg.obj = “这是消息体”; //消息内容
innerHandler.sendMessage(msg);
}

当调用 send()方法发送消息后,发现打出 log:

E/==是否已经回收=: true

也就说明我们的推断是正确的。所以在平时使用中,不要在 handleMessage(Message msg)方法中对 msg 进行异步处理,因为异步处理后,该方法会马上返回,相当于告诉 Looper 已经处理完成了,Looper 就会将其回收。

如果真要在异步中使用,那么可以创建一个新的 Message 对象,并将值赋值过去。

回到前面的问题,我们目前发现了一个 Message 被回收的地方,那么其他地方有调用这个 Message .recycleUnchecked() 吗?接着看看:

//Message.java
public void recycle() {
if (isInUse()) {
if (gCheckRecycle) {
throw new IllegalStateException("This message cannot be recycled because it "

  • “is still in use.”);
    }
    return;
    }
    recycleUnchecked();
    }

Message 还有一个公共的回收方法,就是上面这个了,我们可以手动调用这个进行回收。还有就是消息队列中各种 removeMessage 也会触发回收,调用链太多了,就不贴代码了。

总而言之,因为 Handler 机制在整个 Android 系统中使用太频繁,所以 Android 就采用了一个缓存策略。就是 Message 里面会缓存一个静态的消息池,当消息被处理或者移除的时候就会被回收到消息池,所以推荐使用 Message.obtain()来获取消息对象。


3.7 梳理

到此就把Handler的大致流程分析完了,再画个图重新梳理一下思路:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

把整个Handler机制比作一个流水线的话,那么 Handler 就是工人,可以在不同线程传递 Message到传送带(MessageQueue),而传送带是被马达(Looper)运输的,马达又是一开始就运行了(Looper.loop()),并且只会在一开始的线程,所以无论哪个工人(Handler)在哪里(任意线程)传递产品(Message),都只会在一条传送带(MessageQueue)上被唯一的马达(Looper)运送到终点处理,即 Message 只会在调用 Looper.loop() 的线程被处理。

4 常见问题&技巧


4.1 为什么 Handler 会造成内存泄漏?

先来回顾下基础知识,可能造成内存泄漏的原因可以大致概括如下:

生命周期长的对象引用了生命周期短的对象。

Handler 跟其他一些类一样,本身是不会造成内存泄漏的,Handler 造成内存泄漏的一般原因都是由于匿名内部类引起的,因为匿名内部类隐性地持有外部类的引用(如果不持有引用怎么可以使用外部类的变量方法呢?)。

所以当内部类的生命周期比较长,如跑一个新的线程,碰巧又碰到生命周期短的对象(如Activity)需要回收,就会导致生命周期短的对象还在被生命周期长的对象所引用,进而回收不了。

典型的例子:

public class Main {

int _10m = 1010241024;
byte[] bytes = new byte[4*_10m];

public void run() {
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(100*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}

public static void main(String args[]) {
Main object = new Main();
object.run();
object =null;
System.gc();
}
}

输出log:

[GC (System.gc()) [PSYoungGen: 3341K->880K(38400K)] 44301K->41848K(125952K)
[Full GC (System.gc()) [PSYoungGen: 880K->0K(38400K)] [ParOldGen: 40968K->41697K(87552K)] 41848K->41697K(125952K)

可以看到,即使object引用为空,object 对象还是没有被回收。这就会发生了内存泄漏,如果出现很多次这样的情况,那么就很有可能发生内存溢出(OutOfMemery)。

在 Handler 里面其实是类似的道理,匿名内部类的 Handler 持有 Activity 的引用,而发送的 Message 又持有 Handler 的引用,Message 又存在于 MessageQueue 中,而 MessageQueue 又是 Looper 的成员变量,并且 Looper 对象又是存在于静态常量 sThreadLocal 中。

所以反推回来,因为 sThreadLocal 是方法区常量,所以不会被回收,而 sThreadLocal 又持有 Looper 的引用…balabala…还是看图吧:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

即 sThreadLocal 间接的持有了 Activity 的引用,当 Handler 发送的消息还没有被处理完毕时,比如延时消息,而 Activity 又被用户返回了,即 onDestroy() 后,系统想要对 Activity 对象进行回收,但是发现还有引用链存在,回收不了,就造成了内存泄漏。


4.2 怎么防止 Handler 内存泄漏?

从上面的分析中,可以知道,想要防止 Handler 内存泄漏,一种方法是把 sThreadLocal 到 Activity 的引用链断开就行了。

**最简单的方法就是在 onPause()中使用 Handler 的 removeCallbacksAndMessages(null)方法清除所有消息及回调。**就可以把引用链断开了。

Android 源码中这种方式也很常见,不在 onDestroy()里面调用主要是 onDestroy() 方法不能保证每次都能执行到。

第二种方法就是使用静态类加弱引用的方式:

public class MainActivity extends AppCompatActivity {

public TextView textView;

static class WeakRefHandler extends Handler {

//弱引用
private WeakReference reference;

public WeakRefHandler(MainActivity mainActivity) {
this.reference = new WeakReference(mainActivity);
}

@Override
public void handleMessage(Message msg) {
MainActivity activity = reference.get();
if (activity != null) {
activity.textView.setText(“鸡汤程序员”);
}
}
}
}

因为静态类不会持有外部类的引用,所以需要传一个 Activity 过来,并且使用一个弱引用来引用 Activity 的实例,弱引用在 gc 的时候会被回收,所以也就相当于把强引用链给断了,自然也就没有内存泄漏了。


4.3 Loop.loop() 为什么不会造成应用卡死?

上面也提了这个问题,按照一般的想法来说,loop() 方法是一个死循环,那么肯定会占用大量的 cpu 而导致应用卡顿,甚至说 ANR 。

但是 Android 中即使使用大量的 Looper ,也不会造成这种问题,问什么呢?

由于这个问题涉及到的知识比较深,主要是通过 Linux 的 epoll 机制实现的,这里需要 Linux 、 jni 等知识,我等菜鸟就不分析了,大家可以去搜这类似的文章了解下。

5. 总结


以上就是篇文章的全部分析了,这里总结一下:

1. Handler 的回调方法是在 Looper.loop()所调用的线程进行的;
2. Handler 的创建需要先调用 Looper.prepare() ,然后再手动调用 loop()方法开启循环;
3. App 启动时会在ActivityThread.main()方法中创建主线程的 Looper ,并开启循环,所以主线程使用 Handler 不用调用第2点的逻辑;
4. 延时消息并不会阻塞消息队列;
5. 异步消息不会马上执行,插入队列的方式跟同步消息一样,唯一的区别是当有消息屏障时,异步消息可以继续执行,同步消息则不行;
6. Callback.handleMessage() 的优先级比 Handler.handleMessage()要高*
7. Handler.post(Runnable)传递的 Runnale 对象并不会在新的线程执行;
8. Message 的创建推荐使用 Message.obtain() 来获取,内部采用缓存消息池实现;
9. 不要在 handleMessage()中对消息进行异步处理;
10. 可以通过removeCallbacksAndMessages(null)或者静态类加弱引用的方式防止内存泄漏;
11. Looper.loop()不会造成应用卡死,里面使用了 Linux 的 epoll 机制。

##6. APP开发框架体系,Android架构师脑图,全套视频

  • 6.1 APP开发框架体系;
    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 6.2 BAT主流Android高级架构技术大纲+学习路线+资料分享

架构技术详解,学习路线与资料分享都在博客这篇文章里《“寒冬未过”,阿里P9架构分享Android必备技术点,让你offer拿到手软!》
(包括自定义控件、NDK、架构设计、混合式开发工程师(React native,Weex)、性能优化、完整商业项目开发等)

  • 阿里P8级Android架构师技术脑图
    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • **全套体系化高级架构视频;**七大主流技术模块,视频+源码+笔记

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

下面我将继续深入讲解 Android中的Handler异步通信传递机制的相关知识,如 工作机制流程、源码解析等,感兴趣的同学可以继续关注本人

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

尾声

最后,我再重复一次,如果你想成为一个优秀的 Android 开发人员,请集中精力,对基础和重要的事情做深度研究。

对于很多初中级Android工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。 整理的这些架构技术希望对Android开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。

最后想要拿高薪实现技术提升薪水得到质的飞跃。最快捷的方式,就是有人可以带着你一起分析,这样学习起来最为高效,所以为了大家能够顺利进阶中高级、架构师,我特地为大家准备了一套高手学习的源码和框架视频等精品Android架构师教程,保证你学了以后保证薪资上升一个台阶。

当你有了学习线路,学习哪些内容,也知道以后的路怎么走了,理论看多了总要实践的。

进阶学习视频

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题 (含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
套高手学习的源码和框架视频等精品Android架构师教程,保证你学了以后保证薪资上升一个台阶。

当你有了学习线路,学习哪些内容,也知道以后的路怎么走了,理论看多了总要实践的。

进阶学习视频

[外链图片转存中…(img-7cJImBC8-1713000928749)]

附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题 (含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)

[外链图片转存中…(img-rRP3j6Uf-1713000928750)]

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
[外链图片转存中…(img-1Ag9J1Di-1713000928750)]

  • 16
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1. 你对 Android 操作系统的架构有什么了解? Android 操作系统采用了 Linux 内核,并在其上构建了一个应用程序框架。应用程序框架提供了一套 API,使开发者可以创建应用程序,并与操作系统进行交互。Android 操作系统的架构包括四个层次:应用程序层、应用程序框架层、系统运行时层和 Linux 内核层。 2. 你对 Android 应用程序框架有什么了解? Android 应用程序框架提供了一套 API,使开发者可以创建应用程序,并与操作系统进行交互。应用程序框架主要包括以下模块:活动管理器、资源管理器、视图系统、通知管理器、内容提供者、位置管理器、文本到语音、蓝牙和 Wi-Fi。 3. 你熟悉哪些 Android 开发框架? 我熟悉的 Android 开发框架包括:Android Jetpack、Retrofit、OkHttp、Glide、Dagger、Butter Knife、RxJava、Room、LiveData、Navigation 等。 4. 你了解 Android 的多线程编程吗? 是的。Android 的多线程编程可以通过 Java 的多线程机制来实现。常见的多线程编程方式包括使用 AsyncTask、Handler 和 Thread 等。此外,还可以使用线程池来管理线程,以避免因线程数量过多而导致的性能问题。 5. 你熟悉哪些 Android 数据存储技术? 我熟悉的 Android 数据存储技术包括:SharedPreferences、SQLite、ContentProvider、File、Room 等。SharedPreferences 用于存储应用程序的简单键值对数据;SQLite 用于存储结构化数据;ContentProvider 用于在应用程序之间共享数据;File 用于存储应用程序的文件数据;Room 是 Google 推出的 ORM 框架,用于简化 SQLite 数据库的操作。 6. 你了解 Android 的网络编程吗? 是的。Android 的网络编程可以使用 Java 的网络编程 API 来实现。常见的网络编程方式包括使用 HttpURLConnection 和 OkHttp 等。此外,还可以使用 Retrofit 来简化网络请求的代码。 7. 你了解 Android 的推送技术吗? 是的。Android 的推送技术可以使用 Google 推出的 Firebase Cloud Messaging(FCM)来实现。FCM 可以向设备发送推送通知、数据消息和通知消息。开发者可以使用 FCM 控制台或 API 来向设备发送消息。 8. 你熟悉哪些 Android UI 组件? 我熟悉的 Android UI 组件包括:TextView、Button、EditText、ImageView、ListView、RecyclerView、TabLayout、ViewPager、Toolbar 等。此外,还可以使用自定义 View 和自定义布局来实现更复杂的 UI 组件。 9. 你了解 Android 的性能优化技术吗? 是的。Android 的性能优化技术包括:布局优化、UI 绘制优化、内存优化、网络优化、数据库优化、电量优化等。常见的性能优化方式包括使用 ViewHolder、使用 Lint 工具、使用性能分析器、使用缓存策略、使用数据库索引、使用 JobScheduler 等。 10. 你有哪些 Android 项目经验? 我参与过多个 Android 项目的开发,其中包括基于 Retrofit 和 OkHttp 的网络请求库开发、基于 MVP 架构的天气应用开发、基于 Jetpack 组件的音乐播放器开发等。这些项目涉及了网络编程、多线程编程、数据存储、UI 组件和性能优化等方面的知识和技能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值