糖葫芦


  • Startseite

  • Tags

  • Archiv

  • Suche

性能优化

Veröffentlicht am 2018-11-21 |
  • 布局优化

    • 减少布局层次,单一可以用LinearLayout 解决的页面不用 RelativeLayout,如果布局复杂,则用RelativeLayout 更好,毕竟比起布局嵌套,这种会好些

    • <include> 标签使用,主要用于布局重用

      需要注意的是:此标签内,如果外面指定了 id 里面跟布局也定义id 以外部定义为准。同时如果里面定义了 类似于 android:layout_width="wrap_content" 的属性,需配套 android:layout_height="wrap_content" 同时使用。

    • <merge> 标签一般和 <include> 标签配合使用。一般如果当前根布局例如是:LinearLayout ,include 的布局也是 LinearLayout ,那么 include 的根布局就可以使用 <merge> 标签代替LinearLayout.

    • <ViewStub> 标签,意义为按需加载,轻量级,宽和高都是0,在实际开发中,有很多布局在正常情况下不会显示,比如网络异常的布局,这个时候没有必要在布局加载时就加载进来,通过<ViewStub> 就可以在需要用到的时候再加载,提高程序初始化的性能。

  • 绘制优化

    在 onDraw() 中不要创建新的局部对象,这是因为 onDraw() 可能会频繁的调用,这样就会在一瞬间产生大量的临时对象,这不仅占用过多的内存而且会导致系统频繁的gc, 降低程序的执行效率; 另外也不能做一些耗时的任务。

  • 内存泄漏优化

    • 静态变量导致的内存泄漏

      例如:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      private static Context mContext;

      @Override
      protected void onCreate(@Nullable Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.layout_combine);

      mContext = this;
      }
    • 单例模式导致的内存泄漏

    • 属性动画导致的内存泄漏

      Android 3.0 之后,提供了属性动画,如果开启无限循环动画,隐藏后,未调用 animator,cancel() 来停止动画,也会发生内存泄漏,因为这个时候 Activity 的 View 被动画所持有,而 View 又持有 Activity, 最终Activity 无法释放。0-

  • 响应速度优化和ANR日志

    响应速度优化的核心是避免在主线程做耗时操作, Activity 5s 未做出响应, Service 10s未做出响应,就会出现 ANR ,查看 \data\anr 目录下的 trace,txt 文件分析产生 ANR 的原因

  • 线程优化

    线程优化一般都是采用线程池,避免程序中存在大量的 Thread, 线程池可重用内部的线程,从而避免线程的创建和销毁所带来的性能开销。

高效使用RxJava

Veröffentlicht am 2018-11-20 |

Question

场景一:在页面保存时,需要对页面所填写的一些列信息进行判断,确定正确无误后方可保存。

例如:页面上中需要填写:姓名,性别,职业,年薪,住址等信息。

按照常规做法,我们有可能会这么写:

1
2
3
4
5
6
7
8
9
10
if(Util.checkempty(mUserName)){//判断用户名是否为空
return;
}
if(Util.checkempty(mSex)){//判断性别是否为空
return;
}
.....
if(Util.checkempty(mAddress)){//判断住址是否为空
return;
}

而用 Rxjava.combineLatest

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
//构建被观察者
Observable userNameObservable = Observable.create(new Observable.OnSubscribe<String>() {
@Override
public void call(final Subscriber<? super String> subscriber) {
tv.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {

}

@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {

subscriber.onNext(s.toString());
}

@Override
public void afterTextChanged(Editable s) {

}
});
}
});

Observable sexObservable = Observable.create(new Observable.OnSubscribe<String>() {
@Override
public void call(final Subscriber<? super String> subscriber) {
sex.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {

}

@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {

subscriber.onNext(s.toString());
}

@Override
public void afterTextChanged(Editable s) {

}
});
}
});

Observable.combineLatest(userNameObservable, sexObservable, new Func2<String,String,Boolean>() {

@Override
public Boolean call(String userName, String sex) {
//判断用户名
boolean isUserInvalid = !TextUtils.isEmpty(userName);
//判断性别
boolean isSexInvalid = !TextUtils.isEmpty(sex);
return isUserInvalid && isSexInvalid;
}
}).subscribe(new Action1() {
@Override
public void call(Object o) {//只有都满足条件后才会执行到这里
Log.i("xx","信息:"+o);//可以保存信息了
}
});

场景二:搜索输入,避免多次调用搜索接口,搜索优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
Observable.create(new Observable.OnSubscribe<String>() {
@Override
public void call(final Subscriber<? super String> subscriber) {
tv.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {

}

@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
subscriber.onNext(s.toString());
}

@Override
public void afterTextChanged(Editable s) {

}
});
}
}).filter(new Func1<String, Boolean>() {
@Override
public Boolean call(String s) {

return !TextUtils.isEmpty(s);//过滤为空的
}
}).debounce(1,TimeUnit.SECONDS)
.subscribe(new Subscriber<String>() {
@Override
public void onCompleted() {

}

@Override
public void onError(Throwable e) {

}

@Override
public void onNext(String s) {
Log.i("xx","搜索字符串::"+s);
}
});

RxJava

Veröffentlicht am 2018-11-19 |

1. 变换

  • map()

    它是一对一的

