糖葫芦


  • Startseite

  • Tags

  • Archiv

  • Suche

微信小程序速查笔记

Veröffentlicht am 2018-01-30 |

这个微信小程序也出来好长时间了,一直想做一个属于自己的小程序,这不说动手就动手实现一个吧,比较简易,中间也遇到不少问题,记录一下,方便以后自己查阅。

微信小程序的结构

image.png

默认没有这么些文件夹的,只有index ,然后这个标识 app 开头的文件都是一些全局的设置。可以自己查看下,都有注释,就从这个配置开始说。

修改启动页面

默认启动页面是index页面,如果要修改,打开app.json配置文件,在顶部你会发现有一个pages的配置,如下:

"pages": [

"pages/home/home"

只需要将我们的想要显示的页面的路径写在 第一个 就可以成为启动页,特别注意路径一定要正确。

当然你会新建很多页面,所有的页面都必须写在这个里面,不然跳转的时候会找不到。

底部tab栏切换

就是底部tab栏切换,同样还是在app.json 配置文件中修改如下:

  "tabBar": {
  "color": "#333333",
  "selectedColor": "#2B91D8",
  "backgroundColor": "#eee",
  "borderStyle": "white",
      "list": [
    {
  "pagePath": "pages/home/home",
  "text": "首页",
  "iconPath": "pages/home/images/tab_icon_home_nor.png",
  "selectedIconPath": "pages/home/images/tab_icon_home_pre.png"
    },
    {
  "pagePath": "pages/myCenter/myCenter",
  "text": "我的",
  "iconPath": "pages/home/images/tab_icon_center_nor.png",
  "selectedIconPath": "pages/home/images/tab_icon_center_pre.png"
    }
  ]
},

这里的 pagePath 就是页面的路径,text文字,iconPath图标,注意图片一定要带后缀,selectedIconPath点击态的图标,各自换成你自己的就行,问题不会太大。

加载中

如下:
image.png

有标签可以直接显示这个,在wxml文件中编写:

<loading hidden="{{hidden}}" bindchange="loadingChange">  
       加载中...  
</loading>  

可以控制显示还是隐藏,在.js文件中可以控制:

data: {
    hidden: false,
  }

可以在data 底下配置默认值,hidden: false 默认不显示,赋值的时候必须在这里面写:

that.setData({
    hidden: true
  })

网络请求

 wx.request({
//网络请求地址url
url: 'https://xx',
header: {
  // 'content-type': 'application/x-www-form-urlencoded'
  'content-type': 'application/json'
  // 'content-type': 'text/xml'
},
//请求头参数配置
data: {
  page: pageNo,
  timestamp: timeStamps

},
method: "POST",

success: function (res) {

  //xmlstring2json  是我使用第三方xml转json的一个库
  var xml2json = require('..//lib/xmlstring2json/dist/xml2json');
  var json = JSON.stringify(xml2json(res.data), null, 4);


  var jsonObject = xml2json(res.data);
  var jokeObject = jsonObject.root.joke
  //timestamp
  if(pageNo == 0){
    timeStamps = jsonObject.root.timestamp.text
  }


 // console.log('json::' + jsonObject.root.timestamp.text)
  // var data = new Array();
  // for (var i = 0; i < jokeObject.length;i++){
  //   var text = JSON.stringify(jokeObject[i].text).replace("#", "")
  //   //JSON.parse(text).text
  //   var value = JSON.parse(text).text;
  //   data.push(value)
  // }
  // console.log('data:::' + jokeObject[0].text.text );

  // var l =  jokeObject;
  var l;
  if(pageNo == 0){
    l = jokeObject

  }else{
    var l = that.data.list;
    for (var i = 0; i < jokeObject.length; i++) {
      l.push(jokeObject[i])
    } 
  }
    //页面绑定的数据赋值
  that.setData({
    list: l
  })

  pageNo++

  that.setData({
    hidden: true
  })
},
fail : function(s){
  wx.showModal({
    title: '提示',
    content: '请求接口失败' + JSON.stringify(s)
  })
}

要进行网络请求,首先测试阶段可以将开发工具里的:

image.png

最后一个选项勾选上,不校验…,同时管理员还需在后台配置网络请求的域名,两个缺一不可。

如何给TextView上的drawable上的图片添加动画?

Veröffentlicht am 2018-01-24 |

可能你遇到过这样的场景,就是在 xml 里,给TextView添加了一个drawableLeft或者drawableRight等,你想给这个图片添加一个动画,然而你会发现当你给这个TextView加动画吧并不是我们想要的,我们只想要那个图片做动画,文字不需要做任何动画,目前可以有两种做法。

第一种做法

也是比较通俗的方法,说这个drawable 单独弄成ImageView不就好了,在xml里新增一个ImageView 是吧,干嘛非要加在一个标签里面,要相信布局都是很强大的,肯定可以实现效果。

第二种做法

你说真的没办法,必须写在一个里面,也是有办法的,那就不能在xml里设置drawableLeft了,代码设置,如下:

1
2
3
AnimationDrawable d = (AnimationDrawable) getResources().getDrawable(R.drawable.animation_draw);  
d.setBounds(0,0,d.getIntrinsicWidth(),d.getIntrinsicHeight());
messageText.setCompoundDrawables(d, null, null, null);

这个具体的动画在这个animation_draw里,如下:

1
2
3
4
5
6
7
8
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="false"
>

<item android:drawable="@drawable/ic_launcher"
android:duration="500" />

</animation-list>

这是一个帧动画,里面可以放很多的item,从而形成动画,碰到了这种情况记录下,方便以后查阅。

Android O 新特性 AutoSizing

Veröffentlicht am 2018-01-16 |

自动调整TextView的大小的使用autoSizing

Android 8.0允许根据TextView的大小自动设置文本展开或收缩的大小,这意味着,在不同屏幕上优化文本大小或者优化包含动态内容的文本大小比以往简单多了。

在之前看 文字太多?控件太小?试试 TextView 的新特性 Autosizing 吧! 作者写到关于这个属性的所有用法,在这里我就不多说什么了,用法其实挺简单,关键是我在用的时候遇到了一些小问题,导致一直出不来。

官方文档 点击即可查看,可以看别人讲解的同时也可自行查阅文档,双方对比着看,感觉会好一点。

如下图可以简单明了的说明该特性:

具体最简单的使用有两种:

1.一种就是api >= api 26的可以直接在xml里面该属性:

1
2
3
4
5
<?xml version="1.0" encoding="utf-8"?>
<TextView
android:layout_width="match_parent"
android:layout_height="200dp"
android:autoSizeTextType="uniform" />

在此需要注意的是:使用这个autoSizeTextType的时候,控件的layout_width layout_height 不能使用这个wrap_content否则看不出什么效果,要使用具体可衡量的。这个也不难理解,因为它要计算,你设置一个模糊的宽和高,就算不出来了。

2. api<26 的低设备 的兼容写法

官方文档里也明确说明了兼容低版本The library provides support to Android 4.0 (API level 14) and higher. 也就是兼容到4.0以上,写法有些不同,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">

<TextView
android:layout_width="match_parent"
android:layout_height="200dp"
tools:ignore="MissingPrefix"
app:autoSizeTextType="uniform" />

</LinearLayout>

这个app是这个xmlns:app="http://schemas.android.com/apk/res-auto" 我们在自定义控件的时候经常遇到它,tools是这个xmlns:tools="http://schemas.android.com/tools" 当然以上写法都支持动态编码,具体可查阅文档

敲黑板重点

你会发现你写完之后编译是报错的,错误类似是这样的:

找不到!!!

在写那篇文章作者的帮助下,发现首先要兼容低版本,module 的 build.gradle 文件添加依赖时候 就是这个com.android.support:appcompat-v7:xx 后面的版本号要26以上,你看看你自己项目里有哪个版本你就写上哪个,还要在project 的 build.gradle 文件里添加:
maven{ url 'https://maven.google.com' }添加到对应的位置

1
2
3
4
5
6
7
8
9
allprojects {
repositories {
jcenter()
//添加如下内容
maven{
url 'https://maven.google.com'
}
}
}

至此我发现我的项目终于不报错了,可以运行起来并进行下一步的实践操作了,大家如果在项目中也遇到这样的问题,按我那样改应该没啥问题了,顺便把我写的代码上传到github 上了,又需要的可以下载看看:
demo

另外在查看 Android O新特性中发现一个比较好玩的东西:现在,findViewById() 函数的全部实例均返回 T,而不是 View。以后就可以这么写啦:

1
EditText et =  findViewById(R.id.et);

就是那样,无需强转了,当然了使用了Kotlin的话就忽略吧,因为Kotlin连findViewById也不用写,哈哈。更多好玩特性可查看 文档。

修改自动生成get/set方法模板代码

Veröffentlicht am 2018-01-11 |

今天看到
面对接口脏数据你还在V层if str==null else setText?
一文,觉着写得挺好,开发过程中多思考多动手,会带来意想不到的效果。底下评论大家也都说了各自的方法和见解,文中有一些问题作者后来也解决了,看到评论里有人说:
image.png

说到配置as,就可以实现在创建bean时自动生成return xx==null?"":xx; 觉着很好,因为我觉着这是一种挺简单的方法,但是就如同底下人问了怎么配置呀,我也不知道,于是我就在自己as工具里新建了一个bean对象,按住alt+insert添加get/set方法,发现了解决办法。如下图:
image.png

平时手快也没注意,今天仔细看了看,那上面的意思不就是,get 方法模块,set方法模板吗,后面跟着 IntelliJ Default大概就是默认的生成模板吧,点击后面的三个点
按钮进去看看,如下:
image.png

这是set的,get方法也类似是这样的,大概也能看懂,想着是不是可以直接模板就好了,发现无法修改,后来又发现左上角的+号,原来是自己可以新建一个模板,然后再生成的时候选择自己的模板就好了,如我们在get模板中新建一个自己的模板:

image.png

image.png

我们知道我们只需要改一下最后get方法return那个地方的代码,也就是这儿:
image.png

不确定是不是,我们可以在后面跟着一个“#”号试试:

1
2
3
${name}() {
return $field.name+"#";
}

在生成的时候选择我们自定义的模板:
image.png

发现最后生成的代码:

1
2
3
4
5
6
7
public String getAge() {
return age + "#";
}

public void setAge(String age) {
this.age = age;
}

这样证明确定是这样的,那么我么可以继续进行真正的判断了:
我们先把这个默认里的get方法代码拷贝到 MyGetter中,我们再根据我们自己的需要修改模板代码,其它地方不变,就修改需要修改的地方:
我们想要实现这样的:return xx==null?"":xx,Ok ,那么我认为大概是这么写的,首先判断是否是字符串:

1
2
3
4
5
6
7
8
${name}() {
#if(field.String){
return $field.name == null ? "" : $field.name;
}
#else
return $field.name;
#end
}

我们对比一个default的写法:

1
2
3
${name}() {
return $field.name;
}

好的,我们再继续生成一下:

1
2
3
4
5
6
7
public String getAge() {
return age == null ? "" : age;
}

public void setAge(String age) {
this.age = age;
}

好的,达成效果,耶,居然还可以这么玩,以后可根据自己需求自行修改,谢谢大家。

Activity启动模式

Veröffentlicht am 2018-01-09 |

● standard 标准模式
​ 简述:对于使用standard模式的活动,系统不会在乎这个活动是否已经在返回栈中存在,每次启动都会创建该活动的一个新的实例。
例如:A启动A,A再接着启动A,A再接着启动A,,然后再分别出栈,如图所示:
​
image.png

● singleTop
简述:当活动的启动模式指定为 singleTop ,在启动活动时如果发现返回栈的栈顶已经
是该活动,则认为可以直接使用它,不会再创建新的活动实例。
例如:如果快速点击一个按钮,进入Activity,要保证启动的Activity不为多个,可以使用
将Activity的启动模式改为 singleTop,就不会启动多个了。如图所示:
​
image.png

● singleTask
简述:当活动的启动模式指定为singleTask,每次启动该活动时候,它会先在系统中查找属性值taskaffinity等于它的属性值taskAffinity的任务存在;如果存在这样的任务,他就会这个任务中启动,否则就会在新任务中启动。因此,如果我们想要设置了“singleTask”启动模式的Activity在新的任务中启动,就要为它设置一个独立的taskAffinity属性值。

如果设置了“singleTask”启动模式的Activity不是在新的任务中启动时,它会在已有的任务中查看是否已经存在在相应的Activity实例,如果存在,就会把位于这个Activity实例上面的Activity全部结束掉,即最终这个Activity实例会位于任务的堆栈顶端中。

每次启动该活动时系统首先会在返回栈中检查是否存在该活动的实例,如果发现已经存在则直接使用该实例,并把在这个活动之上的所有活动统统出栈,如果没有发现就会创建一个新的活动实例。

image.png

当栈底部的 A 重新被启动打开时,会执行onNewIntent() onStart() 方法。

使用场景:应用主页面一般是 singleTask

● singleInstance(单独任务栈)
简述:使用singleInstance 模式就可以解决这个问题,在这种模式下会有一个单独的返回栈来管理这个活动,不管是哪个应用程序来访问这个活动,都会共用同一个返回栈,也就解决了共享活动实例的问题。
​
image.png

返回的页面顺序是:C-B-A

AIDL实例

Veröffentlicht am 2018-01-08 |

说到进程间通信,一般首先就会想到AIDL,也看了很多文章,做下笔记,记录一下,方便以后查阅。

对于 AIDL 我是这样理解的,首先进程间是无法通信的,那要通信就得有一个媒介或者说两个进程有统一对外的接口可以相互识别,从这个 AIDL全程的名字Android Interface Definition Language (android 接口定义语言) 来看不难看出它就是我们进程间通信的媒介,它可以实现我们想要的通信。

在翻阅网上各类文章的讲解后,我认为一个比较好的实例可以更好理解这个东西,具体如下:

第一步

我们新创建两个module,代表我们两个进程,进程名称默认就是我们程序的包名:
image.png

第二步

我们先编写service这个module里的代码,先定义一个 Person的一个bean对象,记住一定要implements Parcelable 接口,大致如下:
image.png

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
public class Person implements Parcelable {
private String mName;

public Person(String name) {
mName = name;
}

protected Person(Parcel in) {
mName = in.readString();
}

public static final Creator<Person> CREATOR = new Creator<Person>() {
@Override
public Person createFromParcel(Parcel in) {
return new Person(in);
}

@Override
public Person[] newArray(int size) {
return new Person[size];
}
};

@Override
public int describeContents() {
return 0;
}

@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(mName);
}

@Override
public String toString() {
return "\nPerson{" +
"mName='" + mName + '\'' +
'}';
}

}

然后我们再新建一个文件夹:aidl,新建一个包名和Person类一模一样的包名和与之对应的aidl,还有一个我们对外提供获取person集合的一个aidl:
image.png
在Person.aidl里,我们序列化我们java里的 Person 类:

1
2
3
4
5
package com.example.service.bean;

//还要和声明的实体类在一个包里,同时注意不要新建aidl文件,因为你会发现,新建不了,
//提示你名称唯一,此时你新建一个file,名字为Person.aidl就可以,需要特别注意下
parcelable Person;

IMyAidl.aidl 文件提供对外方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// IMyAidl.aidl
package com.example.service.bean;
//特别注意一定要手动导包,不会自动导包
import com.example.service.bean.Person;

// Declare any non-default types here with import statements
//可以理解为通信媒介
interface IMyAidl {

/**
* 除了基本数据类型,其他类型的参数都需要标上方向类型:in(输入), out(输出), inout(输入输出)
*/
void addPerson(in Person person);

List<Person> getPersonList();
}

然后在我们的java文件里,新建一个MyAidlService,

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
public class MyAidlService extends Service {

private final String TAG = this.getClass().getSimpleName();

private ArrayList<Person> mPersons;

/**
* 创建生成本地的binder ,实现AIDL的方法
*/
private IBinder mIBinder = new IMyAidl.Stub(){

@Override
public void addPerson(Person person) throws RemoteException {
mPersons.add(person);
}

@Override
public List<Person> getPersonList() throws RemoteException {
return mPersons;
}
};

/**
* 客户端与服务端绑定时的回调,返回IBinder对象后客户端就可以通过它远程调用服务端的方法,即实现了通讯

* @param intent
* @return
*/
@Nullable
@Override
public IBinder onBind(Intent intent) {
mPersons = new ArrayList<>();
return mIBinder;
}

@Override
public void onCreate() {
super.onCreate();
}

最后别忘了在清单文件中注册MyAidlService

1
2
3
4
<service
android:name="com.example.service.MyAidlService"
android:enabled="true"
android:exported="true" />

到此,我们完成了service端的工作:

第三步

将我们刚才创建好的aidl文件夹拷贝到 app module下main文件夹下,在MainActivity中绑定服务,

1
2
3
Intent intent = new Intent();
intent.setComponent(new ComponentName("com.example.service","com.example.service.MyAidlService"));
bindService(intent,mConnection,BIND_AUTO_CREATE);

其中这个mConnection可以让我们拿到IMyAidl对象的代理

1
2
3
4
5
6
7
8
9
10
11
12
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {

mAidl = IMyAidl.Stub.asInterface(iBinder);
}

@Override
public void onServiceDisconnected(ComponentName componentName) {
mAidl = null;
}
};

通过返回的IBinder 拿到这个IMyAidl,此时我们就可以通信了,例如我们调用IMyAidl中的addPerson方法,再调用getPersonList看看:

1
2
3
4
5
6
7
8
9
10
11
Random random = new Random();
Person person = new Person("qian"+random.nextInt(20));

try{
mAidl.addPerson(person);
List<Person> mPersons = mAidl.getPersonList();

tvName.setText(mPersons.toString());
}catch (Exception e){
e.printStackTrace();
}

打印发现list 返回了我们添加的Person,完成了app与service两个进程间的通信。
demo地址:https://github.com/QQabby/AIDLDemo

写文件相关

Veröffentlicht am 2017-12-28 |

向sd卡根目录中写入文件

1
2
3
4
5
6
7
8
9
10
11
12
//首先判断是否存在sd卡
if(Environment.getExternalStorageState().equals(
Environment.MEDIA_MOUNTED)){

//获取外部设备
File file=new File(Environment.getExternalStorageDirectory(),"qianqian.txt");
FileOutputStream outStream=new FileOutputStream(file);
//写入文件
outStream.write(content.getBytes());
outStream.close();

}

读取assets下的文件路径

1
2
3
//格式如下
file:///android_asset/myVideo.html
file:///android_asset/xx(文件名称)

读取assets下的文件转化为String

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public String getFromAssets(String fileName) {
try {
InputStreamReader inputReader = new InputStreamReader(
getResources().getAssets().open(fileName));
BufferedReader bufReader = new BufferedReader(inputReader);
String line = "";
String Result = "";
while ((line = bufReader.readLine()) != null)
Result += line;
return Result;
} catch (Exception e) {
e.printStackTrace();
}
return "";
}

LRU算法还一知半解?

Veröffentlicht am 2017-12-19 |

某年某月某日,糖葫芦同学在掘金app上看了几篇文章,偶然看到了一篇熟悉的词LRU算法,脑海里就想这不是经常说的嘛,就那么回事,当天晚上睡觉,LRU算法是啥来着,好像是什么最近最少使用的,白天在地铁上看的文章也不少,但是到晚上想想好像啥也没记住,就记得LRU算法,我发现人大多数是这样的啊,对于自己熟悉的部分呢还能记着点,不熟悉或者不会的可能真的是看过就忘啊~既然这样还不如先把熟悉的弄明白。

第二天来到公司,我觉着还是有必要看一下这个LRU的源码,到底是怎么回事,嗯,糖葫芦同学刷刷得看,下面我们将进入正题,请戴眼镜的同学把眼镜擦一擦,哈哈哈

First

先看源码,再用具体的demo加以验证,我们先看一下这个LruCache这个类的大致结构和方法,如下图所示:
image.png

这又是 get(K),put(K,V), remove(K) 的方法的 给人的感觉就像是一个Map的集合嘛,又有Key ,又有value 的,再看下具体的代码:

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
public class LruCache<K, V> {
private final LinkedHashMap<K, V> map;

/** Size of this cache in units. Not necessarily the number of elements. */
private int size;
private int maxSize;

private int putCount;
private int createCount;
private int evictionCount;
private int hitCount;
private int missCount;

/**
* @param maxSize for caches that do not override {@link #sizeOf}, this is
* the maximum number of entries in the cache. For all other caches,
* this is the maximum sum of the sizes of the entries in this cache.
*/
public LruCache(int maxSize) {
if (maxSize <= 0) {
throw new IllegalArgumentException("maxSize <= 0");
}
this.maxSize = maxSize;
this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
}

看到开头,我们就明白了,哦原来这个LruCache类中维护一个LinkedHashMap的一个集合,缓存我们这个对象,而且构造方法里需要我们传入一个maxSize的一个值,根据上面的注释我们就明白了这个就是我们LruCache缓存对象的最大数目。

有什么用呢?

根据惯性思维,我们可以认为,在put新的缓存对象的时候,根据我们设定的最大值remove集合里的某些缓存对象,进而添加新的缓存对象。

Second

根据我们的分析,我们有必要去看一下这个put方法的源码:

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
/**
* Caches {@code value} for {@code key}. The value is moved to the head of
* the queue.
*
* @return the previous value mapped by {@code key}.
*/
public final V put(K key, V value) {
if (key == null || value == null) {
throw new NullPointerException("key == null || value == null");
}

V previous;
synchronized (this) {
putCount++;
size += safeSizeOf(key, value);
previous = map.put(key, value);
if (previous != null) {
size -= safeSizeOf(key, previous);
}
}

if (previous != null) {
entryRemoved(false, key, previous, value);
}

trimToSize(maxSize);
return previous;
}

代码量也不是特别多,我们看下这个,在这个synchronized同步代码块里,我们看到这个 size,是对put进来缓存对象个数的累加,然后调用集合的map.put方法,返回一个对象 previous ,就是判断这个集合中是否添加了这个缓存对象,如果不为null,就对size减回去。

最后又调用一个 trimToSize(maxSize)方法,上面都是对添加一些逻辑的处理,那么不可能无限制添加啊,肯定有移除操作,那么我们推测这个逻辑可能在这个trimToSize(maxSize) 里处理。

源码如下:

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
/**
* Remove the eldest entries until the total of remaining entries is at or
* below the requested size.
*
* @param maxSize the maximum size of the cache before returning. May be -1
* to evict even 0-sized elements.
*/
public void trimToSize(int maxSize) {
while (true) {
K key;
V value;
synchronized (this) {
if (size < 0 || (map.isEmpty() && size != 0)) {
throw new IllegalStateException(getClass().getName()
+ ".sizeOf() is reporting inconsistent results!");
}

//只要当前size<= maxSize 就结束循环
if (size <= maxSize || map.isEmpty()) {
break;
}
// 获取这个对象,然后从map中移除掉,保证size<=maxSize
Map.Entry<K, V> toEvict = map.entrySet().iterator().next();
key = toEvict.getKey();
value = toEvict.getValue();
map.remove(key);
size -= safeSizeOf(key, value);
evictionCount++;
}

entryRemoved(true, key, value, null);
}
}

注释:Remove the eldest entries until the total of remaining entries is at or below the requested size 大概意思是说:清除时间最久的对象直到剩余缓存对象的大小小于设置的大小。没错是我们想找的。

这里说明一下:maxSize就是我们在构造方法里传入的,自己设置的

1
2
3
4
5
6
7
public LruCache(int maxSize) {
if (maxSize <= 0) {
throw new IllegalArgumentException("maxSize <= 0");
}
this.maxSize = maxSize;
this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
}

这样LruCache的核心方法 trimToSize方法我们就说完了,接下来我将通过实例再次验证下:

设置场景

假设我们设置maxSize 为2,布局里显示3个imageView,分别代表3张我们要显示的图片,我们添加3张图片,看看会不会显示3张?

xml布局显示如下(代码就不贴了,很简单):
image.png

activity代码如下:

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 final int MAX_SIZE = 2;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_lru);

ImageView iv1 = (ImageView) findViewById(R.id.iv1);
ImageView iv2 = (ImageView) findViewById(R.id.iv2);
ImageView iv3 = (ImageView) findViewById(R.id.iv3);

Bitmap bitmap1 = BitmapFactory.decodeResource(getResources(),R.drawable.bg);
Bitmap bitmap2 = BitmapFactory.decodeResource(getResources(),R.drawable.header_img);
Bitmap bitmap3 = BitmapFactory.decodeResource(getResources(),R.drawable.ic_launcher);

LruCache<String,Bitmap> lruCache = new LruCache<>(MAX_SIZE);
lruCache.put("1",bitmap1);
lruCache.put("2",bitmap2);
lruCache.put("3",bitmap3);

Bitmap bitmap = lruCache.get("1");
iv1.setImageBitmap(bitmap);

Bitmap b2 = lruCache.get("2");
iv2.setImageBitmap(b2);

Bitmap b3 = lruCache.get("3");
iv3.setImageBitmap(b3);
}

