Handler是什么:

Handler是Android提供给我们的用来更新UI的一套机制,也是一套消息处理机制,我们可以通过它发送消息,也可以通过它处理消息!

Handler的用法:

img

post(Runable):

img

handler.postDelayed(Runnable,long)

这是一种可以创建多线程消息的函数

使用方法:

1,首先创建一个Handler对象

Handler handler=new Handler();

2,然后创建一个Runnable对象

Runnable runnable=new Runnable(){

@Override

public void run() {

// TODO Auto-generated method stub

//要做的事情,这里再次调用此Runnable对象,以实现每两秒实现一次的定时器操作

handler.postDelayed(this, 2000);

}

};

3,使用PostDelayed方法,两秒后调用此Runnable对象

handler.postDelayed(runnable, 2000);

实际上也就实现了一个2s的一个定时器

4,如果想要关闭此定时器,可以这样操作

handler.removeCallbacks(runnable);

当然,你也可以做一个闹钟提醒延时的函数试试,比如,先用MediaPlayer播放闹钟声音,

如果不想起,被停止播放之后,下次就5分钟后再播放,再被停止的话,下次就4分钟后播放,

只要更改延时的时间就可以实现了,用一个static对象的话会比较容易操作。

是可以异步效果,但Runnable的执行是在Handler对象所在的线程

如果其所在的线程是UI线程的话,Runnable中还是不能执行耗时操作,不然会ANR

前几天我们自己的设备很卡,卡到跳转界面都需要不到1秒的时间,我就把跳转的动作放在Runnable里边,外边加上弹出进度提示框

注:举例说明

img

传递Message:

sendMessage(RunnAble);

sendMessagePostDeayed(RunnAble,lone);

img

img

Handler.Callback()拦截消息回调:

img

Android为什么要设计只能通过Handlerde 方式跟新UI:

img

Handler的原理:

img

HandlerThread:

HandlerThread能够新建拥有Looper的线程。这个Looper能够用来新建其他的Handler。(线程中的Looper)需要注意的是,新建的时候需要被回调。

HandlerThread的特点:

HandlerThread将loop转到子线程中处理,说白了就是将分担MainLooper的工作量,降低了主线程的压力,使主界面更流畅。

开启一个线程起到多个线程的作用。处理任务是串行执行,按消息发送顺序进行处理。HandlerThread本质是一个线程,在线程内部,代码是串行处理的。

但是由于每一个任务都将以队列的方式逐个被执行到,一旦队列中有某个任务执行时间过长,那么就会导致后续的任务都会被延迟处理。

HandlerThread拥有自己的消息队列,它不会干扰或阻塞UI线程。

对于网络IO操作,HandlerThread并不适合,因为它只有一个线程,还得排队一个一个等着。

Handler的用法:

public class MainActivityextends Activity {

private HandlerThreadmyHandlerThread ;

private Handlerhandler ;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

​ setContentView(R.layout.activity_main);

​ //创建一个线程,线程名字:handler-thread

​ myHandlerThread =new HandlerThread(“handler-thread”);

​ //开启一个线程

​ myHandlerThread.start();

​ //在这个线程中创建一个handler对象

​ handler =new Handler(myHandlerThread.getLooper() ){

@Override

​ public void handleMessage(Message msg) {

super.handleMessage(msg);

​ //这个方法是运行在 handler-thread 线程中的 ,可以执行耗时操作

​ Log.d(“handler “ , “消息: “ + msg.what +” 线程: “ + Thread.currentThread().getName() );

​ }

};

​ //在主线程给handler发送消息

​ handler.sendEmptyMessage(1 );

​ new Thread(new Runnable() {

@Override

​ public void run() {

//在子线程给handler发送数据

​ handler.sendEmptyMessage(2 );

​ }

}).start();

}

@Override

protected void onDestroy() {

super.onDestroy();

​ //释放资源

​ myHandlerThread.quit();

}

}

handler引起的内存泄漏以及解决办法

原因:静态内部类持有外部类的匿名引用,导致外部activity无法释放

解决办法:handler内部持有外部activity得到弱引用,并把handler改为静态内部类,mHandler.removeCallback();

#####动态修改指定文字颜色

1
2
3
4
5
String text = "获得银宝箱!";  
SpannableStringBuilder style=new SpannableStringBuilder(text);
style.setSpan(new BackgroundColorSpan(Color.RED),2,5,Spannable.SPAN_EXCLUSIVE_INCLUSIVE); //设置指定位置textview的背景颜色
style.setSpan(new ForegroundColorSpan(Color.RED),0,2,Spannable.SPAN_EXCLUSIVE_INCLUSIVE); //设置指定位置文字的颜色
textView.setText(style);

