Android学习笔记

文章目录


Android 四大组件:

  • 活动activity
  • 服务service
  • 广播接收者Broadcast Receive
  • 内容提供者Content Provider

一、四大组件

1、Activity活动

查看源图像

使用步骤

  1. 创建activity

  2. 创建布局

  3. 在Android Manifest文件中注册

    • 配置主活动
    <action android:name="android.intent.action.MAIN"/>
    <category android:name="android.intent.category.LAUNCHER" />
     
    

生命周期

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-byj5aGbk-1673331437019)(C:\Users\HuangYingying\AppData\Roaming\Typora\typora-user-images\image-20221115172953014.png)]

一个activity启动, oncreate->onStart->onResume,就开始running了

activityA跳转到activityB,先activityA的onPause, 再会启动activityB的oncreate->onStart->onResume,接着才进行activityA的onStop;

如果再转换为activityA,B也是onPause, 接着A:onRestart->onStart->onResume(这是A没有被回收的情况以及B完全遮挡A的情况。如果A被回收了:那么A将会onCreate再走流程;如果A没有完完全被遮挡,那么A不会再onRestart和onStart)。然后B才onStop->onDestroy

启动活动

startActivity();

startActivityForResult();

启动其他活动

startActivity(Intent intent);//启动其他活动
startActivityForResult(Intent intent, int requestCode);//以指定请求码启动activity,而且程序将会获取新启动的Activity返回的结果(通过重写onActivityResult()方法获取)

向另一个应用发送用户,使用Intent

创建 Intent 并设置额外信息后,请调用 startActivity() 以将其发送到系统

获取Activity的结果

活动的启动模式

  • standard,标准模式,同一活动可以启动多次,不管是否在栈顶

  • singleTop模式,若活动已在栈顶,则不会有新的实例活动

  • singleTask模式

    • 启动目标Activity不存在,创建Activity实例,加入Task栈顶
    • 启动目标Activity已经在Task栈顶,与singletop行为相同
    • 存在Task栈中,但不是栈顶,系统会把该Activity上面所有Acitivity移除Task栈,使得目标Activity转入栈顶
  • singleInstance模式[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GH3Kiu2O-1673331437021)(C:\Users\HuangYingying\AppData\Roaming\Typora\typora-user-images\image-20221116105715387.png)]

事件监听器

通过事件处理,使UI和用户互动。

一般用Lambda ,比较简洁

基于回调的事件处理

事件监听机制是一种委托式的事件处理,回调恰恰相反:事件源和事件监听器是统一的,或者是事件监听器完全消失了,当用户在GUI组件上激发某个事件时,组件自己特定的方法会处理该事件。

事件传播

回调方法返回true,该事件不会传播出去;

返回false,会传播出去

优先级:先触发组件绑定的事件监听器,再出发回调方法。

2、service

生命周期:

image.png

手动调用startService()、stopService()、bindService()、unbindService()

自动调用:onCreate()、onstrartCommand()、onDestroy()…

被绑定服务的生命周期是在 为其他应用组件提供服务时,若接触绑定,绑定服务会解除

而被调用启动的生命周期是长期在后台的,与调用者无关。

创建服务,必须要创建Service的子类,重写回调方法,其中onBind必须重写

onBind() //组件想通过bindService()绑定服务时,会调用此方法

onCreate() //首次通过startService()启动服务时,会调用此方法

onStartcommand() //每次通过startService()启动服务时,会调用此方法

onDestroy() //销毁服务时

采用startService()开启服务

onCreate()–> onStartCommand() --> onDestory();

采用绑定方式开启服务

onCreate() —>onBind()—>onunbind()–>onDestory();

3、广播

标准广播

有序广播

4、contentProvider

二、view/布局

View和ViewGroup

img

TextView文本框、Button按钮、EditText编辑框、ImageView图片、ProgressBar进度条、AlertDialog警示弹窗(只能允许当前操作,屏蔽其他控件交互能力)、ProgressDialog(同AlertDialog,多了个进度)

基本布局

  1. 线性布局,LinearLayout
  2. 相对布局,RelaitiveLayout
  3. 帧布局,FrameLayout
  4. 百分比布局

ListView控件

借助适配器来完成

纵向滚动

RecyclerView控件

强化版ListView控件,功能更加完善,效率更加高

定义在了support库,使用的时候要添加依赖的库(在dependencies闭包中添加compile ‘com.android.support:recyclerview-v7:24.2.1’)

需要借助适配器来完成

新增横向滚动和瀑布流布局

addview()添加窗口且指定窗口层级

三、消息机制Handler