图:
bg.pngheader_img.pngic_launcher.png

我们可以先尝试分析一下:因为我们设置的MaxSize 是2 ,那么在put第三个Bitmap的时候,在trimToSize方法中,发现这个size是3 ,maxSize 是2,会继续向下执行,不会break,结合下面代码看下

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 void trimToSize(int maxSize) {
while (true) {
K key;
V value;
synchronized (this) {
if (size < 0 || (map.isEmpty() && size != 0)) {
throw new IllegalStateException(getClass().getName()
+ ".sizeOf() is reporting inconsistent results!");
}
//第一次循环:此时 size 是3,maxSize 是 2
//第二次循环,此时 size 是 2 ,maxSize 是 2 ,满足条件,break,结束循环
if (size <= maxSize || map.isEmpty()) {
break;
}
//获取最先添加的第一个元素
Map.Entry<K, V> toEvict = map.entrySet().iterator().next();
key = toEvict.getKey();
value = toEvict.getValue();
//移除掉第一个缓存对象
map.remove(key);
// size = 2,减去移除的元素
size -= safeSizeOf(key, value);
evictionCount++;
}

entryRemoved(true, key, value, null);
}
}

这个 safeSizeOf 是调用sizeOf方法。

那么也就是说,我们在put第三个bitmap的时候,LruCache 会自动帮我们移除掉第一个缓存对象,因为第一个最先添加进去,时间也最长,当然后添加的bitmap就是新的,最近的,那么我们推断这个iv1是显示不出图片的,因为被移除掉了,其它剩余两个可以显示,分析就到这里,看下运行结果是不是跟我们分析的一样:

result.png

哇!真的跟我们想的一样耶,证明我们想的是对的。这里我们思考一下就是为什么LruCache使用了这个LinkedHashMap,为什么LinkedHashMap的创造方法跟我们平时创建的不太一样,源码是这样的:

1
2
3
4
5
6
7
public LruCache(int maxSize) {
if (maxSize <= 0) {
throw new IllegalArgumentException("maxSize <= 0");
}
this.maxSize = maxSize;
this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
}

这里说一下在掘金发布的评论里 藏地情人评论是:new LinkedHashMap<K, V>(0, 0.75f, true)这句代码表示,初始容量为零,0.75是加载因子,表示容量达到最大容量的75%的时候会把内存增加一半。最后这个参数至关重要。表示访问元素的排序方式,true表示按照访问顺序排序,false表示按照插入的顺序排序。这个设置为true的时候,如果对一个元素进行了操作(put、get),就会把那个元素放到集合的最后。

确实也是这样的,我们看下LinkedHashMap的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* Constructs an empty <tt>LinkedHashMap</tt> instance with the
* specified initial capacity, load factor and ordering mode.
*
* @param initialCapacity the initial capacity
* @param loadFactor the load factor
* @param accessOrder the ordering mode - <tt>true</tt> for
* access-order, <tt>false</tt> for insertion-order
* @throws IllegalArgumentException if the initial capacity is negative
* or the load factor is nonpositive
*/
public LinkedHashMap(int initialCapacity,
float loadFactor,
boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}