#####动态修改指定文字颜色 并添加下划线

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
SpannableString clickString = new SpannableString(exChangeValue);//exChangeValue TextViwe的内容
clickString.setSpan(new ClickableSpan() {
@Override
public void onClick(View widget) {
Intent intent = new Intent(ExchangeActivity.this, WebActivity.class);
intent.putExtra(WebActivity.WEB_URL_PARA, flow.getAgreement().getUrl());
startActivity(intent);
}

@Override
public void updateDrawState(TextPaint ds) {
super.updateDrawState(ds);
ds.setColor(Color.parseColor("#FF7012"));//设置颜色
}
}, i, i1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);//i变色的开始位置 i1变色的结束位置
tv_exchange_rule_value.append(clickString);
tv_exchange_rule_value.setMovementMethod(LinkMovementMethod.getInstance());//开始响应点击事件

######如果只是单纯的字符串比较把SignBean类对象换成String对象道理都是一样的;

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
//定义数据源
List<SignBean> list = new ArrayList<>();

SignBean signBean = new SignBean();
signBean.setName("abc");
signBean.setValue("1");
list.add(signBean);

SignBean signBean1 = new SignBean();
signBean1.setName("abb");
signBean1.setValue("2");
list.add(signBean1);

SignBean signBean2 = new SignBean();
signBean2.setName("cb");
signBean2.setValue("3");
list.add(signBean2);
//排序
Collections.sort(list, new Comparator<SignBean>() {
@Override
public int compare(SignBean o1, SignBean o2) {
//拿到2个bean类中的name字符串进行比较,android中字符串比较是比较的ASCLL码
//compareTo() 字符串比较
int i = o1.getName().compareTo(o2.getName());
if (i > 0) {
return 1;
} else {
return -1;
}
}
});
for (int i = 0; i <list.size() ; i++) {
Log.e("tyl","name="+list.get(i).getName());
Log.e("tyl","value="+list.get(i).getValue());
}
//排序完成 打印数据结果:
name=abb
value=2
name=abc
value=1
name=cb
value=3

#####SignBean 类代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class SignBean  {
private String name;
private String value;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}

public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}

#####相信你肯定在项目中遇到过下面的问题👇

  • Conversion to Dalvik format failed:Unable to execute dex: method
    ID not in [0, 0xffff]: 65536
    或者这样👇
  • trouble writing output:Too many field references: 131000; max is
    65536.You may try using –multi-dex option.
    这两种问题出现的原因都是因为你的项目中的方法数超过65536。前者出现在低版本的手机中,后者出现在较高的版本之中。这里的版本以5.0来区分。

#####如何解决
如果你的minSdkVersion设置成21及更高,你只需要在build.gradle设置multiDexEnabled为true就可以了:

1
2
3
4
5
6
7
8
9
android {
defaultConfig {
...
minSdkVersion 21
targetSdkVersion 25
multiDexEnabled true
}
...
}

如果你的minSdkVersion设置成20及以下,除了上述步骤外你还需要添加依赖库并进行相关配置:

1
2
3
4
5
6
7
8
9
10
11
12
android {
defaultConfig {
...
minSdkVersion 15
targetSdkVersion 25
multiDexEnabled true
}
...
}
dependencies {
compile 'com.android.support:multidex:1.0.1'
}

在MyApplication中加入下面的代码进行配置:

1
2
3
4
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base); MultiDex.install(this);
}

1、Activity的 runOnUiThread

textView = (TextView) findViewById( R.id.tv );

​ new Thread(new Runnable() {

​ @Override

​ public void run() {

​ runOnUiThread(new Runnable() {

​ @Override

​ public void run() {

​ textView.setText( “更新UI了”);

​ }

​ });

​ }

​ }).start();

android Activity runOnUiThread() 方法使用

2、Handler sendEmptyMessage()

package lib.com.myapplication;

import android.os.Handler;

import android.os.Message;

import android.support.v7.app.AppCompatActivity;

import android.os.Bundle;

import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

private TextView textView ;

Handler handler = new Handler( ) {

​ @Override

​ public void handleMessage(Message msg) {

​ super.handleMessage(msg);

​ textView.setText( “Ui更新了”);

​ }

};

@Override

