基于Volley框架的返回数据的范型处理

在平时最普通的Volley的网络请求中,我们StringRequest是这样请求网络数据的:

StringRequest stringRequest = new StringRequest("http://www.baidu.com",
        new Response.Listener<String>() {
            @Override
            public void onResponse(String response) {
                Log.d("TAG", response);
            }
        }, new Response.ErrorListener() {
    @Override
    public void onErrorResponse(VolleyError error) {
        Log.e("TAG", error.getMessage(), error);
    }

注意在onResponse的时候是拿到的string类型,拿到string后对其再进行相关的解析,我们是否是可以对其直接封装然后拿到具体想要类型的model数据结构呢?所以对其网络请求架构进行一次封装,到达如下效果:

GetGoodDetailByGidRequest getGoodDetailByGidRequest = new GetGoodDetailByGidRequest(mCouponId,
           new RequestListener<List<CouponModel>>() {
               @Override
               public void onSuccess(List<CouponModel> result) {
               }
               @Override
               public void onError(Exception e) {
                   e.printStackTrace();
                   stopLoadingDialog();
               }
           });

这里我们在构造Request的时候指定了返回数据的类型,这样的话就方便了我们在写业务的时候直接使用解析好的数据结构,具体如何做到的呢?

一、让每个Request基于一个带有范型请求类

public abstract class BaseApiRequest<T>  

这里的T就是目标请求期望的model类
在具体实现的时候继承基类,并指定返回类型,下面是一个例子:

public class GetGoodDetailByGidRequest extends BaseApiRequest<List<CouponModel>> {
    public static final String url = CURL.GoodDetailURL;
    public GetGoodDetailByGidRequest(String goodId, RequestListener<List<CouponModel>> requestListener) {
            super(requestListener);
            this.mUrlParams.put("id", goodId);
    }
    @Override
    public String getBaseUrl() {
            return url;
        }
}

二、在基类中构造网络请求

  protected StringRequest getStringRequest() {
    return new StringRequest(requestMethod, getRequestUrl(),
            response -> parseJson(response),
            error -> requestListener.onError(error)) {
        @Override
        protected Map<String, String> getParams() {
            return mEntityParams;
        }
    };
}

在此处实现可以看到Request在基类中进行,然后分别处理返回结果

三、对返回结果进行解析

    private void parseJson(String response) {
    int responseCode = 0;
    int errorCode = 400;
    try {
        JSONObject jsonObject = new JSONObject(response);

        String resultString = jsonObject.getString("data");

        if (jsonObject.has("code")) {
            responseCode = jsonObject.getInt("code");
        }
        if (jsonObject.has("error")) {
            errorCode = jsonObject.getInt("error");
        }

        if (responseCode == 200 || errorCode == 0) {
            if (!TextUtils.isEmpty(response)) {
                Type type = getTType(requestListener.getClass());
                //泛型是实体或者List等类型
                T t = JsonUtils.fromJson(resultString, type);
                requestListener.onSuccess(t);
                return;
            }
            ToastUtils.showToast("Data is empty!");
        }
        ToastUtils.showToast("Response code is error.");
        requestListener.onError(new ParseError());
    } catch (JSONException e) {
        ToastUtils.showToast(e.toString());
        e.printStackTrace();
    }
}

这里是最关键的一步,由于和后端约定好相关返回字段,那么只需要解析字段中目标model的数据,其中比较重要的是这段代码

Type type = getTType(requestListener.getClass());
//泛型是实体或者List等类型
 T t = JsonUtils.fromJson(resultString, type);
 requestListener.onSuccess(t);

通过封装好的 JsonUtils将String转化为对应的model类型,我们知道json转实体对象的时候,需要指明其类type,那这里的type是如何获取到的呢?

其中getTType ()的具体实现为:

 public static Type getTType(Class<?> clazz) {
    //以Type的形式返回本类直接实现的接口.
    Type[] types = clazz.getGenericInterfaces();
    clazz.getInterfaces();
    if (types.length > 0) {
        //返回表示此类型实际类型参数的 Type 对象的数组
        Type[] interfacesTypes = ((ParameterizedType) types[0]).getActualTypeArguments();
        return interfacesTypes[0];
    }
    return null;
}

通过次方法能够获取到请求实现中所指明的请求类型,其中getGenericInterfaces等相关原理可以阅读:https://my.oschina.net/617669559/blog/3012228

所以对于

public class GetGoodDetailByGidRequest extends BaseApiRequest<List<CouponModel>>

那么获取到的就是List类型

四、通过Listener回调相关解析结果

拿到解析好的result并回调给构造Request方法中的listener使用

T t = JsonUtils.fromJson(resultString, type);
requestListener.onSuccess(t);

这样对整个网络请求后的返回数据直接进行解析方便多了。

总结:

1、本文最主要是对基本Request类进行改造,以达到不需要每次重复写解析返回的String数据

2、在获取目标的类的类型的时候,主要是去获取基类中的“T”类型

3、设计不仅适用用Volley同样适用于其他类似的网络请求框架

小弟不才,如有问题,欢迎指出。

Java反射中getGenericInterfaces和getInterfaces的解读

今天在做解析网络请求后得到的数据的转化的时候用到了:getGenericInterfaces这个方法。

 /**
  * 获取回调接口中 T 的具体类型
  *
  * @param clazz
  * @return
  */
   public static Type getTType(Class clazz) {
    //以Type的形式返回本类直接实现的接口.
    Type[] types = clazz.getGenericInterfaces();
    if (types.length > 0) {
        //返回表示此类型实际类型参数的 Type 对象的数组
        Type[] interfacesTypes = ((ParameterizedType) types[0]).getActualTypeArguments();
        return interfacesTypes[0];
    }
    return null;
}

其中回调接口为:

new RequestListener>() {
   @Override
    public void onSuccess(List result) {}

在解析数据的时候这样操作,目的是为了对所有返回的数据进行数据转化为所指定的类型:

Type type = getTType(requestListener.getClass());
     //泛型是实体或者List等类型
     T t = JsonUtils.fromJson(resultString, type);
     requestListener.onSuccess(t);`

类RequestListener为:

public interface RequestListener {
    void onSuccess(T result);
    void onError(Exception e);
}

使用Gson进行json的解析,T fromJson(String json, Type typeOfT);那么怎么才能获取到RequestListener中的的类型呢?
于是我们从接口获取参数化类型处理。

官方文档解释

getGenericInterfaces:

Returns the {@code Type}s representing the interfaces directly implemented by the class or interface represented by this object.释意:返回表示由此对象表示的类或接口直接实现的接口的{@code Type}。

getInterfaces:

Determines the interfaces implemented by the class or interface represented by this object.
释意:返回由此对象表示的类或接口实现的接口。

从解释上面来看出来了,差异在于“接口实现的接口的Type”,接下来用具体示例来解释区别

private class Food{
    String foodName;
}
private interface Eat{
    void eat(String things);
}
private interface Run{
    void run();
}
private class Dog implements Eat,Run{
    @Override
    public void run() { }
    @Override
    public void eat(String things) { }
}
private void main() {
    Class clazz = Dog.class;
    Type[] genericInterfaces = clazz.getGenericInterfaces();
    Class[] interfaces = clazz.getInterfaces();
}
运行结果
![](https://oscimg.oschina.net/oscnet/245442107557694aef0f07c25be0740187c.jpg)

我们可以看到,clazz.getGenericInterfaces()与clazz.getInterfaces()并没有任何差异。因为 并没有:“实现的接口的Type”

接下来看另一段代码,我们对Eat接口改造一下,增加一个参数化类型

private class Food{
    String foodName;
}
private interface Eat{
    void eat(T things);
}
private interface Run{
    void run();
}

private class Dog implements Eat,Run{
    @Override
    public void run() { }
    @Override
    public void eat(Food things) { }
}
private void main() {
    Class clazz = Dog.class;
    Type[] genericInterfaces = clazz.getGenericInterfaces();
    Class[] interfaces = clazz.getInterfaces();
}
运行结果:

关于位运算和HashMap中一个求最小2次幂的算法

今天在HashMap的内部源码的时候,看到这样一个算法:

/**
 * Returns a power of two size for the given target capacity.
 * 返回大于或等于 cap 的最小2次幂
 */
static final int tableSizeFor(int cap) {
    int n = cap - 1;
    n |= n >>> 1;
    n |= n >>> 2;
    n |= n >>> 4;
    n |= n >>> 8;
    n |= n >>> 16;
    return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}

第一眼看起来确实是一脸懵逼,通过只知道这是一个获取该数的大于或等于 cap 的最小2次幂,这么厉害,咋实现的呀?

1、数据在内存中如何存储?

我们知道”<<” 和 “>>” 分别代表 左移和右移位运算符号,表示 乘以2 和除以2(大多数时候适用),”>>>”还是第一次见,这是代表什么意思呢?说到这里我们不得不去了解一下数据是如何存储在内存中的:

在32位的计算机系统中,int型数据占几个字节? 4字节。其中每个字节有8个比特位,表示二进制位,位是计算机内部数据储存的最小单位。这是所有编程语言学习者都知道的。也就是说 int类型在内存中有4*8 == 32个比特为 所以如果以整形数10为例,那么它在内存中完整存储的形式为:

00000000 00000000 00000000 00001010 ->对应 1x2^3+0x2^2+1x2^1+0x2^0 =10

那么int类型表示最大的数是不是就是:

11111111 11111111 11111111 11111111 ->对应 1x2^31+1x2^30…1x2^1+1x2^0

但为我们知道int类型的最大值为:2^31-1,显然上面的答案不是正确的。

这是因为在所有被int类型占用的比特位中,左起第一个位(即最高位)就是符号位。int类型的符号位上,0表示正数,1表示负数。在32位操作系统下,其余后面31位是数值位。也就是说:

11111111 11111111 11111111 11111111 所代表的数字为:1x2^30+1x2^29…1x2^1+1x2^0 的相反数为:-(2^31-1)

这里需要注意的是,按原先的逻辑去理解的话

00000000 00000000 00000000 00000000 为+0

10000000 00000000 00000000 00000000 为-0

那他们表示的意义是一样的么?
实际上,在32位系统下int类型中,我们计算机已经强行规定了这种情况,数字0采用“+0”的表示方法,即 00000000 00000000 00000000 00000000;而“-0”这个特殊的数字被定义为了-2^31。

因此我们看到32位系统下int类型的取值范围中,负数部分比正数部分多了一个数字,正数的最大取值是2^31-1,而负数的最小取值是-2^31。正数部分之所以要减去1,是因为被数字0占用了“+0”,而负数部分不需要用来表示0,因此原本的“-0”就用来表示-2^31这个数字。

2、位运算如何进行?

至此我们明白了数据在计算机中的存储形式,那位运算具体怎么运行的呢?
以10和-10为例,其二进制完整表示为:00000000 00000000 00000000 00001010 和 10000000 00000000 00000000 00001010 为了便于观察,我们取后面8位:00001010

  • 对于符号位移

例如将10的二进制向左移1位:那么变成 0001010 0 == 20 原先二进制数的第一位被移除,而最后一位被舍弃。将10的二进制向右移1位 原先二进制数最后一位被移除,第一位补0,则变成 000101 ==5

如将-10的二进制向左移1位, 10000000 00000000 00000000 00001010则变成:

10000000 00000000 00000000 0010100 为-20

如将-10的二进制向右移1位, 10000000 00000000 00000000 00001010则变成 :

注意这里多了一个0-> 1 00000000 00000000 00000000 0000101 <-注意这里少了位

也就是说符号移动,会保留原来的符号位,不会因为右移左移而带走符号位。

  • 对于无符号位移

相反无符号位移会不关注符号位。
例如将-10向右无符号右移就会变成:

010000000 00000000 00000000 0000101 变成了一个很大的正数了!!

如果将-10无符号左移,则变成:

00000000 00000000 00000000 00001010 = 20

但是!!并没有无符号左移动这样一件事情!
跟右移运算不同的是,无符号左移和左移是一样的。因此java没有无符号左移运算。(<<<和<<<=将报错)

因为无符号右移运算需要考虑符号位的右移,而符号位只存在于二进制表示的最左边,最右边没有。所以不用区分无符号左移和左移运算。

3、关于返回大于或等于 cap 的最小2次幂的算法

我们以传入10为例子

由这张图看起来,算法很容易懂了,其实最主要的是为了去让各个位从高到低 从0变成1或者维持1不变,这样就能找到该数最小的2次幂

另外,需要注意一下的是,第一步 int n = cap - 1; 这个操作,执行这个操作的主要原因是为了防止在cap已经是2的n次幂的情况下,经过运算后得到的结果是cap的二倍的结果,例如如果n为l6,经过一系列运算之后,得到的结果是0001 1111,此时最后一步n+1 执行之后,就会返回32,有兴趣的可以自己进行尝试;

小弟不才,如有问题,欢迎指出。

参考来源:

https://blog.csdn.net/c10WTiybQ1Ye3/article/details/89411471
https://www.jianshu.com/p/927009730809

Java虚拟机类的加载机制

什么是虚拟机类的加载机制?

虚拟机把描述类的数据从class文件加载到内存,并对数据进行校验,转换,解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。

类从加载到虚拟机的内存中开始,直到卸载出内存为止,整个生命周期为:

  • 加载(loading)

  • 验证(verification)

  • 准备(preparation)

  • 解析(resolution)

  • 初始化(initialization)

  • 使用(using)

  • 卸载(unloading)

其中 验证、准备、解析部分统称为连接

接下来依次讲解,各个步骤所做的事

第一部分 加载


“加载”是“类加载”的一个阶段,注意区分概念。类的加载由类加载器(后面介绍)加载主要完成三件事情:

1、通过一个类的全限定名来获取其定义的二进制字节流。

2、将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。

3、在Java堆中生成一个代表这个类的java.lang.Class对象,作为对方法区中这些数据的访问入口。

加载阶段完成后,虚拟机外部的二进制字节流将按照虚拟机所需的格式存储在方法区中,同时在内存中实例化一个java.lang.Class的实例对象。相对于HotSpot,这个实例对象比较特殊,虽然是一个对象,但并没有放置在堆中,而是放置在方法区中。这个对象将作为程序访问方法区中这些类数据的外部接口。

第二部分 验证


这一步主要是确保Class文件的字节流符合虚拟机的规范

主要验证以下几个部分:

1、文件格式验证
验证是否以魔数开头、主次版本号是否在当前虚拟机处理范围内…

这一验证阶段主要是保证输入的字节流能正确地解析并存储与方法区内,格式上符合Java类型信息的要求。只有通过这个阶段,字节流才会进入内存的方法区中存储,后面的三个验证方式也都是给予方法区中的数据验证,不再会操作字节流。

2、元数据验证 验证这个类是否有父类、这个类是否继承了不允许继承的类…

该阶段主要对类的元数据进行语义校验,保证符合java语言规范的元数据信息。

3、字节码验证

最复杂的一个阶段,主要目的是通过数据流和控制流分析,确定程序语义是否合法、符合逻辑。

4、符号引用验证

这个阶段的校验发生在虚拟机将符号引用转化为直接引用的时候,这个转化动作发生在连接的解析阶段。目的是确保解析动作正常执行,如果无法通过验证,将抛出 IllegalAccessError、NoSuchFieldError、NoSuchMethodError等异常。

第三部分 准备


准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些变量所实用的内存将在方法区中进行分配。

这时候的分配仅仅是类变量(staic修饰的变量),而实例变量将会在对象实例化时随对象一起分配在Java对中。

假设一个类变量为:public static int count = 10;这时候会分配0,而不是10,分配10是在程序编译后。

第四部分 解析


解析阶段是虚拟机将常量池的符号引用替换为直接引用的阶段

1、类或者接口的的解析

2、字段解析

3、类方法解析

4、接口方法解析

符号引用与虚拟机实现的布局无关,引用的目标并不一定要已经加载到内存中。各种虚拟机实现的内存布局可以各不相同,但是它们能接受的符号引用必须是一致的,因为符号引用的字面量形式明确定义在Java虚拟机规范的Class文件格式中。

直接引用可以是指向目标的指针,相对偏移量或是一个能间接定位到目标的句柄。如果有了直接引用,那引用的目标必定已经在内存中存在。

第五部分 初始化


在准备阶段,变量已经被分配赋值过初始值,在初始化阶段根据代码的逻辑初始化真实的变量和其他资源。

关于类加载器


什么是类加载器?

在“加载”阶段中,通过一个类的全限定名来获取其定义的二进制字节流。这一动作是放到了Java虚拟机外部去实现的,是为了方便让应用自己去决定如何获取所需要的类,实现这个动作的功能是常说的“类加载器(ClassLoader)”

类加载器主要有三种:

1.启动类加载器(Bootstrap ClassLoader)

负责加载<JAVA_HOME>\lib

2.扩展类加载器(Exension ClassLoader)

负责加载<JAVA_HOME>\lib\ext

3.应用程序类加载器(Applicaion ClassLoader)

负责加载ClassPath上指定的类库

类加载器工作原理

介绍类加载器原理之前,必须得了解双亲委派模型(Parents Delegation Model)

双亲委派模式的工作原理的是:如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载。

如图所示,这种层次结构关系被称为双亲委派模型
以下为其实现代码,集中在java.lang.ClassLoader中的loadClass()方法中

    protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
    {
    synchronized (getClassLoadingLock(name)) {
        // First, check if the class has already been loaded
    //首先检查类是否被加载
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            long t0 = System.nanoTime();
            try {
                if (parent != null) {
        //如果有父加载器,则先委托父加载,否则由启动类加载器加载,如果启动类加载器没有找到,则返回null
                    c = parent.loadClass(name, false);
                } else {
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // ClassNotFoundException thrown if class not found
                // from the non-null parent class loader
        //这里的ClassNotFoundException来自父加载器
            }
            if (c == null) {
                // If still not found, then invoke findClass in order
                // to find the class.
        //在父类Classloader还没办法加载的时候
        //再调用本身的findclass方法来加载类
                long t1 = System.nanoTime();
                c = findClass(name);
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}

代码的逻辑很清楚:

先加载类是否已经被加载过,若没有则调用父的loadClass()方法,如果父 类加载器为空,则使用启动类加载器作为父加载器,如果父 类加载器加载失败,再调用自己的findClass()方法进行加载

Android中Handler使用导致的内存泄漏

1.什么是内存泄漏

用动态存储分配函数动态开辟的空间,在使用完毕后未被得到释放,结果一直占据该用内存单元,直到程序结束,即所谓的内存泄漏。

2.是内存泄漏与内存溢出的区别

内存溢出 Out of Memory,是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory;比如申请了一个integer,但给它存了long才能存下的数,那就是内存溢出。

内存泄露 Memory Leak,是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。

用一个很形象的例子来说明:一个仓库,被无用的物资所占据,而得不到管理员的清理,这里的无用货物占用仓库空间的行为被叫做”内存泄漏“,而某一天仓库由于所存储的物品太多,而无法继续存放物资,这个时候就被叫做“内存溢出”。

3.内存泄漏导致的问题

相关内存无法被系统给回收,随着程序运行可以用的内存会越来越少,机子越来越卡,直到内存溢出。(这也是为什么手机电脑很卡之后重启一下后会好很多,主要是相关未被系统回收的内存被回收)

4、安卓中的内存泄漏

典型的可能产生内存泄漏的代码:

public class MemoryLeakActivity extends MyActivity {
//可能会导致内存泄漏的代码
private Handler handler = new Handler() {
    [@Override](https://my.oschina.net/u/1162528)
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
    }
};}

产生内存泄漏可能的原因:Handler的工作机制中Handler与Looper以及MessageQueue一起工作的,App启动之后,系统会默认创建一个为主线程服务的Looper对象,负责处理主线程中所有的Message对象,它的生命周期则为整个应用的生命周期。在主线程使用Handler都会默认绑定到这个Looper上面,主线程创建Handler对象,会立即关联Looper对象的MessageQueue,这时发送MessageQueue重的Message会持有Handler的引用, 这样在Looper处理Message时候才会回调到Handler的handleMessage方法。因此,如果Message没有被处理完成,那么Handler对象就不会被垃圾回收。

上面的代码,将Handler的实例声明为MemoryLeakActivity类的内部类,在Java中:非静态内部匿名类会持有外部类的一个隐式引用,这样就可能导致外部类无法被垃圾回收。

最终由于MessageQueue中的Message 没有处理完成,就会持有Handler对象的引用,而非静态的Handler对象会持有外部类Activity的引用,这个activity无法被回收,从而导致内存泄漏。

5、解决方案

1、将Handler声明为静态内部类,这样就不会持有对外部类的引用。

2、创建一个Looper与一般Java对象一样的生命周期

private static InnerHandler extends Handler{       
      // 声明一个静态Handler类,并持有外部类引用
    private final WeakReference<MemoryLeakActivity> mActivity;
        public InnerHandler(MemoryLeakActivity activity){
                this.mActivity = new WeakReference<MemoryLeakActivity>(activity);
    }
}

LayoutInflater源码解析

我们经常实用的LayoutInflater这样用:

View view = LayoutInflater.from(context).inflate(R.layout.resource,root,flase);

进入inflate进行源码解析

   public View inflate(@LayoutRes int resource, [@Nullable](https://my.oschina.net/u/2896689) ViewGroup root, boolean attachToRoot) {
    final Resources res = getContext().getResources();
    if (DEBUG) {
        Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
                + Integer.toHexString(resource) + ")");
    }
    final XmlResourceParser parser = res.getLayout(resource);
    try {
        return inflate(parser, root, attachToRoot);
    } finally {
        parser.close();
    }
}

主要做了一件事:
建立XmlResourceParser为后面解析xml文件做准备
XmlResourceParser的解析原理可以去这里看看

继续进入inflate(为方便阅读删除一些调试代码和异常捕获代码,只保留了核心代码)

public View inflate(XmlPullParser parser, [@Nullable](https://my.oschina.net/u/2896689) ViewGroup root, boolean attachToRoot) {
  synchronized (mConstructorArgs) {
      final Context inflaterContext = mContext;
      final AttributeSet attrs = Xml.asAttributeSet(parser);
      Context lastContext = (Context) mConstructorArgs[0];
      mConstructorArgs[0] = inflaterContext;
      View result = root;
      try {
          if (TAG_MERGE.equals(name)) {
              if (root == null || !attachToRoot) {
                  throw new InflateException("<merge /> can be used only with a valid "
                          + "ViewGroup root and attachToRoot=true");
              }
              rInflate(parser, root, inflaterContext, attrs, false);
          } else {
              // Temp is the root view that was found in the xml
              final View temp = createViewFromTag(root, name, inflaterContext, attrs);
              ViewGroup.LayoutParams params = null;
              if (root != null) {
                  // Create layout params that match root, if supplied
                  params = root.generateLayoutParams(attrs);
                  if (!attachToRoot) {
                      // Set the layout params for temp if we are not
                      // attaching. (If we are, we use addView, below)
                      temp.setLayoutParams(params);
                  }
              }
              rInflateChildren(parser, temp, attrs, true);
              // We are supposed to attach all the views we found (int temp)
              // to root. Do that now.
              if (root != null && attachToRoot) {
                  root.addView(temp, params);
              }
              // Decide whether to return the root that was passed in or the
              // top view found in xml.
              if (root == null || !attachToRoot) {
                  result = temp;
              }
          }
      } catch (XmlPullParserException e) {...}

`

这段源码中看出主要逻辑为:

1、判断xml局中标签是否为merge,如果是则走rInflate直接去遍历创建xml所有的View对象

2、进入非merge的逻辑里面,会创建根View,主要的过程在createViewFromTag创建View

3、rInflateChildren创建子View

3、接下来如果传入的root不为null,并且attachToRoot==false,则对创建好的View的ViewGroup.LayoutParams是通过generateLayoutParams生成的。

4、如果root不为null,attachToRoot==true,那么则将整个View作为一个子View加入到父布局中,否则直接返回这个View

继续进入createViewFromTag源码(为方便理解去除异常彩蛋和ignoreThemeAttr属性的代码)
`

View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
        boolean ignoreThemeAttr) {
    if (name.equals("view")) {
        name = attrs.getAttributeValue(null, "class");
    }
    try {
        View view;
        if (mFactory2 != null) {
            view = mFactory2.onCreateView(parent, name, context, attrs);
        } else if (mFactory != null) {
            view = mFactory.onCreateView(name, context, attrs);
        } else {
            view = null;
        }
        if (view == null && mPrivateFactory != null) {
            view = mPrivateFactory.onCreateView(parent, name, context, attrs);
        }
        if (view == null) {
            final Object lastContext = mConstructorArgs[0];
            mConstructorArgs[0] = context;
            try {
                if (-1 == name.indexOf('.')) {
                    view = onCreateView(parent, name, attrs);
                } else {
                    view = createView(name, null, attrs);
                }
            } finally {
                mConstructorArgs[0] = lastContext;
            }
        }
        ……

这段源码中看出主要逻辑为:

1、标签为view时获取view的class属性作为要创建的View的name(注意View和view的区别)

2、主要通过不同的Factory通过createView() 去创建View

3、其中有个逻辑需要在onCreateView执行之前判断是否存在“.”,存在点则表示不是系统的View,需要单独处理,在后面createView的代码可以看到有这样一段加入了“android.view.”,后面会讲这句的用途。

protected View onCreateView(String name, AttributeSet attrs)
        throws ClassNotFoundException {
    return createView(name, "android.view.", attrs);
}

继续进入onCreateView源码

public final View createView(String name, String prefix, AttributeSet attrs)
      throws ClassNotFoundException, InflateException {
  Constructor<? extends View> constructor = sConstructorMap.get(name);
  if (constructor != null && !verifyClassLoader(constructor)) {
      constructor = null;
      sConstructorMap.remove(name);
  }
  Class<? extends View> clazz = null;
  try {
      if (constructor == null) {
          // Class not found in the cache, see if it's real, and try to add it
          clazz = mContext.getClassLoader().loadClass(
                  prefix != null ? (prefix + name) : name).asSubclass(View.class);

          if (mFilter != null && clazz != null) {
              boolean allowed = mFilter.onLoadClass(clazz);
              if (!allowed) {
                  failNotAllowed(name, prefix, attrs);
              }
          }
          constructor = clazz.getConstructor(mConstructorSignature);
          constructor.setAccessible(true);
          sConstructorMap.put(name, constructor);
      } else {
          // If we have a filter, apply it to cached constructor
          if (mFilter != null) {
              // Have we seen this name before?
              Boolean allowedState = mFilterMap.get(name);
              if (allowedState == null) {
                  // New class -- remember whether it is allowed
                  clazz = mContext.getClassLoader().loadClass(
                          prefix != null ? (prefix + name) : name).asSubclass(View.class);

                  boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
                  mFilterMap.put(name, allowed);
                  if (!allowed) {
                      failNotAllowed(name, prefix, attrs);
                  }
              } else if (allowedState.equals(Boolean.FALSE)) {
                  failNotAllowed(name, prefix, attrs);
              }
          }
      }
      Object lastContext = mConstructorArgs[0];
      if (mConstructorArgs[0] == null) {
          // Fill in the context if not already within inflation.
          mConstructorArgs[0] = mContext;
      }
      Object[] args = mConstructorArgs;
      args[1] = attrs;
      final View view = constructor.newInstance(args);
      if (view instanceof ViewStub) {
          // Use the same context when inflating ViewStub later.
          final ViewStub viewStub = (ViewStub) view;
          viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
      }
      mConstructorArgs[0] = lastContext;
      return view;
  } 

这段源码虽然很复杂,但主要做的事就是 通过反射的方式去加载一个View类

这段代码就能解释上面为什么要加“android.view.”,这段代码会将系统的View的路径拼起来,把类加载进来;

clazz = mContext.getClassLoader().loadClass(prefix != null ? (prefix + name) : name).asSubclass(View.class) 

到现在,我们讲完了对于xml根view的创建逻辑,还有个很重要的流程没有讲:

子View创建 的逻辑在inflate中的rInflateChildren

进入rInflateChildren,这里依然会进入到rInflate()

   void rInflate(XmlPullParser parser, View parent, Context context,
        AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {

    final int depth = parser.getDepth();
    int type;
    boolean pendingRequestFocus = false;
    while (((type = parser.next()) != XmlPullParser.END_TAG ||
            parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
        if (type != XmlPullParser.START_TAG) {
            continue;
        }
        final String name = parser.getName();
        if (TAG_REQUEST_FOCUS.equals(name)) {
            pendingRequestFocus = true;
            consumeChildElements(parser);
        } else if (TAG_TAG.equals(name)) {
            parseViewTag(parser, parent, attrs);
        } else if (TAG_INCLUDE.equals(name)) {
            if (parser.getDepth() == 0) {
                throw new InflateException("<include /> cannot be the root element");
            }
            parseInclude(parser, context, parent, attrs);
        } else if (TAG_MERGE.equals(name)) {
            throw new InflateException("<merge /> must be the root element");
        } else {
            final View view = createViewFromTag(parent, name, context, attrs);
            final ViewGroup viewGroup = (ViewGroup) parent;
            final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
            rInflateChildren(parser, view, attrs, true);
            viewGroup.addView(view, params);
        }
    }
    if (pendingRequestFocus) {
        parent.restoreDefaultFocus();
    }
    if (finishInflate) {
        parent.onFinishInflate();
    }
}

这段源码的大致可以总结为:

1、总的逻辑为获取xml文档的层级数,解析每一层级的数据

2、解析过程首先进行View的合理性校验,include、merge等标签;

3、最后还是会走到createViewFromTag 创建出 View 对象,如果是 ViewGroup则递归调用rInflateChildren

到这里基本上所有的流程讲完了,这里总结一下加载流程:

1、拿到Xml解析对象,为后续解析做准备

2、对整个Xml中的布局控制处理由 root、attachToRoot这两个参数控制

3、解析子View,通过createViewFromTag创建实例对象**

从一则知乎看到的一段视频说起

今晚躺在床上,刷刷知乎,看到一则回答:https://www.zhihu.com/question/278030511/answer/453342056

让我陷入一些深思
回答中中有这样一段视频,自己反复看了几遍,每一遍好像都有不同的感触。第一遍,感受到大叔打着的节拍是多么的有节奏感,让人听了之后恨不得跟着他一起拍起来。第二遍,我发现大叔后面的节奏相比于紧促了起来,大叔内心有了更多的投入,而且貌似最后还有不好听的字眼,貌似在发泄了。第三遍,大叔一定是一个有故事的人,大叔右手旁边的朋友都已经湿了眼角。。。

《假行僧》一首能把人唱得热泪盈眶的歌曲,我在想大叔是不是想起了他自己的过去,亦或是有太多的不满此刻想要发泄出来。我想前者会更多一点的吧。

是呀,生活在这样一个忙忙碌碌的人心浮躁社会里,人的压力自然而然就增加了,再也没有儿时那样的无牵无挂。今天和好朋友去了一趟西塘古镇,主要还是因为周末了,最近在一些地方也遇到了小挫折,周末了没有任何人来打扰自己,比起待在家里更想的还是出去走走比较好。

今天的西塘下着雨,没有了燥热的天气,人心还是挺平静的,或许是因为雨的缘故,景区并不是人满为患,自然也有了更多的空间去慢慢欣赏“江南水乡”。

因为最近刚看完《边城》,脑子里总是将其场景带入西塘,可是看着被商业气息冲刺着的古镇,自然也带入不了了。

走在河边的烟雨长廊上,朋友说道 十年后再来这里。啊!到现在这个年龄,自己真的对于时间太敏感了,真的有点害怕了!消逝的是那时间,留下的却是那一串串回忆。

那时间 永远不再回来。自己上个月刚过了二十二周岁的生日,还记得以前Taylor有一首歌叫做《22》,有一句:But i am feeling 22,evething will be alright(我感觉我回到了22岁,所有事都如此美好),所以我现在就处在这样一个霉霉很想回到的一个年龄?可是她回不到了,我自然也回不到了我的18了。

那回忆 有美好的不美好的。回忆里面有对酒当歌,有秉烛夜游,也有蒲苇磐石,也有来日方长,当然还有曲终人散。还记得那本《挪威的森林》,或许现在它还在某处静静的躺着吧,因为它回忆里也多了好多的故事。也还记得那一回因为一些压力,在酒吧里和朋友唱了首《挪威的森林》。

一切都已经过去了,也许明天就回来了,也许不会再回来。

就这样吧,晚安。
2018/08/26 01:35

十年祭·512汶川大地震

今晚不知在哪突然看到有关512汶川地震的相关报道,汶川地震已发生了十年了。十年呀!人们总是对于十年、二十之类的整数年比较在意,当然我也不例外。作为一个在外漂泊对人来说,提起故乡往事也总是诸多感慨与回忆。

还记得在十年前自己还是一副稚嫩对模样,一米五不到,留着一个乖乖头的小孩子。记得那天在发生地震之前一切都很普通,普通得我都忘了那之前有发生过什么事情。那时候我们正趴在课桌上睡午觉,突然桌子一阵摇晃,我也不知道发生了什么。我只是抬头一看天花板上面的风扇在摇晃,那时候的天真我以为外面吹大风了,把风扇吹摇晃起来了,把房子吹摇晃起来了。可是十几秒钟之后,整个楼层嘈杂了起来,我也不知道发生了什么,只看到外面一群人都在往外边跑,我也跟着往外边跑,我们的教室在三楼,从三楼走到操场上,我感觉整个地面像是海绵做的一样,踩上去特别特别的软(现在想起来,当时房屋震动起伏的高度应该有个二三十厘米),整个人走在上面都在摇摇晃晃,依稀的听得一些窗户玻璃碎了的声音。不一会全校的人都站在了操场上面,此时地面依然在震动,当时大家都不知道发生了什么,可是脚都在不由自主地颤抖,可能是先天的一种对大自然的畏惧吧,站在操场上地面震动的时间持续了二三十秒钟,到后来渐渐的停止了震动,大家也才反应了过来。

有老师在大吼大喊组织大家在操场上站着,不让大家再回教室,地面停止震动了,其中我印象很深的一个情节:班上一个人由于刚睡了午觉口很渴,想回教室去拿水,校长看到他向教室里面走去了,直接过去就是两耳光,然后一顿骂。同学很是不开心,还暗地里骂校长,现在想想真的能理解当时校长的心情。从那之后“地震”这个词才深深的印在我们的脑海里,原来呀,这就是地震。

全校都站在操场上无目的等(现在想来当时应该在等上级的通知,是否是叫我们放假吧),也不记得等了多久,然后学校突然说放假了!!!当时那叫一个兴奋(现在去查了查日历,当时是星期一)这可了得,刚放完周末又马上要回去了,我记得好多人都高兴得跺脚了。。。可是大家往家走的路上,看到有些东西有点不对劲了,路上有一些比较老一点的房子直接全部垮塌了,然后途中遇到一个学生他妈来接他,我也不知道具体说了些什么了,大致记得“余震”这个事,说的是之后还会有余震,当时那叫一个怕,以为余震和刚才的大地震一样,还会再来一次,真的是很害怕了。

越往家走心里越害怕,万一自己家的房子垮了怎么办呢?带着担心回到了家,发现还好还好,房子还没有垮,悬着的心一下子放松了下来。作为小孩子的我们,又聚集了一堆小伙伴玩呀,好像一切都与我们无关。记得当时已经停电了,电视没法看了,也没有手机电脑,家里的电话也大不出去了,不知道谁弄来了一个小随身听,听广播播报说:2008年5月12日啥啥啥一大堆的。现在想想当时有个很搞笑的场景:我们从新闻里面听说了7.8级,也听到了8.0级之类的,大家以为是 成都还是哪里发生了8.0级地震,某某地方发生了7.8级地震。于是有个人赶紧问在听FM的小伙伴说:“绵阳多少级?”

我们小孩子依然像往常一样,在整个村里面到处蹿,发现整个村都在忙,从家里搬重要的东西、搭帐篷等等,甚是热闹,小孩子好像就喜欢热闹的场景,记得我们那天玩到了比平时晚很久很久才回家。晚上一家人住在了帐篷里面,当时我也不知道为什么不住家里呀,房子应该够结实的呀?一家人都围在一个小小的帐篷里面,我拿着我小小的手电筒躲在被窝里,好像还有自己的一些小玩意儿,然后又把手电筒挂在帐篷最上面,照着我们,那时候真的一点都不害怕,甚至是感觉到自己是有多么的幸福。

晚上,下起了大雨,很大的雨听到了雨水和帐篷上一直发出哒哒哒的声音,外面的大雨并没有破坏帐篷内的宁静。现在想想爷爷真是伟大,帐篷是他搭的,我还记得他在下午的时候,就已经在帐篷周围挖好了排水道,这样使得帐篷周围不会有积水,也使帐篷内地面保持干燥。我就安然地睡着了,睡得很香,啥事没有一样。记得半夜,大概两三点的时候吧,整个村又吵起来了,原来是在刚刚发生了一场余震,而我全然不知。记得爷爷当时给我们讲了下当年唐山大地震的一些事,我也记不清楚太多了,只记得唐山离我们很远,但是好像这边震感还是很强烈(没有汶川这次强烈),但是每家每户也都搭起了帐篷,然后讲了很多,我记不清了。。。

第二天,好像消息很多很多就传来了,我也是听爷爷给我讲的,他在茶馆听谁谁谁说了啥啥啥,大家都在讨论啥啥啥。现在想想当时镇上的茶馆里面大家都讨论这些的时候是有多热闹,等我到初中的时候,我甚至将想象中爷爷当时在茶馆的场景和老舍的三幕剧《茶馆》做对比,《茶馆》里写着“莫谈国事”的标语,一大群人都安安静静地端着茶杯各个喝着自己的茶,而爷爷的“茶馆”这边却是大家对“国事”各抒己见的热闹场景,想想真的是有趣。

记得过了一两天来电了,终于能看到电视了,打开电视发现所有的频道都放的是一个画面!!里面的场景记得大多与担架、救援人员、救灾物质、废墟等等相关。我清晰的记得有个场景是一位父亲举着一叠奖状向镜头展示,然后哭着说道:全是三好学生。。。父亲一边哭一边看着已经永远离开了自己的女儿。还有那位母亲的那条短信:“亲爱的宝贝,如果你能活着,一定要记住我爱你”。看惯了生离死别的医生却在这一刻落泪了,手机传递着,每个看到短信的人都落泪了。。。看到了太多太多的感动,在感动的同时,也感慨生命的脆弱。
活着真好。

太多太多的感动一时间无法全部诉说。。。

这次地震无疑给自己留下了很多阴影,初中的时候一台压路机从教室外边路过,整个房子微微颤抖都特别特别的害怕、有时候谁在桌子边抖抖腿也感觉特别的害怕。自己从来不敢去做 类似蹦极、过山车之类的事,上次去走玻璃栈道也是战战兢兢的。有的同学说我怕死呀?我并不怕死,只是对生命敬畏。

十年过去了,岁月变迁太快,爷爷和他撑起了家庭的帐篷已不再,心里留下的只有诸多的怀念。上天很眷顾我,我也很珍惜,珍惜自己的家人,珍惜每一份情。

最后也感谢各位以前对我们四川人民的帮助,滴水之恩,永远铭记!

愿逝者安息,生者如斯🙏

愿逝者安息,生者如斯

---2018/05/12 00:28

程序员学习之路--小白成长记(网站网页篇)

时间飞逝,一名程序员已经由一脸茫然渐渐地懂得了很多东西。

在很久很久以前,一名少年正思索着一些东西,他发现他对网站很有兴趣,可是他不知道该怎么做,于是百度了起来,开始慢慢地了解了起来。

开始渐渐地明白了网页原来是用html代码写出来的,去搜了很多教程,都是从最简单的显示Hello World开始,他发现很多教程老师总是用记事本或者Notepad++新建一个txt文件,然后把后缀改成html,然后点击一下这个html文件,哇!浏览器就显示出来HelloWorld,至于是为什么,也不知道。

再后来,学了很多标签像 input\div\span等等,他这时候发现就这样输出的效果好单调啊,于是他后来又知道了CSS,CSS属性就像是一罐罐彩色原料,它能让之前写的html变得绚丽起来。再后来他有一天想做一个表单,在一个input框里输入一个名字,然后想要在页面上显示:XX天下无敌,啊。。。这可怎么做啊,之前的html只能写什么输出什么,没有一点交互作用,于是在这样的情况下,他认识到了JavaScript,他学会了js的一些小语法,alert(‘XX天下无敌’);js就像是一个个灵活的小部件,他能让页面转动起来。他好像开始对编程有兴趣了。

他一如既往的用记事本一个一个字母瞧着html css js代码,总感觉这样子太慢了,去百度搜了一下:网页编辑器。于是Dreamweaver 进入了它的世界,它发现这个编辑器真是爽,打一个“,这简直是大大的节省了敲代码的时间啊,他开始在怀疑为什么老师之前要叫我们一个一个字母的敲,有这样的一个编辑器不是很爽么?只用打首字母就出来了,这件事在后来的后来他才明白。他发现Dreamweaver还可以不用写代码–只需要拖拖控件就能生成一堆代码出来,啊!那简直是好啊,我们直接拖控件就OK了?为什么我们要写代码?少年乐此不疲地拖拖玩玩,发现挺有意思的。

某一天,他有点很生气,因为他发现Dreamweaver拖的代码,页面布局总是不是他想象的那样子,界面总是乱动,做一些简单的东西,拖一拖还行,稍微复杂一点的就哭了,这并不能满足他。他又开始去学习,怎么才能做一个好网站,那时他还没有能够理解到网站和网页的区别。他开始稍微系统一点的去学学前端了,他认识到了Bootstrap,CSS是一罐罐彩色原料的话,它需要对原料色进行调色才能到达最好的效果,然后一笔一笔地去涂好html。Bootstrap更像是一只只彩色笔,它把颜色已经调好了,只需要直接去使用,大大地节约了书写代码的时间。他也认识了JQuery这个打着“write Less,Do More”旗号的库,这个库也真的是申请了,简简单单的一两行代码可以做好多好多的事。

现在它的界面逐渐变得漂亮了起来,它发现页面感觉太枯燥了,界面连个动的东西都没有,他说我要做个动态网站,我要让页面有绚丽的动画起来。傻傻的少年,天真的认为动态网站就是网页有动画的效果那就是动态,于是他去学了学HTML5、CSS3,一下子页面炫酷多了。他开始迷上了网页编程。

他开始对同学说:我最近做了个网页,同学们:哇,可以给我们看看么?他:啊?怎么给你看看?把你的网址给我呗。。。他回到家又开始琢磨了起来,怎么才能把自己的网页通过网址给别人看呢?噢。需要一个服务器,于是他稀里糊涂地去在腾讯云租了个几十块钱一个月的服务器,最开始的他一脸懵逼啊,这个怎么玩,一大堆乱七八糟的东西。东搞西搞,他了解到了Linux操作系统,懂得了一些操作命令,终于配置好了一个Centos 6.5系统的服务器系统,用FlashXP将自己做的网页传了上去,他访问一下服务器的IP地址 123.2XX.24.1XX,啊哈,看到了自己做的页面展示在了网页上,但是他发现,为什么别人都是什么http://www.xxx.com进去呀,我的为什么是数字,于是他明白了要去租个域名,将域名解析到服务器上。。。几经周转,当他输入http://www.xxxxx.com弹出了他的网页,并将这个网址分享给朋友,他心里成就感满满。

他的网页是在页面上显示一些小笑话,但是久而久之,他发现每次要录入一个新笑话都得把同样的代码复制一遍,然后把域里面的数据再替换成新的内容。这样下去可怎么了得,万一以后录入了上百上千个笑话,那这个html文件岂不是有上千上万行咯?那才是真正的笑话呢。于是引入了数据库,得把内容录入到数据库,然后再利用后台文件将数据库的内容读取出来。对于数据库,他学了MySQL以及和MySQL天生是一对的PHP!!!

因为开始用PHP写后台脚本了,他发现Dreamweaver体验真的不太好,于是接触到了宇宙最强 IDE公司JetBrains的产品–Phpstrom。要运行php文件那得搭建个php运行环境呢,MAMP(Mac Apach Mysql Php)简直不能太好用,就下载一个这个安装好就好了,不用单独去安装其他什么的,在Windows 系统上装个WAMP,在Linux系统上装个LAMP就OK了。万事俱全,只欠开始敲代码。

他开始在html文件中加入了php脚本,将原来的重复的代码使用foreach循环将读出来的数据打印出来,这下页面精致了很多,不过页面由html改成了php,他似乎能理解动态页面和静态页面的区别了,动态页面原来是指数据的动态。就这样,他利用数据库的CRUD(Create Read Update Delete)貌似好像能做很多很多事情了,他学会提交表单到数据库,并且他知道了每次数据提交都是通过GET或者POST提交到一个后缀为php的后台文件里面,然后后台获取到GET或者POST请求数据,再做一些处理最后再返回处理结果。他发现每次提交一次数据之后页面都会跑到php处理文件里面去,然后再在里面写返回之前提交页面的代码,这样感觉实在是太不友好了,它开始接触到了一个新的技术–Ajax,再利用利用json数据格式进行前后端交互,使得异步操作这东西说起来就那么高大上,放JQuery里面就几行代码都事情。她似乎对这门技术越来越热爱。

他某一天发现自己的数据库被人更改了,这是为什么呀?没人知道我的密码呀?被黑客攻击了?几经周折,他在网站安全一块进行了相关探索,原来啊自己被SQL注入了,自己写的简单语句被人类似于“select * from admin where user=’’or ‘a’=’a’ and passwd=’’or ‘a’=’a’”最简单的SQL注入了,他开始想办法将SQL语句写得更安全一点,利用面向对象的思想将查询语句封装起来。除此之外他又顺带了解了XSS、CSRF攻击以及HTTP和HTTPS的区别,这样使得页面安全性提高了起来。

他写的网页文件越来越多了,但都是写小项目,就几个php文件搞定了。他看到了网上有个叫翁天信少年写了他自己的个人博客,那博客真的是棒不,幻想自己也能做一个,说干就干。他从网上找了个模板,解压出来,惊呆了!!怎么这么多html文件!这样一个一个去还去写,简直要哭啊!在这种情况下,他学习到了MVC三层分离思想,将数据与视图通过控制器结合起来。又引入了基于MVC的ThinkPHP框架,加上thinkphp模块化的设计让整个项目逻辑思路特别的清晰明了,程序的耦合性大大降低,因为框架的封装性也使前面的安全问题减少了不少。

通过git命令安装好了Thinkphp框架,然后自己写了个后台管理系统,管理自己的博客文章以及留言,他发现每次进自己的管理系统都要输入用户名和密码实在是有点麻烦,于是又去了解到了Session和Cookie机制,将数据保存在浏览器以及服务器上,这样使得服务器能够认识他,就不需要每次再输入密码和用户名了。

于是他沉浸在Thinkphp的世界里,过了没多久个人博客终于搭建完了。网上有人对他的代码设计很感兴趣问能不能分享一下他的源代码,最初他直接把代码拷贝发送给别人,可是问的人多了,总是这样做实在是太麻烦。于是他认识了Github这个新朋友,将代码托管上去,本地又配置好了其版本控制,通过Commit Pull Push使得代码管理似乎更容易一些了。

2017年710随笔 于嘉兴-福州列车

此刻正在前往福州的火车上,在长达十五个小时的绿皮车时间里,真的让人很难受。想睡觉,可是又不能好好地睡,现在很累很困,却睡不着,所以在这漫长的夜里面陷入深深地思考。

站在窗前,看到窗外的风景变幻多端,偶尔点点灯光、偶尔路灯透亮、偶尔通过窗户看到的只是自己的面孔。看着玻璃里面阴影的自己,头前已经有一两根白头发了,索性拔掉,因为我觉得我还很年轻,这种东西与我无关。

二十一年走过来了,想想多么的可怕,二十一年了呀!这是一个多么吓人的时间。或许是长大了,对于一些实物关注的东西也不一样了,二十一年父母也从一两根白头发过度到了半头白发,貌似就只是发生在这几年,父母老的速度感觉已经比之前更快了。现在每次再见到父母,再也没有以前那种憧憬与期盼了,更多的是对他们的心疼与内心的愧疚。

还有一年多就要出去工作了,既憧憬,又彷徨。憧憬的是能够走出学校了,能自己挣钱了,自己挣的钱再怎么花也没有像现在这样心疼,或许可以做更多以前因为没有足够的钱可以做到的事,只是怕再也没有以前那样的心;彷徨于自己要像个大人一样活着了,有些事再也不会因为你是小孩,因为你是学生而同情你可怜你;彷徨于要为自己的以后的人生想办法了,面对工作、对象、父母,又该如果去处理这些问题呢?

工作,用兴趣去劳动。如何选择?喜欢编程这个东西,因为当看到自己一行行写出来的代码转化为想要的效果的时候,是一种多么有成就感的事情。用代码去解决一些问题,也是一种乐事。可是…难道要敲一辈子代码么?难道要给别人打一辈子的工么?

对象,以后要陪一辈子的人。何去寻觅?她可以什么都不做,但是他能在我最苦闷的时候来安慰我;能在我得意的时候,和我一起分享;不管我有多穷,而不会看不起我不管我我做什么事,她都能支持;不管发生什么,她都能陪在我身边。这样的女人,我宁愿累死也愿意会为她努力。
一个人,一辈子。炊烟起了,我在门口等你;夕阳下了,我在山边等你;叶子黄了,我在树下等你;月儿弯了,我在十五等你;细雨来了,我在伞下等你;流水冻了,我在河畔等你;生命累了,我在天堂等你;我们老了,我在来生等你。无论如何我和她,她和我都能够一直在一起。

父母,一直支持我们的人。如何回报?或许一个好一点的工作,不错的对象,是对他们最大的回报。父母最大的期盼就是希望子女能够过得好,拼命地去挣钱,可却苦了自己。处处节约,只希望子女能过得好。对于我们,他们付出太多了。也许他们经常说我们这不行那不行,可是心里却很是因为我们而自豪。

此刻好冷,一件T恤加衬衫伴随一条阳光牛仔小短裤完全抵挡不住半夜这咄咄逼人的寒气,让人冷得发抖,可却并没有多余的衣服了,只能默默地扛着。生活,不也如此么?
回家的火车因为天气原因而晚点了,在听到这则通知时,你也只能发一下牢骚,也只能去默默接受这个事实;心爱的球队这场比赛输了,在终场哨声响起时,你也只能作几声叹息,也只能去默默接受这个结果。期待已久的电影让你失望了,在荧幕开始呈现演员表的时候,你也只能扫兴而归,也只能默默接受这样的一个故事情节。

有些事情无法被我们所改变,但唯一可以改变的是心态。心态好,或许可以让一个人活得并不那么累。
回家的火车因为天气而晚点了,我们或许可以这样想:还好还好只是晚点一小时,并不是晚点两个小时;心爱的球队这场比赛输了,或许我们可以这样想:实力很对面差距本来就很大的,不过比分是0:1不是0:3还是。能接受的;期待已久的电影让你失望了,或许我们可以这样想:和女朋友出来看电影的,能和她在一起这样一两个小时,我已经很开心了。

想起那句烂大街的话:生活不止眼前的苟且,还有诗和远方的田野。放这里再适合不过了。能够:”宠辱不惊,看庭前花开花落;去留无意,望天上云卷云舒”的人,我想应该也过得很幸福。

2017年07月10日 03:13

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×