里面这个assessOrder 注释里也说的很明白:the ordering mode - <tt>true</tt> for access-order, <tt>false</tt> for insertion-order -> true 呢就表示会排序,false 就代表按照插入的顺序。
默认不传就是 false ,而且我们每次 get(K) put(K,V) 的时候 会根据这个变量调整元素在集合里的位置。而这么做的目的也只有一个:保留最近使用的缓存对象,举个例子说明一下:

我们向这个集合里添加了三种元素

LruCache<String, Bitmap> lruCache = new LruCache<>(MAX_SIZE);(MAX_SIZE=2)
lruCache.put("1", bitmap1);
lruCache.put("2", bitmap2);
lruCache.put("3", bitmap3);

此时它们在集合里的顺序是这样的:
order.png

那比如说我们在put 3 元素之前,使用了1元素,就是调用了get("1")方法,我们知道LinkedHashMap就会改变链表里元素的存储顺序,代码是这样的:

1
2
3
4
lruCache.put("1", bitmap1);
lruCache.put("2", bitmap2);
lruCache.get("1");
lruCache.put("3", bitmap3);

那么此时对应链表里的顺序就是:

image.png

当我们再调用显示的时候,循环遍历就会优先把第一个位置的key = "2" 的缓存对象移除掉,保证了最近使用的原则,当然了因为把这个max_size = 2所以在我们执行lruCache.put("3", bitmap3); 时,集合最终会变成这样:
result.png

集合里只剩下 1 ,3对应的缓存对象。

至此,LruCache就说完了,如果看完的你有不明白的地方可以留言,一起讨论下~

Hello World

Veröffentlicht am 2017-12-11 |

Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.

Quick Start

Create a new post

1
$ hexo new "My New Post"

More info: Writing

Run server

1
$ hexo server

More info: Server

Generate static files

1
$ hexo generate

More info: Generating

Deploy to remote sites

1
$ hexo deploy

More info: Deployment

1…78

QQabby

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