protected void onCreate(Bundle savedInstanceState) {

​ super.onCreate(savedInstanceState);

​ setContentView(R.layout.activity_main);

​ textView = (TextView) findViewById( R.id.tv );

​ new Thread(new Runnable() {

​ @Override

​ public void run() {

​ try {

​ Thread.sleep( 2000 );

​ } catch (InterruptedException e) {

​ e.printStackTrace();

​ }

​ handler.sendEmptyMessage( 2 ) ;

​ }

​ }).start();

}

}

3、Handler post()

package lib.com.myapplication;

import android.os.Bundle;

import android.os.Handler;

import android.support.v7.app.AppCompatActivity;

import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

private TextView textView ;

Handler handler = new Handler();

@Override

protected void onCreate(Bundle savedInstanceState) {

​ super.onCreate(savedInstanceState);

​ setContentView(R.layout.activity_main);

​ textView = (TextView) findViewById( R.id.tv );

​ new Thread(new Runnable() {

​ @Override

​ public void run() {

​ try {

​ Thread.sleep( 2000 );

​ } catch (InterruptedException e) {

​ e.printStackTrace();

​ }

​ handler.post(new Runnable() {

​ @Override

​ public void run() {

​ textView.setText( “Ui更新了”);

​ }

​ }) ;

​ }

​ }).start();

}

}

在子线程中切换到主线程

new Thread(new Runnable() {

@Override

public void run() {

​ LogUtil.d( “ttt 11111111111” + Thread.currentThread().getName() );

​ new Handler(Looper.getMainLooper()).post(new Runnable() {

​ @Override

​ public void run() {

​ LogUtil.d( “ttt 55555555” + Thread.currentThread().getName() );

​ }

​ });

​ LogUtil.d( “ttt 22222222222” + Thread.currentThread().getName() );

​ LogUtil.d( “ttt 33333333333” + Thread.currentThread().getName() );

​ LogUtil.d( “ttt 44444444444” + Thread.currentThread().getName() );

}

}).start();

  结果

ttt 11111111111Thread-155

ttt 22222222222Thread-155

ttt 33333333333Thread-155

ttt 44444444444Thread-155

ttt 55555555main

  可见这种方式可以快速切换线程,从log日志来看,切换到主线程不会阻塞子线程。

4、view Post()

textView = (TextView) findViewById( R.id.tv );

​ new Thread(new Runnable() {

​ @Override

​ public void run() {

​ try {

​ Thread.sleep( 2000 );

​ } catch (InterruptedException e) {

​ e.printStackTrace();

​ }

​ textView.post(new Runnable() {

​ @Override

​ public void run() {

​ textView.setText( “Ui更新了”);

​ }

​ }) ;

​ }

​ }).start();

总结:

1、其实上面的四种方式都可归结于一种方式:handler 用于Android线程之间的通信。

2、为什么android要求只能在UI线程进行UI操作? 主要还是为了避免多线程造成的并发的问题。在单线程操作UI是安全的。

android原生的viewpager默认不支持自定义滑动速度,直接复制FixedSpeedScroller 类使用即可:
#####使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

controlViewPagerSpeed(this,splash_viewpager,400);//毫秒 速度
// 自定义viewpager滑动速度
private void controlViewPagerSpeed(Context context, ViewPager viewpager, int DurationSwitch) {
try {
Field mField;
mField = ViewPager.class.getDeclaredField("mScroller");
mField.setAccessible(true);
FixedSpeedScroller mScroller =
new FixedSpeedScroller(context, new AccelerateInterpolator());
mScroller.setmDuration(DurationSwitch);
mField.set(viewpager, mScroller);
} catch (Exception e) {
e.printStackTrace();
}
}
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
import android.content.Context;
import android.view.animation.Interpolator;
import android.widget.Scroller;

/**
* Created by tyl
* 2018/9/30/030
* Describe:
*/
public class FixedSpeedScroller extends Scroller {

private int mDuration = 1500; // 默认滑动速度 1500ms

public FixedSpeedScroller(Context context) {
super(context);
}

public FixedSpeedScroller(Context context, Interpolator interpolator) {
super(context, interpolator);
}

@Override
public void startScroll(int startX, int startY, int dx, int dy, int duration) {
// Ignore received duration, use fixed one instead
super.startScroll(startX, startY, dx, dy, mDuration);
}

@Override
public void startScroll(int startX, int startY, int dx, int dy) {
// Ignore received duration, use fixed one instead
super.startScroll(startX, startY, dx, dy, mDuration);
}

/**
* set animation time
*
* @param time
*/
public void setmDuration(int time) {
mDuration = time;
}

/**
* get current animation time
*
* @return
*/
public int getmDuration() {
return mDuration;
}
}