1
2
3
4
5
6
7
8
9
10
11
12
Observable.just("image/logo.png")//输入类型String,图片地址
.map(new Func1<String, Bitmap>() {
@Override
public Bitmap call(String filePath) {
return getBitmap(filePath);//转换为bitmap
}
}).subscribe(new Action1<Bitmap>() {
@Override
public void call(Bitmap bitmap) {
showBitmap(bitmap);//显示bitmap
}
});
  • flatMap()

    多对多转换

假如有这样一个场景,我们需要打印每个学生所修的课程,学生是一个集合,课程也是一个集合,如果按照map() 的做法如下:

这里我选用我项目里随意一个bean 对象,意思不变

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
UserInfo[] userInfos = new UserInfo[3];
Observable.from(userInfos)
.map(new Func1<UserInfo, List<AppInfo>>() {
@Override
public List<AppInfo> call(UserInfo userInfo) {
return userInfo.getAppInfos();
}
}).subscribe(new Action1<List<AppInfo>>() {

@Override
public void call(List<AppInfo> appInfos) {
for(AppInfo info : appInfos){
Log.i("xx",info.getAppName());
}
}
});

有种情况就是假如我不想要循环遍历呢,而是直接拿到 AppInfo 对象,显然 map() 是无法实现的,map() 我们前面也说过是 一对一的转换,而现在是一对多,那此时就应该用 flatMap().

使用flatMap() 的话,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Observable.from(userInfos)
.flatMap(new Func1<UserInfo, Observable<AppInfo>>() {
@Override
public Observable<AppInfo> call(UserInfo userInfo) {
return Observable.from(userInfo.getAppInfos());
}
}).subscribe(new Subscriber<AppInfo>() {
@Override
public void onCompleted() {

}

@Override
public void onError(Throwable e) {

}

@Override
public void onNext(AppInfo appInfo) {
Log.i("xx","appInfo.name::"+appInfo.getAppName());
}
});

上述是用 flatMap() 编写的,去除了 for 循环,flatMap() 和 map() 有一个相同点:它们都是把传入的参数转化之后返回另一个对象。但需要注意的是,和map() 不同的是,flatMap() 中返回的是 Observable 对象。

flatMap() 原理如下:

  • 使用传入的事件对象创建一个 Observable 对象;
  • 并不发送这个 Observable 对象,而是将它激活,于是它开始发送事件;
  • 每一个创建出来的 Observable 发送的事件,都被汇入同一个 Observable,而这个 Observale 负责将这些事件统一交给 Subscriber 的回调方法。

时间复杂度

Veröffentlicht am 2018-11-19 |

衡量代码的好坏,包括两个非常重要的指标:

  • 运行时间
  • 占用空间

由于运行环境和输入规模的影响,代码的绝对执行时间是无法估计的,但是我们却可以预估代码的基本执行次数。我可以通俗的理解为:通过执行次数可以大概推算这个时间复杂度。

示例:

  • 一个有序排列好的数组

    1
    int[] arry = {-5,-1,0,5,9,11,13,15,22,35,46},输入一个x,int x = 31

    在数组中找出和为 x 的两个数,例如: 9 +22 = 31。要求时间复杂度为O(n).

好,当我们看到这个问题的时候,我们认为这很容易嘛(先不考虑时间复杂度),于是写了如下代码:

第一种写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

int[] array = {-5,-1,0,5,9,11,13,15,22,35,46};
int sum = 31;

public void getIndex(int[] array,int sum){
//sum 在数据中找出和为sum的两个数
int x = 0;
int y = 0;
for(int i=0;i<array.length;i++){
for(int j=i+1;j<array.length;j++){
//判断是否相加=sum
if(array[i] + array[j] == sum){
x = i;
y = j;
break;
}
}
}
System.out.println("第一个数的为:"+array[x]);
System.out.println("第二个数的为:"+array[y]);
}

根据我们上面对时间复杂度的了解,我们知道改算法的时间复杂度是 O(n^2), 是最低的一种,可以说,那么我们如何把时间复杂度设置为O(n) ,线性复杂度,其实就是需要一遍for 循环;

第二种算法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int[] array = {-5,-1,0,5,9,11,13,15,22,35,46};
int sum = 31;

public void getSimpleIndex(int[] array,int target){
int x = 0;
int y = 0;
HashMap<Integer, Integer> hm = new HashMap<Integer, Integer>();
for (int i = 0; i < array.length; i++) {//第一次遍历数组,将元素和下标以K-V方式存入hm中
hm.put(array[i], i);//key 为数组里面的值,value是下标
}
for(int i=0;i<array.length;i++) {//第二次遍历数组,查找是否有和为target的元素对
if (hm.containsKey(target - array[i]) && (i != hm.get(target - array[i]))) {
//保证两个元素不是同一个,否则如果target恰好是某个元素的2倍时就不符合题意
x = i;
y = hm.get(target - array[i]);
break;
}
}
System.out.print(array[x]+" "+array[y]);
}

这是一种典型以空间换时间的解法。

总结

常见的时间复杂度有如下:

  • T(n) = O(n)
  • T(n) = O(logn)
  • T(n) = O(1)
  • T(n) = O(n²)

这四种时间复杂度谁用时更长,更节省时间呢?

O(1) < O(logn) < O(n) < O(n²)

java生产者消费者模式

Veröffentlicht am 2018-11-19 |

