Open longtaozz opened 5 years ago
Service分为本地服务(LocalService)和远程服务(RemoteService) 1、本地服务依附在主进程上而不是独立的进程,这样在一定程度上节约了资源,另外Local服务因为是在同一进程因此不需要IPC,也不需要AIDL。相应bindService会方便很多。主进程被Kill后,服务便会终止。
2、远程服务为独立的进程,对应进程名格式为所在包名加上你指定的android:process字符串。由于是独立的进程,因此在Activity所在进程被Kill的时候,该服务依然在运行,不受其他进程影响,有利于为多个进程提供服务具有较高的灵活性。该服务是独立的进程,会占用一定资源,并且使用AIDL进行IPC稍微麻烦一点。
service的启动方式分为三种,①startService()、②bindService()和前面两种都用,第一种为正常启动方式,服务启动后会无限制运行下去,除非调用外部调用stopService()或内部调用stopSelf(),第二种启动方式,服务启动后,可以利用IBinder接口得到service来进行组件交互,一个服务可以多个绑定,也可以调用onunService()进行解除绑定,当没有任何东西与此服务绑定的时候,此服务会销毁。 (两种启动方式service生命周期不同,如上图)
bindService()
生命周期:onCreate()>onBind()>onUnbind()>onDestroy()
onBind:绑定
onUnbind:解除绑定
public boolean bindService(Intent service, ServiceConnection conn, int flags)
想要与service交互,需要填入第二个参数,实现ServiceConnection接口,可在onServiceConnected拿到绑定的service。
MService mService;
boolean isBind=false;
ServiceConnection connection=new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
//绑定成功,通过此回调获得IBinder
isBind=true;
mService= (MService) iBinder;
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
//解除绑定回调
isBind=false;
}
};
介绍三个框架的组合使用,以及各自的作用,实现原理等
结合当前三个框架,主要充当请求配置的作用。
//手动创建一个OkHttpClient并设置超时时间
okHttpBuilder = new OkHttpClient.Builder();
配置 okhttp配置主要使用的是拦截器(Interceptor),可以设置缓存,配置请求头,通过addInterceptor添加入okhttpclient中。还可配置请求超时等。
/**
* 设置缓存
*/
File cacheFile = new File(context.getExternalCacheDir(), CACHE_NAME);
Cache cache = new Cache(cacheFile, 1024 * 1024 * 50);
Interceptor cacheInterceptor = new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
if (!NetConnect.isConnected(context)) {
request = request.newBuilder()
.cacheControl(CacheControl.FORCE_CACHE)
.build();
}
Response response = chain.proceed(request);
if (!NetConnect.isConnected(context)) {
int maxAge = 0;
// 有网络时 设置缓存超时时间0个小时
response.newBuilder()
.header("Cache-Control", "public, max-age=" + maxAge)
.removeHeader(CACHE_NAME)// 清除头信息,因为服务器如
果不支持,会返回一些干 扰信息,不清除下面无法生效
.build();
} else {
// 无网络时,设置超时为4周
int maxStale = 60 * 60 * 24 * 28;
response.newBuilder()
.header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale)
.removeHeader(CACHE_NAME)
.build();
}
return response;
}
};
okHttpBuilder.cache(cache).addInterceptor(cacheInterceptor);
/**
* 设置头信息
*/
Interceptor headerInterceptor = new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request originalRequest = chain.request();
Request.Builder requestBuilder = originalRequest.newBuilder()
.addHeader("Accept-Encoding", "gzip")
.addHeader("Accept", "application/json")
.addHeader("Content-Type", "application/json; charset=utf-8")
.method(originalRequest.method(), originalRequest.body());
requestBuilder.addHeader("x-token", BaseData.TOKEN);//添加请求头信息,
服务器进行token有效性验证
Request request = requestBuilder.build();
//如果没有token获取token,按具体需求来
if (TextUtils.isEmpty(BaseData.TOKEN)) {
Response response = chain.proceed(request);
List<String> strList = response.headers("x-token");
if (strList != null && strList.size() > 0) {
BaseData.TOKEN = strList.get(0);
}
}
return chain.proceed(request);
}
};
okHttpBuilder.addInterceptor(headerInterceptor);
也是使用拦截器,不过使用的是log专用的拦截器 HttpLoggingInterceptor,设置log打印等(看需要,正式版本的时候要去掉):
//设置是否打印log
if (AndroidUtil.isApkInDebug(context)) {
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
@Override
public void log(String message) {
Logger.e(message);
}
});
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
//设置 Debug Log 模式
okHttpBuilder.addInterceptor(loggingInterceptor);
}
RequestBody requestBody = FormBody.create(MediaType.parse("application/json; charset=utf-8"), json);
//创建request
Request request = new Request.Builder().header("x-token", Web.getToken())
.post(requestBody).url(url).build();
整合配合Retrofit使用 通过Retrofit.Builder的client方法将自定义好的okhttp配置进去
//3 将Request封装为Call
Call call = mOkHttpClient.newCall(request);
执行call请求,分为异步和同步请求。
//执行Call,异步请求,由Callback接收请求回调
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
myNetCall.failed(call, e);
}
@Override
public void onResponse(Call call, Response response) throws IOException {
//请求完成处理
}
});
//执行Call,同步请求,得到response
Response response = null;
try {
response = call.execute();
} catch (IOException e) {
e.printStackTrace();
}
#### 原理
- 流程
通过OkHttpClient将构建的Request转换为Call,然后在RealCall中进行异步或同步任务,最后通过一些的拦截器interceptor发出网络请求和得到返回的response。
![okhttp请求流程](https://github.com/longtaozz/my_img/blob/master/android_img/9984264-3f4ba9f2e1dc097f.png?raw=true)
- OkHttpClient
上面说过了,OkHttpClient作为主要的组件,主要是用来做请求配置的,详细参数:
final Dispatcher dispatcher; //重要:分发器,分发执行和关闭由request构成的Call
final Proxy proxy; //代理
final List<Protocol> protocols; //协议
final List<ConnectionSpec> connectionSpecs; //传输层版本和连接协议
final List<Interceptor> interceptors; //重要:拦截器
final List<Interceptor> networkInterceptors; //网络拦截器
final ProxySelector proxySelector; //代理选择
final CookieJar cookieJar; //cookie
final Cache cache; //缓存
final InternalCache internalCache; //内部缓存
final SocketFactory socketFactory; //socket 工厂
final SSLSocketFactory sslSocketFactory; //安全套接层socket 工厂,用于HTTPS
final CertificateChainCleaner certificateChainCleaner; // 验证确认响应证书 适用 HTTPS 请求连接的主机名。
final HostnameVerifier hostnameVerifier; // 主机名字确认
final CertificatePinner certificatePinner; // 证书链
final Authenticator proxyAuthenticator; //代理身份验证
final Authenticator authenticator; // 本地身份验证
final ConnectionPool connectionPool; //连接池,复用连接
final Dns dns; //域名
final boolean followSslRedirects; //安全套接层重定向
final boolean followRedirects; //本地重定向
final boolean retryOnConnectionFailure; //重试连接失败
final int connectTimeout; //连接超时
final int readTimeout; //read 超时
final int writeTimeout; //write 超时
- RealCall
请求的组件,RealCall实现了Call接口,
dependencies {
classpath 'com.android.tools.build:gradle:3.1.2'
}
repositories {
google()
jcenter()
}
distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
把传入的int类型值当作资源文件id使用。然而用资源文件id去查找资源的时候又找不到所对应的资源。
主要是指app在无网络的情况下,显示已缓存的数据,缓存方式有内存缓存和文件缓存。
SharedPreferences,它是一个轻量级的存储类,特别适合用于保存软件配置参数 。 优点: 1.轻量级,以键值对的方式进行存储,使用方便,易于理解 2.采用的是xml文件形式存储在本地,程序卸载后会也会一并被清除,不会残留信息 缺点: 1.有于是对文件IO读取,因此在IO上的瓶颈是个大问题,因为在每次进行get和commit时都要将数据从内存写入到文件中,或从文件中读取 2.多线程场景下效率较低,在get操作时,会锁定SharedPreferences对象,互斥其他操作,而当put,commit时,则会锁定Editor对象,使用写入锁进行互斥,在这种情况下,效率会降低 3.不支持跨进程通讯 4.由于每次都会把整个文件加载到内存中,因此,如果SharedPreferences文件过大,或者在其中的键值对是大对象的json数据则会占用大量内存,读取较慢是一方面,同时也会引发程序频繁GC,导致的界面卡顿。 使用注意点 1.建议不要存储较大数据或者较多数据到SharedPreferences中 2.频繁修改的数据修改后统一提交,而不是修改过后马上提交 3.在跨进程通讯中不去使用SharedPreferences 4.键值对不宜过多
应用无响应 ANR类型:
//How long we wait until we timeout on key dispatching.
staticfinal int KEY_DISPATCHING_TIMEOUT = 5*1000
所以在UI线程中如果使用一个非常耗时的操作(>5S),是否会ANR应该取决于在这个过程中是否有收到 touch event ,如果有,那必然会产生ANR 弹窗,如果没有,理论上不会。
事件分发是由外向内的,依靠传统的事件分发无法处理一些特殊的事件,比如滚动嵌套悬停,首先我们滑动的是下面的内容区域,而移动却是外部的ViewGroup在移动,所以按照传统的方式,肯定是外部的Parent拦截了内部的Child的事件,Parent滑动到一定程度时,Child又开始滑动了,中间整个过程是没有间断的。从正常的事件分发(不手动调用分发事件,不手动去发出事件)角度去做是不可能的,因为当Parent拦截之后,是没有办法再把事件交给Child的,事件分发,对于拦截,相当于一锤子买卖,只要拦截了,当前手势接下来的事件都会交给Parent(拦截者)来处理。 所以我们要使用滚动嵌套来实现。 CoordinatorLayout 用这个来实现嵌套悬停是相当的简单的CoordinatorLayout使用。 CoordinatorLayout是基于NestedScroll机制去实现的,NestedScroll涉及四个类:NestedScrollingChild, NestedScrollingChildHelper 和 NestedScrollingParent , NestedScrollingParentHelper NestedScroll提供了一个反向的机制,内层的view在接收到ACTION_MOVE的时候,将滚动消息先传回给外层的ViewGroup,看外层的ViewGroup是不是需要消耗一部分的移动,然后内层的View再去消耗剩下的移动.内层view可以消耗剩下的滚动的一部分,如果还没有消耗完,外层的view可以再选择把最后剩下的滚动消耗掉.
Parcelable是Android为我们提供的序列化的接口,Parcelable相对于Serializable的使用相对复杂一些,但Parcelable的效率相对Serializable也高很多,这一直是Google工程师引以为傲的,有时间的可以看一下Parcelable和Serializable的效率对比 Parcelable vs Serializable 号称快10倍的效率 实现Parcelable接口序列化 实现Parcelable接口主要实现是实现writeToParcel与describeContents接口
@Override
public void writeToParcel(Parcel dest, int flags) {
}
@Override
public int describeContents() {
return 0;
}
describeContents:对象描述。只针对一些特殊的需要描述信息的对象,需要返回1,其他情况返回0就可以
writeToParcel:序列化。通过writeToParcel方法实现序列化,writeToParcel返回了Parcel,所以我们可以直接调用Parcel中的write方法基本的数据类型除了boolean其他都有,Boolean可以使用int或byte存储
反序列化
饭序列化主要是实现两个方法:protected DemoParcelable(Parcel in)和public static final Creator
//反序列化
protected DemoParcelable(Parcel in) {
//普通对象读取
count = in.readInt();
name = in.readString();
//读取对象需要提供一个类加载器去读取,因为写入的时候写入了类的相关信息
user = in.readParcelable(User.class.getClassLoader());
//集合读取
in.createTypedArrayList(User.CREATOR);
}
//负责反序列化
public static final Creator<DemoParcelable> CREATOR = new Creator<DemoParcelable>() {
//从序列化对象中,获取原始的对象
@Override
public DemoParcelable createFromParcel(Parcel in) {
return new DemoParcelable(in);
}
//创建指定长度的原始对象数组
@Override
public DemoParcelable[] newArray(int size) {
return new DemoParcelable[size];
}
};
整体代码:
public class DemoParcelable implements Parcelable {
private Integer count;
private String name;
private User user;
private List<User> users;
//反序列化
protected DemoParcelable(Parcel in) {
//普通对象读取
count = in.readInt();
name = in.readString();
//读取对象需要提供一个类加载器去读取,因为写入的时候写入了类的相关信息
user = in.readParcelable(User.class.getClassLoader());
//集合读取
in.createTypedArrayList(User.CREATOR);
}
//负责反序列化
public static final Creator<DemoParcelable> CREATOR = new Creator<DemoParcelable>() {
//从序列化对象中,获取原始的对象
@Override
public DemoParcelable createFromParcel(Parcel in) {
return new DemoParcelable(in);
}
//创建指定长度的原始对象数组
@Override
public DemoParcelable[] newArray(int size) {
return new DemoParcelable[size];
}
};
/**
* 接口1.对象描述
* 只针对一些特殊的需要描述信息的对象,需要返回1,其他情况返回0就可以
*
* @return
*/
@Override
public int describeContents() {
return 0;
}
/**
* 接口2.序列化
* 通过writeToParcel方法实现序列化,writeToParcel返回了Parcel,
* 所以我们可以直接调用Parcel中的write方法
* 基本的数据类型除了boolean其他都有,Boolean可以使用int或byte存储
*
* @param parcel
* @param i
*/
@Override
public void writeToParcel(Parcel parcel, int i) {
//普通对象写入
parcel.writeInt(count);
parcel.writeString(name);
parcel.writeParcelable(user, 0);
parcel.writeTypedList(users);
}
public Integer getCount() {
return count;
}
public void setCount(Integer count) {
this.count = count;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public List<User> getUsers() {
return users;
}
public void setUsers(List<User> users) {
this.users = users;
}
public static class User implements Parcelable {
private String phone;
protected User(Parcel in) {
phone = in.readString();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
}
@Override
public int describeContents() {
return 0;
}
public static final Creator<User> CREATOR = new Creator<User>() {
@Override
public User createFromParcel(Parcel in) {
return new User(in);
}
@Override
public User[] newArray(int size) {
return new User[size];
}
};
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
}
}
android生命周期
流程
启动Activity:系统会先调用onCreate方法,然后调用onStart方法,最后调用onResume,Activity进入运行状态。
当前Activity被其他Activity覆盖其上或被锁屏:系统会调用onPause方法,暂停当前Activity的执行。
当前Activity由被覆盖状态回到前台或解锁屏:系统会调用onResume方法,再次进入运行状态。
当前Activity转到新的Activity界面或按Home键回到主屏,自身退居后台:系统会先调用onPause方法,然后调用onStop方法,进入停滞状态。
用户后退回到此Activity:系统会先调用onRestart方法,然后调用onStart方法,最后调用onResume方法,再次进入运行状态。
当前Activity处于被覆盖状态或者后台不可见状态,即第2步和第4步,系统内存不足,杀死当前Activity,而后用户退回当前Activity:再次调用onCreate方法、onStart方法、onResume方法,进入运行状态。
用户退出当前Activity:系统先调用onPause方法,然后调用onStop方法,最后调用onDestory方法,结束当前Activity。
onStart()与onResume()有什么区别?
onStart()是activity界面被显示出来的时候执行的,但不能与它交互;
onResume()是当该activity与用户能进行交互时被执行,用户可以获得activity的焦点,能够与用户交互。
Android类加载器
Android平台上虚拟机运行的是Dex字节码,一种对class文件优化的产物,传统Class文件是一个Java源码文件会生成一个.class文件,而Android是把所有Class文件进行合并,优化,然后生成一个最终的class.dex,目的是把不同class文件重复的东西只需保留一份。
Android 应用 (APK) 文件包含 Dalvik Executable (DEX) 文件形式的可执行字节码文件,其中包含用来运行您的应用的已编译代码。Dalvik Executable 规范将可在单个 DEX 文件内可引用的方法总数限制在 65,536,其中包括 Android 框架方法、库方法以及您自己代码中的方法。--64k限制
处理办法(分包)
multiDexEnabled true//分包
android:name="android.support.multidex.MultiDexApplication"
MultiDex.install(this);
线程
Thread直接创建线程缺点
线程池优点
线程池分类
1. newCachedThreadPool可缓存线程池
如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
2. newFixedThreadPool定长线程池
定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
3. newScheduledThreadPool周期性定长线程池
每3秒执行一次
延迟一秒执行,每三秒执行一次
4. newSingleThreadExecutor单线程线程池
线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
视图绘制(详细介绍)
view绘制流程
每一个视图的绘制过程都必须经历三个最主要的阶段,即onMeasure()、onLayout()和onDraw()。
一、onMeasure()
视图测量,系统会默认使用很多流程测量出视图的大小,最后会调用setMeasuredDimension()方法来设定测量出的大小。
一般这些对我们来说都不重要,要知道的是:
在setMeasuredDimension()方法调用之后,我们才能使用getMeasuredWidth()和getMeasuredHeight()来获取视图测量出的宽高,以此之前调用这两个方法得到的值都会是0。
二、onLayout()
视图布局过程。 首先会判断onMeasure测量的数据是否有变化,然后进行摆放布局,由于摆放复杂,通常由其子类实现自己的摆放过程。 重点: 在此方法结束之后可以调用getWidth()方法和getHeight()方法来获取视图的宽高。
getWidth()方法和getMeasureWidth()方法区别 结合上面的就很好理解这两个区别了。
三、onDraw()
layout过程是摆放view的过程,View不需要实现,通常由ViewGroup实现,在实现onLayout时可以通过getMeasuredWidth等方法获取measure过程测量的结果进行摆放。 draw过程先是绘制背景,其次调用onDraw()方法绘制view的内容,再然后调用dispatchDraw()调用子view的draw方法,最后绘制滚动条。ViewGroup默认不会执行onDraw方法,如果复写了onDraw(Canvas)方法,需要调用 setWillNotDraw(false);清楚不需要绘制的标记。
事件分发机制(详细介绍)
一般情况下,事件列是从用户按下那一刻产生的。
事件分发的本质
将点击事件(MotionEvent)传递到某个具体的View & 处理的整个过程。
事件传递的过程 = 分发过程。
基础事件
Touch事件的相关细节(发生触摸的位置、时间等)被封装成MotionEvent对象
三个非常重要的方法
事件分发机制的执行顺序
Activity=>ViewGroup=>View
界面刷新
requestLayout()
重新进行测量(onMeasure())、布局(onLyout())、绘制(onDraw())
invalidate()
需要在UI线程中实现,只调用绘制(onDraw())方法,重新绘制需要绘制的区域。
postInvalidate()
与invalidate差不多,此方法为异步实现。它会先判断视图是否被添加进窗口。条件满足时会利用Handerl发消息(MSG_INVALIDATE)通知主线程更新UI(调用invalidate();)
fragment懒加载
主要是利用fragment中的setUserVisibleHint(boolean isVisibleToUser)中的isVisibleToUser判断界面是否给用户显示,还有就是需要判断数据是否加载过,通过定义变量来判断。子类只需要实现数据加载的方法就行了。
我们一般都是使用viewPage+fragment实现的。viewPager会预加载下一页销毁其他页面。如果页面不多的话我们可以设置
viewPager.setOffscreenPageLimit(*);
加载所有页面。如果很多的话就预加载两页就够了。另外要说的是setUserVisibleHint()只有在ViewPager+Fragment的时候才有效,单用Fragment的时候可以考虑使用onHiddenChanged(boolean hidden)方法
recycleview(基本使用)
大概就是ListView与GridView的集合,LayoutManager控制recycleview的各种形状,如横向、纵向、网格之类的。
适配器 简单的Adapter示例:
有点像listView的BaseAdpter有点类似。
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等)。
apk打包
第一步:打包资源文件,生成R.java文件
【输入】Resource文件(就是工程中res中的文件)、Assets文件(相当于另外一种资源,这种资源Android系统并不像对res中的文件那样优化它)、AndroidManifest.xml文件(包名就是从这里读取的,因为生成R.java文件需要包名)、Android基础类库(Android.jar文件) 【输出】打包好的资源(一般在Android工程的bin目录可以看到一个叫resources.ap_的文件就是它了)、R.java文件(在gen目录中,大家应该很熟悉了) 【工具】aapt工具,它的路径在${ANDROID_SDK_HOME}/platform-tools/aapt(如果你使用的是Windows系统,按惯例路径应该这样写:%ANDROID_SDK_HOME%\platform-tools\aapt.exe,下同)。
第二步:处理AIDL文件,生成对应的.java文件(当然,有很多工程没有用到AIDL,那这个过程就可以省了)
【输入】源码文件、aidl文件、framework.aidl文件 【输出】对应的.java文件 【工具】aidl工具
第三步:编译Java文件,生成对应的.class文件
【输入】源码文件(包括R.java和AIDL生成的.java文件)、库文件(.jar文件) 【输出】.class文件 【工具】javac工具
第四步:把.class文件转化成Davik VM支持的.dex文件
【输入】 .class文件(包括Aidl生成.class文件,R生成的.class文件,源文件生成的.class文件),库文件(.jar文件) 【输出】.dex文件 【工具】javac工具
第五步:打包生成未签名的.apk文件
【输入】打包后的资源文件、打包后类文件(.dex文件)、libs文件(包括.so文件,当然很多工程都没有这样的文件,如果你不使用C/C++开发的话) 【输出】未签名的.apk文件 【工具】apkbuilder工具
第六步:对未签名.apk文件进行签名
【输入】未签名的.apk文件 【输出】签名的.apk文件 【工具】jarsigner
第七步:对签名后的.apk文件进行对齐处理(不进行对齐处理是不能发布到Google Market的)
【输入】签名后的.apk文件 【输出】对齐后的.apk文件 【工具】zipalign工具
apk安装流程
apk瘦身
APK主要由以下几部分组成:
其中占据较大内存的是res资源、lib、class.dex,因此我们可以从下面的几个方面下手:
代码方面可以通过代码混淆,这个一般都会去做。平时也可以删除一些没有使用类。
去除无用资源。使用lint工具来检测没有使用到的资源,或者在gradle中配置shrinkResources来删除包括库中所有的无用的资源,需要配合proguard压缩代码使用。这里需要注意项目中是否存在使用getIdentifier方式获取资源,这种方式类似反射lint及shrinkResources无法检测情况。如果存在这种方式,则需要配置一个keep.xml来记录使用反射获取的资源。压缩代码和资源
去除无用国际化支持。对于一些第三库来说(如support),因为国际化的问题,它们可能会支持了几十种语言,但我们的应用可能只需要支持几种语言,可以通过配置resConfigs提出不要的语言支持。
不同尺寸的图片支持。通常情况下只需要一套xxhpi的图片就可以支持大部分分辨率的要求了,因此,我们只需要保留一套图片。
图片压缩。 png压缩或者使用webP图片,完美支持需要Android版本4.2.1+
使用矢量图形。简单的图标可以使用矢量图片。
组件化
DataBinding(详细使用)
布局
使用 Data Binding 之后,xml 的布局文件就不再用于单纯地展示 UI 元素,还需要定义 UI 元素用到的变量。所以,它的根节点不再是一个 ViewGroup,而是变成了 layout,并且新增了一个节点 data
在activity的onCreate中用 DatabindingUtil.setContentView() 来替换掉 setContentView()。
DataBindingUtil.setContentView(this, R.layout.activity_main);
创建一个对象,通过binding.set...让对象与 variable 进行绑定
basebinding.setStr(new StrBean());
特殊用法
alias="..."
来取别名,避免冲突android:text="@{user.displayName ?? user.lastName}"
android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"
android:padding="@{large? (int)@dimen/largePadding : (int)@dimen/smallPadding}"
动态控件数据绑定(如ListView, RecyclerView等)
在构造 Holder 时把 View 与自动生成的 XXXBinding 进行绑定
@NonNull private List mUsers;
public UserAdapter() { mUsers = new ArrayList<>(10); for (int i = 0; i < USER_COUNT; i ++) { User user = new User(RandomNames.nextFirstName(), RandomNames.nextLastName()); mUsers.add(user); } }
public static class UserHolder extends RecyclerView.ViewHolder { private UserItemBinding mBinding;
}
@Override public UserHolder onCreateViewHolder(ViewGroup viewGroup, int i) { View itemView = LayoutInflater.from(viewGroup.getContext()) .inflate(R.layout.user_item, viewGroup, false); return new UserHolder(itemView); }
@Override public void onBindViewHolder(UserHolder holder, int position) { holder.bind(mUsers.get(position)); }
@Override public int getItemCount() { return mUsers.size(); } }
getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state)
可以通过outRect.set(l,t,r,b)设置指定itemview的paddingLeft,paddingTop, paddingRight, paddingBottom
可以通过一系列c.drawXXX()方法在绘制itemView之前绘制我们需要的内容。
在绘制itemView之后绘制,具体表现形式,就是绘制的内容在itemview上层。
getItemOffsets->onDraw->onDrawOver
启动前黑白屏
是由于程序启动时,window打开时还没加载到视图,页面会呈现window的背景色。
解决办法