一、引言

  数据的序列化在Android开发中占据着重要的地位,无论是在进程间通信、本地数据存储又或者是网络数据传输都离不开序列化的支持。而针对不同场景选择合适的序列化方案对于应用的性能有着极大的影响。

  从广义上讲,数据序列化就是将数据结构或者是对象转换成我们可以存储或者传输的数据格式的一个过程,在序列化的过程中,数据结构或者对象将其状态信息写入到临时或者持久性的存储区中,而在对应的反序列化过程中,则可以说是生成的数据被还原成数据结构或对象的过程。

  这样来说,数据序列化相当于是将我们原先的对象序列化概念做出了扩展,在对象序列化和反序列化中,我们熟知的有两种方法,其一是Java语言中提供的Serializable接口,其二是Android提供的Parcelable接口。而在这里,因为我们对这个概念做出了扩展,因此也需要考虑几种专门针对数据结构进行序列化的方法,如现在那些个开放API一般返回的数据都是JSON格式的,又或者是我们Android原生的SQLite数据库来实现数据的本地存储,从广义上来说,这些都可以算做是数据的序列化。
QQ图片20180302171522.png

二、Serializable接口

正如前面提到的,Serializable接口是Java语言的特性,是最简单也是使用最广泛的序列化方案之一,这边需要注意的一点是Serializable接口是一个标识接口,无需实现方法,Java便会对这个对象进行序列化操作。

在这里实现了Serializable接口的对象才可以序列化,将Java对象转换成字节序列,而对应的反序列化则是将字节序列恢复成Java对象的过程。

在需要序列化的类中会用到serialVersionUID去标识这个序列化对象,即仅当序列化后的数据中的SerialVersionUID与当前类的serialVersionUID相同时才能被正常的反序列化。
import java.io.*;
public class User implements Serializable{
private static final long serialVersionUID= 123456;
public int userId;
public String userName;
public boolean isMale;
public User(int userId,String userName,boolean isMale){
this.userId=userId;
this.userName=userName;
this.isMale = isMale;
}
public boolean toSerial(User user) throws IOException{
ObjectOutputStream out=null;
boolean status=false;
try{
out = new ObjectOutputStream(new FileOutputStream(“cache.txt”));
out.writeObject(user);
status=true;
}catch(FileNotFoundException e){
System.out.println(“NO FILE”);
}finally{
if(out!=null)
out.close();
}
return status;
}
public User toObject(String filename) throws IOException{
ObjectInputStream in=null;
boolean status=false;
User user=null;
try{
in = new ObjectInputStream(new FileInputStream(filename));
user=(User) in.readObject();
}catch(ClassNotFoundException e){
System.out.println(“No file”);
}finally{
if(in!=null)
in.close();
}
return user;
}
public static void main(String[] args) throws IOException{
User user = new User(0,”jake”,true);
System.out.println(user.toSerial(user));
System.out.println(user.toObject(“cache.txt”).getClass());
}
}
此外,需要注意的:静态成员变量是属于类而不属于对象的,所以显然它不会参与到对象的序列化过程中。其次用transient关键字标记的成员变量不参与到序列化过程中。最后,这种序列化方式是基于磁盘或者网络的。

三、Parcelable接口

Parcelable接口是Android API提供的接口,从某种程度上来说,它更适用于Android平台上。不同于Serializable,它是基于内存的,由于内存中读写速度高于磁盘,所以Parcelable接口被广泛用于跨进程对象的传递。

下面贴上一个简单的Parcelable接口的序列化过程:

import android.os.Parcel;
import android.os.Parcelable;
public class User implements Parcelable {
public int userId;
public String userName;
public boolean isMale;
public User(int userId,String userName,boolean isMale) {
this.userId=userId;
this.userName=userName;
this.isMale=isMale;
}

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 User(Parcel in) {
    userId=in.readInt();
    userName = in.readString();
    isMale=in.readInt()==1;
}

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

@Override
public void writeToParcel(Parcel out, int flags) {
    out.writeInt(userId);
    out.writeString(userName);
    out.writeInt(isMale?1:0);
}

}
从上面可以看出,实现一个Parcelable接口,需要实现以下几个方法:

1.构造函数:从序列化后的对象中创建原始对象

2.describeContents :接口内容的描述,一般默认返回0即可

3.writeToParcel:序列化的方法,将类的数据写到parcel容器中

4.静态的parcelable.Creator接口,这个接口包含两个方法

1)createFormParcel:反序列化的方法,将Parcel还原成Java对象

2)newArray:提供给外部类反序列化这个数组使用。

四、两种对象序列化方法的对比

Serializable是Java中的序列化接口,其使用起来简单但开销较大(因为Serializable在序列化过程中使用了反射机制,故而会产生大量的临时变量,从而导致频繁的GC),并且在读写数据过程中,它是通过IO流的形式将数据写入到硬盘或者传输到网络上。

而Parcelable则是以IBinder作为信息载体,在内存上开销比较小,因此在内存之间进行数据传递时,推荐使用Parcelable,而Parcelable对数据进行持久化或者网络传输时操作复杂,一般这个时候推荐使用Serializable。

另外Serializable在使用时比较简单,而Parcelable在使用时需要手动去实现接口中的方法,为了规避使用Parcelable接口时的麻烦,我们下面介绍一个插件,从而自动生成对应的代码。

五、Parcelable插件

为了避免写大量的模板代码,这边介绍一个在Android Strudio中的插件,Android Parcelable code generator。在Pulgins中下载并按照该插件,接下来当我们需要用到Parcelable接口时,该插件就能自动帮我们将类对象转换成实现Parcelable接口的形式。

具体示例如下,

/**

  • Created by DB on 2017/6/24.
    */

public class BookItem {
public String mName;
public long mLastTime;
public String mTitle;
public String mPath;
}
然后类似与生成getter和setter代码那样,我们就可以直接自动生成Parcelable形式的代码,结果如下所示:

import android.os.Parcel;
import android.os.Parcelable;

/**

  • Created by DB on 2017/6/24.
    */

public class BookItem implements Parcelable {
public String mName;
public long mLastTime;
public String mTitle;
public String mPath;

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

@Override
public void writeToParcel(Parcel dest, int flags) {
    dest.writeString(this.mName);
    dest.writeLong(this.mLastTime);
    dest.writeString(this.mTitle);
    dest.writeString(this.mPath);
}

public BookItem() {
}

protected BookItem(Parcel in) {
    this.mName = in.readString();
    this.mLastTime = in.readLong();
    this.mTitle = in.readString();
    this.mPath = in.readString();
}

public static final Parcelable.Creator<BookItem> CREATOR = new Parcelable.Creator<BookItem>() {
    @Override
    public BookItem createFromParcel(Parcel source) {
        return new BookItem(source);
    }

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

}
有了这个插件,使用Parcelable接口显然方便了许多(可以偷好多懒)

六、数据的序列化方案

下面讲到的是广义上的序列化方案,不同于前面两种狭义或者说是对象序列化方案,接下来的几种方案针对于数据的传输和存储过程中的序列化方案

1.SQLite 

SQLite主要用于存储复杂的关系型数据,Android支持原生支持SQLite数据库相关操作(SQLiteOpenHelper),不过由于原生API接口并不友好,所以产生了不少封装了SQLite的ORM框架。

2.SharedPreferences

SharedPreferences是Android平台上提供的一个轻量级存储API,一般用于存储常用的配置信息,其本质是一个键值对存储,支持常用的数据类型如boolean、float、int、long以及String的存储和读取。

使用SharedPreferences读取和存储操作如下:

读取:

1) 获取Sharedpreferences对象

SharedPreferences mPreferences = context.getCSharedPreferences(PREFERENCES_NAME,Context.MODE_PRIVATE);

2.通过SharedPReferences对象读取存储在SharedPreferences中的数据

mPreferences.getBoolean(key,defValue);

存储:

1)获取SharedPreferences.Editor对象

SharedPreferences.Editor editor = mPreferences.edit();

2)通过SharedPreferences.Editor对象写入数据到SharedPreferences中。

mEditor.putBoolean(key,b);

3)调用commit函数将写入的数据提交,从而完成数据存储操作。

mEditor.commit();

3.JSON

JSON是一种轻量级的数据交互格式,由于其相对于XML,体积更小,在网络上传输时更加介绍浏览,被广泛用于移动端。大部分APP与服务端的通信都是使用JSON格式进行交互。

转自:http://blog.csdn.net/wangchunlei123/article/details/51345130