作用

  1. 在新启动的线程中发送消息
  2. 在主线程中获取、处理消息
handleMessage(Message msg);//处理消息的方法,常用于被重写
hasMessages(int what);//检查消息队列中是否包含what属性为指定值的消息
hasMessages(int what, Object object);//检查消息队列中是否包含what属性为指定值且object属性为指定对象的消息
多个重载的Message obtainMessage();//获取消息
sendEmptyMessage(int what);//发送空消息
sendEmptyMessageDelayed(int what, long delayMillis);//指定多少毫秒后发送空消息
sendMessage(Message msg);//立即发送消息
sendMessageDelayed(Message msg, long delayMillis);//指定多少毫秒后发送消息    

example code: 疯狂android讲义中3.5

部分源自博客

  • Handler:快递员(属于某个快递公司的职员)
  • Message:包裹(可以放置很多东西的箱子)
  • MessageQueue:快递分拣中心(分拣快递的传送带)
  • Looper:快递公司(具有处理包裹去向的管理中心)

Binder/Socket用于进程间的通讯,Handler用于同一进程的不同线程之间的通信

一个线程只能有一个Looper,一个MassageQueue,可以多个Handler

Handler只能绑定一个线程的Looper

  1. Handler将Message发送到Looper的MessageQueue中

  2. 等待Looper的循环读取Message,处理Massage

  3. 调用Message的target,即附属的Handler的dispatchMessage()de方法,然后完成更新UI操作。

sendMessage(),发送消息

dispatchMessage(), Looper发回消息

①Handler的创建、方法

Handler的构造方法做的事

  1. 确认Handler所在线程的Looper对象,mLooper = Looper.myLooper();
  2. 若Looper不空,获取Looper的消息队列,赋值,mQueue = mLooper.mQueue;
  3. 设置Calback处理消息回调,mCallback = callback;

②MessageQueue

Handler发送来的Message在MessageQueue中,选择一个合适的位置插入

③Looper

将消息取出,通过Handler的dispatchMessage()发送回去给Handler

主线程中:系统已经初始化,直接创建Handler

子线程中:要自己创建一个Looper对象

四、Binder机制

进程间的通信

Linux下的是共享内存、管道、Socket、消息队列、信号等方式,但是在Android上看不到,所以需要用Binder机制来进行

Binder的功能

  1. 驱动程序来推进进程间的通信
  2. 通过共享内存来提高性能
  3. 为进程请求分配每个进程的线程池
  4. 针对系统中的对象引入了引用计数和跨进程的对象引用映射
  5. 进程间同步调用

AIDL

AIDL简介
AIDL使用1
AIDL使用2

AIDL支持的原语类型

  1. Java中的所有原语类型int、long…
  2. String
  3. CharSequence
  4. List(List中的元素是以上列表中支持的数据类型)
  5. Map

基础类型步骤

  1. 在服务端创建了一个.aidl文件,定义好接口,服务端的.aidl和客户端的.aidl要完全一致
package com.example.android.process1;
interface Aidl {
    int Sum(int num);
}
  1. 在服务端实现.aidl中的接口,要继承自Stub(要先编译,才会生成.aidl对应的.java文件)
package com.example.android.process1;
import android.os.RemoteException;
import android.util.Log;

public class AidlImpl extends Aidl.Stub {
    String TAG = "AidlImpl";
    @Override
    public int Sum(int num) throws RemoteException {
        Log.d(TAG, "num:" + num);
        Log.d(TAG, "Sum: ");
        return (num+1)*num/2;
    }
}
  1. 在服务端的Service中,把aidl的接口暴露出去
package com.example.android.process1;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;

public class MyService extends Service {
    public MyService() {
    }
    private AidlImpl binder = new AidlImpl();
    
    @Override
    public IBinder onBind(Intent intent) {
        Log.d("AidlImpl", "onBind.....");
        return binder;
    }
}
  1. 在客户端中bindservice
public class MainActivity extends AppCompatActivity {
    public static String TAG = "MainActivity";

    private Aidl service;//声明服务端


    private ServiceConnection conn = new ServiceConnection() {//绑定成功后会回调
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            Log.d(TAG, "onServiceConnected: Service Connected");
            service = Aidl.Stub.asInterface(iBinder);
        }
        @Override
        public void onServiceDisconnected(ComponentName componentName) {
            Log.d(TAG, "onServiceConnected: Service Connected");
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d(TAG, "onCreate: MainActivity");
        setContentView(R.layout.activity_main);
        Button bt = findViewById(R.id.bt1);
		//绑定服务
        Intent intent = new Intent();
        ComponentName cp = new ComponentName("com.example.android.process1", "com.example.android.process1.MyService");
        intent.setComponent(cp)
                .setPackage(getPackageName());
        bindService(intent, conn, Context.BIND_AUTO_CREATE);


        bt.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                try {
                    if(service == null) return ;
                    int ans = service.Sum(10);//rpc,远程调用,和调用本地方法一致
                    Log.d(TAG, "ans: " + ans);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        });
    }