Producter.java 生产者:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/**
* 生产者
*/
public class Producter extends Thread {

private Queue<Integer> queue;
String name;
int maxSize;
int i = 0;

public Producter(String name,Queue<Integer> queue,int maxSize){
super(name);
this.name = name;
this.queue = queue;
this.maxSize = maxSize;
}

@Override
public void run() {
while (true){
synchronized (queue){
while(queue.size() == maxSize){
try{
System.out.println("Queue is full,Productr["+name+" ]thread waiting for");
//等待
queue.wait();
}catch (Exception e){
e.printStackTrace();
}
}
System.out.println("["+name+"] producting value:+"+i );
queue.offer(i++);
queue.notifyAll();

try{
Thread.sleep(new Random().nextInt(1000));
}catch (Exception e){
e.printStackTrace();
}
}
}
}
}

Consumer.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class Consumer extends Thread {

private Queue<Integer> queue;
String name;
int maxSize;

public Consumer(String name,Queue<Integer> queue,int maxSize){
super(name);
this.name = name;
this.queue = queue;
this.maxSize = maxSize;
}

@Override
public void run() {
while (true){
synchronized (queue){
while (queue.isEmpty()){
try{
System.out.println("Queue is empty,Consumer ["+name+"] thread is waiting for Producer");
//如果为空,就等待
queue.wait();
}catch (Exception e){
e.printStackTrace();
}
}

int x = queue.poll();
System.out.println("["+name+" Consumer value : "+x);
queue.notifyAll();

try{
Thread.sleep(new Random().nextInt(1000));
}catch (Exception e){
e.printStackTrace();
}
}
}
}
}

main.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static void main(String args[]){

Queue<Integer> queue = new LinkedList<>();

Thread product1 = new Producter("p-1",queue,5);
Thread product2 = new Producter("p-2",queue,5);

Thread consumer1 = new Consumer("C1",queue,5);
Thread consumer2 = new Consumer("C2",queue,5);
Thread consumer3 = new Consumer("C3",queue,5);

product1.start();
product2.start();

consumer1.start();
consumer2.start();
consumer3.start();
}

Android知识点三

Veröffentlicht am 2018-11-15 |

1. RecycleView 如何设置分割线的?

它是读取系统自带的属性 R.attr.listDivider 属性,来设置分割线的,支持横向和纵向。

2. RecycleView 分割线的样式如何修改?

  • 第一种办法:因为 R.attr.listDivider 定义在系统样式中,那么我们可以重写这个属性修改,如下:

    在styles.xml 中添加该属性:

    1
    2
    3
    4
    5
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
    <!-- Customize your theme here. -->
    ....
    <item name="android:listDivider">@drawable/drawable_bg</item>
    </style>

    其中 drawable_bg 为自定义分割线的drawable.

  • 第二种办法:可以通过自带的 setDrawable() 改变,如下:

    1
    2
    3
    4
    DividerItemDecoration itemDecoration = new DividerItemDecoration(this,DividerItemDecoration.VERTICAL);
    Drawable drawable = getResources().getDrawable(R.drawable.drawable_bg);
    itemDecoration.setDrawable(drawable);
    recyclerView.addItemDecoration(itemDecoration);

    3. RecycleView 缓存

    缓存涉及三个类,分别是:

    • Recycler

    • RecycledViewPool

    • ViewCachedExtension

  • Recycler : 用于管理已经废弃或者与 RecyclerView 分离的 ViewHolder

    • mChangedScrap : 与 RecyclerView 分离的 ViewHolder 列表
    • mAttachedScrap : 未与 RecyclerView 分离的 ViewHolder 列表
    • mCachedViews : ViewHolder 缓存列表
  • RecycledViewPool : ViewHolder 缓存池

  • ViewCachedExtension : 开发者可以控制的 ViewHolder 缓存的帮助类