程序员必知的6点编程秘诀,编程三板斧将解决90%问题!

新手程序员刚入门编程,要经常加班。有些还经常被bug虐杀。

作为一名新手程序员,在编程职业初期应当建立起正确的编程观念。

img

打开网易新闻 查看更多精彩图片

下面w3cschool就给程序员小伙伴们分享6点编程秘诀:

0、造轮子?

有些新手程序员万般嫌弃别人的轮子不够圆,以现成的类库坑多为由不用,一定要自己造个轮子。

结果,发现等于是给自己挖了个坑,再跳进去。

1、看官方文档

大多数编程老手会告诉你编程的一条真理:看官方文档是最快的学习方式。

你对于一些问题认识不够清晰吗?那你就去看官方文档,看完之后,你会发现一切都豁然开朗了。

img

2、不靠谱需求最后做

把觉得不靠谱的需求放到最后做。为什么呢?

因为很可能到时候需求就变了。不急的需求不要急着做,可能之后需求就没有了。——引用编程大神的一句话。

3、工作日志能提升脑容量

程序员一般都会写工作日志,不过这是因项目的需要。

做为一名新手程序员,如果你养成记录技术博客的习惯,它将提升你的脑容量。

在自我总结的同时,让自己受益,同时别人可能会遇到同样的问题,也会受益颇多。

img

4、解决问题三板斧

你碰到问题苦思冥想,却毫无办法。

不妨使用编程问题解决的三板斧:Google + StackOverflow + Github,它可以解决日常开发中90%的问题。

img

5、制定计划

程序员的工作比较琐碎,一定要给自己定个学习计划,这个季度要学什么,下个季度要学什么,这一点非常重要。

时间是学习的成果的衡量标尺。就拿每次的跳槽为例,一些平时有定学习计划的程序员,总能找到薪水更高的工作。

而没有学习计划的程序员,换来换去工作都差不多。如果你能把目光放到一年之后,甚至5年之后,你就会有更多的时间去准备,成为一个更厉害的程序员!

转自:网易新闻

####Recurrence Rule
重复规则 rrule(Recurrence Rule) 属于 icalendar 属性中的一个,配合 dtstart 可以完整描述一个事件的重复行为并计算出重复事件的具体发生 (Occurence)。

重复规则包含多个属性, 每个属性以 NAME = VALUE 对的形式存在, 属性与属性之间用分号区分, 属性之间没有特定的顺序要求,在同一个重复规则中每个属性最多只能出现一次。

####FREQ
FREQ 属性表示重复规则的类型, 是重复规则中必须定义的一条属性。 可选的 VALUE 有:

SECONDLY, 表示以秒为间隔单位进行重复。
MINUTELY, 表示以分钟为间隔单位进行重复。
HOURLY, 表示以小时为间隔单位进行重复。
DAILY, 表示以天为间隔单位进行重复。
WEEKLY, 表示以周为间隔单位进行重复。
MONTHLY, 表示以月为间隔单位进行重复。
YEARLY, 表示以年为间隔单位进行重复。
####INTERVAL
INTERVAL 属性表示重复规则的间隔, 必须为正整数。 默认值为1, 对应上述不同的 FREQ 值分别表示每一秒,每一分钟, 每一小时, 每一天, 每一周, 每一月, 每一年。
####UNTIL
UNTIL 属性定义了一个日期-时间值,用以限制重复规则。 这个日期-时间值表示这个重复规则的最后一次事件的发生时间。 如果重复规则中未包含 UNTIL 和 COUNT 属性, 则表示该重复规则无限重复。
####COUNT
COUNT 属性通过定义重复事件的发生次数来限制重复规则。 正整数。
####BYSECOND, BYMINUTE, BYHOUR
BYSECOND 取值范围 0 - 59, 可以理解为 “…… 的 n 秒”。
BYMINUTE 取值范围 0 - 59, 可以理解为 “…… 的 n 分”。
BYHOUR 取值范围 0 - 23, 可以理解为 “…… 的 n 时”。
####BYDAY
BYDAY 取值范围: MO(周一), TU(周二), WE(周三), TU(周四), FR(周五), SA(周六), SU(周日)。可以有多个值,用逗号分隔。

每个值可以在前面加上一个正整数(+n)或者负整数(-n),用以在 MONTHLY 或者 YEARLY 的重复类型中表示第 n 个周几。 例如,在一个 MONTHLY 类型的重复规则中, +1MO(或者1MO)表示这个月的第1个周一,如果是 -1MO 则表示这个月的最后1个周一。