两个进程间通信需要对应的aidl接口,且需一致。后来,为了方便,不用每次修改就去对应一 一修改,就把他提出来,写成了.aidl文件

target:

// aidl 数据类型 √
// 传输数据的大小 √
// 死亡监听 √
// 序列化 √
// 线程同步
// 重连问题(挂掉重连)

IPC传递对象步骤(Parcelable序列化

  1. 服务端中定义好实体类,继承自parcelable,(该实现的实现,没实现会有提醒,可定义好成员变量后一键生成)
  2. 再服务端中,在aidl文件夹下建立2个.aidl文件

在这里插入图片描述

  • 用于定义接口,注意参数前有个in
// Aidl.aidl
package com.example.android.process1;
import com.example.android.process1.userMessageAIDL;
import com.example.android.process1.userMessage;
// Declare any non-default types here with import statements

interface Aidl {
    List<userMessage> getuserMessageListIn(in userMessage um);
}
  • 固定是parcelable+实体类类名
package com.example.android.process1;

parcelable userMessage;
  1. 在服务端的Service里面暴露出去
package com.example.android.process1;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import androidx.annotation.Nullable;

public class MyService extends Service {
    public MyService() {
    }
    public AidlImpl binder = new AidlImpl();

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return binder;
    }
}
  1. 将服务端的实体类,aidl文件夹原封不动的copy到客户端中,注意包名要完全一致
  2. 一样的绑定方法,将实体类对象

AIDL权限问题

Android IPC之AIDL使用(二)线程同步、权限验证 - 掘金 (juejin.cn)

出于安全考虑。

五、Intent

Action、Category属性与intent-filter配置

  • 一个Intent对象最多只能包括一个Action属性,程序可以通过调用Intent的setAction(String str)方法来设置Action属性值;
  • 但一个Intent对象可以包括多个Category属性,程序可调用Intent的addCategory(String str)方法来为Intent添加Category属性。

Data、Type属性与intent-filter配置

Data属性接收一个Uri对象,格式:scheme://host:port/path

六、同步Synchronous和异步Asynchronous

同步和异步关注的是消息通信机制(synchronous communication/ asynchronous communication)。同步,就是调用某个东西是,调用方得等待这个调用返回结果才能继续往后执行。异步,和同步相反,调用方不会理解得到结果,而是在调用发出后调用者可用继续执行后续操作,被调用者通过状体来通知调用者,或者通过回调函数来处理这个调用

七、模式

img

MVC

img

img

MVP

img

MVVM

Model, View, ViewModel

img

  1. MVVM应该改成M-VM-V会更容易直观地理解。View-Model作为胶水层,把视图View和数据模型Model粘合在一起。

  2. MVVM不是一个纯前端的架构模式。它适用于所有的包含GUI(Graphical User Interface 图形用户接口)的应用程序中(包含后端部分)。

  3. MVVM其实可以细分为M-C-VM-V的四层架构。

  4. 对于以上M-C-VM-V层的理解:

    1. M(odel)层:定义数据结构,建立应用的抽象模型。
    2. C(ontroller)层:实现业务逻辑,向上暴露数据更新的接口,调用Model层来进行模型数据的增删改查,以达到应用数据更新的目的。
    3. V(iew)-M(odel)层:将Model层的抽象模型转换为视图模型用于展示,同时将视图交互事件绑定到Controller层的数据更新接口上。
    4. V(iew)层:将视图模型通过特定的GUI展示出来,并在GUI控件上绑定视图交互事件。
  5. 说白了,对于一款拥有GUI的应用程序来说,用户与计算机进行交流的过程,不过是IO(输入输出)的过程。计算机通过输出设备(显示器、扬声器、机械马达等。不过这里我们针对于图形接口来讲的话,一般就是显示器)将视图数据进行展示,用户通过输入设备(键盘、鼠标、触摸板等)来触发特定的事件达到模型的更新。

  6. 我们之所以要发明这种分层架构,最主要的原因是为了让Model层和Controller层能够复用。甚至于对于同一款应用程序在不同的GUI上进行展示时,View-Model层也是复用的,仅仅只是把View层进行了替换而已。

  7. 再拓展一下,假如我们的应用程序需要在非GUI界面进行实现,而是通过其他UI方式来实现呢?只需要将View-Model层替换成新的UI-Model,再与新的UI进行桥接,同样的功能便可以跨UI进行实现了。

  8. 对于上述这点,举个例子:针对于残障人士(比如盲人),我们的应用程序应该更加方便易用。或许我们需要考虑使用扬声器来代替显示器进行输出,同时使用麦克风来进行输入。这时,我们可以将上述的View-Model替换为Audio-Model作为语音模型,UI层即Audio层用于播放语音和接收语音输入。

  9. 综上所述,对于UI应用程序(给用户提供了用户接口的应用程序),都可以抽象成M-I-IM(其中I指Interface)架构模式来达到模型、逻辑、表征之间的分离解耦,并提高开发效率。