获取缓存里的 ViewHolder 的关键方法是: tryGetViewHolderForPositionByDeadline 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
@Nullable
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs) {
if (position < 0 || position >= mState.getItemCount()) {
throw new IndexOutOfBoundsException("Invalid item position " + position
+ "(" + position + "). Item count:" + mState.getItemCount()
+ exceptionLabel());
}
boolean fromScrapOrHiddenOrCache = false;
ViewHolder holder = null;
// 0) If there is a changed scrap, try to find from there
// 0) 如果需要改变废弃的view,从这里查找
if (mState.isPreLayout()) {
//这里是 mChangedScrap 集合中取出
holder = getChangedScrapViewForPosition(position);
fromScrapOrHiddenOrCache = holder != null;
}
// 1) Find by position from scrap/hidden list/cache
// 1) 这是从 mAttachedScrap 集合中查找
if (holder == null) {
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
if (holder != null) {
if (!validateViewHolderForOffsetPosition(holder)) {
// recycle holder (and unscrap if relevant) since it can't be used
if (!dryRun) {
// we would like to recycle this but need to make sure it is not used by
// animation logic etc.
holder.addFlags(ViewHolder.FLAG_INVALID);
if (holder.isScrap()) {
removeDetachedView(holder.itemView, false);
holder.unScrap();
} else if (holder.wasReturnedFromScrap()) {
holder.clearReturnedFromScrapFlag();
}
recycleViewHolderInternal(holder);
}
holder = null;
} else {
fromScrapOrHiddenOrCache = true;
}
}
}
if (holder == null) {
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
if (offsetPosition < 0 || offsetPosition >= mAdapter.getItemCount()) {
throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item "
+ "position " + position + "(offset:" + offsetPosition + ")."
+ "state:" + mState.getItemCount() + exceptionLabel());
}

final int type = mAdapter.getItemViewType(offsetPosition);
// 2) Find from scrap/cache via stable ids, if exists
// 2)从 mCachedViews 集合中查找缓存
if (mAdapter.hasStableIds()) {
holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
type, dryRun);
if (holder != null) {
// update position
holder.mPosition = offsetPosition;
fromScrapOrHiddenOrCache = true;
}
}
if (holder == null && mViewCacheExtension != null) {
// We are NOT sending the offsetPosition because LayoutManager does not
// know it.
// 3 从mViewCacheExtension 获取
final View view = mViewCacheExtension
.getViewForPositionAndType(this, position, type);
if (view != null) {
holder = getChildViewHolder(view);
if (holder == null) {
throw new IllegalArgumentException("getViewForPositionAndType returned"
+ " a view which does not have a ViewHolder"
+ exceptionLabel());
} else if (holder.shouldIgnore()) {
throw new IllegalArgumentException("getViewForPositionAndType returned"
+ " a view that is ignored. You must call stopIgnoring before"
+ " returning this view." + exceptionLabel());
}
}
}
if (holder == null) { // fallback to pool
if (DEBUG) {
Log.d(TAG, "tryGetViewHolderForPositionByDeadline("
+ position + ") fetching from shared pool");
}
// 4)从缓存池中获取 recycledViewPool
holder = getRecycledViewPool().getRecycledView(type);
if (holder != null) {
holder.resetInternal();
if (FORCE_INVALIDATE_DISPLAY_LIST) {
invalidateDisplayListInt(holder);
}
}
}
if (holder == null) {
long start = getNanoTime();
if (deadlineNs != FOREVER_NS
&& !mRecyclerPool.willCreateInTime(type, start, deadlineNs)) {
// abort - we have a deadline we can't meet
return null;
}
//如果都没有 就重新创建 createViewHolder
holder = mAdapter.createViewHolder(RecyclerView.this, type);
.....
}
}
}

总结缓存的机制如下:

  • 1.从 mChangedScrap 或 mAttachScrap 中获取
  • 2.从 mCacheViews
  • 3.从 mRecycledViewPool 缓存池中获取

4. RecycleView 刷新闪屏

一般刷新:

1
adapter.notifyItemChanged(0); 参数:position

实际上会全部刷新,局部刷新应使用:

1
adapter.notifyItemChanged(0,"q"); 参数: position,payload

并且在 adapter 中重写方法: `onBindViewHolder(RecyclerView.ViewHolder holder, int position, List payloads)方法`

payloads 的 size 始终为1.

1
2
3
4
5
6
7
8
9
@Override
public void onBindViewHolder(ViewHolder holder, int position, List<Object> payloads) {
if(payloads.isEmpty()){
onBindViewHolder(holder,position);
}else{
String type = payloads.get(0);//获取为 q
holder.name.setText(list.get(position));
}
}

5. RecycleView 和 listView

//https://zhuanlan.zhihu.com/p/23339185

  • 缓存层级不同

    recycleView 比 listView 多两级缓存,支持开发者自定义缓存处理,支持多个recycleView共用同一个 recycleViewPool(缓存池)

listView 缓存(两级缓存):

是否需要回调createView 是否需要回调bindVIew 生命周期 备注
mActiveViews 否 否 onLayout函数周期内 用于屏幕内itemView快速重用
mScrapViews 否 是 与mAdapter一致,当Adapter被更换时,mScrapViews即被清空

RecycrleView 缓存( 四级缓存):

是否回调createView 回调bindVIew 生命周期 备注
mAttachedScrap 否 否 onLayout 用于屏幕内itemView快速重用
mCacheViews 否 否 与mAdapter一致,当Adapter被更换时,mCacheVIews被缓存到mRecyclerPool 默认缓存大小是2
mViewCacheExtension 用户自定义实现
mRecyclerPool 否 是 与自身生命周期一致,不再引用时被释放 默认大小是5

ListView 和 RecyclerView 缓存机制:

  • mActiveViews 和 mAttachedScrap 功能相似,意义在于快速重用屏幕上可见的列表项 itemView,而不需要重新createView 和 bindView
  • mScrapView 和 mCachedViews + mRecyclerViewPool 功能相似,意义在于缓存离开屏幕的itemView,目的是让即将进入屏幕的 itemView 重用
  • RecycleView 的优势在于 mCacheVIews 的使用,可以做到屏幕外的列表项itemView 进入屏幕内时也无须bindView 快速重用; mRecycleViewPool 可以供多个recycleView 共同使用,在特定场景下,如viewpager 加多个列表页有优势。

6. RecycleView 的回收复用机制的内部实现都是哪个类完成的?

RecycleView 的回收复用机制都是由内部类 Recycler 类,核心方法:tryGetViewHolderForPositionByDeadline() 方法中完成的。

随笔源码记录:

复用机制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public View getViewForPosition(int position) {
return getViewForPosition(position, false);
}