如果前面没有数字,则表示在这个重复类型中的所有的周几, 比如在一个 MONTHLY 的重复类型中, MO 表示这个月里所有的周一。
####BYMONTHDAY
BYMONTHDAY 取值范围 1 - 31 或者 -31 - -1,表示一个月的第几天。 比如, -10 表示一个月的倒数第10天。可以有多个值,用逗号分隔。
####BYYEARDAY
BYYEARDAY 取值范围 1 - 366 或者 -366 - -1, 表示一年的第几天。 比如, -1 表示一年的最后一天, 306 表示一年的第306天。可以有多个值,用逗号分隔。
####BYWEEKNO
BYWEEKNO 取值范围 1 - 53 或者 -53 - -1, 表示一年的第几周, 只在 YEARLY 类型的重复规则中有效。 比如, 3 表示一年的第 3 周。可以有多个值,用逗号分隔。(注:一年的第一周是指第一个至少包含该年4天时间的那一周)
####BYMONTY
BYMONTH 取值范围 1 - 12, 表示一年的第几个月。可以有多个值,用逗号分隔。
####WKST
WKST 取值范围 MO, TU, WE, TH, FR, SA, SU。 默认值为 MO。 当一个 WEEKLY 类型的重复规则, INTERVAL 大于 1, 且带有 BYDAY 属性时, 则必须带有 WKST 属性。 当一个 YEARLY 类型的重复规则带有 BYWEEKNO 属性时, 也必须带有 WKST 属性。
####BYSETPOS
BYSETPOS 取值范围 1 - 366 或者 -366 - -1, 表示规则指定的事件集合中的第n个事件, 必须与另外的 BYxxx 属性共同使用。 比如,每月的最后一组工作日可以表示为: RRULE:FREQ=MONTHLY;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=-1

如果一个 BYxxx 属性的值超过了它对应的范围,则该属性会被忽略。

当有多个 BYxxx 属性存在的时候, 在代入了 FREQ 和 INTEVAL 属性后,按照以下顺序代入到已有规则上:BYMONTH, BYWEEKNO, BYYEARDAY, BYMONTHDAY, BYDAY, BYHOUR, BYMINUTE, BYSECOND,BYSETPOS

例如: RRULE:FREQ=YEARLY;INTERVAL=2;BYMONTH=1;BYDAY=SU;BYHOUR=8,9; BYMINUTE=30

首先,将 INTERVAL=2 代入到 FREQ=YEARLY 上,得到“每2年”, 然后在这基础上代入 BYMONTH=1 得到
“每2年的1月”, 再代入 BYDAY=SU, 得到“每2年的1月的所有周日”, 再代入 BYHOUR=8,9, 得到
“每2年的1月的所有周日的8点和9点”(注意是8点和9点,不是8点到9点), 最后代入 BYMINUTE=30, 得到“每2年的1月的所有周日的8点30分和9点30分”。
规则中未注明的时间信息,以开始时间(dtstart)为准。
####Examples
每天发生一次,重复10次:
RRULE:FREQ=DAILY;COUNT=10

每天发生一次,直到1997年12月24日:
RRULE:FREQ=DAILY;UNTIL=19971224T000000Z

每2天发生一次,直到永远:
RRULE:FREQ=DAILY;INTERVAL=2

每10天发生一次,重复5次:
RRULE:FREQ=DAILY;INTERVAL=10;COUNT=5

当前日期为1998年1月1日9点0分0秒,之后的3年里每年的1月每天发生一次:
RRULE:FREQ=YEARLY;UNTIL=20000131T090000Z;BYMONTH=1;BYDAY=SU,MO,TU,WE,TH,FR,SA
或者:
RRULE:FREQ=DAILY;UNTIL=20000131T090000Z;BYMONTH=1

每周一次,共发生10次:
RRULE:FREQ=WEEKLY;COUNT=10

每周一次,直到1997年12月24日:
RRULE:FREQ=WEEKLY;UNTIL=19971224T000000Z

每2周一次, 直到永远:
RRULE:FREQ=WEEKLY;INTERVAL=2;WKST=SU

当前时间为1997年9月2日9点0分0秒,每周二和周四各发生一次,持续5周:
RRULE:FREQ=WEEKLY;UNTIL=19971007T000000Z;WKST=SU;BYDAY=TU,TH
或者:
RRULE:FREQ=WEEKLY;COUNT=10;WKST=SU;BYDAY=TU,TH