八、组件

Databinding

自动绑定UI的框架。

DataBinding学习博客

Livedata

LiveData是一种可观察的数据存储器类。与常规的可观察类不同,LiveData 具有生命周期感知能力,意指它遵循其他应用组件(如 activity、fragment 或 service)的生命周期。这种感知能力可确保 LiveData 仅更新处于活跃生命周期状态的应用组件观察者。

作用

  1. 实时刷新数据
  2. 防止内存泄露

使用

  1. 创建LiveData实例,指定源数据类型
  2. 创建Observer实例,实现onChanged()方法,用于接收源数据变化并刷新UI
  3. LiveData实例使用observe()方法添加观察者,并传入LifecycleOwner
  4. LiveData实例使用setValue()/postValue()更新源数据 (子线程要postValue())
public class LiveDataTestActivity extends AppCompatActivity{

   private MutableLiveData<String> mLiveData;
   
   @Override
   protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_lifecycle_test);
       
       //liveData基本使用
       mLiveData = new MutableLiveData<>();
       mLiveData.observe(this, new Observer<String>() {
           @Override
           public void onChanged(String s) {
               Log.i(TAG, "onChanged: "+s);
           }
       });
       Log.i(TAG, "onCreate: ");
       mLiveData.setValue("onCreate");//activity是非活跃状态,不会回调onChanged。变为活跃时,value被onStart中的value覆盖
   }
   @Override
   protected void onStart() {
       super.onStart();
       Log.i(TAG, "onStart: ");
       mLiveData.setValue("onStart");//活跃状态,会回调onChanged。并且value会覆盖onCreate、onStop中设置的value
   }
   @Override
   protected void onResume() {
       super.onResume();
       Log.i(TAG, "onResume: ");
       mLiveData.setValue("onResume");//活跃状态,回调onChanged
   }
   @Override
   protected void onPause() {
       super.onPause();
       Log.i(TAG, "onPause: ");
       mLiveData.setValue("onPause");//活跃状态,回调onChanged
   }
   @Override
   protected void onStop() {
       super.onStop();
       Log.i(TAG, "onStop: ");
       mLiveData.setValue("onStop");//非活跃状态,不会回调onChanged。后面变为活跃时,value被onStart中的value覆盖
   }
   @Override
   protected void onDestroy() {
       super.onDestroy();
       Log.i(TAG, "onDestroy: ");
       mLiveData.setValue("onDestroy");//非活跃状态,且此时Observer已被移除,不会回调onChanged
   }
}

Lifecycle

  • LifeCycle是一个抽象类,用来存储生命周期状体的;

  • 是Android Jetpack-Architecture(架构组件)中的一员

  • LifeCycle—对组件的解耦;

  • Jetpack提供了2个类:LifecycleOwner(被观察者), LifecycleObserver(观察者)。

  • 通过观察者模式,实现对页面生命周期的监听

  • Lifecycle 的实现机制是观察者模式

Lifecycle类

跟踪生命周期的流程如下:(某个state发生event的时候,状态变化图)

img

例子:(观察Activity的生命周期)

  1. 继承DefaultLifecycleObserver,自定义观察者类

    class MyObserver : DefaultLifecycleObserver {
        override fun onResume(owner: LifecycleOwner) {
            // 执行业务逻辑
        }
    
        override fun onPause(owner: LifecycleOwner) {
            // 执行业务逻辑
        }
        ...
    }
    
  2. 在Activity中向生命周期添加观察者; getLifeCycle().addObserver(MyObserver());//有getLifecycle()这个方法,是因为高版本Fragment和Activity已实现LifecycleOwner接口

    class MainActivity: AppCompatActivity{
    	@Override
    	fun onCreate(){
    		getLifecycle().addObserver(MyObserver())
    	}
    }
    

LifecycleOwner接口

(Activity和Fragment组件已经默认实现了此接口)

