androidvlc源码分析
㈠ Android源码解析RPC系列(一)---Binder原理
看了几天的Binder,决定有必要写一篇博客,记录一下学习成果,Binder是Android中比较综合的一块知识了,目前的理解只限于JAVA层。首先Binder是干嘛用的?不用说,跨进程通信全靠它,操作系统的不同进程之间,数据不共享,对于每个进程来说,它都天真地以为自己独享了整个系统,完全不知道其他进程的存在,进程之间需要通信需要某种系统机制才能完成,在Android整个系统架构中,采用了大量的C/S架构的思想,所以Binder的作用就显得非常重要了,但是这种机制为什么是Binder呢?在linux中的RPC方式有管道,消息队列,共享内存等,消息队列和管道采用存储-转发方式,即数据先从发送方缓存区拷贝到内核开辟的缓存区中,然后再从内核缓存区拷贝到接收方缓存区,这样就有两次拷贝过程。共享内存不需要拷贝,但控制复杂,难以使用。Binder是个折中的方案,只需要拷贝一次就行了。其次Binder的安全性比较好,好在哪里,在下还不是很清楚,基于安全性和传输的效率考虑,选择了Binder。Binder的英文意思是粘结剂,Binder对象是一个可以跨进程引用的对象,它的实体位于一个进程中,这个进程一般是Server端,该对象提供了一套方法用以实现对服务的请求,而它的引用却遍布于系统的各个进程(Client端)之中,这样Client通过Binder的引用访问Server,所以说,Binder就像胶水一样,把系统各个进程粘结在一起了,废话确实有点多。
为了从而保障了系统的安全和稳定,整个系统被划分成内核空间和用户空间
内核空间:独立于普通的应用程序,可以访问受保护的内存空间,有访问底层硬件设备的所有权限。
用户空间:相对与内核空间,上层运用程序所运行的空间就是用户空间,用户空间访问内核空间的唯一方式就是系统调用。一个4G的虚拟地址空间,其中3G是用户空间,剩余的1G是内核空间。如果一个用户空间想与另外一个用户空间进行通信,就需要内核模块支持,这个运行在内核空间的,负责各个用户进程通过Binder通信的内核模块叫做Binder驱动,虽然叫做Binder驱动,但是和硬件并没有什么关系,只是实现方式和设备驱动程序是一样的,提供了一些标准文件操作。
在写AIDL的时候,一般情况下,我们有两个进程,一个作为Server端提供某种服务,然后另外一个进程作为Client端,连接Server端之后,就 可以使用Server里面定义的服务。这种思想是一种典型的C/S的思想。值得注意的是Android系统中的Binder自身也是C/S的架构,也有Server端与Client端。一个大的C/S架构中,也有一个小的C/S架构。
先笼统的说一下,在整个Binder框架中,由系列组件组成,分别是Client、Server、ServiceManager和Binder驱动程序,其中Client、Server和ServiceManager运行在用户空间,Binder驱动程序运行内核空间。运行在用户空间中的Client、Server和ServiceManager,是在三个不同进程中的,Server进程中中定义了服务提供给Client进程使用,并且Server中有一个Binder实体,但是Server中定义的服务并不能直接被Client使用,它需要向ServiceManager注册,然后Client要用服务的时候,直接向ServiceManager要,ServiceManager返回一个Binder的替身(引用)给Client,这样Client就可以调用Server中的服务了。
场景 :进程A要调用进程B里面的一个draw方法处理图片。
分析 :在这种场景下,进程A作为Client端,进程B做为Server端,但是A/B不在同一个进程中,怎么来调用B进程的draw方法呢,首先进程B作为Server端创建了Binder实体,为其取一个字符形式,可读易记的名字,并将这个Binder连同名字以数据包的形式通过Binder驱动发送给ServiceManager,也就是向ServiceManager注册的过程,告诉ServiceManager,我是进程B,拥有图像处理的功能,ServiceManager从数据包中取出名字和引用以一个注册表的形式保留了Server进程的注册信息。为什么是以数据包的形式呢,因为这是两个进程,直接传递对象是不行滴,只能是一些描述信息。现在Client端进程A联系ServiceManager,说现在我需要进程B中图像处理的功能,ServiceManager从注册表中查到了这个Binder实体,但是呢,它并不是直接把这个Binder实体直接给Client,而是给了一个Binder实体的代理,或者说是引用,Client通过Binder的引用访问Server。分析到现在,有个关键的问题需要说一下,ServiceManager是一个进程,Server是另一个进程,Server向ServiceManager注册Binder必然会涉及进程间通信。当前实现的是进程间通信却又要用到进程间通信,这就好象蛋可以孵出鸡前提却是要找只鸡来孵蛋,确实是这样的,ServiceManager中预先有了一个自己的Binder对象(实体),就是那只鸡,然后Server有个Binder对象的引用,就是那个蛋,Server需要通过这个Binder的引用来实现Binder的注册。鸡就一只,蛋有很多,ServiceManager进程的Binder对象(实体)仅有一个,其他进程所拥有的全部都是它的代理。同样一个Server端Binder实体也应该只有一个,对应所有Client端全部都是它的代理。
我们再次理解一下Binder是什么?在Binder通信模型的四个角色里面;他们的代表都是“Binder”,一个Binder对象就代表了所有,包括了Server,Client,ServiceManager,这样,对于Binder通信的使用者而言,不用关心实现的细节。对Server来说,Binder指的是Binder实体,或者说是本地对象,对于Client来说,Binder指的是Binder代理对象,也就是Binder的引用。对于Binder驱动而言,在Binder对象进行跨进程传递的时候,Binder驱动会自动完成这两种类型的转换。
简单的总结一下,通过上面一大段的分析,一个Server在使用的时候需要经历三个阶段
1、定义一个AIDL文件
Game.aidl
GameManager .aidl
2、定义远端服务Service
在远程服务中的onBind方法,实现AIDL接口的具体方法,并且返回Binder对象
3、本地创建连接对象
以上就是一个远端服务的一般套路,如果是在两个进程中,就可以进程通信了,现在我们分析一下,这个通信的流程。重点是GameManager这个编译生成的类。
从类的关系来看,首先接口GameManager 继承 IInterface ,IInterface是一个接口,在GameManager内部有一个内部类Stub,Stub继承了Binder,(Binder实现了IBinder),并且实现了GameManager接口,在Stub中还有一个内部类Proxy,Proxy也实现了GameManager接口,一个整体的结构是这样的
现在的问题是,Stub是什么?Proxy又是什么?在上面说了在Binder通信模型的四个角色里面;他们的代表都是“Binder”,一个Binder对象就代表了所有,包括了Server,Clinet,ServiceManager,为了两个进程的通信,系统给予的内核支持是Binder,在抽象一点的说,Binder是系统开辟的一块内存空间,两个进程往这块空间里面读写数据就行了,Stub从Binder中读数据,Proxy向Binder中写数据,达到进程间通信的目的。首先我们分析Stub。
Stub 类继承了Binder ,说明了Stub有了跨进程传输的能力,实现了GameManager接口,说明它有了根据游戏ID查询一个游戏的能力。我们在bind一个Service之后,在onServiceConnecttion的回调里面,就是通过asInterface方法拿到一个远程的service的。
asInterface调用queryLocalInterface。
mDescriptor,mOwner其实是Binder的成员变量,Stub继承了Binder,在构造函数的时候,对着两个变量赋的值。
如果客户端和服务端是在一个进程中,那么其实queryLocalInterface获取的就是Stub对象,如果不在一个进程queryLocalInterface查询的对象肯定为null,因为不同进程有不同虚拟机,肯定查不到mOwner对象的,所以这时候其实是返回的Proxy对象了。拿到Stub对象后,通常在onServiceConnected中,就把这个对象转换成我们多定义AIDL接口。
比如我们这里会转换成GameManager,有了GameManager对象,就可以调用后querryGameById方法了。如果是一个进程,那直接调用的是自己的querryGameById方法,如果不是一个进程,那调用了就是代理的querryGameById方法了。
看到其中关键的一行是
mRemote就是一个IBinder对象,相对于Stub,Proxy 是组合关系(HAS-A),内部有一个IBinder对象mRemote,Stub是继承关系(IS-A),直接实现了IBinder接口。
transact是个native方法,最终还会回掉JAVA层的onTransact方法。
onTransact根据调用号(每个AIDL函数都有一个编号,在跨进程的时候,不会传递函数,而是传递编号指明调用哪个函数)调用相关函数;在这个例子里面,调用了Binder本地对象的querryGameById方法;这个方法将结果返回给驱动,驱动唤醒挂起的Client进程里面的线程并将结果返回。于是一次跨进程调用就完成了。
***Please accept mybest wishes for your happiness and success ! ***
㈡ Android socket源码解析(三)socket的connect源码解析
上一篇文章着重的聊了socket服务端的bind,listen,accpet的逻辑。本文来着重聊聊connect都做了什么?
如果遇到什么问题,可以来本文 https://www.jianshu.com/p/da6089fdcfe1 下讨论
当服务端一切都准备好了。客户端就会尝试的通过 connect 系统调用,尝试的和服务端建立远程连接。
首先校验当前socket中是否有正确的目标地址。然后获取IP地址和端口调用 connectToAddress 。
在这个方法中,能看到有一个 NetHooks 跟踪socket的调用,也能看到 BlockGuard 跟踪了socket的connect调用。因此可以hook这两个地方跟踪socket,不过很少用就是了。
核心方法是 socketConnect 方法,这个方法就是调用 IoBridge.connect 方法。同理也会调用到jni中。
能看到也是调用了 connect 系统调用。
文件:/ net / ipv4 / af_inet.c
在这个方法中做的事情如下:
注意 sk_prot 所指向的方法是, tcp_prot 中 connect 所指向的方法,也就是指 tcp_v4_connect .
文件:/ net / ipv4 / tcp_ipv4.c
本质上核心任务有三件:
想要能够理解下文内容,先要明白什么是路由表。
路由表分为两大类:
每个路由器都有一个路由表(RIB)和转发表 (fib表),路由表用于决策路由,转发表决策转发分组。下文会接触到这两种表。
这两个表有什么区别呢?
网上虽然给了如下的定义:
但实际上在Linux 3.8.1中并没有明确的区分。整个路由相关的逻辑都是使用了fib转发表承担的。
先来看看几个和FIB转发表相关的核心结构体:
熟悉Linux命令朋友一定就能认出这里面大部分的字段都可以通过route命令查找到。
命令执行结果如下:
在这route命令结果的字段实际上都对应上了结构体中的字段含义:
知道路由表的的内容后。再来FIB转发表的内容。实际上从下面的源码其实可以得知,路由表的获取,实际上是先从fib转发表的路由字典树获取到后在同感加工获得路由表对象。
转发表的内容就更加简单
还记得在之前总结的ip地址的结构吗?
需要进行一次tcp的通信,意味着需要把ip报文准备好。因此需要决定源ip地址和目标IP地址。目标ip地址在之前通过netd查询到了,此时需要得到本地发送的源ip地址。
然而在实际情况下,往往是面对如下这么情况:公网一个对外的ip地址,而内网会被映射成多个不同内网的ip地址。而这个过程就是通过DDNS动态的在内存中进行更新。
因此 ip_route_connect 实际上就是选择一个缓存好的,通过DDNS设置好的内网ip地址并找到作为结果返回,将会在之后发送包的时候填入这些存在结果信息。而查询内网ip地址的过程,可以成为RTNetLink。
在Linux中有一个常用的命令 ifconfig 也可以实现类似增加一个内网ip地址的功能:
比如说为网卡eth0增加一个IPV6的地址。而这个过程实际上就是调用了devinet内核模块设定好的添加新ip地址方式,并在回调中把该ip地址刷新到内存中。
注意 devinet 和 RTNetLink 严格来说不是一个存在同一个模块。虽然都是使用 rtnl_register 注册方法到rtnl模块中:
文件:/ net / ipv4 / devinet.c
文件:/ net / ipv4 / route.c
实际上整个route模块,是跟着ipv4 内核模块一起初始化好的。能看到其中就根据不同的rtnl操作符号注册了对应不同的方法。
整个DDNS的工作流程大体如下:
当然,在tcp三次握手执行之前,需要得到当前的源地址,那么就需要通过rtnl进行查询内存中分配的ip。
文件:/ include / net / route.h
这个方法核心就是 __ip_route_output_key .当目的地址或者源地址有其一为空,则会调用 __ip_route_output_key 填充ip地址。目的地址为空说明可能是在回环链路中通信,如果源地址为空,那个说明可能往目的地址通信需要填充本地被DDNS分配好的内网地址。
在这个方法中核心还是调用了 flowi4_init_output 进行flowi4结构体的初始化。
文件:/ include / net / flow.h
能看到这个过程把数据中的源地址,目的地址,源地址端口和目的地址端口,协议类型等数据给记录下来,之后内网ip地址的查询与更新就会频繁的和这个结构体进行交互。
能看到实际上 flowi4 是一个用于承载数据的临时结构体,包含了本次路由操作需要的数据。
执行的事务如下:
想要弄清楚ip路由表的核心逻辑,必须明白路由表的几个核心的数据结构。当然网上搜索到的和本文很可能大为不同。本文是基于LInux 内核3.1.8.之后的设计几乎都沿用这一套。
而内核将路由表进行大规模的重新设计,很大一部分的原因是网络环境日益庞大且复杂。需要全新的方式进行优化管理系统中的路由表。
下面是fib_table 路由表所涉及的数据结构:
依次从最外层的结构体介绍:
能看到路由表的存储实际上通过字典树的数据结构压缩实现的。但是和常见的字典树有点区别,这种特殊的字典树称为LC-trie 快速路由查找算法。
这一篇文章对于快速路由查找算法的理解写的很不错: https://blog.csdn.net/dog250/article/details/6596046
首先理解字典树:字典树简单的来说,就是把一串数据化为二进制格式,根据左0,右1的方式构成的。
如图下所示:
这个过程用图来展示,就是沿着字典树路径不断向下读,比如依次读取abd节点就能得到00这个数字。依次读取abeh就能得到010这个数字。
说到底这种方式只是存储数据的一种方式。而使用数的好处就能很轻易的找到公共前缀,在字典树中找到公共最大子树,也就找到了公共前缀。
而LC-trie 则是在这之上做了压缩优化处理,想要理解这个算法,必须要明白在 tnode 中存在两个十分核心的数据:
这负责什么事情呢?下面就简单说说整个lc-trie的算法就能明白了。
当然先来看看方法 __ip_dev_find 是如何查找
文件:/ net / ipv4 / fib_trie.c
整个方法就是通过 tkey_extract_bits 生成tnode中对应的叶子节点所在index,从而通过 tnode_get_child_rcu 拿到tnode节点中index所对应的数组中获取叶下一级别的tnode或者叶子结点。
其中查找index最为核心方法如上,这个过程,先通过key左移动pos个位,再向右边移动(32 - bits)算法找到对应index。
在这里能对路由压缩算法有一定的理解即可,本文重点不在这里。当从路由树中找到了结果就返回 fib_result 结构体。
查询的结果最为核心的就是 fib_table 路由表,存储了真正的路由转发信息
文件:/ net / ipv4 / route.c
这个方法做的事情很简单,本质上就是想要找到这个路由的下一跳是哪里?
在这里面有一个核心的结构体名为 fib_nh_exception 。这个是指fib表中去往目的地址情况下最理想的下一跳的地址。
而这个结构体在上一个方法通过 find_exception 获得.遍历从 fib_result 获取到 fib_nh 结构体中的 nh_exceptions 链表。从这链表中找到一模一样的目的地址并返回得到的。
文件:/ net / ipv4 / tcp_output.c
㈢ Android源码解析Window系列第(一)篇---Window的基本认识和Activity的加载流程
您可能听说过View ,ViewManager,Window,PhoneWindow,WindowManager,WindowManagerService,可是你知道这几个类是什么关系,干嘛用的。概括的来说,View是放在Window中的,Window是一个抽象类,它的具体实现是PhoneWindow,PhoneWindow还有个内部类DecorView,WindowManager是一个interface,继承自ViewManager,它是外界访问Window的入口,,提供了add/remove/updata的方法操作View,WindowManager与WindowManagerSerice是个跨进程的过程,WindowManagerService的职责是对系统中的所有窗口进行管理。如果您不太清楚,建议往下看,否则就不要看了。
Android系统的Window有很多种,大体上来说,Framework定义了三种窗口类型;
这就是Framework定义了三种窗口类型,这三种类型定义在WindowManager的内部类LayoutParams中,WindowManager讲这三种类型 进行了细化,把每一种类型都用一个int常量来表示,这些常量代表窗口所在的层,WindowManagerService在进行窗口叠加的时候,会按照常量的大小分配不同的层,常量值越大,代表位置越靠上面, 所以我们可以猜想一下,应用程序Window的层值常量要小于子Window的层值常量,子Window的层值常量要小于系统Window的层值常量。 Window的层级关系如下所示。
上面说了Window分为三种,用Window的type区分,在搞清楚Window的创建之前,我们需要知道怎么去描述一个Window,我们就把Window当做一个实体类,给我的感觉,它必须要下面几个字段。
实际上WindowManager.LayoutParams对Window有很详细的定义。
提取几个重要的参数
Window是一个是一个抽象的概念,千万不要认为我们所看到的就是Window,我们平时所看到的是视图,每一个Window都对应着一个View,View和Window通过ViewRootImpl来建立联系。有了View,Window的存在意义在哪里呢,因为View不能单独存在,它必须依附着Window,所以有视图的地方就有Window,比如Activity,一个Dialog,一个PopWindow,一个菜单,一个Toast等等。
通过上面我们知道视图和Window的关系,那么有一个问题,是先有视图,还是先有Window。这个答案只有在源码中找了。应用程序的入口类是ActivityThread,在ActivityThread中有performLaunchActivity来启动Activity,这个performLaunchActivity方法内部会创建一个Activity。
如果activity不为null,就会调用attach,在attach方法中通过PolicyManager创建了Window对象,并且给Window设置了回调接口。
PolicyManager的实现类是Policy
这样Window就创建出来了, 所以先有Window,后有视图,视图依赖Window存在 ,再说一说视图(Activity)为Window设置的回调接口。
Activity实现了这个回调接口,当Window的状态发生变化的时候,就会回调Activity中实现的这些接口,有些回调接口我们还是熟悉的,dispatchTouchEvent,onAttachedToWindow,onDetachedFromWindow等。
下面分析view是如何附属到window上的,通过上面可以看到,在attach之后就要执行callActivityOnCreate,在onCreate中我们会调用setContentView方法。
getWindow获取了Window对象,Window的具体实现类是PhoneWindow,所以要看PhoneWindow的setContentView方法。
这里涉及到一个mContentParent变量,他是一个DecorView的一部分,DecorView是PhoneWindow的一个内部类,我先介绍一下关于DecorView的知识。
DecorView是Activity的顶级VIew,DecorView继承自FrameLayout,在DecorView中有上下两个部分,上面是标题栏,下面是内容栏,我们通过PhoneWindow的setContentView所设置的布局文件是加到内容栏(mContentParent)里面的,View层的事件都是先经过DecorView在传递给我们的View的。
OK在回到setContentView的源码分析,我们可以得到Activity的Window创建需要三步。
- 1、 如果没有DecorView,在installDecor中创建DecorView。
- 2、将View添加到decorview中的mContentParent中。
- 3、回调Activity的onContentChanged接口。
先看看第一步,installDecor的源码
installDecor中调用了generateDecor,继续看
直接给new一个DecorView,有了DecorView之后,就可以加载具体的布局文件到DecorView中了,具体的布局文件和系统和主题有关系。
在看第二步,将View添加到decorview中的mContentParent中。
直接将Activity视图加到DecorView的mContentParent中,最后一步,回调Activity的onContentChanged接口。在Activity中寻找onContentChanged方法,它是个空实现,我们可以在子Activity中处理。
到此DecorView被创建完毕,我们一开始从Thread中的handleLaunchActivity方法开始分析,首先加载Activity的字节码文件,利用反射的方式创建一个Activity对象,调用Activity对象的attach方法,在attach方法中,创建系统需要的Window并为设置回调,这个回调定义在Window之中,由Activity实现,当Window的状态发生变化的时候,就会回调Activity实现的这些回调方法。调用attach方法之后,Window被创建完成,这时候需要关联我们的视图,在handleLaunchActivity中的attach执行之后就要执行handleLaunchActivity中的callActivityOnCreate,在onCreate中我们会调用setContentView方法。通过setContentView,创建了Activity的顶级View---DecorView,DecorView的内容栏(mContentParent)用来显示我们的布局。 这个是我们上面分析得到了一个大致流程,走到这里,这只是添加的过程,还要有一个显示的过程,显示的过程就要调用handleLaunchActivity中的handleResumeActivity方法了。最后会调用makeVisible方法。
这里面首先拿到WindowManager对象,用tWindowManager 的父接口ViewManager接收,ViewManager可以
最后调用 mDecor.setVisibility(View.VISIBLE)设置mDecor可见。到此,我们终于明白一个Activity是怎么显示在我们的面前了。
参考链接:
http://blog.csdn.net/feiclear_up/article/details/49201357
㈣ 编译android-vlc支持rtsp,是不是需要添加live555谁有详细的步骤呢给说下,最好有编译好的源码
vlc-android是直接支持rtsp的,可以播放rtsp。http,mms网络流 我编译好了一份源代码,你可以下载看看 http://download.csdn.net/detail/wng2010/4971056
㈤ Android TV 焦点原理源码解析
相信很多刚接触AndroidTV开发的开发者,都会被各种焦点问题给折磨的不行。不管是学技术还是学习其他知识,都要学习和理解其中原理,碰到问题我们才能得心应手。下面就来探一探Android的焦点分发的过程。
Android焦点事件的分发是从ViewRootImpl的processKeyEvent开始的,源码如下:
源码比较长,下面我就慢慢来讲解一下具体的每一个细节。
dispatchKeyEvent方法返回true代表焦点事件被消费了。
ViewGroup的dispatchKeyEvent()方法的源码如下:
(2)ViewGroup的dispatchKeyEvent执行流程
(3)下面再来瞧瞧view的dispatchKeyEvent方法的具体的执行过程
惊奇的发现执行了onKeyListener中的onKey方法,如果onKey方法返回true,那么dispatchKeyEvent方法也会返回true
可以得出结论:如果想要修改ViewGroup焦点事件的分发,可以这么干:
注意:实际开发中,理论上所有焦点问题都可以通过给dispatchKeyEvent方法增加监听来来拦截来控制。
(1)dispatchKeyEvent方法返回false后,先得到按键的方向direction值,这个值是一个int类型参数。这个direction值是后面来进行焦点查找的。
(2)接着会调用DecorView的findFocus()方法一层一层往下查找已经获取焦点的子View。
ViewGroup的findFocus方法如下:
View的findFocus方法
说明:判断view是否获取焦点的isFocused()方法, (mPrivateFlags & PFLAG_FOCUSED) != 0 和view 的isFocused()方法是一致的。
其中isFocused()方法的作用是判断view是否已经获取焦点,如果viewGroup已经获取到了焦点,那么返回本身即可,否则通过mFocused的findFocus()方法来找焦点。mFocused其实就是ViewGroup中获取焦点的子view,如果mView不是ViewGourp的话,findFocus其实就是判断本身是否已经获取焦点,如果已经获取焦点了,返回本身。
(3)回到processKeyEvent方法中,如果findFocus方法返回的mFocused不为空,说明找到了当前获取焦点的view(mFocused),接着focusSearch会把direction(遥控器按键按下的方向)作为参数,找到特定方向下一个将要获取焦点的view,最后如果该view不为空,那么就让该view获取焦点。
(4)focusSearch方法的具体实现。
focusSearch方法的源码如下:
可以看出focusSearch其实是一层一层地网上调用父View的focusSearch方法,直到当前view是根布局(isRootNamespace()方法),通过注释可以知道focusSearch最终会调用DecorView的focusSearch方法。而DecorView的focusSearch方法找到的焦点view是通过FocusFinder来找到的。
(5)FocusFinder是什么?
它其实是一个实现 根据给定的按键方向,通过当前的获取焦点的View,查找下一个获取焦点的view这样算法的类。焦点没有被拦截的情况下,Android框架焦点的查找最终都是通过FocusFinder类来实现的。
(6)FocusFinder是如何通过findNextFocus方法寻找焦点的。
下面就来看看FocusFinder类是如何通过findNextFocus来找焦点的。一层一层往下看,后面会执行findNextUserSpecifiedFocus()方法,这个方法会执行focused(即当前获取焦点的View)的findUserSetNextFocus方法,如果该方法返回的View不为空,且isFocusable = true && isInTouchMode() = true的话,FocusFinder找到的焦点就是findNextUserSpecifiedFocus()返回的View。
(7)findNextFocus会优先根据XML里设置的下一个将获取焦点的View ID值来寻找将要获取焦点的View。
看看View的findUserSetNextFocus方法内部都干了些什么,OMG不就是通过我们xml布局里设置的nextFocusLeft,nextFocusRight的viewId来找焦点吗,如果按下Left键,那么便会通过nextFocusLeft值里的View Id值去找下一个获取焦点的View。
可以得出以下结论:
1. 如果一个View在XML布局中设置了focusable = true && isInTouchMode = true,那么这个View会优先获取焦点。
2. 通过设置nextFocusLeft,nextFocusRight,nextFocusUp,nextFocusDown值可以控制View的下一个焦点。
Android焦点的原理实现就这些。总结一下:
为了方便同志们学习,我这做了张导图,方便大家理解~
㈥ Android RecyclerView的布局管理器 GridLayoutManager源码分析<三>
Android RecyclerView绘制页面的源码分析<一>
Android RecyclerView布局管理器LinearLayoutManager源码分析<二>
以上两篇讲述了RecycerView LinearLayoutManager 页面绘制以及子条目的布局,LinearLayoutManager 是一个线性的管理器即是控制垂直以及水平展示 一个条目表示一行,然而显示生活中有很多需求是一行展示多个子条目这个时候就用到了 GridLayoutManager 表格布局管理器 来实现这种需求
GridLayoutManager :继承于LinearLayoutManager的网格状布局管理器 默认一行展示一个
GridLayoutManager网格布局管理器 实现一行展示多个条目,本章解释了GridLayoutManager的简单源码实现表格布局的大概调用,下一章展示复杂的表格布局即展示不顾地条目数的表格布局
㈦ vlc for android 源码能不能在windows环境下编译
1. 准备编译环境
基本上按照这篇wiki的介绍就足够了,为了顺利完成编译,建议首先保证相关的软件或者依赖库都已经下载好了,我再强调一下几个重点注意事项。
(1) Android SDK:必须使用SDK Platform Android 5.0, API 21,因为VLC-for-android用到了Android 5.0 的一些API。
(2) 最好通过apt-get install 把下面这些依赖的软件都安装一遍,或更新到最新版
git,apache-ant (or ant), autoconf, automake, autopoint, cmake,
gawk (or nawk), gcc, g++, libtool, m4, patch, pkg-config, ragel,
subversion, unzip.
2. 下载源码包
直接通过git下载VLC-for-android最新的源码即可:
git clone git://git.videolan.org/vlc-ports/android.git
3. 编译VLC源码和VLC Android工程
(1) 配置编译环境变量
具体参考wiki的介绍,你可以写个shell脚本来执行,避免每次编译都要配置,下面是我的环境变量,可以根据你的路径修改:
#! /bin/sh
export ANDROID_SDK=/opt/android/sdk/
export ANDROID_NDK=/opt/android/android-ndk-r10/
export ANT_DIR=/opt/android/ant/
export PATH=$PATH:$ANDROID_SDK/platform-tools:$ANDROID_SDK/tools:$ANT_DIR
export ANDROID_ABI=armeabi-v7a
(2) 执行编译
sh compile.sh
VLC不愧是使用这么广泛的播放器,它的编译脚本写得非常强大和智能,直接通过执行compile.sh,它会自动check所有的依赖,并通过网络去下载缺失的库。
首先,它会下载vlc的源码,并存放在当前目录下。然后去下载依赖的第三方库文件。
当然,由于GFW的存在,有的时候下载会失败,这个时候,就需要你手动去Google搜索它正在下载的依赖文件,手动下载好了之后放到 vlc/contrib/tarballs目录下,然后再回到命令行重新执行 sh compile.sh
它依赖的全部第三方库文件如图所示:
(3) 编译问题
编译过程还算顺利,只出现过一个大问题,如下:
google/protobuf/unittest.proto:853:21: Missing field number.
google/protobuf/unittest.proto:862:1: Reached end of input in message definition (missing '}').
make[3]: *** [unittest_proto_middleman] Error 1
网上也搜不到解决方案,我看了下GitHub上Protobuf的Readme,然后下载了最新的protobuf放到vlc/contrib
/tarballs/contrib-android-arm-linux-androideabi/protobuf目录下,执行.
/configure --disable-shared,再编译,没想到就直接过了。
4. 加载VLC-For-Android的Java工程
编译通过后,就可以直接在vlc-android/bin目录下看到debug版的apk了,下面简单说说在Eclipse中加载vlc-android的整个工程。
打开Eclipse,选择Import,把vlc-for-android目录下所有的工程到导入到Eclipse中(我去掉了TV工程),如图所
示,有5个必须的工程,其中,VLC是主工程,其他四个都是Lib工程。没有什么意外的话,直接运行VLC工程,就可以在Android手机上看到VLC
播放器应用了!
㈧ Android-ViewPager源码解析与性能优化
ViewPager高度设置为wrap_content或者具体的高度值无效,是因为ViewPager的onMeasure方法在度量宽高的时候,在方法体的最开始就直接调用了setMeasuredDimension()方法将自身的宽高度量,但是并没有在其onMeasure()计算完其具体的子View的宽高之后,重新度量一次自身的宽高
从这里我们可以看到,ViewPager的宽高会受其父容器的宽高的限制,但是并不会因为自身子View的宽高而影响ViewPager的宽高。
看setMeasuredDimension的源码调用可以看出,当父容器的高度确定时,ViewPager的宽高其实就是父容器的宽高,ViewPager就是在onMeasure方法一进来的时候就直接填充满整个父容器的剩余空间。在计算孩子节点之前,就已经计算好了ViewPager的宽高,在计算完孩子节点之后,并不会再去重新计算ViewPager的宽高。
自定义一个ViewPager,根据子View的宽高重新度量ViewPager的宽高。其实做法就是在自定义onMeasure的super.onMeasure(widthMeasureSpec, heightMeasureSpec);之前重新计算heightMeasureSpec,将原本ViewPager接收的父容器的限定的heightMeasureSpec替换成我们自定义的heightMeasureSpec。
但是这样的做法,会有种问题,即在ViewPager的子View是采用LinearLayout作为根布局的时候,并且给LinearLayout设置了固定的高度值,那么会出现ViewPager动态高度无效的问题
其实具体的做法,就是仿造measureChild的做法,自定义子View的heightMeasureSpec然后度量整个子View,其实子View的宽度也可以这样做。
这里其实是源码层做了限制,在setOffscreenPageLimit中设置了一个默认值,而这个默认值的大小为1
所以从这里可以看出,ViewPager的最小缓存的limit是1,而不能小于1,当小于1的时候就会被强制的设置为1。
而populate()函数就是用来处理ViewPager的缓存的。
populate()的生命周期是与Adapter的生命周期绑定的。
其实在setOffscreenPageLimit()的时候,调用的populate(),而populate()内部调用的
而pupulate(int newCurrentItem)方法在另一处调用的地方就是在setCurrentItem。
其实ViewPager缓存都是基于ItemInfo这个类来进行的,
看下ViewPager.addNewItem的源码
其实ViewPager.addNewItem就是通过调用Adapter.instantiateItem来创建对应的View,并且将View保存到ItemInfo中的object属性,并且判断ViewPager缓存中是否已经有ItemInfo,如果没有,则添加,如果有则做修改替换
从分析FragmentStatePagerAdapter来看,setUserVisibleHint方法会优先于Fragment的生命周期函数 执行。因为在FragmentStatePagerAdapter中提交事务,是在调用finishUpdate方法中进行的,只有提交事务的时候,才会去执行Fragment的生命周期。
FragmentStatePagerAdapter中的instantiateItem和destroyItem都实现了对fragment的事务的添加和删除,而finishUpdate实现了事务的提交,所以在实现FragmentStatePagerAdapter的时候,并不需要重写instantiateItem和destroyItem
㈨ 怎样评价罗升阳的android系统源代码分析
我干了3年Android sdk开发,觉得到了瓶劲没法更进一步,于是花了一年多点时间,大概摸到点门径。根据前辈的经验,Android底层完全入门需要两年。 先说下我的入门过程: 第零步,下载源码,我下的4.2的,框架层源码10G,内核2G多,ctags给框架层建的标签文件都有600M,当时让我有点震撼,用的vim+ctags+cscope来阅读,还算不错,架构挺清晰的。 第一步,我找到了一本好书《Android的设计与实现 第一卷》它讲了Android框架层的启动,初始化,服务框架初始化,Binder,消息循环,PackageManagerService,ActivityManagerService。据作者说后面会出讲UI子系统的第二卷,拭目以待。其实这本书看了几十页我就发现需要第二步的知识,否则看不下去,于是跳去第二步。 第二步,学习Linux系统编程,在看《Android的设计与实现》的时候我发现,框架层的Native部分,全是Linux编程。为了掌握这部分知识,我花了4个月学习了《Linux系统编程手册》(TLPI)这本1000多页的书,我以前是搞WIndows文件系统这块的,所以C语言还比较熟,TLPI的习题很有意思,量也比较大,坚持下来还是收获很多。 第三步,花了4个月学习了一些Linux内核的知识,看了LKD,PLKA看了一半多。越学越没底,觉得不懂得越来越多,不过这个也正常,只有靠慢慢磨,估计以后要不断的磨这块。 第四步,回头看Android源码,这次一口气看完了《Android的设计与实现 第一卷》,终于对框架层有了谱。同时真的数次把我看晕,前面看Linux内核源码都没这么晕,不断在Java层和Native层之间跳有点磨脑浆。其中我又觉得Java的基础没有打太牢,回去补了一个月的《Core Java》第八版。但是这书没有涉及UI子系统,于是又看了《Android内核剖析》 第五步,《Android内核剖析》(这本书实际上是讲框架层的,作者也是个搞嵌入式的,所以他在写框架层的时候文笔不太好,很罗嗦,不过还是有很多看点,到他后来写做ROM,玩开发板时估计是说到了他的本行,一下子遛起来了看得出还是挺有水平的,这本书知识有点旧毕竟讲的是2.3很多代码已经过时,但是作者很多点子很有参考价值)这本书讲UI子系统和按键/触摸消息处理系统还是很有分量的,尤其13章View绘制那里,结合源码研究很有收获。而后面他讲编译框架和ROM相关的东西都是挺宝贵的资料。 第六步,为了再补一下其他诸如电源管理模块等子系统的知识看了,《深入理解android》系列,个人认为这个系列看起来有点不太舒服,不过作为补充印证还是比较有价值。 第七步,《Android系统源代码情景分析》,罗升阳的源码分析大作,比《Android的设计与实现》分析得更细致,但缺点是涉及到模块比较少,选用的源码也比《Android的设计与实现》更旧一点。看完书后需要去研究作者的博客,东西挺多的,一定让你满意。 第八步,买块开发板自己玩。这步我还没走到,原因是我觉得我还差点准备知识。可能要再几个月,到时准备入块6410或者树莓派。 最后,由于我11年以前都是搞Windows这块的,所以对Linux知识不是很了解,不得已看了这么些书,如果是一直做Linux的人,很多步骤估计可以省掉了。直接上源码才是正道。 我本身做着移动GIS开发的工作,学框架层全是因为兴趣,但招聘平台Android框架层开发人员还是蛮有竞争力的有不少定制ROM,智能电视的工作都处于人才难求状态,毕竟有一定的门槛,现在各种ios培训,让奔着钱干开发的人纷纷涌入,而ios只能干sdk开发的缺点就暴露出来了,一堆新手老手,菜鸟大牛全挤在SDK开发这块,我觉得不太妙。 反观Android这边,虽然入门菜鸟没有搞ios来钱,但是可持续性很好,从sdk-》框架》驱动》内核这样干下去。干着干着发现自己渐渐变成了Linux开发者/嵌入式开发者的人也不少,新人,老手,菜鸟大牛各居其位,层次性很好。 转载