每周一, 周三, 周五各一次,直到1997年12月24日:
RRULE:FREQ=WEEKLY;INTERVAL=2;UNTIL=19971224T000000Z;WKST=SU;BYDAY=MO,WE,FR

每2周的周二和周四各发生一次,共发生8次: RRULE:FREQ=WEEKLY;INTERVAL=2;COUNT=8;WKST=SU;BYDAY=TU,TH

每月的第一个周五发生一次,共发生10次:
RRULE:FREQ=MONTHLY;COUNT=10;BYDAY=1FR

每月的第一个周五发生一次,直到1997年12月24日:
RRULE:FREQ=MONTHLY;UNTIL=19971224T000000Z;BYDAY=1FR

每2个月的第一个周日和最后一个周日个发生一次,共发生10次: RRULE:FREQ=MONTHLY;INTERVAL=2;COUNT=10;BYDAY=1SU,-1SU

每月的倒数第二个周一发生一次,共发生6次:
RRULE:FREQ=MONTHLY;COUNT=6;BYDAY=-2MO

每月的倒数第三天发生一次,直到永远:
RRULE:FREQ=MONTHLY;BYMONTHDAY=-3

每月的第2天和第15天各发生一次,共发生10次:
RRULE:FREQ=MONTHLY;COUNT=10;BYMONTHDAY=2,15

每月的第1天和最后1天各发生一次,共发生10次:
RRULE:FREQ=MONTHLY;COUNT=10;BYMONTHDAY=1,-1

每个18个月的1号至15号每天发生一次,共发生10次:
RRULE:FREQ=MONTHLY;INTERVAL=18;COUNT=10;BYMONTHDAY=10,11,12,13,14,15

每2个月的所有周二每天发生一次:
RRULE:FREQ=MONTHLY;INTERVAL=2;BYDAY=TU

每年6月和7月各发生一次,共发生10次:
RRULE:FREQ=YEARLY;COUNT=10;BYMONTH=6,7

每2年的一月,二月,三月各发生一次,共10次:
RRULE:FREQ=YEARLY;INTERVAL=2;COUNT=10;BYMONTH=1,2,3

每3年的第一天,第100天和第200天各发生一次,共10次:
RRULE:FREQ=YEARLY;INTERVAL=3;COUNT=10;BYYEARDAY=1,100,200

每年的第20个周一发生一次,直到永远:
RRULE:FREQ=YEARLY;BYDAY=20MO

每年的第20周的周一(以周一为一周起始日)发生一次,直到永远:
RRULE:FREQ=YEARLY;BYWEEKNO=20;BYDAY=MO

每年3月的所有周四,直到永远:
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=TH

每年6月,7月,8月的所有周四,直到永远:
RRULE:FREQ=YEARLY;BYDAY=TH;BYMONTH=6,7,8

每一个黑色星期五(13号那天为周五)发生一次,直到永远:
RRULE:FREQ=MONTHLY;BYDAY=FR;BYMONTHDAY=13

每月第一个周日之后那一周的周六发生一次,直到永远:
RRULE:FREQ=MONTHLY;BYDAY=SA;BYMONTHDAY=7,8,9,10,11,12,13

每4年的11月的第一个周一之后的那个周二发生一次,直到永远
RRULE:FREQ=YEARLY;INTERVAL=4;BYMONTH=11;BYDAY=TU;BYMONTHDAY=2,3,4,5,6,7,8

The 3rd instance into the month of one of Tuesday, Wednesday or Thursday, for the next 3 months(没法翻译,自己理解):
RRULE:FREQ=MONTHLY;COUNT=3;BYDAY=TU,WE,TH;BYSETPOS=3

每月的倒数第2个工作日,直到永远:
RRULE:FREQ=MONTHLY;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=-2

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
    /**
* 10进制转20进制
* data 传入的10进制
* */
private static String to20Jinzhi(int data) {
String str = "abcdefgh234lmntuwxyz";//自定义字符 多少字符为多少进制
int scale = str.length(); //转化目标进制
String s = "";
if (data == 0) {
return str.charAt(0) + "";
}
while (data > 0) {
if (data < scale) {
s = str.charAt(data) + s;
data = 0;
} else {
int r = data % scale;
s = str.charAt(r) + s;
data = (data - r) / scale;
}
}
// 字符不足3位前面补—线 自己定义
// if (s.length() < 3) {
// s = "_" + s;
// }
return s;
}