LifecycleOwner接口只有一个方法getLifecycle(), 返回的是Lifecycle对象。实现了这个接口,就表示这个类是具有生命周期的。

如果想管理整个APP进程的生命周期,则需要用ProcessLifecycleOwner类

自定义类实现LifecycleOwner接口
class MyObject : LifecycleOwner {
    private lateinit var mLR: LifecycleRegistry

    fun onCreate {
        mLR = LifecycleRegistry(this)
        // 设置此时状态
        mLR.markState(Lifecycle.State.CREATED)
    }

    fun onStart() {
    	// 设置此时状态
        mLR.markState(Lifecycle.State.STARTED)
    }

	/** 返回生命周期实例 */
    override fun getLifecycle(): Lifecycle = mLR
}

Viewmodel

学习博客

九、Fragment

Activity片段

Fragment要继承Fragment基类。

onCreate();//系统创建Fragment对象后回调该方法
onCreateView();//当Fragment绘制界面组件时会回调该方法,该方法必须返回一个View,该View也就是该Fragment所显示的View
onPause();//当用户离开该Fragment时会回调此方法

Fragment和Activity通信

为了在Activity中显示Fragment,要把Fragment添加到Activity

  • 在布局文件中使用<fragment…/>元素添加Fragment,<fragment…/>元素的android.name属性指定Fragment的实现类
  • 在代码中通过FragmentTransaction对象的add()方法来添加Fragment。

getSupportFragmentManager()方法可返回FragmentManager, FragmentManager对象的beginTransaction()方法可以开启并返回FragmentTransaction对象。

Fragment管理,Fragment事务

FragmentManager:

Activity管理Fragment主要靠FragmentManager

FragmentTransaction:

代表了Activity对Fragment执行的多个改变操作。

生命周期

  • 运行状态,位于前台,用户可见,可获得焦点
  • 暂停状态,其他Activity位于前台,Fragment位于前台,不可获得焦点
  • 停止状态,Fragment不可见,失去焦点
  • 销毁状态,Fragment完全被删除/该Fragment所在的Activity被结束

onAttach() 在Fragment 和 Activity 建立关联是调用(Activity 传递到此方法内)
onCreateView() 当Fragment 创建视图时调用
onActivityCreated() 在相关联的 Activity 的 onCreate() 方法已返回时调用。
onDestroyView() 当Fragment中的视图被移除时调用
onDetach() 当Fragment 和 Activity 取消关联时调用。

img

Activity和Fragment生命周期关联

  • Activity就是一个大页面,Fragment是嵌套在Activity里,所以,Fragment先生成,然后有了Activity的页面,才有Fragment的页面,Activity先运行,Fragment再运行。
  • Fragment先暂停,Activity再暂停,停止也是如此
  • 重启动时Activity先重启动,Fragment再启动,运行也是Activity先运行
  • 销毁的话,是都停止之后,Fragment先销毁,Activity再销毁。

img

img

十、Context

博客

十一、多线程

多线程的应用在Android开发中是非常常见的,常用方法主要有:

  1. 继承Thread类
  2. 实现Runnable接口
  3. Handler
  4. AsyncTask
  5. HandlerThread

img

Runnable

定义

Runnable接口是线程辅助类,仅定义了一个方法run()方法
作用
实现多线程
优点
灵活:Runnable可以继承其他类实现对Runnable实现类的增强,避免了Thread类由于继承Thread类而无法继承其他类的问题
共享资源:Runnable接口的run()方法可以被多个线程共享,适用于多个进程处理一种资源的问题

使用方法

  1. 实现Runnable接口
  2. 重写run()方法
  3. 创建runnable实例
  4. 创建Thread实例
  5. 将Runnable实例放入Thread实例中
  6. 通过线程实例控制线程的行为(运行,停止),在运行时会调用Runnable接口中的run方法。

注意:Java中真正能创建新线程的只有Thread类对,通过实现Runnable的方式,最终还是通过Thread类对象来创建线程

HandlerThread

Android多线程:手把手教你使用HandlerThread - 简书 (jianshu.com)

img

// 步骤1:创建HandlerThread实例对象
// 传入参数 = 线程名字,作用 = 标记该线程
   HandlerThread mHandlerThread = new HandlerThread("handlerThread");

// 步骤2:启动线程
   mHandlerThread.start();

// 步骤3:创建工作线程Handler & 复写handleMessage()
// 作用:关联HandlerThread的Looper对象、实现消息处理操作 & 与其他线程进行通信
// 注:消息处理操作(HandlerMessage())的执行线程 = mHandlerThread所创建的工作线程中执行
  Handler workHandler = new Handler( mHandlerThread.getLooper() ) {
            @Override
            public boolean handleMessage(Message msg) {
                ...//消息处理
                return true;
            }
        });

