android 学习笔记2
1、new Handler(Looper.getMainLooper())
Handler一定要在主线程实例化吗?new Handler()和new Handler(Looper.getMainLooper())的区别
如果你不带参数的实例化:Handler handler = new Handler();那么这个会默认用当前线程的looper
一般而言,如果你的Handler是要来刷新操作UI的,那么就需要在主线程下跑。
情况:
1.要刷新UI,handler要用到主线程的looper。那么在主线程 Handler handler = new Handler(); 如果在其他线程,也要满足这个功能的话,要
Handler handler = new Handler(Looper.getMainLooper());
2.不用刷新ui,只是处理消息。 当前线程如果是主线程的话,Handler handler = new Handler(); 不是主线程的话,
Looper.prepare(); Handler handler = new Handler(); Looper.loop();
或者
Handler handler = new Handler (Looper.getMainLooper());
若是实例化的时候用 Looper.getMainLooper() 就表示放到主UI线程去处理。
如果不是的话,因为只有UI线程默认 Loop.prepare(); Loop.loop() 过,其他线程需要手动调用这两个,否则会报错。
2、LayoutParams
先来看两个例子:
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams( LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT); layoutParams.bottomMargin = 20; LinearLayout linearLayout = new LinearLayout(this); Button button = new Button(this); button.setText("What"); TextView tv = new TextView(this); tv.setText("What1"); //调用Activity的addContentView函数 linearLayout.addView(tv); linearLayout.addView(button); this.addContentView(linearLayout, layoutParams); // 第二个例子 setContentView(R.layout.activity_main); LinearLayout linearLayout = (LinearLayout) findViewById(R.id.ll); Button button = new Button(this); button.setText("What"); TextView tv = new TextView(this); tv.setText("What1"); LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams( LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT); linearLayout.addView(tv, layoutParams); linearLayout.addView(button, layoutParams);
LayoutParams 类是用于child view(子视图) 向 parent view(父视图)传达自己的意愿的一个东西(孩子想变成什么样向其父亲说明)。其实子视图父视图可以简单理解成
一个LinearLayout 和 这个 LinearLayout 里边一个 TextView 的关系 TextView 就算 LinearLayout 的子视图 child view 。需要注意的是LayoutParams 只是 ViewGroup 的一个内部类这里边这个也就是ViewGroup里边这个 LayoutParams 类是 base class 基类实际上每个不同的ViewGroup 都有自己的 LayoutParams 子类。
因此我们可以看到都是将 LayoutParams 用到子 view 身上。
在继承BaseAdapter的时候,用getView返回View的时候,用代码控制布局,需要用到 View.setLayoutParams,但是报错了,报的是类型转换错误,经过研究,发现,这里不能使用ViewGroup.LayoutParams而必须使用对应父View的LayoutParams类型。如:某 View 被 LinearLayout 包含,则该View 的 setLayoutParams 参数类型必须是 LinearLayout.LayoutParams。
原因在于 LinearLayout(或其他继承自 ViewGroup 的layout,如:RelativeLayout )在进行递归布局的时候,LinearLayout 会获取子 View 的 LayoutParams,并强制转换成 LinearLayout.LayoutParams,如
1LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();或者是如下定义:
1LayoutParams lp = (LayoutParams) child.getLayoutParams();以转换成内部类型 LinearLayout.LayoutParams。
自己测试运行的时候报空指针,原因为 child.getLayoutParams(); 这里没有获得到子控件所在的布局,查看代码发现 parent.addView(child); 应该写在上面,之后 child 才能 getLayoutParams();
在看一个例子:
//第一个参数为宽的设置,第二个参数为高的设置。 (用的时候注意修改LinearLayout前缀,其实很多时候可以不写它的) LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( LinearLayout.LayoutParams.FILL_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT ); //设置居中显示: lp.gravity = Gravity.CENTER; //设置它的上下左右的margin:4个参数按顺序分别是左上右下 lp.setMargins(10,10,10,10); //还可以这样添加规则: lp.addRule(RelativeLayout.ALIGN_PARENT_RIGHT, RelativeLayout.TRUE); lp.addRule(RelativeLayout.ALIGN_PARENT_TOP, RelativeLayout.TRUE); //给某个View设置LayoutParams参数: btn1.setLayoutParams(lp); //最后将添加View到Layout中:(如果是在某个自定义的Layout布局中,前缀也可以省略) mLayout.addView(textView, lp); //其实不用LayoutParams也不会死啦 mLayout.addView(textView, tvWidth, tvHeight);
在看一个自定义 view 的例子
public class Card extends FrameLayout { public Card(Context context) { super(context); LayoutParams lp = null; background = new View(getContext()); lp = new LayoutParams(-1, -1); lp.setMargins(10, 10, 0, 0); background.setBackgroundColor(0x33ffffff); addView(background, lp); label = new TextView(getContext()); label.setTextSize(28); label.setGravity(Gravity.CENTER); lp = new LayoutParams(-1, -1); lp.setMargins(10, 10, 0, 0); addView(label, lp); setNum(0); } }
3、paint 中获取文字的宽度
在使用Canvas绘制文字时,需要得到字符串的长度,Paint类内给了两个方法,measureText(),getTextBound();
可是对于同于字符串两个方法得出来的值有些差别:
getTextBounds() 得到的宽度总是比 measureText() 得到的宽度要小一点。
其中紫色的是 measureText(); 红色的标记是 getTextBounds。其实我们从方法名字也可以看出区别,一个是取宽度,一个是取字体边界。
4、文字基准定义和写字
-
基准点是baseline
-
ascent:是baseline之上至字符最高处的距离
-
descent:是baseline之下至字符最低处的距离
-
leading:是上一行字符的descent到下一行的ascent之间的距离,也就是相邻行间的空白距离
-
top:是指的是最高字符到baseline的值,即ascent的最大值
-
bottom:是指最低字符到baseline的值,即descent的最大值
Paint的ascent和descent方法--算高度
这两个是native方法//Return the distance above (negative) the baseline (ascent) based on the current typeface字体 and text size. public native float ascent(); //Return the distance below (positive) the baseline (descent) based on the current typeface and text size. public native float descent();可以通过 mPaint.ascent()+ mPaint.descent() 获取文字高度
/** * text:要绘制的文字 * x:绘制原点x坐标 * y:绘制原点y坐标 * paint:用来做画的画笔 */ public void drawText(String text, float x, float y, Paint paint)
上面这个构造函数是最常用的drawText方法,传进去一个String对象就能画出对应的文字。
但这里有两个参数需要非常注意,表示原点坐标的x和y.很多同学可能会认为,这里传进去的原点参数(x,y)是所在绘制文字所在矩形的左上角的点。但实际上并不是!比如,我们上面如果要画"harvic's blog"这几个字,这个原点坐标应当是下图中绿色小点的位置
在(x,y)中最让人捉急的是y坐标,一般而言,(x,y)所代表的位置是所画图形对应的矩形的左上角点。但在drawText中是非常例外的,y所代表的是基线的位置!
5、fresco 网上获取图片
/** * 获取服务端的图片 * 为了避免图片的尺寸过大,我们要对其进行一定的大小控制, * 避免其过大撑破影响其他模板 *//* private void prefetchImageTag(String url, final ImageView imageView) { ImageRequest request = ImageRequestBuilder.newBuilderWithSource(Uri.parse(url)).build(); Fresco.getImagePipeline().fetchDecodedImage(request, FeedRuntime.getAppContext()).subscribe( new BaseBitmapDataSubscriber() { @Override protected void onNewResultImpl(@Nullable Bitmap bitmap) { if (bitmap != null) { imageView.setImageBitmap(bitmap); } } @Override protected void onFailureImpl(DataSource> dataSource) { } }, UiThreadImmediateExecutorService.getInstance()); } */
6、Looper.loop()为什么不会阻塞主线程
Android是基于事件驱动的,即所有Activity的生命周期都是通过Handler事件驱动的。loop方法中会调用MessageQueue的next方法获取下一个message,当没有消息时,基于Linux pipe/epoll机制会阻塞在loop的queue.next()中的nativePollOnce()方法里,并不会消耗CPU。
7、IdleHandler
IdleHandler 是一个回调接口,可以通过MessageQueue的addIdleHandler添加实现类。当MessageQueue中的任务暂时处理完了(没有新任务或者下一个任务延时在之后),这个时候会回调这个接口,返回false,那么就会移除它,返回true就会在下次message处理完了的时候继续回调。
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() { @Override public boolean queueIdle() { //此处添加处理任务 return false; } });
queueIdle()返回true表示可以反复执行该方法,即执行后还可以再次执行;返回false表示执行完该方法后会移除该IdleHandler,即只执行一次。
注意,在主线程中使用时queueIdle中不能执行太耗时的任务。
getWidth()方法和getMeasureWidth()区别
首先getMeasureWidth()方法在measure()过程结束后就可以获取到了,而getWidth()方法要在layout()过程结束后才能获取到。另外,getMeasureWidth()方法中的值是通过setMeasuredDimension()方法来进行设置的,而getWidth()方法中的值则是通过视图右边的坐标减去左边的坐标计算出来的。
requestLayout,invalidate,postInvalidate区别与联系
-
相同点:三个方法都有刷新界面的效果。
-
不同点:invalidate和postInvalidate只会调用onDraw()方法;requestLayout则会重新调用onMeasure、onLayout、onDraw。
调用了invalidate方法后,会为该View添加一个标记位,同时不断向父容器请求刷新,父容器通过计算得出自身需要重绘的区域,直到传递到ViewRootImpl中,最终触发performTraversals方法,进行开始View树重绘流程(只绘制需要重绘的视图)。
调用requestLayout方法,会标记当前View及父容器,同时逐层向上提交,直到ViewRootImpl处理该事件,ViewRootImpl会调用三大流程,从measure开始,对于每一个含有标记位的view及其子View都会进行测量onMeasure、布局onLayout、绘制onDraw。
Android View 深度分析requestLayout、invalidate与postInvalidate
Binder机制,共享内存实现原理
为什么使用Binder?概念
进程隔离
进程空间划分:用户空间(User Space)/内核空间(Kernel Space)
系统调用:用户态与内核态
原理
跨进程通信是需要内核空间做支持的。传统的 IPC 机制如管道、Socket 都是内核的一部分,因此通过内核支持来实现进程间通信自然是没问题的。但是 Binder 并不是 Linux 系统内核的一部分,那怎么办呢?这就得益于 Linux 的动态内核可加载模块(Loadable Kernel Module,LKM)的机制;模块是具有独立功能的程序,它可以被单独编译,但是不能独立运行。它在运行时被链接到内核作为内核的一部分运行。这样,Android 系统就可以通过动态添加一个内核模块运行在内核空间,用户进程之间通过这个内核模块作为桥梁来实现通信。
在 Android 系统中,这个运行在内核空间,负责各个用户进程通过 Binder 实现通信的内核模块就叫 Binder 驱动(Binder Dirver)。
那么在 Android 系统中用户进程之间是如何通过这个内核模块(Binder 驱动)来实现通信的呢?难道是和前面说的传统 IPC 机制一样,先将数据从发送方进程拷贝到内核缓存区,然后再将数据从内核缓存区拷贝到接收方进程,通过两次拷贝来实现吗?显然不是,否则也不会有开篇所说的 Binder 在性能方面的优势了。
这就不得不通道 Linux 下的另一个概念:内存映射。
Binder IPC 机制中涉及到的内存映射通过 mmap() 来实现,mmap() 是操作系统中一种内存映射的方法。内存映射简单的讲就是将用户空间的一块内存区域映射到内核空间。映射关系建立后,用户对这块内存区域的修改可以直接反应到内核空间;反之内核空间对这段区域的修改也能直接反应到用户空间。
一次完整的 Binder IPC 通信过程通常是这样:
-
首先 Binder 驱动在内核空间创建一个数据接收缓存区;
-
接着在内核空间开辟一块内核缓存区,建立内核缓存区和内核中数据接收缓存区之间的映射关系,以及内核中数据接收缓存区和接收进程用户空间地址的映射关系;
-
发送方进程通过系统调用 copyfromuser() 将数据 copy 到内核中的内核缓存区,由于内核缓存区和接收进程的用户空间存在内存映射,因此也就相当于把数据发送到了接收进程的用户空间,这样便完成了一次进程间的通信。
Binder通讯模型
Binder是基于C/S架构的,其中定义了4个角色:Client、Server、Binder驱动和ServiceManager。
-
Binder驱动:类似网络通信中的路由器,负责将Client的请求转发到具体的Server中执行,并将Server返回的数据传回给Client。
-
ServiceManager:类似网络通信中的DNS服务器,负责将Client请求的Binder描述符转化为具体的Server地址,以便Binder驱动能够转发给具体的Server。Server如需提供Binder服务,需要向ServiceManager注册。
具体的通讯过程 -
Server向ServiceManager注册。Server通过Binder驱动向ServiceManager注册,声明可以对外提供服务。ServiceManager中会保留一份映射表。
-
Client向ServiceManager请求Server的Binder引用。Client想要请求Server的数据时,需要先通过Binder驱动向ServiceManager请求Server的Binder引用(代理对象)。
-
向具体的Server发送请求。Client拿到这个Binder代理对象后,就可以通过Binder驱动和Server进行通信了。
-
Server返回结果。Server响应请求后,需要再次通过Binder驱动将结果返回给Client。
ServiceManager是一个单独的进程,那么Server与ServiceManager通讯是靠什么呢?
当Android系统启动后,会创建一个名称为servicemanager的进程,这个进程通过一个约定的命令BINDERSETCONTEXT_MGR向Binder驱动注册,申请成为为ServiceManager,Binder驱动会自动为ServiceManager创建一个Binder实体。并且这个Binder实体的引用在所有的Client中都为0,也就说各个Client通过这个0号引用就可以和ServiceManager进行通信。Server通过0号引用向ServiceManager进行注册,Client通过0号引用就可以获取到要通信的Server的Binder引用。
写给 Android 应用工程师的 Binder 原理剖析
一篇文章了解相见恨晚的 Android Binder 进程间通讯机制
序列化的方式
Serializable是Java提供的一个序列化接口,是一个空接口,用于标示对象是否可以支持序列化,通过ObjectOutputStrean及ObjectInputStream实现序列化和反序列化的过程。注意可以为需要序列化的对象设置一个serialVersionUID,在反序列化的时候系统会检测文件中的serialVersionUID是否与当前类的值一致,如果不一致则说明类发生了修改,反序列化失败。因此对于可能会修改的类最好指定serialVersionUID的值。
Parcelable是Android特有的一个实现序列化的接口,在Parcel内部包装了可序列化的数据,可以在Binder中自由传输。序列化的功能由writeToParcel方法来完成,最终通过Parcel的一系列write方法完成。反序列化功能由CREAOR来完成,其内部标明了如何创建序列化对象和数组,并通过Parcel的一系列read方法来完成反序列化的过程。
Fragment的懒加载实现
Fragment可见状态改变时会被调用setUserVisibleHint()方法,可以通过复写该方法实现Fragment的懒加载,但需要注意该方法可能在onVIewCreated之前调用,需要确保界面已经初始化完成的情况下再去加载数据,避免空指针。
RecyclerView与ListView(缓存原理,区别联系,优缺点)
缓存区别:
层级不同:-
ListView有两级缓存,在屏幕与非屏幕内。
-
RecyclerView比ListView多两级缓存,支持多个离屏ItemView缓存(匹配pos获取目标位置的缓存,如果匹配则无需再次bindView),支持开发者自定义缓存处理逻辑,支持所有RecyclerView共用同一个RecyclerViewPool(缓存池)。缓存不同:
- ListView缓存View。
- RecyclerView缓存RecyclerView.ViewHolder,抽象可理解为:
- View + ViewHolder(避免每次createView时调用findViewById) + flag(标识状态);
优点
RecylerView提供了局部刷新的接口,通过局部刷新,就能避免调用许多无用的bindView。
RecyclerView的扩展性更强大(LayoutManager、ItemDecoration等)。
Android两种虚拟机区别与联系
Android中的Dalvik虚拟机相较于Java虚拟机针对手机的特点做了很多优化。
Dalvik基于寄存器,而JVM基于栈。在基于寄存器的虚拟机里,可以更为有效的减少冗余指令的分发和减少内存的读写访问。
Dalvik经过优化,允许在有限的内存中同时运行多个虚拟机的实例,并且每一个 Dalvik应用作为一个独立的Linux进程执行。
java虚拟机运行的是java字节码。(java类会被编译成一个或多个字节码.class文件,打包到.jar文件中,java虚拟机从相应的.class文件和.jar文件中获取相应的字节码)
Dalvik运行的是自定义的.dex字节码格式。(java类被编译成.class文件后,会通过一个dx工具将所有的.class文件转换成一个.dex文件,然后dalvik虚拟机会从其中读取指令和数据)
Android开发之浅谈java虚拟机和Dalvik虚拟机的区别
apk打包流程
-
aapt工具打包资源文件,生成R.java文件
-
aidl工具处理AIDL文件,生成对应的.java文件
-
javac工具编译Java文件,生成对应的.class文件
-
把.class文件转化成Davik VM支持的.dex文件
-
apkbuilder工具打包生成未签名的.apk文件
-
jarsigner对未签名.apk文件进行签名
-
zipalign工具对签名后的.apk文件进行对齐处理
apk安装流程
-
复制APK到/data/app目录下,解压并扫描安装包。
-
资源管理器解析APK里的资源文件。
-
解析AndroidManifest文件,并在/data/data/目录下创建对应的应用数据目录。
-
然后对dex文件进行优化,并保存在dalvik-cache目录下。
-
将AndroidManifest文件解析出的四大组件信息注册到PackageManagerService中。
-
安装完成后,发送广播。
apk瘦身
APK主要由以下几部分组成:
-
META-INF/ :包含了签名文件CERT.SF、CERT.RSA,以及 manifest 文件MANIFEST.MF。
-
assets/ : 存放资源文件,这些资源不会被编译成二进制。
-
lib/ :包含了一些引用的第三方库。
-
resources.arsc :包含res/values/中所有资源,例如strings,styles,以及其他未被包含在resources.arsc中的资源路径信息,例如layout 文件、图片等。
-
res/ :包含res中没有被存放到resources.arsc的资源。
-
classes.dex :经过dx编译能被android虚拟机理解的Java源码文件。
-
AndroidManifest.xml :清单文件
其中占据较大内存的是res资源、lib、class.dex,因此我们可以从下面的几个方面下手:
-
代码方面可以通过代码混淆,这个一般都会去做。平时也可以删除一些没有使用类。
-
去除无用资源。使用
lint工具来检测没有使用到的资源,或者在gradle中配置shrinkResources来删除包括库中所有的无用的资源,需要配合proguard压缩代码使用。这里需要注意项目中是否存在使用getIdentifier方式获取资源,这种方式类似反射lint及shrinkResources无法检测情况。如果存在这种方式,则需要配置一个keep.xml来记录使用反射获取的资源。(https://developer.android.com/studio/build/shrink-code)[%5B)压缩代码和资源] -
去除无用国际化支持。对于一些第三库来说(如support),因为国际化的问题,它们可能会支持了几十种语言,但我们的应用可能只需要支持几种语言,可以通过配置resConfigs提出不要的语言支持。
-
不同尺寸的图片支持。通常情况下只需要一套xxhpi的图片就可以支持大部分分辨率的要求了,因此,我们只需要保留一套图片。
-
图片压缩。 png压缩或者使用webP图片,完美支持需要Android版本4.2.1+
-
使用矢量图形。简单的图标可以使用矢量图片。
HTTP缓存机制

缓存的响应头:

-
Cache-control:标明缓存的最大存活时常;
-
Date:服务器告诉客户端,该资源的发送时间;
-
Expires:表示过期时间(该字段是1.0的东西,当cache-control和该字段同时存在的条件下,cache-control的优先级更高);
-
Last-Modified:服务器告诉客户端,资源的最后修改时间;
-
还有一个字段,这个图没给出,就是E-Tag:当前资源在服务器的唯一标识,可用于判断资源的内容是否被修改了。
除以上响应头字段以外,还需了解两个相关的Request请求头:If-Modified-since、If-none-Match。这两个字段是和Last-Modified、E-Tag配合使用的。大致流程如下:
服务器收到请求时,会在200 OK中回送该资源的Last-Modified和ETag头(服务器支持缓存的情况下才会有这两个头哦),客户端将该资源保存在cache中,并记录这两个属性。当客户端需要发送相同的请求时,根据Date + Cache-control来判断是否缓存过期,如果过期了,会在请求中携带If-Modified-Since和If-None-Match两个头。两个头的值分别是响应中Last-Modified和ETag头的值。服务器通过这两个头判断本地资源未发生变化,客户端不需要重新下载,返回304响应。
okhttp原理
OkHttpClient通过newCall可以将一个Request构建成一个Call,Call表示准备被执行的请求。Call调用executed或enqueue会调用Dispatcher对应的方法在当前线程或者一步开始执行请求,经过RealInterceptorChain获得最终结果,RealInterceptorChain是一个拦截器链,其中依次包含以下拦截器:
-
自定义的拦截器
-
retryAndFollowUpInterceptor 请求失败重试
-
BridgeInterceptor 为请求添加请求头,为响应添加响应头
-
CacheInterceptor 缓存get请求
-
ConnectInterceptor 连接相关的拦截器,分配一个Connection和HttpCodec为最终的请求做准备
-
CallServerInterceptor 该拦截器就是利用HttpCodec完成最终请求的发送
类的加载机制
程序在启动的时候,并不会一次性加载程序所要用的所有class文件,而是根据程序的需要,通过Java的类加载机制(ClassLoader)来动态加载某个class文件到内存当中的,从而只有class文件被载入到了内存之后,才能被其它class所引用。所以ClassLoader就是用来动态加载class文件到内存当中用的。
类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸载(Unloading)7个阶段。其中准备、验证、解析3个部分统称为连接(Linking)。
-
加载:查找和导入Class文件;
-
链接:把类的二进制数据合并到JRE中;
(a) 验证:检查载入Class文件数据的正确性;
(b) 准备:给类的静态变量分配存储空间;
(c) 解析:将符号引用转成直接引用; -
初始化:对类的静态变量,静态代码块执行初始化操作
什么时候发生类初始化
-
遇到new、getstatic、putstatic或invokestatic这4条字节码指令时,如果类没有进行过初始化,则需要先触发其初始化。生成这4条指令的最常见的Java代码场景是:使用new关键字实例化对象的时候,读取或设置一个类的静态字段(被final修饰、已在编译期把结果放入常量池的静态字段除外)的时候,以及调用一个类的静态方法的时候。
-
使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化。
-
当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
-
当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类。
-
当使用JDK 1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例左后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄锁对应的类没有进行过初始化时。
singleTask
singleTask保证了在栈中Activity的唯一性,如果被SingleTask标记的Activity处于栈底,站定的Activity发送Intent返回到栈底时,栈中间的Activity会被清除,这个时候被标记过SingleTask的生命周期会响应的做出调整。
onCreate是用来创建一个Activity也就是创建一个窗体,但一个Activty处于任务栈的顶端,若再次调用startActivity去创建它,则不会再次创建。
若你想利用已有的Acivity去处理别的Intent时,你就可以利用onNewIntent来处理。在 onNewIntent里面就会获得新的Intent.如果IntentActivity处于任务栈的顶端,也就是说之前打开过的Activity,现在处于onPause,onStop 状态的话其他应用再发送Intent的话,执行顺序为:
onNewIntent,onRestart,onStart,onResume
参考文章:
https://www.kancloud.cn/aslai/interview-guide/1126388