View getViewForPosition(int position, boolean dryRun) {
return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
}
@Nullable
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs) {
//复用机制的原理
- 1.mCacheView
- 2.recyclerViewPool
- 3.扩展
- 4.都没有,重新创建一个ViewHolder(createViewHolder)
...
}

回收机制

也是 Recycler 类中的,核心方法:recycleViewHolderInternal(holder)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void recycleView(View view) {
// This public recycle method tries to make view recycle-able since layout manager
// intended to recycle this view (e.g. even if it is in scrap or change cache)
ViewHolder holder = getChildViewHolderInt(view);
if (holder.isTmpDetached()) {
removeDetachedView(view, false);
}
if (holder.isScrap()) {
holder.unScrap();
} else if (holder.wasReturnedFromScrap()) {
holder.clearReturnedFromScrapFlag();
}
//回收的内部实现
recycleViewHolderInternal(holder);
}

回收的逻辑:由 LayoutManager 来遍历移出屏幕的卡位,然后对每个卡位进行回收操作,回收时,都是把 viewHolder 放在 mCachedViews 里面,如果mCachedViews 满了,那就在mCachedViews 里拿一个ViewHolder 扔到 ViewPool 缓存里,然后mCachedViews 就可以空出位置来放新回收的ViewHolder 了。

mCachedViews 里存放的 ViewHolder 只有原本位置的卡位才能复用

参考链接:https://blog.csdn.net/xJ032w2j4cCjhOW8s8/article/details/79946425

===================================================================================

  • updateViewCacheSize()
1
2
3
4
5
void updateViewCacheSize() {
int extraCache = mLayout != null ? mLayout.mPrefetchMaxCountObserved : 0;
mViewCacheMax = mRequestedCacheMax + extraCache;
....
}

在第一次向上滑动

7. RecycleView 性能优化

7.1 数据处理和视图加载适度分离

什么意思呢?就是我们异步从服务端拉取数据,拿到数据之后就把数据丢给 ViewHolder 进行处理,当然有些数据需要单独处理,那么其实这些处理逻辑也应该异步执行,这样的话就可以让 adapter 在 notifyDatasetChanged 之后,ViewHolder 就可以简单处理视图与数据的绑定工作。

7.2 数据刷新

如果是个别数据发生改变,调用局部刷新的方法

7.3 布局优化

减少 GPU 过度绘制;

7.4 杂七杂八

  1. 升级 RecycleView 版本到 25.1.0 以上使用 Prefetch 功能,开启这个功能之后对页面复杂度的敏感性降低,在高复杂度情况下,Prefetch 可以显著提高页面流畅度。Android 5.0 以后引入 Render Thread 提升动画流畅性。
  2. 如果item 高度固定的话,可以使用 RecyclerView.setHasFixSize(true) 来避免 requestLayout 浪费资源;
  3. 共用 RecycleViewPool ;
  4. 通过 getExtraLayoutSpace 来增加 RecyclerView 预留的额外空间,默认是一页数据的数量

8. 自定义属性获取完属性后为什么调用TypeArray.recycle()?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* Recycles the TypedArray, to be re-used by a later caller. After calling
* this function you must not ever touch the typed array again.
*
* @throws RuntimeException if the TypedArray has already been recycled.
*/
//大概意思是说回收这个TypedArray, 将会被复用当再次调用的时候,当你调用这个方法之后你就不能再操作not touch the typed
public void recycle() {
if (mRecycled) {
//如果已经被回收,就会抛出异常
throw new RuntimeException(toString() + " recycled twice!");
}

mRecycled = true;

// These may have been set by the client.
mXml = null;
mTheme = null;
mAssets = null;
//看下面
mResources.mTypedArrayPool.release(this);
}
1
2
3
4
public class Resources{
// Pool of TypedArrays targeted to this Resources object.
final SynchronizedPool<TypedArray> mTypedArrayPool = new SynchronizedPool<>(5);
}

看上面的那个大概意思就是从一个同步池子里获取到的,默认大小是5. 平常我们获取的属性是通过:

1
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.xx);