// 步骤4:使用工作线程Handler向工作线程的消息队列发送消息
// 在工作线程中,当消息循环时取出对应消息 & 在工作线程执行相关操作
  // a. 定义要发送的消息
  Message msg = Message.obtain();
  msg.what = 2; //消息的标识
  msg.obj = "B"; // 消息的存放
  // b. 通过Handler发送消息到其绑定的消息队列
  workHandler.sendMessage(msg);

// 步骤5:结束线程,即停止线程的消息循环
  mHandlerThread.quit();

十二、设计模式

观察者模式

当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)。比如,当一个对象被修改时,则会自动通知依赖它的对象。观察者模式属于行为型模式。

十三、Android事件分发机制

摘抄自
博客

事件传递返回值:onInterceptTouchEvent

true:拦截,不继续传递

false:不拦截,继续传递

事件处理返回值:onTouchEvent
true:处理了,不上报

false:处理了,但上报

MotionEvent

1、MotionEvent事件是什么

**点击事件(Touch事件)**会封装成MotionEvent对象

img

img

传递对象

Activity、ViewGroup、View

Activity(ViewGroup(View))

事件分发顺序

Activity->ViewGroup->View

事件分发方法

img

2、事件分发机制流程

Activity事件分发机制

Android事件分发机制首先会将点击事件传递到Activity中,具体是执行dispatchTouchEvent()进行事件分发。

/**
  * 源码分析:Activity.dispatchTouchEvent()
  */ 
  public boolean dispatchTouchEvent(MotionEvent ev) {

    // 仅贴出核心代码

    // ->>分析1
    if (getWindow().superDispatchTouchEvent(ev)) {

        return true;
        // 若getWindow().superDispatchTouchEvent(ev)的返回true
        // 则Activity.dispatchTouchEvent()就返回true,则方法结束。即 :该点击事件停止往下传递 & 事件传递过程结束
        // 否则:继续往下调用Activity.onTouchEvent

    }
    // ->>分析3
    return onTouchEvent(ev);
  }

/**
  * 分析1:getWindow().superDispatchTouchEvent(ev)
  * 说明:
  *     a. getWindow() = 获取Window类的对象
  *     b. Window类是抽象类,其唯一实现类 = PhoneWindow类
  *     c. Window类的superDispatchTouchEvent() = 1个抽象方法,由子类PhoneWindow类实现
  */
  @Override
  public boolean superDispatchTouchEvent(MotionEvent event) {

      return mDecor.superDispatchTouchEvent(event);
      // mDecor = 顶层View(DecorView)的实例对象
      // ->> 分析2
  }

/**
  * 分析2:mDecor.superDispatchTouchEvent(event)
  * 定义:属于顶层View(DecorView)
  * 说明:
  *     a. DecorView类是PhoneWindow类的一个内部类
  *     b. DecorView继承自FrameLayout,是所有界面的父类
  *     c. FrameLayout是ViewGroup的子类,故DecorView的间接父类 = ViewGroup
  */
  public boolean superDispatchTouchEvent(MotionEvent event) {

      return super.dispatchTouchEvent(event);
      // 调用父类的方法 = ViewGroup的dispatchTouchEvent()
      // 即将事件传递到ViewGroup去处理,详细请看后续章节分析的ViewGroup的事件分发机制

  }
  // 回到最初的分析2入口处

/**
  * 分析3:Activity.onTouchEvent()
  * 调用场景:当一个点击事件未被Activity下任何一个View接收/处理时,就会调用该方法
  */
  public boolean onTouchEvent(MotionEvent event) {

        // ->> 分析5
        if (mWindow.shouldCloseOnTouch(this, event)) {
            finish();
            return true;
        }
        
        return false;
        // 即 只有在点击事件在Window边界外才会返回true,一般情况都返回false,分析完毕
    }

/**
  * 分析4:mWindow.shouldCloseOnTouch(this, event)
  * 作用:主要是对于处理边界外点击事件的判断:是否是DOWN事件,event的坐标是否在边界内等
  */
  public boolean shouldCloseOnTouch(Context context, MotionEvent event) {

  if (mCloseOnTouchOutside && event.getAction() == MotionEvent.ACTION_DOWN
          && isOutOfBounds(context, event) && peekDecorView() != null) {

        // 返回true:说明事件在边界外,即 消费事件
        return true;
    }

    // 返回false:在边界内,即未消费(默认)
    return false;
  } 

img

img

ViewGroup事件分发机制

