先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7
深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
如果你需要这些资料,可以添加V获取:vip204888 (备注Android)
正文
Activity的生命周期都是依靠主线程的Looper.loop,当收到不同Message时则采用相应措施:一旦退出消息循环,那么你的程序也就可以退出了。 从消息队列中取消息可能会阻塞,取到消息会做出相应的处理。如果某个消息处理时间过长,就可能会影响UI线程的刷新速率,造成卡顿的现象。
thread.attach(false)
方法函数中便会创建一个Binder线程(具体是指ApplicationThread
,Binder的服务端,用于接收系统服务AMS发送来的事件),该Binder线程通过Handler将Message发送给主线程。「Activity 启动过程」
比如收到msg=H.LAUNCH_ACTIVITY
,则调用ActivityThread.handleLaunchActivity()
方法,最终会通过反射机制,创建Activity实例,然后再执行Activity.onCreate()
等方法;
再比如收到msg=H.PAUSE_ACTIVITY
,则调用ActivityThread.handlePauseActivity()
方法,最终会执行Activity.onPause()
等方法。
主线程的消息又是哪来的呢?当然是App进程中的其他线程通过Handler发送给主线程
system_server进程
system_server
进程是系统进程,java framework
框架的核心载体,里面运行了大量的系统服务,比如这里提供ApplicationThreadProxy
(简称ATP),ActivityManagerService
(简称AMS),这个两个服务都运行在system_server
进程的不同线程中,由于ATP和AMS都是基于IBinder接口,都是binder线程,binder线程的创建与销毁都是由binder驱动来决定的。
App进程
App进程则是我们常说的应用程序,主线程主要负责
Activity/Service
等组件的生命周期以及UI相关操作都运行在这个线程; 另外,每个App进程中至少会有两个binder线程ApplicationThread(
简称AT)和ActivityManagerProxy
(简称AMP),除了图中画的线程,其中还有很多线程
Binder
Binder用于不同进程之间通信,由一个进程的Binder客户端向另一个进程的服务端发送事务,比如图中线程2向线程4发送事务;而handler用于同一个进程中不同线程的通信,比如图中线程4向主线程发送消息。
结合图说说Activity生命周期,比如暂停Activity,流程如下:
1.线程1的AMS中调用线程2的ATP;(由于同一个进程的线程间资源共享,可以相互直接调用,但需要注意多线程并发问题)
2.线程2通过binder传输到App进程的线程4;
3.线程4通过handler消息机制,将暂停Activity的消息发送给主线程;
4.主线程在looper.loop()
中循环遍历消息,当收到暂停Activity的消息时,便将消息分发给ActivityThread.H.handleMessage()
方法,再经过方法的调用,
5.最后便会调用到Activity.onPause()
,当onPause()
处理完后,继续循环loo
p下去。
补充:
ActivityThread的main
方法主要就是做消息循环,一旦退出消息循环,那么你的程序也就可以退出了。
从消息队列中取消息可能会阻塞,取到消息会做出相应的处理。如果某个消息处理时间过长,就可能会影响UI线程的刷新速率,造成卡顿的现象。
最后通过《Android开发艺术探索》的一段话总结 :
ActivityThread
通过ApplicationThread
和AMS进行进程间通讯,AMS以进程间通信的方式完成ActivityThread的请求后会回调ApplicationThread
中的Binder方法,然后ApplicationThread
会向H发送消息,H收到消息后会将ApplicationThread
中的逻辑切换到ActivityThread
中去执行,即切换到主线程中去执行,这个过程就是。主线程的消息循环模型
另外,ActivityThread
实际上并非线程,不像HandlerThread
类,ActivityThread
并没有真正继承Thread类
那么问题又来了,既然ActivityThread不是一个线程,那么ActivityThread中Looper绑定的是哪个Thread,也可以说它的动力是什么?
回答三:ActivityThread 的动力是什么?
进程每个app运行时前首先创建一个进程,该进程是由Zygote fork
出来的,用于承载App上运行的各种Activity/Service等组件。进程对于上层应用来说是完全透明的,这也是google有意为之,让App程序都是运行在Android Runtime
。大多数情况一个App就运行在一个进程中,除非在AndroidManifest.xml
中配置Android:process
属性,或通过native代码fork进程。
线程 线程对应用来说非常常见,比如每次new Thread().start
都会创建一个新的线程。该线程与App所在进程之间资源共享,从Linux角度来说进程与线程除了是否共享资源外,并没有本质的区别,都是一个task_struct
结构体,在CPU看来进程或线程无非就是一段可执行的代码,CPU采用CFS调度算法,保证每个task都尽可能公平的享有CPU时间片。
其实承载ActivityThread的主线程就是由Zygote fork而创建的进程。
回答四:Handler 是如何能够线程切换
其实看完上面我们大致也清楚线程间是共享资源的。所以Handler处理不同线程问题就只要注意异步情况即可。
这里再引申出Handler的一些小知识点。 Handler创建的时候会采用当前线程的Looper来构造消息循环系统,Looper在哪个线程创建,就跟哪个线程绑定,并且Handler是在他关联的Looper对应的线程中处理消息的。(敲黑板)
那么Handler内部如何获取到当前线程的Looper呢—–ThreadLocal
。ThreadLocal
可以在不同的线程中互不干扰的存储并提供数据,通过ThreadLocal
可以轻松获取每个线程的Looper。
当然需要注意的是:
①线程是默认没有Looper的,如果需要使用Handler,就必须为线程创建Looper。我们经常提到的主线程,也叫UI线程,它就是ActivityThread,
②ActivityThread
被创建时就会初始化Looper,这也是在主线程中默认可以使用Handler的原因。
系统为什么不允许在子线程中访问UI?(摘自《Android开发艺术探索》) 这是因为Android的UI控件不是线程安全的,如果在多线程中并发访问可能会导致UI控件处于不可预期的状态,那么为什么系统不对UI控件的访问加上锁机制呢?
缺点有两个:
①首先加上锁机制会让UI访问的逻辑变得复杂
②锁机制会降低UI访问的效率,因为锁机制会阻塞某些线程的执行。 所以最简单且高效的方法就是采用单线程模型来处理UI操作。
那么问题又来了,子线程一定不能更新UI?
看到这里,又留下两个知识点等待下篇详解:View的绘制机制与Android Window内部机制。
回答五:子线程有哪些更新UI的方法
主线程中定义Handler
,子线程通过mHandler
发送消息,主线程Handler的handleMessage
更新UI。 用Activity对象的runOnUiThread
方法。 创建Handler,传入getMainLooper
。 View.post(Runnabler)
。
runOnUiThread
第一种咱们就不分析了,我们来看看第二种比较常用的写法。
先重新温习一下上面说的
Looper
在哪个线程创建,就跟哪个线程绑定,并且Handler是在他关联的Looper
对应的线程中处理消息的。(敲黑板)
new Thread(new Runnable() {
@Override
public void run() {
runOnUiThread(new Runnable() {
@Override
public void run() {
//DO UI method
}
});
}
}).start();
final Handler mHandler = new Handler();
public final void runOnUiThread(Runnable action) {
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);//子线程(非UI线程)
} else {
action.run();
}
}
进入Activity类里面,可以看到如果是在子线程中,通过mHandler发送的更新UI消息。 而这个Handler是在Activity中创建的,也就是说在主线程中创建,所以便和我们在主线程中使用Handler更新UI没有差别。 因为这个Looper,就是ActivityThread
中创建的Looper(Looper.prepareMainLooper())
。
创建Handler,传入getMainLooper
那么同理,我们在子线程中,是否也可以创建一个Handler,并获取MainLooper
,从而在子线程中更新UI呢? 首先我们看到,在Looper类中有静态对象sMainLooper
,并且这个sMainLooper
就是在ActivityThread
中创建的MainLooper
。
private static Looper sMainLooper; // guarded by Looper.class
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException(“The main Looper has already been prepared.”);
}
sMainLooper = myLooper();
}
}
所以不用多说,我们就可以通过这个sMainLooper
来进行更新UI操作。
new Thread(new Runnable() {
@Override
public void run() {
Log.e(“qdx”, "step 1 "+Thread.currentThread().getName());
Handler handler=new Handler(getMainLooper());
handler.post(new Runnable() {
@Override
public void run() {
//Do Ui method
Log.e(“qdx”, "step 2 "+Thread.currentThread().getName());
}
});
}
}).start();
View.post(Runnabler)
老样子,我们点入源码
//View
/**
-
Causes the Runnable to be added to the message queue.
- The runnable will be run on the user interface thread.
- @param action The Runnable that will be executed.
- @return Returns true if the Runnable was successfully placed in to the
-
message queue. Returns false on failure, usually because the
-
looper processing the message queue is exiting.
*/
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action); //一般情况走这里
}
// Postpone the runnable until we know on which thread it needs to run.
// Assume that the runnable will be successfully placed after attach.
getRunQueue().post(action);
return true;
}
/**
- A Handler supplied by a view’s {@link android.view.ViewRootImpl}. This
- handler can be used to pump events in the UI events queue.
*/
final Handler mHandler;
居然也是Handler从中作祟,根据Handler的注释,也可以清楚该Handler可以处理UI事件,也就是说它的Looper也是主线程的sMainLooper。这就是说我们常用的更新UI都是通过Handler实现的。
另外更新UI 也可以通过AsyncTask
来实现,难道这个AsyncTask
的线程切换也是通过 Handler 吗? 没错,也是通过Handler……
Handler实在是…
回答六:子线程中Toast,showDialog,的方法
可能有些人看到这个问题,就会想: 子线程本来就不可以更新UI的啊 而且上面也说了更新UI的方法
兄台且慢,且听我把话写完
new Thread(new Runnable() {
@Override
public void run() {
Toast.makeText(MainActivity.this, “run on thread”, Toast.LENGTH_SHORT).show();//崩溃无疑
}
}).start();
看到这个崩溃日志,是否有些疑惑,因为一般如果子线程不能更新UI控件是会报如下错误的(子线程不能更新UI)
所以子线程不能更新Toast的原因就和Handler有关了,据我们了解,每一个Handler都要有对应的Looper对象,那么。 满足你。
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
Toast.makeText(MainActivity.this, “run on thread”, Toast.LENGTH_SHORT).show();
Looper.loop();
}
}).start();
这样便能在子线程中Toast,不是说子线程…? 老样子,我们追根到底看一下Toast内部执行方式。
//Toast
/**
- Show the view for the specified duration.
*/
public void show() {
INotificationManager service = getService();//从SMgr中获取名为notification的服务
String pkg = mContext.getOpPackageName();
TN tn = mTN;
tn.mNextView = mNextView;
try {
service.enqueueToast(pkg, tn, mDuration);//enqueue? 难不成和Handler的队列有关?
} catch (RemoteException e) {
// Empty
}
}
在show方法中,我们看到Toast的show方法和普通UI 控件不太一样,并且也是通过Binder进程间通讯方法执行Toast绘制。这其中的过程就不在多讨论了,有兴趣的可以在NotificationManagerService
类中分析。
现在把目光放在TN 这个类上(难道越重要的类命名就越简洁,如H类),通过TN 类,可以了解到它是Binder的本地类。在Toast的show方法中,将这个TN对象传给NotificationManagerService
就是为了通讯!并且我们也在TN中发现了它的show方法。
private static class TN extends ITransientNotification.Stub {//Binder服务端的具体实现类
/**
- schedule handleShow into the right thread
*/
@Override
public void show(IBinder windowToken) {
mHandler.obtainMessage(0, windowToken).sendToTarget();
}
final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
IBinder token = (IBinder) msg.obj;
handleShow(token);
}
};
}
看完上面代码,就知道子线程中Toast报错的原因,因为在TN中使用Handler,所以需要创建Looper对象。 那么既然用Handler来发送消息,就可以在handleMessage
中找到更新Toast的方法。 在handleMessage
看到由handleShow处理。
//Toast的TN类
public void handleShow(IBinder windowToken) {
mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
mParams.x = mX;
mParams.y = mY;
mParams.verticalMargin = mVerticalMargin;
mParams.horizontalMargin = mHorizontalMargin;
mParams.packageName = packageName;
mParams.hideTimeoutMilliseconds = mDuration ==
Toast.LENGTH_LONG ? LONG_DURATION_TIMEOUT : SHORT_DURATION_TIMEOUT;
mParams.token = windowToken;
if (mView.getParent() != null) {
mWM.removeView(mView);
}
mWM.addView(mView, mParams);//使用WindowManager的addView方法
trySendAccessibilityEvent();
}
}
最后
其实要轻松掌握很简单,要点就两个:
- 找到一套好的视频资料,紧跟大牛梳理好的知识框架进行学习。
- 多练。 (视频优势是互动感强,容易集中注意力)
你不需要是天才,也不需要具备强悍的天赋,只要做到这两点,短期内成功的概率是非常高的。
对于很多初中级Android工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。
阿里P7Android高级教程
下面资料部分截图,诚意满满:特别适合有3-5年开发经验的Android程序员们学习。
附送高清脑图,高清知识点讲解教程,以及一些面试真题及答案解析。送给需要的提升技术、近期面试跳槽、自身职业规划迷茫的朋友们。
Android核心高级技术PDF资料,BAT大厂面试真题解析;
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注Android)
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
近期面试跳槽、自身职业规划迷茫的朋友们。
Android核心高级技术PDF资料,BAT大厂面试真题解析;
[外链图片转存中…(img-7GGIMWLh-1713348067304)]
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注Android)
[外链图片转存中…(img-gqrJxqsE-1713348067305)]
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!