Android面试总结ing.
由于近期在准备面试,所以就把面试题作为简书的开始之旅吧。
一.自定义Handler的时候,如何有效避免内存泄漏的问题
问题原因:一般非静态内部类持有外部类引用的情况下,当外部类使用完成时无法被系统回收内存,从而造成内存泄漏。这里非静态的Handler持有Activity等组件的引用,当Activity被销毁后,该Handler依然持有Activity的引用,从而造成内存泄漏。
Handler:是一个消息发送和处理机制的核心组件之一,用来处理异步消息,通常与Message、MessageQueue、Looper配合使用。
Handler工作机制:在主线程创建一个Handler对象,并重写handleMessage()方法,然后在子线程需要发送消息的地方创建Message对象(一般推荐Message.obtain()这个静态的方法获取,因为这个方法会从消息池中获取Message对象,如果消息池是空的,才会构建一个新的Message对象),通过handler把消息发送出去。之后这条消息进入MessageQueue中等待被处理,通过Looper对象会一直从MessageQueue中取出待处理的消息,它会开启无限循环(Loper.loop())并不停的从 MessageQueue 中查看是否有新消息,如果有就拿出来处理,如果没有呢,就阻塞(其实真正的阻塞在 MessageQueue 的 next 里),最后分发返回Handler的handleMessage()方法中。
Message和Runnable类是消息的载体,通过sendMessage发送消息;
MessageQueue是一个消息等待的队列,存放着等待处理的消息;
Looper则负责从队列中取出消息。
为什么使用Handler:
主线程无法进行时间比较繁长的任务,所以需要子线程进行处理,然而子线程无法进行UI的界面更新,所以我们需要使用handler来传递消息给主线程,让其完成UI的更新。由于主线程和子线程进行不同的时间工作,所要需要用MessageQueue来存放子线程的消息,Looper取出消息交给主线程响应。
在主线程创建Handler和子线程创建的区别:
1.在主线程中创建handler,并不需要经历初始化looper等操作,因为在主线程刚创建的时候系统就自动为该主线程创建好了并启动了循环(系统在应用启动的入口ActivityThread类的main()方法中帮我们调用了),所以我们只需要在主线程中new一个handler并且重写handleMessage方法,当其他的子线程需要更新UI的时候,只需要获取该handle对象,并将信息发送即可。
2.在子线程创建handler,需要先初始化Looper,调用Looper.prepare();然后 创建looper对象,looper=Looper.myLooper();最后启动looper循环,looper.loop();
Handler的2个主要作用:
1.安排调度(scheule)消息和可执行的runnable,可以立即执行,也可以安排在某个将来的时间点执行。
2.让某一个行为(action)在其他线程中执行。
解决方法:
1.保证Activity被finish()时该线程的消息队列没有这个Activity的handler内部类的引用。这个场景是及其常见的,因为handler经常被用来发延时消息。一个补救的办法就是在该类需要回收的时候,手动地把消息队列中的消息清空:mHandler.removeCallbacksAndMessages(null);
2.要么让这个handler不持有Activity等外部组件实例,让该Handler成为静态内部类。(静态内部类是不持有外部类的实例的,因而也就调用不了外部的实例方法了)
3.在2方法的基础上,为了能调用外部的实例方法,传递一个外部的弱引用进来)
4.将Handler放到抽取出来放入一个单独的顶层类文件中。
引用类型 | 内容 |
---|---|
强引用(Strong Reference) | 默认引用,如果一个对象具有强引用,垃圾回收器绝不会回收它。在内存空 间不足时,Java虚拟机宁愿抛出OutOfMemory的错误,使程序异常终止,也不会强引用的对象来解决内存不足问题 |
软引用(SoftReference) | 如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。 |
弱引用(WeakReference) | 在垃圾回收器一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。 |
虚引用(PhantomReference) | 如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。 |
弱引用示例:
public class TestWeakActivity extends AppCompatActivity {
private MyHandler myHandler;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
myHandler=new MyHandler(TestWeakActivity.this);
}
public static class MyHandler extends Handler{
private WeakReference<TestWeakActivity> wActivity;
private MyHandler(TestWeakActivity activity){
wActivity=new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
TestWeakActivity tActivity=wActivity.get();
if(null != tActivity){
//处理逻辑信息
}
}
}
}
二、Activity与Fragment的通信方式总结
一般我们可以通过以下几种方式实现Activity和Fragment的通信:
- Bundle
- Handler
- EventBus
- 广播
- 接口
Bundle
//Activity
Bundle bundle=new Bundle();
bundle.putString("name","张三");
TestFragment testFragment=new TestFragment();
testFragment.setArguments(bundle);
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.activity_main, null);
Bundle bundle = getArguments();
//这里就拿到了之前传递的参数
String name = bundle.getString("name");
return view;
}
Handler
public class MainActivity extends AppCompatActivity {
public Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
//相应的逻辑处理代码
}
}
}
public class MainFragment extends Fragment{
//保存Activity传递的handler
private Handler mHandler;
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
if(activity instance MainActivity){
mHandler = ((MainActivity)activity).mHandler;
}
}
}
这种方式的缺点:
-
Fragment对具体的Activity存在耦合,不利于Fragment复用
-
不利于维护,若想删除相应的Activity,Fragment也得改动
-
没法获取Activity的返回数据
-
所以一般不建议使用这种方法。
EventBus
// Activity 注册订阅者
EventBus.getDefault().register(this);
//定义接收信息的方法
@Subscribe
public void onEventMainThread(String msg) {
Log.d("接收到消息: ",msg);
}
//发送消息
EventBus.getDefault().post("Hello");
广播
//接收广播
public static class JumpReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
KLog.d("aaa_收到广播: "+intent.getAction());
}
}
//注册和发送广播
private JumpReceiver receiver;
IntentFilter intentFilter=new IntentFilter();
intentFilter.addAction("jumpOrder");
receiver=new JumpReceiver();
registerReceiver(receiver,intentFilter);
//触发广播
Intent mIntent = new Intent("jumpOrder");
sendBroadcast(mIntent);
接口回调
- 在 Fragment 中定义一个接口
- 调用接口中的抽象方法
- 在 Activity 中实现接口,并具体实现接口中的方法,完成通信。
三、内存泄漏的原因以及解决方法
内存泄漏:当一个对象使用完成后,本该被系统回收内存时,而有另外一个正在使用的对象持有它的引用从而导致对象不能回收。这种本该被回收的对象不能被系统回收而停留在堆内存中,就产生了内存泄漏。
内存溢出(OOM):程序在申请内存时,没有足够的内存空间供其使用,就会出现OOM。也就是内存不够用。
内存泄漏的根本原因:生命周期长的对象持有生命周期短的对象的引用,持有引用者的生命周期>被引用者的生命周期;
内存溢出的原因:
1.当发生过多的内存泄漏就会导致程序内存空间不够,所需的内存空间会超出系统分配的空间,从而内存溢出(关键原因);
2.还有一种情况就是手机本身的内存不足;
内存溢出场景:1.往内存中加载超大文件;2.循环创建大量对象;
java的GC内存回收机制:当对象不再持有任何引用的时候才会进行回收。
常见的内存泄漏场景以及解决方法:
1.资源对象使用完后没有关闭
- cursor 数据库游标使用后关闭游标cursor.close()
- file 文件流记得关闭流InputStream / OutputStream.close()
- EventBus和广播BraodcastReceiver 记得注销注册
- 动画 设置成无限循环播放repeatCount = “infinite”后,在Activity退出时记得停止动画
2.Static修饰的成员变量以及单例模式
原因:static关键字修饰的成员变量(生命周期) = 应用程序(生命周期)
例如: 当Activity需销毁时,由于mContext的生命周期 = 应用程序的生命周期,则 Activity无法被回收,从而出现内存泄露
解决方法:如果使用Context ,尽可能使用ApplicationContext
单例模式就是静态成员变量,所以在创建单例时,传入的Context最好是ApplicationContext
3.非静态内部类持有外部类的引用
非静态内部类创建静态实例引起的内存泄漏;
Handler持有Activity的引用
解决办法:将Handler声明为静态内部类,就不会持有外部类的引用,其生命周期就和外部类无关
如果Handler里面需要context的话,可以通过弱引用方式引用外部类
4.线程操作不当
原因:线程持有对象的引用在后台执行,与对象的生命周期不一致
解决办法:静态实例+弱引用(WeakReference)方式,使其生命周期一致
如何避免内存泄漏:
(1)编码规范上:
①资源对象用完一定要关闭,最好加finally
②静态集合对象用完要清理
③接收器、监听器使用时候注册和取消注册成对出现
④context使用注意生命周期,如果是静态类引用直接用ApplicationContext
⑤使用静态内部类
⑥结合业务场景,设置软引用,弱引用,确保对象可以在合适的时机回收
(2)建设内存监控体系
线下监控:
①使用ArtHook检测图片尺寸是否超出imageview自身宽高的2倍
②编码阶段Memery Profile看app的内存使用情况,是否存在内存抖动,内存泄漏,结合Mat分析内存泄漏
线上监控:
①上报app使用期间待机内存、重点模块内存、OOM率
②上报整体及重点模块的GC次数,GC时间
③使用LeakCannery自动化内存泄漏分析
如何确定是否存在内存泄漏:
- Android Monitors的内存分析:最直观的看内存增长情况,知道该动作是否发生内存泄露;
- 使用MAT内存分析工具:MAT分析heap的总内存占用大小来初步判断是否存在泄露;
内存优化:
(1)为应用申请更大内存,把manifest上的largeheap设置为true
(2)减少内存的使用
①使用优化后的集合对象,比如SpaseArray;
②使用微信的mmkv替代sharedpreference;
③对于经常打log的地方使用StringBuilder来组拼,替代String拼接
④统一带有缓存的基础库,特别是图片库,如果用了两套不一样的图片加载库就会出现2个图片各自维护一套图片缓存
⑤给ImageView设置合适尺寸的图片,列表页显示缩略图,查看大图显示原图
⑥优化业务架构设计,比如省市区数据分批加载,需要加载省就加载省,需要加载市就加载失去,避免一下子加载所有数据
⑦界面优化(层级减少、界面尽量不刷新或者局部刷新)也会优化内存
(3)避免内存泄漏
四、Android的启动模式LanuchMode
1.standard:标准模式、默认模式,每次创建一个Activity都会创建一个新的实例;
应用场景:大多数的普通Activity
2.singleTop:栈顶复用模式
如果该Activity处于栈顶,则不会创建新的Activity实例,直接复用栈顶的Acticity,会调用onNewIntent();
如果该Activity不存在或者不处于栈顶,则会跟standard模式一样;
应用场景:假设你在当前的Activity中又要启动同类型的Activity,此时建议将此类型Activity的启动模式指定为SingleTop,能够降低Activity的创建,节省内存(一般常见于社交应用中的通知栏行为功能,例如:App 用户收到几条好友请求的推送消息,需要用户点击推送通知进入到请求者个人信息页,将信息页设置为 SingleTop 模式就可以增强复用性)
3.singleTask:栈内复用模式,不会存在多个实例
如果该Activity不存在栈中,则会新建一个Activity实例;
如果该Activity处于栈中,则会移除该Activity上面的所有Activity,使之成为栈顶;
应用场景:一般用作应用的首页,例如浏览器主页,用户可能从多个应用启动浏览器,但主界面仅仅启动一次,其余情况都会走onNewIntent(),并且会清空主界面上面的其他页面
4.singleInstance:全局单例模式
是全局单例模式,是一种加强的SingleTask模式。它除了具有它所有特性外,还加强了一点:具有此模式的Activity仅仅能单独位于一个任务栈中。
应用场景:这个经常使用于系统中的应用,比如Launch、锁屏键的应用等等,整个系统中仅仅有一个!所以在我们的应用中一般不会用到(模式常应用于独立栈操作的应用,如闹钟的提醒页面,当你在A应用中看视频时,闹钟响了,你点击闹钟提醒通知后进入提醒详情页面,然后点击返回就再次回到A的视频页面,这样就不会过多干扰到用户先前的操作了)
五、如何实现多线程
多线程在应用中是很常见,通常有以下几种实现方式:
- 继承 Thread类
- 实现 Runnable接口
- 异步消息机制Handler
- AsyncTask
⑴继承Thread类
public class MyThread extends Thread{
@Override
public void run() {
//处理耗时逻辑
}
}
new MyThread().start(); //启动线程
⑵实现Runnable接口
public class MyThread implements Runnable{
@Override
public void run() {
//处理逻辑
}
}
new Thread(new MyThread()).start();//启动线程
也可以使用匿名类来实现 Runnable 接口:
new Thread(new Runnable() {
@Override
public void run() {
}
}).start();
⑶Handler
3.1Handler的POST方式
// 声明一个Handler对象
private static Handler handler=new Handler();
// 新启动一个子线程
new Thread(new Runnable() {
@Override
public void run() {
handler.post(new Runnable() {
@Override
public void run() {
//进行逻辑处理
}
});
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
handler.postDelayed(new Runnable() {
@Override
public void run() {
//延迟2秒处理逻辑
}
},2000);
}
}).start();
3.2Handler的Message方式
private Handler handler=new Handler(){
@Override
public void handleMessage(Message msg) {
//根据消息码判断处理
switch (msg.what){
case 1:
break;
default:
}
}
};
发送消息
// 获取一个Message对象,设置what为1
Message msg=Message.obtain();
msg.what=1;
msg.obj="内容";
// 发送这个消息到消息队列中
handler.sendMessage(msg);
⑷AsyncTask
为了更方便地在子线程中进行更新 UI 操作,Android 基于异步处理消息机制封装了一个工具类 AsyncTask。
AsyncTask 是个抽象类,所以要使用它必须创建一个子类 。 在继承时可以指定 3 个泛型参数:
参数 | 说明 |
---|---|
Params | 开始异步任务执行时传入的参数类型,对应excute()中传递的参数 |
Progress | 异步任务执行过程中,返回下载进度值的类型 |
Result | 返回的结果类型,与doInBackground()的返回值类型保持一致 |
使用步骤:
- 创建一个类并继承 ,根据实际需求实现核心方法
- 初始化上一步创建的类
- 手动调用 从而执行异步线程任务
public class MyAsyncTask extends AsyncTask<Params, Progress, Result>{
// 作用:执行线程任务前的操作
@Override
protected void onPreExecute() {
}
// 作用:接收输入参数、执行任务中的耗时操作、返回线程任务执行的结果
@Override
protected Object doInBackground(Object[] objects) {
// 自定义的线程任务
return null;
}
// 作用:在主线程显示线程任务执行的进度
@Override
protected void onProgressUpdate(Object[] values) {
}
// 作用:接收线程任务执行结果、将执行结果显示到UI组件
@Override
protected void onPostExecute(Object o) {
}
// 作用:将异步任务设置为:取消状态
@Override
protected void onCancelled() {
super.onCancelled();
}
}
MyAsyncTask myAsyncTask=new MyAsyncTask();
myAsyncTask.execute();
比如模拟一个加载进度条的demo:
public class MyAsyncTask extends AsyncTask<String, Integer, String>{
// 作用:执行线程任务前的操作
@Override
protected void onPreExecute() {
progress_Tv.setText("加载中...");
}
@Override
protected String doInBackground(String... result) {
// 自定义的线程任务
try {
int count = 0;
int length = 1;
while (count<99) {
count += length;
// 可调用publishProgress显示进度, 之后将执行onProgressUpdate
publishProgress(count);
// 模拟耗时任务
Thread.sleep(50);
}
}catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
// 作用:在主线程显示线程任务执行的进度
@Override
protected void onProgressUpdate(Integer... progress) {
progressBar.setProgress(progress[0]);
progress_Tv.setText("加载..." + progress[0] + "%");
}
// 作用:接收线程任务执行结果、将执行结果显示到UI组件
@Override
protected void onPostExecute(String result) {
// 执行完成后,则更新UI
progress_Tv.setText("加载完成");
}
// 作用:将异步任务设置为:取消状态
@Override
protected void onCancelled() {
progress_Tv.setText("已取消");
progressBar.setProgress(0);
}
}
注意事项
- 在Activity 或 Fragment中使用 AsyncTask时,最好在Activity 或 Fragment的onDestory()调用 cancel(boolean)
- AsyncTask如果被声明为Activity的非静态内部类,应该改为静态内部类,以防止内存泄漏
- 线程任务执行结果丢失: 当Activity重新创建时(屏幕旋转 / Activity被意外销毁时后恢复),之前运行的AsyncTask(非静态的内部类)持有的之前Activity引用已无效,故复写的onPostExecute()将不生效,即无法更新UI操作。所以建议在Activity恢复时的对应方法重启任务线程
如何终止线程
Thread的一些主要函数
函数名 | 作用 |
---|---|
start | 启动线程 |
run | 线程运行时所执行的代码 |
sleep | 线程休眠,进入阻塞状态,sleep方法不会释放锁(其它线程不会进入synchronized方法体或方法块) |
wait | 进入阻塞状态,wait 方法释放锁,只有调用对象的 notify() ,线程才会继续运行 |
interrupt | 中断线程,注意只能中断阻塞状态的线程 |
- 使用一个volatile变量来标识,如果变量为true则跳出 run()方法
public volatile boolean isNext= false;
@Override
public void run() {
super.run();
while (!isNext){
// thread runing
}
}
- 使用interrupt()
public class SleepThread extends Thread{
@Override
public void run() {
super.run();
int i = 0;
while(!isInterrupted()){ // 判断线程是否被打断
try {
i++;
Thread.sleep(1000);
Log.i("thread",Thread.currentThread().getName()+":Running()_Count:"+i);
} catch (InterruptedException e) {
e.printStackTrace();
Log.i("thread",Thread.currentThread().getName()+"异常抛出,停止线程");
break;// 抛出异常跳出循环
}
}
}
}
多线程的同步:
1.同步机制
在多线程中,有可能多个线程同时访问一个有限资源,必须防止这种情况的发生,所以引入了同步机制。在线程使用一个资源时为其加锁,这样其他线程便不能访问该资源,必须等待解锁后才能访问。
实现:
1.1使用synchronized创建方法,被synchronized修饰的方法叫同步方法;
1.2使用synchronized创建同步代码块,通过使用synchronized同步代码块,锁定一个对象,该对象作为可执行的标志从而达到同步的效果;
如果想要使用synchronized同步代码块达到和使用synchronized方法同样的效果,可以锁定this引用:
synchronized(this){
…
}
区别:
- synchronized同步代码块只是锁定了该代码块,代码块外面的代码还是可以被访问的;
- synchronized方法是粗粒度的并发控制,某一个时刻只能有一个线程执行该synchronized方法;
- synchronized同步代码块是细粒度的并发控制,只会将块中的代码同步,代码块之外的代码可以被其他线程同时访问。
2.共享成员变量
成员变量与局部变量
成员变量:如果一个变量是成员变量,那么多个线程对同一个对象的成员变量进行操作,这多个线程是共享一个成员变量的;
局部变量:如果一个变量是局部变量,那么多个线程对同一个对象进行操作,每个线程都会有一个该局部变量的拷贝。他们之间的局部变量互不影响。
eg:
实现了Runnable的线程类:
//两个线程操作同一个对象,共享成员变量
//int i;
@Override
public void run() {
//两个线程操作同一个对象,各自保存局部变量的拷贝
int i = 0;
while(i<100){
System.out.println(i);
i++;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
在main方法中用两个线程操作同一个对象:
public static void main(String[] args) {
MyThread3 myThread = new MyThread3();
//下面两个线程对同一个对象(Runnable的实现类对象)进行操作
Thread thread = new Thread(myThread);
Thread thread2 = new Thread(myThread);
//各自保存局部变量的拷贝,互不影响,输出200个数字
thread.start();
thread2.start();
}
这里如果把i变成成员变量,则输出100个数字。
3.使用volatile关键字修饰
public volatile int inc = 0;
一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:
- 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的,在多线程的情况下保证每个线程的数据都是最新的
- 禁止进行指令重排序。
关于volatile可以参考这篇文章
4.reentrantLock加锁结合Condition条件设置,在线程调度上保障数据同步
5.cas=compare and swap(set), 在保证操作原子性上,确保数据同步
六、抽象类和接口的区别
1.抽象类可以有构造方法,接口没有;
2.抽象类是继承的,接口是实现的;
3.抽象类的访问速度比接口快,因为接口需要时间去寻找类中实现的方法;
4.抽象类可以做方法的声明,也可以做方法的实现,接口只能做方法的声明;
5.抽象类里面的变量可以是普通变量,接口里面的变量只能是公共的静态变量;
6.一个类只能继承一个抽象类,但能实现多个接口;
应用场景
抽象类:
- 在既需要接口,又需要实例变量或者缺省方法情况下可以使用
- 有一些方法并且想让部分方法有默认的实现
- 如果基本功能在不断的改变
接口:
- 想实现多重继承,通过实现多个接口完成
七、ANR出现的场景以及解决方法
ANR:application not responding,应用程序无响应。
在Android系统中,应用的响应能力是由活动管理器(Activity Manager)和窗口管理器(Window Manager)这两个系统服务来监控的,通常在以下三种情况出现:
- 用户触摸屏幕或者键盘输入,5s时间内没有响应用户的输入事件
- BroadcastReceiver在10s内无法结束
- Service服务时间在20s无法处理完成
造成的原因主要是在主线程进行了耗时操作,阻塞主线程;
解决方法:
对于耗时的操作,比如网络访问、文件读写、数据库读写等,需要开辟子线程完成操作,避免死锁的出现,主线程主要实现UI更新。
八、谈下Broadcast Receiver
Broadcast(广播)是一种针对应用程序间传输信息,进行通信的机制;
Broadcast Receiver(广播接收器)则是接收来自系统和应用程序间的广播并对其做出响应的组件。
广播的类型:
- 普通广播
- 有序广播
- 本地广播
1.普通广播
普通广播是一种完全异步执行的广播,当一条广播发送出去后,所有广播接收器几乎同一时刻都会接收到该广播,因此它们的接收先后顺序是随机的。普通广播不能被接收器截断。
发送一个普通广播很简单,通过Intent把要发送的广播数值传递进来,再调用sendBroadcast()方法把广播发送出去,这样所有监听该广播的接收器都会收到信息。
Intent castIntent=new Intent("testBroadCast");//指明广播值
sendBroadcast(castIntent);
2.有序广播
有序广播是一种同步执行的广播,当一条广播发送出去后,同一时刻只有一个广播接收器会接收到该消息,当这个广播接收器处理完逻辑后,广播才会继续传递,所以这个接收有先后顺序,优先级(priority)高的接收器会优先接收到广播消息。有序广播可以被广播接收器截断广播消息,使之无法继续传递。
2.1 调用sendOrderedBroadcast()发送
2.2 广播接收者会按照priority优先级从大到小进行排序
2.3 优先级相同的广播,动态注册的广播优先处理(推荐使用动态注册,因为8.0后限制绝大部分广播只能动态注册)
Intent castIntent=new Intent("testBroadCast");//指明广播值
sendOrderedBroadcast(castIntent,null);
3.本地广播
以上两种广播都是属于全局广播,即发送出去的广播可以被其他应用所接收,我们也能接收其他应用发送的广播。为了能够简单解决全局广播带来的安全性问题,Android引入了一套本地广播机制(仅支持动态注册),使用这个机制发送的广播消息,只能够在应用程序内部传递,广播接收器也只能接收本应用程序发送的广播消息。
优点:
- 效率更高
- 发送的广播不会离开我们的应用,不会泄露关键数据
- 其他程序无法将广播发送到我们程序内部,不会有安全漏洞
3.1 通过LocalBroadcastManager.getInstance(this)方法获取一个LocalBroadcastManager实例
3.2 用LocalBroadcastManager提供的registerReceiver()和unregisterReceiver()方法来动态注册和取消接收器
3.3 sendBroadcast()方法发送本地广播
private LocalBroadcastManager localBroadcastManager;
private MyReceiver myReceiver;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
localBroadcastManager=LocalBroadcastManager.getInstance(this);
//动态注册本地广播
IntentFilter intentFilter=new IntentFilter();
intentFilter.addAction("testLocalBroadCast");
myReceiver=new MyReceiver();
localBroadcastManager.registerReceiver(myReceiver,intentFilter);
findViewById(R.id.all).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent=new Intent("testLocalBroadCast");
localBroadcastManager.sendBroadcast(intent);//发送本地广播
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
localBroadcastManager.unregisterReceiver(myReceiver);
}
public class MyReceiver extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
//接收广播处理逻辑
}
}
九、简述app启动的流程
APP启动是通过Binder机制的,在此先简单介绍下Binder机制相关知识:
1.Linux内核:
①用户空间和内核空间:用户空间是指用户程序所运行的空间,内核空间指的是Linux内核运行的空间。它们是相互隔离的,也就预防了即使用户程序奔溃了,也不影响到内核;
②系统调用:用户空间访问内核空间的唯一方式就是系统调用(文件、网络等);通过这个统一入口接口,所有的资源访问都在内核的控制下执行,以免导致对用户程序对系统资源的越权访问,内核统一维护,从而保障了系统的安全和稳定;
③Binder驱动:用户空间可以通过系统指定的入口进行调用访问内核,但是用户空间想要访问另外一个用户空间,就需要内核添加一个模块(Linux 的动态可加载内核模块(Loadable Kernel Module,LKM)机制)作为内核的一部分运行在内核中,用户之间通过这个模块作为桥梁,就能相互进行通信。在Android系统中,这个内核模块就是binder驱动。
2.Binder通信机制:
Linux内核本身也提供了很多进程通信方式,比如socket、管道之类的,但是为什么就专门为Android定制了一套binder机制呢?
- 性能
Android是移动设备,相比于PC机的性能会有差距,在移动设备上进行频繁的跨进程通信本身就是一个极大的考验,Binder相比较于传统的socket/管道通信而言,更加高效,它在IPC(Inter-Process Communication 进程间通信)时,只需要数据拷贝1次,而传统的socket之类的需要2次; - 安全性
传统的进程间通信对于通信双方的身份没有进行严格的验证,只有上层协议才会进行架构,比如说,socket通信时,IP地址是手动填写的,可以进行人为的伪造,而Binder支持通信双方进行身份校验,极大的保障了安全性;
通信机制分为三大步:
⑴ServiceManager进程在内部维护一张表,表里存储了向其注册过的进程信息
⑵服务器端进程向ServiceManager注册其信息
⑶客户端进程向ServiceManager访问获取相关信息,通过binder驱动和服务器端进程通讯
通常意义上来说,Binder就是指Andriod的通信机制。
下面正式进入APP启动流程介绍:
首先,APP启动涉及到很多进程和类,先简单介绍下它们:
-
Lanucher进程,整个APP启动流程的起点,相当于一个总的Activity,屏幕上的APP等于这个Activity的item,可以点击、长按进行相应的操作,当点击APP的时候,会从Lanuncher跳转到其他页面
-
SystemServer进程,是属于Application Framework层的,负责管理Android中的所有服务,例如WindowsManager、AMS等都是由这个进程fork出来的;
-
APP进程;需要启动的APP所运行的进程;
-
ActivityManagerService,AMS是Android中核心的服务之一,主要负责系统中四大组件的启动、切换以及应用进程的管理和调度工作;
-
ActivityThread,应用的入口类,通过调用main方法,开启消息循环队列,ActivityThread所在的线程被称为主线程;
-
ApplicationThread,提供Binder通讯接口,AMS则通过代理调用此APP进程的本地方法;
-
ActivityManagerProxy,AMS服务在当前进程的代理类,负责与AMS通信;
-
ApplicationThreadProxy,ApplicationThread在AMS服务中的代理类,负责与ApplicationThread通信;
所以,APP启动步骤如下:
①点击APP图标后,Launcher进程通过Binder向AMS发送打开Activity的请求,在SystemServer进程中的服务端AMS收到 START_ACTIVITY_TRANSACTION 命令后进行处理,调用 startActivity() 方法
②如果应用没有启动过,则需要创建一个新的进程去运行APP,通过ActivityStarter处理Intent和Flag等信息,通过Socket通知zygote进程fork新进程
③zygote进程会进行孵化(虚拟机和资源的复制),然后fork出新进程,在创建新进程的时候,AMS会保存一个 ProcessRecord 信息,Activity 应用程序中的AndroidManifest.xml 配置文件中,我们没有指定 Application 标签的 process 属性,系统就会默认使用 package 的名称。每一个应用程序都有自己的 uid,因此,这里 uid + process 的组合就可以为每一个应用程序创建一个ProcessRecord。每次在新建新进程前的时候会先判断这个 ProcessRecord 是否已存在,如果已经存在就不会新建进程了,这就属于应用内打开 Activity 的过程了
④进程创建成功切换至 App 进程,进入 app 进程后将 ActivityThread 类加载到新进程,zygote进程会调用ActivityThread的main()方法,这是应用程序的入口,主线程Looper和Handler的创建、开启主线程Looper的消息循环以及创建了ActivityThread执行 attach(false) 方法
public static void main(String[] args) {
// 1. mainLooper
Looper.prepareMainLooper();
// 2. 创建ActivityThread,执行attach
ActivityThread thread = new ActivityThread();
thread.attach(false);
// 3. Handler
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
// 4. loop()
Looper.loop();
}
attach函数最终调用了AMS的远程接口ActivityManagerProxy的attachApplication函数,传入的参数是mAppThread,这是一个ApplicationThread类型的Binder对象,这个工程其实是将本地的 ApplicationThread 传递到 AMS。然后 AMS 就可以通过 ApplicationThread 的代理 ApplicationThreadProxy 来调用应用程序 ApplicationThread.bindApplication来创建Application
private void attach(boolean system) {
if (!system) {
final IActivityManager mgr = ActivityManager.getService();
try {
mgr.attachApplication(this.mAppThread);
} catch (RemoteException var5) {
throw var5.rethrowFromSystemServer();
}
}
}
attachApplication()方法内部调用了attachApplicationLocked()方法
@Override
public final void attachApplication(IApplicationThread thread) {
synchronized (this) {
int callingPid = Binder.getCallingPid();
final long origId = Binder.clearCallingIdentity();
attachApplicationLocked(thread, callingPid);
Binder.restoreCallingIdentity(origId);
}
}
在attachApplicationLocked()方法中,会调用thread对象的bindApplication()方法,thread对象就是我们传递过来的ApplicationThread对象。顾名思义,bindApplication()方法应该就是创建Android应用Application的方法
private final boolean attachApplicationLocked(IApplicationThread thread,
int pid) {
......
if (app.instr != null) {
thread.bindApplication(processName, appInfo, providers,
app.instr.mClass,
profilerInfo, app.instr.mArguments,
app.instr.mWatcher,
app.instr.mUiAutomationConnection, testMode,
mBinderTransactionTrackingEnabled, enableTrackAllocation,
isRestrictedBackupMode || !normalMode, app.persistent,
new Configuration(getGlobalConfiguration()), app.compat,
getCommonServicesLocked(app.isolated),
mCoreSettingsObserver.getCoreSettingsLocked(),
buildSerial);
} else {
thread.bindApplication(processName, appInfo, providers, null, profilerInfo,
null, null, null, testMode,
mBinderTransactionTrackingEnabled, enableTrackAllocation,
isRestrictedBackupMode || !normalMode, app.persistent,
new Configuration(getGlobalConfiguration()), app.compat,
getCommonServicesLocked(app.isolated),
mCoreSettingsObserver.getCoreSettingsLocked(),
buildSerial);
}
......
if (normalMode) {
try {
/*
* 调用ActivityStackSupervisor的attachApplicationLocked()方法
* 在该方法中调用了realStartActivityLocked()方法来启动一个Activity
*/
if (mStackSupervisor.attachApplicationLocked(app)) {
didSomething = true;
}
} catch (Exception e) {
......
}
}
return true;
}
⑤在执行完bindApplication()之后,会调用ActivityStackSupervisor的attachApplicationLocked()方法来,在该方法中会调用realStartActivityLocked()方法来启动一个Activity,ActivityThread利用ClassLoader去加载Activity,创建Activity实例,回调oncreate()方法,Activity执行完onResume()方法之后,调用makeVisible()方法,才会真正的把视图展示出来
ApplicationThread.scheduleLaunchActivity()
//发送消息 H.LAUNCH_ACTIVITY。
sendMessage(H.LAUNCH_ACTIVITY, r);
ActivityThread.handleLaunchActivity()
//最终回调目标 Activity 的 onConfigurationChanged(),初始化 WindowManagerService。
//调用 ActivityThread.performLaunchActivity()
ActivityThread.performLaunchActivity() {
//类似 Application 的创建过程,通过 classLoader 加载到 activity.
activity = mInstrumentation.newActivity(classLoader,
component.getClassName(), r.intent);
//因为 Activity 有界面,所以其 Context 是 ContextThemeWrapper 类型,但实现类仍是ContextImpl.
Context appContext = createBaseContextForActivity(r, activity);
activity.attach(context,mInstrumentation,application,...);
//与 Window 进行关联
//attach 后调用 activity 的 onCreate()方法。
mInstrumentation.callActivityOnCreate(activity,...)
}
//在ActivityThread.handleLaunchActivity里,接着调用
Activity.performCreate() -> onCreate()
//最终回调目标 Activity 的 onCreate()。
Activity.setContentView()
//设置 layout 布局
ActivityThread.performResumeActivity()
//最终回调目标 Activity 的 onResume()。
十、Android的事件分发机制
事件传递的对象顺序流程: Activity > ViewGroup > View
事件分发的的核心方法:
方法 | 作用 | 调用时刻 |
---|---|---|
dispatchTouchEvent() | 分发点击事件 | 当点击事件能够传递给当前view时,此方法就会被调用 |
onInterceptTouchEvent() | 判断是否拦截某个事件(该方法只存在ViewGroup中) | 在ViewGroup()的dispathchTouchEvent内部调用 |
onTouchEvent() | 处理点击事件 | 在dispathchTouchEvent内部调用 |
View点击事件的分发流程
View的 onTouch() 先于onClick() 执行
事件分发总结流程
onTouch() 和 onTouchEvent() 的区别
- 都是在View.dispatchTouchEvent()中调用
- 但onTouch()优先于onTouchEvent执行;若手动复写在onTouch()中返回true(即 将事件消费掉),将不会再执行onTouchEvent()