从上面Activity的事件分发机制可知,在Activity.dispatchTouchEvent()实现了将事件从Activity->ViewGroup的传递,ViewGroup的事件分发机制从dispatchTouchEvent()开始。

/**
  * 源码分析:ViewGroup.dispatchTouchEvent()
  */ 
  public boolean dispatchTouchEvent(MotionEvent ev) { 

  // 仅贴出关键代码
  ... 

  if (disallowIntercept || !onInterceptTouchEvent(ev)) {  
  // 分析1:ViewGroup每次事件分发时,都需调用onInterceptTouchEvent()询问是否拦截事件
    // 判断值1-disallowIntercept:是否禁用事件拦截的功能(默认是false),可通过调用requestDisallowInterceptTouchEvent()修改
    // 判断值2-!onInterceptTouchEvent(ev) :对onInterceptTouchEvent()返回值取反
        // a. 若在onInterceptTouchEvent()中返回false,即不拦截事件,从而进入到条件判断的内部
        // b. 若在onInterceptTouchEvent()中返回true,即拦截事件,从而跳出了该条件判断
        // c. 关于onInterceptTouchEvent() ->>分析1

  // 分析2
    // 1. 通过for循环,遍历当前ViewGroup下的所有子View
    for (int i = count - 1; i >= 0; i--) {  
        final View child = children[i];  
        if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE  
                || child.getAnimation() != null) {  
            child.getHitRect(frame);  

            // 2. 判断当前遍历的View是不是正在点击的View,从而找到当前被点击的View
            if (frame.contains(scrolledXInt, scrolledYInt)) {  
                final float xc = scrolledXFloat - child.mLeft;  
                final float yc = scrolledYFloat - child.mTop;  
                ev.setLocation(xc, yc);  
                child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  

                // 3. 条件判断的内部调用了该View的dispatchTouchEvent()
                // 即 实现了点击事件从ViewGroup到子View的传递(具体请看下面章节介绍的View事件分发机制)
                if (child.dispatchTouchEvent(ev))  { 

                // 调用子View的dispatchTouchEvent后是有返回值的
                // 若该控件可点击,那么点击时dispatchTouchEvent的返回值必定是true,因此会导致条件判断成立
                // 于是给ViewGroup的dispatchTouchEvent()直接返回了true,即直接跳出
                // 即该子View把ViewGroup的点击事件消费掉了

                mMotionTarget = child;  
                return true; 
                      }  
                  }  
              }  
          }  
      }  
    }  
  ...
  return super.dispatchTouchEvent(ev);
  // 若无任何View接收事件(如点击空白处)/ViewGroup本身拦截了事件(复写了onInterceptTouchEvent()返回true)
  // 会调用ViewGroup父类的dispatchTouchEvent(),即View.dispatchTouchEvent()
  // 因此会执行ViewGroup的onTouch() -> onTouchEvent() -> performClick() -> onClick(),即自己处理该事件,事件不会往下传递
  // 具体请参考View事件分发机制中的View.dispatchTouchEvent()
  ... 
}
/**
  * 分析1:ViewGroup.onInterceptTouchEvent()
  * 作用:是否拦截事件
  * 说明:
  *     a. 返回false:不拦截(默认)
  *     b. 返回true:拦截,即事件停止往下传递(需手动复写onInterceptTouchEvent()其返回true)
  */
  public boolean onInterceptTouchEvent(MotionEvent ev) {  
    
    // 默认不拦截
    return false;

  } 
  // 回到调用原处

img

img

View事件分发机制
/**
  * 源码分析:View.dispatchTouchEvent()
  */
  public boolean dispatchTouchEvent(MotionEvent event) {  

       
        if ( (mViewFlags & ENABLED_MASK) == ENABLED && 
              mOnTouchListener != null &&  
              mOnTouchListener.onTouch(this, event)) {  
            return true;  
        } 

        return onTouchEvent(event);  
  }
  // 说明:只有以下3个条件都为真,dispatchTouchEvent()才返回true;否则执行onTouchEvent()
  //   1. (mViewFlags & ENABLED_MASK) == ENABLED
  //   2. mOnTouchListener != null
  //   3. mOnTouchListener.onTouch(this, event)
  // 下面对这3个条件逐个分析

/**
  * 条件1:(mViewFlags & ENABLED_MASK) == ENABLED
  * 说明:
  *    1. 该条件是判断当前点击的控件是否enable
  *    2. 由于很多View默认enable,故该条件恒定为true(除非手动设置为false)
  */