context 的一个静态方法,点进去看看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//最终到了这里
public class TypedArray {

static TypedArray obtain(Resources res, int len) {
//这里 就是从上述一个SynchronizedPool 中获取的
final TypedArray attrs = res.mTypedArrayPool.acquire();
if (attrs != null) {
attrs.mLength = len;
attrs.mRecycled = false;

// Reset the assets, which may have changed due to configuration changes
// or further resource loading.
attrs.mAssets = res.getAssets();

final int fullLen = len * AssetManager.STYLE_NUM_ENTRIES;
if (attrs.mData.length >= fullLen) {
return attrs;
}

attrs.mData = new int[fullLen];
attrs.mIndices = new int[1 + len];
return attrs;
}

return new TypedArray(res,
new int[len*AssetManager.STYLE_NUM_ENTRIES],
new int[1+len], len);
}

SynchronizedPool

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
/**
* Simple (non-synchronized) pool of objects.
*
* @param <T> The pooled type.
*/
public static class SimplePool<T> implements Pool<T> {
private final Object[] mPool;

private int mPoolSize;

/**
* Creates a new instance.
*
* @param maxPoolSize The max pool size.
*
* @throws IllegalArgumentException If the max pool size is less than zero.
*/
public SimplePool(int maxPoolSize) {
if (maxPoolSize <= 0) {
throw new IllegalArgumentException("The max pool size must be > 0");
}
mPool = new Object[maxPoolSize];
}

@Override
@SuppressWarnings("unchecked")
//从pool 中获取
public T acquire() {
if (mPoolSize > 0) {
final int lastPooledIndex = mPoolSize - 1;
T instance = (T) mPool[lastPooledIndex];
mPool[lastPooledIndex] = null;
mPoolSize--;
return instance;
}
return null;
}

@Override
public boolean release(T instance) {
if (isInPool(instance)) {
throw new IllegalStateException("Already in the pool!");
}
if (mPoolSize < mPool.length) {
mPool[mPoolSize] = instance;
mPoolSize++;
return true;
}
return false;
}

private boolean isInPool(T instance) {
for (int i = 0; i < mPoolSize; i++) {
if (mPool[i] == instance) {
return true;
}
}
return false;
}
}

维护一个同步栈结构的对象池,避免重复频繁创建属性值 TypeArray 对象,使用时记得调用 Recycle() 方法将不用的对象返回至对象池来达到重用的目的,如果不调用 recycle() 方法将会随着 Activity的每一次创建而创建,因此系统频繁创建 array, 对内存和性能是一个不小的开销,如果不使用池,每次都让 GC 来回收,很可能就会造成 OOM.

9. 自定义属性

1
2
3
4
5
6
7
8
9
10
11
12
//自定义属性一般我们会在values-attr.xml 中定义,如下:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!--第一种方式,在declare-styleable标签中-->
<declare-styleable name="customerView">
<attr name="colors" format="color" />
</declare-styleable>

<!--第二种方式,单独定义属性-->
<attr name="AloneAttr" format="color" />

</resources>

两种方式有什么区别呢?

第一种方式会把有关这个控件的属性包裹起来,达到一个分组的概念,不零散,属性的使用范围更加明确,

第一种方式获取属性值:

1
TypedArray ta = context.obtainStyledAttributes(attrs,R.styleable.customerView);

第二种方式获取属性值:

1
2
int[] array = {R.attr.AloneAttr};
TypedArray ta = context.obtainStyledAttributes(attrs,array);

Android知识点总结二

Veröffentlicht am 2018-11-14 |

1. Canvas.save() 跟 Canvas.restore()的调用时机

save: 用来保存 Canvas 的状态。save 之后,可以调用Canvas 的平移,放缩,旋转等操作。

restore: 用来恢复canvas 之前保存的状态。防止save 后对Canvas 执行的操作对后续的绘制有影响。

2. Android P(9.0) 的新特性

  • 支持WiFi室内定位
  • 适配刘海屏
  • 通知栏改进:可以显示对话,附加照片和表情等
  • 多摄像头api
  • 对于非 http 连接,将直接拦截,推荐使用https

Android 8.0

PHONE 权限组新增加两个权限

  • ANSWER_PHONE_CALLS:允许您的应用通过编程方式接听呼入电话。要在您的应用中处理呼入电话,您可以使用 assceptRingingCall() 函数
  • READ_PHONE_NUMBERS: 权限允许您的应用读取设备中存储的电话号码
  • 通知适配

    为了更好的管理通知的提醒,不想一些不重要的通知打扰用户,新增加用户渠道,用户可根据渠道屏蔽一些不重要的通知

  • 安装apk

    在 AndroidManifest 文件中添加未知来源应用的权限:

    <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/> 这样系统会自动询问用户完成授权。

    也可以通过 canRequestPackageInstalls 查询是否有此权限,如果没有的话就使用 Settings.ACTION_MANAGE_UNKNOW_APP_SOURCE 引导用户安装未知应用权限去授权。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    if(Build,VERSION>SDK_INT >= 26){
    Boolean isInstall = getPackageManager().canRequestPackageInstalls();//26以上
    if(isInstall){
    //有此权限,安装应用
    }else{
    //跳转到 安装未知应用 权限界面,引导用户开启权限
    Uri selfPackageUri = Uri.parse("package:" + this.getPackageName());
    Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES, selfPackageUri);
    startActivityForResult(intent, REQUEST_CODE_UNKNOWN_APP)
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == REQUEST_CODE_UNKNOWN_APP) {
    //如果权限开启,则开始安装应用
    }
    }
  • 静态广播无法正常接受

Android N(7.0) 的新特性

  • 私有文件外部将不能访问,通过FileProvider 可解决
  • SharedPreferences 进程间通信模式闪退,SecurityException

Android 6.0

  • 运行时权限的申请

  • https 的推荐

3. APK 内容

  • assets 存放需要打包到 APK 中的静态文件
  • lib 目录 native库
  • res 目录 存放应用程序的资源
  • META-INF 存放应用程序签名和证书的目录
  • AndroidManifest.xml
  • classes.dex
  • resources.arsc 资源配置文件

4. Binder 机制的优点

Linux 中使用的 IPC 通信机制有:pipe(管道),signal(信号量),Socket

  • socket:是一个通用接口,传输效率低,开销比较大;
  • 管道和消息队列:采用存储转发方式,所以至少需要拷贝2次数据,效率低,共享内存虽然在传输时没有拷贝数据,但其控制机制复杂,而 Binder 更好传输性能。
  • 安全性更高,可以建立私有通道,有身份标识(UID/PID)
  • 另外一个优点就是,通过binder 可以很方便的调用server 端的方法,犹如调用本地方法一样。