/**
  * 条件2:mOnTouchListener != null
  * 说明:
  *   1. mOnTouchListener变量在View.setOnTouchListener()里赋值
  *   2. 即只要给控件注册了Touch事件,mOnTouchListener就一定被赋值(即不为空)
  */
  public void setOnTouchListener(OnTouchListener l) { 

    mOnTouchListener = l;  

} 

/**
  * 条件3:mOnTouchListener.onTouch(this, event)
  * 说明:
  *   1. 即回调控件注册Touch事件时的onTouch();
  *   2. 需手动复写设置,具体如下(以按钮Button为例)
  */
  button.setOnTouchListener(new OnTouchListener() {  
      @Override  
      public boolean onTouch(View v, MotionEvent event) {  
   
        return false;  
        // 若在onTouch()返回true,就会让上述三个条件全部成立,从而使得View.dispatchTouchEvent()直接返回true,事件分发结束
        // 若在onTouch()返回false,就会使得上述三个条件不全部成立,从而使得View.dispatchTouchEvent()中跳出If,执行onTouchEvent(event)
        // onTouchEvent()源码分析 -> 分析1
      }  
  });

/**
  * 分析1:onTouchEvent()
  */
  public boolean onTouchEvent(MotionEvent event) {  

    ... // 仅展示关键代码

    // 若该控件可点击,则进入switch判断中
    if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {  

        // 根据当前事件类型进行判断处理
        switch (event.getAction()) { 

            // a. 事件类型=抬起View(主要分析)
            case MotionEvent.ACTION_UP:  
                    performClick(); 
                    // ->>分析2
                    break;  

            // b. 事件类型=按下View
            case MotionEvent.ACTION_DOWN:  
                postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());  
                break;  

            // c. 事件类型=结束事件
            case MotionEvent.ACTION_CANCEL:  
                refreshDrawableState();  
                removeTapCallback();  
                break;

            // d. 事件类型=滑动View
            case MotionEvent.ACTION_MOVE:  
                final int x = (int) event.getX();  
                final int y = (int) event.getY();  

                int slop = mTouchSlop;  
                if ((x < 0 - slop) || (x >= getWidth() + slop) ||  
                        (y < 0 - slop) || (y >= getHeight() + slop)) {  
                    removeTapCallback();  
                    if ((mPrivateFlags & PRESSED) != 0) {  
                        removeLongPressCallback();  
                        mPrivateFlags &= ~PRESSED;  
                        refreshDrawableState();  
                    }  
                }  
                break;  
        }  

        // 若该控件可点击,就一定返回true
        return true;  
    }  
  // 若该控件不可点击,就一定返回false
  return false;  
}

/**
  * 分析2:performClick()
  */  
  public boolean performClick() {  

      if (mOnClickListener != null) {
          // 只要通过setOnClickListener()为控件View注册1个点击事件
          // 那么就会给mOnClickListener变量赋值(即不为空)
          // 则会往下回调onClick() & performClick()返回true
          playSoundEffect(SoundEffectConstants.CLICK);  
          mOnClickListener.onClick(this);  
          return true;  
      }  
      return false;  
  }  

img

img

3、总的来说

img

4、MotionEvent对应的ACTION

  1. ACTION_DOWN
  2. ACTION_MOVE
  3. ACTION_UP
  4. ACTION_CANCEL

KeyEvent

一个键的down或者up,都会创建一个keyevent对象来对应这个操作

相关API:

KeyEvent:

  • int ACTION_DOWN = 0:标识down的常量
  • int ACTION_UP = 1:标识up的常量
  • int getAction():得到事件类型
  • int getKeyCode():得到按键的keycode(唯一标识)
  • startTracking():追踪事件,用于长按监听

Activity:

  • boolean dispatchKeyEvent(KeyEvent event):分发事件
  • boolean onKeyDown(int keyCode, KeyEvent event):按下按键的回调
  • boolean onKeyUp(int keyCode, KeyEvent event):松开按键的回调
  • boolean onKeyLongPress(int keyCode, KeyEvent event):长按按键的回调

Android按键事件处理流程 – KeyEvent - xiaoweiz - 博客园 (cnblogs.com)

KeyEvent的分发流程非常简单,那就是直接给当前获取了焦点的View,谁有焦点,KeyEvent就给谁。

流程

Android Tv 中的按键事件 KeyEvent 分发处理流程 - 请叫我大苏 - 博客园 (cnblogs.com)

Android KeyEvent分发与焦点切换 - 掘金 (juejin.cn):解决了以上链接中未解决的问题

dispatchKeyEvent事件分发传递流程图.jpg

Model内部数据流向:

创建过程:

在这里插入图片描述

数据更新过程:

在这里插入图片描述

Request过程

在这里插入图片描述

View和ViewModel绑定,

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值