5. Gradle 的生命周期

  • 1.初始化

    初始化主要是读取 settings.gragle 文件,用于确定哪些项目参与构建,并创建Project 实例

  • 2.配置

    配置阶段主要是为每个 build.gradle 文件配置 project 对象

  • 3.执行

    主要根据 gradle 命令和传入参数创建并执行任务

随笔记录Android

Veröffentlicht am 2018-11-13 |

1. Android中数据存储的方式

  • FIle
  • SharedPreference
  • Sqlite
  • 网络
  • ContentProvider
  • FileProvider : 继承于 ContentProvider 的子类,可以用于解决Android7.0中禁止我们的应用对外部公开file://的问题。

2. SharedPreference 是进程同步的吗

sharedPreference 默认不是线程同步的,可以设置模式为:MODE_MULTI_PROCESS 做到进程同步,系统默认也是有缓存的,有很多问题,在Android N(7.0)以上废弃不能使用了,会抛出异常。推荐使用 ContentProvider

sharedPreference 四种模式:

  • MODE_PRIVATE
  • MODE_MULTI_PROCESS
  • MODE_WORLD_READABLE
  • MODE_WORLD_WRITEABLE

3.Shareferences commit 和 apply 的区别

在很早的sdk当中,一般都使用 commit 方法,同步,直接写入磁盘,并且有返回结果 boolean 类型,而 apply 是异步, 先写入内存,然后异步写入磁盘。 如果操作频繁的话,apply 的性能优于 commit.

在阿里巴巴开发手册中也记录到:

SharedPreference 提 交 数 据 时 , 尽 量 使 用 Editor#apply()
,而非Editor#commit()。一般来讲,仅当需要确定提交结果,并据此有后续操作时,才使用 Editor#commit()。

4. View 的 measureSpec 由谁决定的

  • View的MeasureSpec 由这个父控件的MeasureSpec 和自身的 LayoutParams 决定
  • 顶级 DecorView 由窗口尺寸和自身的 LayoutParams 共同确定

5.ACTION_CANCEL 事件

一般来说,如果一个子视图接受了父视图分发给它的 ACTION_DOWN 事件,那么与ACTION_DOWN 事件相关的事件都要分发给这个子视图,但是如果父视图希望拦截其中的一些事件,不再继续转发事件给这个子视图的话,那么就需要给子视图一个ACTION_CANCEL 事件。

6. View的invalidate postInvalidate requestLayout区别

  • invalidate 会调用 onDraw 进行重绘,只能在主线程
  • postIncalidate 可以在其它线程,如子线程
  • requestLayout 会调用 onLayout 和 onMeasure ,不一定会调用 onDraw

7. View 的生命周期

    1. Creation 创建(从xml中加载或者layout 文件中定义加载)

      1. onFinishInflate() 从xml中加载完成
    2. Layout 布局

      1. onMeasure()
      2. onLayout()
    3. Drawing 绘制

      1. onDraw()
    4. Event processing 事件处理

    5. Focus 聚焦

      1. onFocusChanged()
      2. onWindowFocusChanged()
    6. Attaching 附上

      1. onAttachingToWindow()
      2. onDetachedFromWindow()
      3. onVisibiltyChanged()
      4. onWindowVisibiltyChanged()

java基础二

Veröffentlicht am 2018-11-13 |

1. HashMap

1
2
3
4
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {
.....
}

未完待续….仍需要查阅大量资料学习….

2.Java 内存分配

  • 栈区:方法区内的局部变量,对象的引用等 优点:速度快,缺点:大小和生命周期必须是确定的。
  • 堆区:new出来的对象,垃圾回收器回收的是堆区的内存
  • 方法区(静态区):静态变量,常量,保存类信息

3.垃圾

什么是垃圾?要通过科学的方法才能判断是否是为垃圾,有两种方法

  • 引用计数法:可能会导致内部循环
  • 可达性分析:是否可达

借助一个图快速理解 引用计数法:

image.png

代码:

1
2
3
4
5
6
7
8
9
10
11
12
/**循环问题*/
public class Demo{
public Demo instance;
public static void main(String[] args) {
Demo a=new Demo();
Demo b=new Demo();
a.instance=b;
b.instance=a;
a=null;
b=null;
}
}

再看下这个 可达性分析 :

image.png

ObjD 和 ObjE 虽然内部存在引用,但是定点不可达,所以也是垃圾回收的对象。

4.垃圾回收的方法

  • 标记-清除:减少停顿时间,但会造成内存碎片

  • 标记-整理:可以解决内存碎片,但是会增加停顿时间

  • 复制清除:从一个地方拷贝到另一个地方,适合有大量回收的场景,比如:新生代回收

    • 优点:效率高于标记清除,活着的对象是整齐排列的,没有内存碎片
    • 缺点: 浪费空间,毕竟如果按照1:1比例划分空间的话,那么将会有50%的空间被浪费。不过在jvm中,新生代空间并不是按照1:1来划分的,而是按照8:1:1的比例分为一个eden 区 和两个survivor(survivor0,survivor1) 区,然后一个eden 区,两个survivor 区。大部分对象在Eden 区中生成。回收时先将eden 区存活对象复制到一个survicor0 区,然后清空eden 区,当这个survivor0区也存放满了时,则将eden区和survivor0区存活对象复制到另一个survivor1 区,然后清空eden 区和survivor0 区,此时survivor0区是空的,然后将survivor0区和survivor1区交换,即保持survivor1区为空,这样的过程也叫做Minor GC,每进行Minor GC 一次,存活着的对象的年龄就会+1,当存活着的对象的年龄到达15岁时,就会被送进年老代。 当然,当整个survivor1区不足以存放Eden和survivor0的存活对象时,也会将存活对象直接放到年老代。若是年老代也满了就会触发一次Full GC,也就是新生代,老年代都进行回收。
  • 分代收集:把内存区域分成不同代,根据年代不同采取不同的策略;

    • 新生代:存放新创建的对象,采用复制回收方法
    • 年老代:这些对象垃圾回收的频率较低,采取标记整理法
    • 永久代:存放Java本身的一些数据,当类不再使用时,也会被回收

    手稿:

image.png

5. Full GC 触发的条件

  • 调用 System.gc 时,系统建议执行Full GC 但是不必然执行
  • 年老代或者永久代空间不足

6. 线程调度

  • wait() : object 方法,必须在同步代码块中使用,使当前线程处于等待状态,释放锁
  • notify() : 和 wait 联合使用,通知一个线程,具体通知哪个由jvm 决定,使用不当可能发生死锁
  • notifyAll() : 和wait 方法联合使用,通知所有线程,具体哪个线程获得运行权 jvm 决定
  • sleep() : 睡眠状态

7.线程同步

  • Synchronzied 修饰
  • ThreadLocal : 每个线程都有一个局部变量的副本,互不干扰。一种以空间换时间的方式。
  • 线程安全的容器和方法,可以实现线程同步,如: Collections.SynchronizedList() 将 List 转为线程同步;用ConurrentHashMap 实现hashmap 线程同步。

volatile 修饰的变量不会缓存在寄存器中,每次使用都会从主存器中读取;保证可见性,不保证原子性

Java基础知识

Veröffentlicht am 2018-11-12 |

1. 如何实现对象克隆?

有两种方式:

  • 实现Cloneable 接口并重写 clone 方法(浅拷贝);

  • 实现Serializable ,通过对象的序列化和反序列化实现真正的深度克隆(深拷贝),代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    public class MyUtil {

        private MyUtil() {
            throw new AssertionError();
        }

        public static <T> T clone(T obj) throws Exception {

            ByteArrayOutputStream bout = new ByteArrayOutputStream();

            ObjectOutputStream oos = new ObjectOutputStream(bout);

            oos.writeObject(obj);


            ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());

            ObjectInputStream ois = new ObjectInputStream(bin);

            return (T) ois.readObject();


            // 说明:调用ByteArrayInputStream或ByteArrayOutputStream对象的close方法没有任何意义

            // 这两个基于内存的流只要垃圾回收器清理对象就能够释放资源,这一点不同于对外部资源(如文件流)的释放

        }
    }

    注意:基于序列化和反序列化实现的克隆不仅仅是深度克隆,更重要的是通过泛型界定,可以检查出要克隆对象是否支持序列化,这项检查是编译器完成的,不是运行时抛出异常,这种方案明显优于使用object类的clone方法克隆对象。让问题在编译的时候暴漏出来总是优于把问题留在运行时。

2. 内部类访问局部变量的时候,为什么加final?

  • 内部类和局部变量的生命周期不同,方法结束后局部变量的生命周期就结束了,而内部类只要有引用就不结束,内部类生命周期>=局部变量

  • Java会在编译时在内部类的构造方法里边,将局部变量以参数形式传递给内部类

  • 而如果局部变量发生改变,内部类不知情的场景,所以要加fianl ,保证引用不可改变

    注意:在Java8中,可以不适用final关键字,但是如果我们修改了局部变量还是会发生错误,从而保证局部变量的引用不变。

3. transient 关键字

如果使用transient 修饰变量,当对象存储时,它的值不需要维持。换句话来说就是用transient 修饰的变量不参与序列化的过程。

4. String 和 StringBuild,StringBuffer的区别

String 是只读字符串,创建完成之后是不能修改,如果修改也是创建一个新对象,回收旧对象的过程,所以执行效率比较低,StringBuild 和 StringBuffer 可以改变,StringBuild 线程不安全,但是效率比较高,StringBuffter 线程安全

效率快慢:Stringbuilder> StringBuffer> String

5. String 为什么设计成不可变

  • 安全性
    • 本身是final 类不可修改,不可变极为安全
    • String 常被用来作为 HashMap 的key 如果可变会引来安全问题,例如两个key 相同
  • 效率高
    • 通过字符串池可以节省很多空间
    • 每一个String 对应一个 hashcode ,再次使用不用重新计算

6. java 中的四种引用及应用场景

  • 强引用
  • 弱引用—-引用到达,不可达就会被回收,即便是内存充足,可用于图片Bitmap 缓存,当不再使用Bitmap 时,就会被回收
  • 软引用—–内存不足时被回收,也可用于Bitmap回收,当内存不足时,可回收
  • 虚引用—-它指向的对象回收时,它本身会被加入到引用队列中,这样我们就知道它指向的对象何时被销毁
1…456…8

QQabby

79 Artikel
63 Tags
© 2020 QQabby
Erstellt mit Hexo
|
Theme — NexT.Pisces v5.1.3