关于使用LruCache缓存你想缓存的数据

今天我们来一起学习一下缓存技术,相信大家做开发的时候都知道请求网络数据的重要,但是有一些只用请求一次就过时性的消息比如某些新闻信息,如果我们每次进入新闻界面就从新从网络上获取势必会给用户带来不好的体验,所以我们需要缓存技术来帮我们解决这一问题。
1,LruCache介绍
核心的类是LruCache (此类在android-support-v4的包中提供) 。这个类非常适合用来缓存图片,它的主要算法原理是把最近使用的对象用强引用存储在 LinkedHashMap 中,并且把最近最少使用的对象在缓存值达到预设定值之前从内存中移除。
在过去,我们经常会使用一种非常流行的内存缓存技术的实现,即软引用或弱引用 (SoftReference or WeakReference)。但是现在已经不再推荐使用这种方式了,因为从 Android 2.3 (API Level 9)开始,垃圾回收器会更倾向于回收持有软引用或弱引用的对象,这让软引用和弱引用变得不再可靠。另外,Android 3.0 (API Level 11)中,图片的数据会存储在本地的内存当中,因而无法用一种可预见的方式将其释放,这就有潜在的风险造成应用程序的内存溢出并崩溃。
2,LruCache使用 下面我们就来写一个简单的demo来学习LruCache,效果也是每次请求一次第二次直接从缓存中提取出来不用再次请求网络[?]

/**
     * 缓存json数据
     */
    private LruCache<integer, string=""> mJsonCache;
    /**
     * 缓存图片信息
     */
    private LruCache<integer, bitmap=""> mBitmapCache;
 
    public Util() {
        mJsonCache = new LruCache<integer, string="">(1 * 1024 * 1024);
        mBitmapCache = new LruCache<integer, bitmap="">(2 * 1024 * 1024);
    }
 
    /**
     * 添加进入缓存列表
     * 
     * @param key
     * @param value
     */
    public void addJsonLruCache(Integer key, String value) {
        mJsonCache.put(key, value);
    }
 
    public void addBitmapLruCache(Integer key, Bitmap value) {
        mBitmapCache.put(key, value);
    }
 
    /**
     * 从缓存列表中拿出来
     * 
     * @param key
     * @return
     */
    public String getJsonLruCache(Integer key) {
        return mJsonCache.get(key);
    }
 
    public Bitmap getBitmapLruCache(Integer key) {
        return mBitmapCache.get(key);
    }

可以看到我们准备缓存Bitmap与String,只需要拿到信息的时候put进缓存中,需要的时候get出来,是不是非常简单,我们为我们String分配了1m为我们的Bitmap分配了2m空间,这只是我们的demo为了简单这样使用,实际上我们应该更加详细的考虑到底应该为缓存分配多大的空间[?]

// 获取到可用内存的最大值,使用内存超出这个值会引起OutOfMemory异常。

// LruCache通过构造函数传入缓存值,以KB为单位。

int maxMemory = (int) (Runtime.getRuntime().maxMemory() /1024);

一般来说最大值的1/8左右就可以了。?

public class MainActivity extends Activity implements OnItemClickListener {
    private static final String LIST_DATA = http://api.yi18.net/top/list;
    private ListView mListView;
    private ArrayAdapter<string> mAdapter;
    private ArrayList<integer> mListId;
    private Util util;
 
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        util = new Util();
        mListView = (ListView) findViewById(R.id.list);
        mListId = new ArrayList<integer>();
        mAdapter = new ArrayAdapter<string>(this,
                android.R.layout.simple_list_item_1);
        mListView.setAdapter(mAdapter);
        mListView.setOnItemClickListener(this);
        new DownLoadJson().execute(LIST_DATA);
    }

这一段就是普通的请求数据添加到ListView中。

private void getJsonData(String json) {
        try {
            JSONObject jsonObject = new JSONObject(json);
            if (jsonObject.getBoolean(success)) {
                JSONArray jsonArray = jsonObject.getJSONArray(yi18);
                for (int i = 0; i < jsonArray.length(); i++) {
                    JSONObject jsonObject2 = (JSONObject) jsonArray.opt(i);
                    if (i < 5) {
                        mAdapter.add(jsonObject2.getString(title));
                        mListId.add(jsonObject2.getInt(id));
                    }
                }
            }
        } catch (JSONException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
 
    class DownLoadJson extends AsyncTask<string, string=""> {
 
        @Override
        protected String doInBackground(String... params) {
            return util.downLoadJson(params[0]);
        }
 
        @Override
        protected void onPostExecute(String result) {
            if (result != null) {
                getJsonData(result);
            }
        }
 
    }

data-cke-saved-src=/uploadfile/Collfiles/20150207/20150207084912191.png
我们就简单的取了前五条数据用来模拟我们的新闻,用的是热点热词的Api。

3,缓存

@Override
    public void onItemClick(AdapterView<!--?--> arg0, View arg1, int arg2, long arg3) {
        // TODO Auto-generated method stub
        String message = util.getJsonLruCache(mListId.get(arg2));
        Bitmap bitmap = util.getBitmapLruCache(mListId.get(arg2));
 
        if (message != null) {
            intentNewsInfo(arg2, message, bitmap);
        } else {
            intentNewsInfo(arg2, null, null);
        }
 
    }
 
    public void intentNewsInfo(int arg2, String message, Bitmap bitmap) {
        Intent intent = new Intent(MainActivity.this, NewsinfoActivity.class);
        intent.putExtra(message, message);
        intent.putExtra(bitmap, bitmap);
        intent.putExtra(index, arg2);
        intent.putExtra(id, mListId.get(arg2));
        startActivityForResult(intent, 100);
    }

可以看到我们这里先是查找缓存中是否存在数据如果存在直接传给新闻详情界面,如果没有则在第二个界面获取再传回来。

public class NewsinfoActivity extends Activity {
 
    private String NEWS_INFO = http://api.yi18.net/top/show?id=;
    private String imageRes[] = {
            http://d.hiphotos.baidu.com/image/h%3D360/sign=405b763459afa40f23c6c8db9b65038c/562c11dfa9ec8a13508c96e6f403918fa0ecc026.jpg,
            http://c.hiphotos.baidu.com/image/h%3D360/sign=798b4f82caea15ce5eeee60f86013a25/9c16fdfaaf51f3dece3f986397eef01f3a297923.jpg,
            http://f.hiphotos.baidu.com/image/h%3D360/sign=20a94e03940a304e4d22a6fce1c9a7c3/ac4bd11373f082028719ab3848fbfbedab641b29.jpg,
            http://b.hiphotos.baidu.com/image/h%3D360/sign=3a1af7349145d688bc02b4a294c37dab/4b90f603738da977c0f5b82cb351f8198718e3db.jpg,
            http://d.hiphotos.baidu.com/image/h%3D360/sign=75e596560f33874483c5297a610ed937/55e736d12f2eb9381891b2f4d6628535e5dd6f3c.jpg };
    private Intent intent;
    private Util util;
    private int newId, index;
    private ImageView imageView;
    private TextView textView;
    private Bitmap bitmap;
    private String message;
 
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_newsinfo);
        intent = getIntent();
        util = new Util();
        imageView = (ImageView) findViewById(R.id.image);
        textView = (TextView) findViewById(R.id.message);
        newId = intent.getExtras().getInt(id);
        index = intent.getExtras().getInt(index);
        if (intent.getExtras().getString(message) != null) {
            message = intent.getExtras().getString(message);
            bitmap = intent.getParcelableExtra(bitmap);
            textView.setText(Html.fromHtml(message));
            imageView.setImageBitmap(bitmap);
            Toast.makeText(this, 没有访问网络哦, 2000).show();
        } else {
            new DownLoadJson().execute(NEWS_INFO + newId);
            new DownLoadBitmap().execute(imageRes[index]);
            Toast.makeText(this, 访问网络哦, 2000).show();
        }
 
    }
 
    @Override
    public void onBackPressed() {
        Intent dataIntent = new Intent();
        dataIntent.putExtra(message, message);
        dataIntent.putExtra(bitmap, bitmap);
        dataIntent.putExtra(newId, newId);
        setResult(20, dataIntent);
        finish();
        super.onBackPressed();
    }
 
    private void getJsonData(String json) {
        try {
            JSONObject jsonObject = new JSONObject(json);
            if (jsonObject.getBoolean(success)) {
                JSONObject jsonObject2 = new JSONObject(
                        jsonObject.getString(yi18));
                message = jsonObject2.getString(message);
                textView.setText(Html.fromHtml(jsonObject2.getString(message)));
            }
        } catch (JSONException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
 
    class DownLoadJson extends AsyncTask<string, string=""> {
 
        @Override
        protected String doInBackground(String... params) {
            return util.downLoadJson(params[0]);
        }
 
        @Override
        protected void onPostExecute(String result) {
            if (result != null) {
                getJsonData(result);
            }
        }
 
    }
 
    class DownLoadBitmap extends AsyncTask<string, bitmap=""> {
 
        @Override
        protected Bitmap doInBackground(String... params) {
            // TODO Auto-generated method stub
            return util.downLoadBitmap(params[0]);
        }
 
        @Override
        protected void onPostExecute(Bitmap result) {
            if (result != null) {
                bitmap = result;
                imageView.setImageBitmap(result);
            }
        }
    }
 
}

这就比较清晰明白了,每次我们都把这个界面获取到的信息存到LruCache里面。

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    int newId = data.getExtras().getInt(newId);
    String message = data.getExtras().getString(message);
    Bitmap bitmap = data.getParcelableExtra(bitmap);
    util.addJsonLruCache(newId, message);
    util.addBitmapLruCache(newId, bitmap);
    super.onActivityResult(requestCode, resultCode, data);
}

4,效果


data-cke-saved-src=/uploadfile/Collfiles/20150207/20150207084914192.gif

5.Android LruCache源码介绍

package android.util;  
 
import java.util.LinkedHashMap;  
import java.util.Map;  
 
/** 
* A cache that holds strong references to a limited number of values. Each time 
* a value is accessed, it is moved to the head of a queue. When a value is 
* added to a full cache, the value at the end of that queue is evicted and may 
* become eligible for garbage collection. 
* Cache保存一个强引用来限制内容数量,每当Item被访问的时候,此Item就会移动到队列的头部。
* 当cache已满的时候加入新的item时,在队列尾部的item会被回收。
* <p>If your cached values hold resources that need to be explicitly released, 
* override {@link #entryRemoved}. 
* 如果你cache的某个值需要明确释放,重写entryRemoved()
* <p>If a cache miss should be computed on demand for the corresponding keys, 
* override {@link #create}. This simplifies the calling code, allowing it to 
* assume a value will always be returned, even when there's a cache miss. 
* 如果key相对应的item丢掉啦,重写create().这简化了调用代码,即使丢失了也总会返回。
* <p>By default, the cache size is measured in the number of entries. Override 
* {@link #sizeOf} to size the cache in different units. For example, this cache 
* is limited to 4MiB of bitmaps: 默认cache大小是测量的item的数量,重写sizeof计算不同item的
*  大小。
* <pre>   {@code 
*   int cacheSize = 4 * 1024 * 1024; // 4MiB 
*   LruCache<String, Bitmap> bitmapCache = new LruCache<String, Bitmap>(cacheSize) { 
*       protected int sizeOf(String key, Bitmap value) { 
*           return value.getByteCount(); 
*       } 
*   }}</pre> 
* 
* <p>This class is thread-safe. Perform multiple cache operations atomically by 
* synchronizing on the cache: <pre>   {@code 
*   synchronized (cache) { 
*     if (cache.get(key) == null) { 
*         cache.put(key, value); 
*     } 
*   }}</pre> 
* 
* <p>This class does not allow null to be used as a key or value. A return 
* value of null from {@link #get}, {@link #put} or {@link #remove} is 
* unambiguous: the key was not in the cache.
* 不允许key或者value为null
*  当get(),put(),remove()返回值为null时,key相应的项不在cache中
*/  
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;  //put的次数
   private int createCount;  //create的次数
   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);  
   }  
 
   /** 
    * Returns the value for {@code key} if it exists in the cache or can be 
    * created by {@code #create}. If a value was returned, it is moved to the 
    * head of the queue. This returns null if a value is not cached and cannot 
    * be created. 通过key返回相应的item,或者创建返回相应的item。相应的item会移动到队列的头部,
    * 如果item的value没有被cache或者不能被创建,则返回null。
    */  
   public final V get(K key) {  
       if (key == null) {  
           throw new NullPointerException("key == null");  
       }  
 
       V mapValue;  
       synchronized (this) {  
           mapValue = map.get(key);  
           if (mapValue != null) {  
               hitCount++;  //命中
               return mapValue;  
           }  
           missCount++;  //丢失
       }  
 
       /* 
        * Attempt to create a value. This may take a long time, and the map 
        * may be different when create() returns. If a conflicting value was 
        * added to the map while create() was working, we leave that value in 
        * the map and release the created value. 
        * 如果丢失了就试图创建一个item
        */  
 
       V createdValue = create(key);  
       if (createdValue == null) {  
           return null;  
       }  
 
       synchronized (this) {  
           createCount++;//创建++  
           mapValue = map.put(key, createdValue);  
 
           if (mapValue != null) {  
               // There was a conflict so undo that last put  
               //如果前面存在oldValue,那么撤销put() 
               map.put(key, mapValue);  
           } else {  
               size += safeSizeOf(key, createdValue);  
           }  
       }  
 
       if (mapValue != null) {  
           entryRemoved(false, key, createdValue, mapValue);  
           return mapValue;  
       } else {  
           trimToSize(maxSize);  
           return createdValue;  
       }  
   }  
 
   /** 
    * 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) {  //返回的先前的value值
               size -= safeSizeOf(key, previous);  
           }  
       }  
 
       if (previous != null) {  
           entryRemoved(false, key, previous, value);  
       }  
 
       trimToSize(maxSize);  
       return previous;  
   }  
 
   /** 
    * @param maxSize the maximum size of the cache before returning. May be -1 
    *     to evict even 0-sized elements. 
    *  清空cache空间
    */  
   private 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!");  
               }  
 
               if (size <= maxSize) {  
                   break;  
               }  
 
               Map.Entry<K, V> toEvict = map.eldest();  
               if (toEvict == null) {  
                   break;  
               }  
 
               key = toEvict.getKey();  
               value = toEvict.getValue();  
               map.remove(key);  
               size -= safeSizeOf(key, value);  
               evictionCount++;  
           }  
 
           entryRemoved(true, key, value, null);  
       }  
   }  
 
   /** 
    * Removes the entry for {@code key} if it exists. 
    * 删除key相应的cache项,返回相应的value
    * @return the previous value mapped by {@code key}. 
    */  
   public final V remove(K key) {  
       if (key == null) {  
           throw new NullPointerException("key == null");  
       }  
 
       V previous;  
       synchronized (this) {  
           previous = map.remove(key);  
           if (previous != null) {  
               size -= safeSizeOf(key, previous);  
           }  
       }  
 
       if (previous != null) {  
           entryRemoved(false, key, previous, null);  
       }  
 
       return previous;  
   }  
 
   /** 
    * Called for entries that have been evicted or removed. This method is 
    * invoked when a value is evicted to make space, removed by a call to 
    * {@link #remove}, or replaced by a call to {@link #put}. The default 
    * implementation does nothing. 
    * 当item被回收或者删掉时调用。改方法当value被回收释放存储空间时被remove调用,
    * 或者替换item值时put调用,默认实现什么都没做。
    * <p>The method is called without synchronization: other threads may 
    * access the cache while this method is executing. 
    * 
    * @param evicted true if the entry is being removed to make space, false 
    *     if the removal was caused by a {@link #put} or {@link #remove}. 
    * true---为释放空间被删除;false---put或remove导致
    * @param newValue the new value for {@code key}, if it exists. If non-null, 
    *     this removal was caused by a {@link #put}. Otherwise it was caused by 
    *     an eviction or a {@link #remove}. 
    */  
   protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {}  
 
   /** 
    * Called after a cache miss to compute a value for the corresponding key. 
    * Returns the computed value or null if no value can be computed. The 
    * default implementation returns null. 
    * 当某Item丢失时会调用到,返回计算的相应的value或者null
    * <p>The method is called without synchronization: other threads may 
    * access the cache while this method is executing. 
    * 
    * <p>If a value for {@code key} exists in the cache when this method 
    * returns, the created value will be released with {@link #entryRemoved} 
    * and discarded. This can occur when multiple threads request the same key 
    * at the same time (causing multiple values to be created), or when one 
    * thread calls {@link #put} while another is creating a value for the same 
    * key. 
    */  
   protected V create(K key) {  
       return null;  
   }  
 
   private int safeSizeOf(K key, V value) {  
       int result = sizeOf(key, value);  
       if (result < 0) {  
           throw new IllegalStateException("Negative size: " + key + "=" + value);  
       }  
       return result;  
   }  
 
   /** 
    * Returns the size of the entry for {@code key} and {@code value} in 
    * user-defined units.  The default implementation returns 1 so that size 
    * is the number of entries and max size is the maximum number of entries. 
    * 返回用户定义的item的大小,默认返回1代表item的数量,最大size就是最大item值
    * <p>An entry's size must not change while it is in the cache. 
    */  
   protected int sizeOf(K key, V value) {  
       return 1;  
   }  
 
   /** 
    * Clear the cache, calling {@link #entryRemoved} on each removed entry. 
    * 清空cacke
    */  
   public final void evictAll() {  
       trimToSize(-1); // -1 will evict 0-sized elements  
   }  
 
   /** 
    * For caches that do not override {@link #sizeOf}, this returns the number 
    * of entries in the cache. For all other caches, this returns the sum of 
    * the sizes of the entries in this cache. 
    */  
   public synchronized final int size() {  
       return size;  
   }  
 
   /** 
    * For caches that do not override {@link #sizeOf}, this returns the maximum 
    * number of entries in the cache. For all other caches, this returns the 
    * maximum sum of the sizes of the entries in this cache. 
    */  
   public synchronized final int maxSize() {  
       return maxSize;  
   }  
 
   /** 
    * Returns the number of times {@link #get} returned a value that was 
    * already present in the cache. 
    */  
   public synchronized final int hitCount() {  
       return hitCount;  
   }  
 
   /** 
    * Returns the number of times {@link #get} returned null or required a new 
    * value to be created. 
    */  
   public synchronized final int missCount() {  
       return missCount;  
   }  
 
   /** 
    * Returns the number of times {@link #create(Object)} returned a value. 
    */  
   public synchronized final int createCount() {  
       return createCount;  
   }  
 
   /** 
    * Returns the number of times {@link #put} was called. 
    */  
   public synchronized final int putCount() {  
       return putCount;  
   }  
 
   /** 
    * Returns the number of values that have been evicted. 
    * 返回被回收的数量
    */  
   public synchronized final int evictionCount() {  
       return evictionCount;  
   }  
 
   /** 
    * Returns a copy of the current contents of the cache, ordered from least 
    * recently accessed to most recently accessed. 返回当前cache的副本,从最近最少访问到最多访问
    */  
   public synchronized final Map<K, V> snapshot() {  
       return new LinkedHashMap<K, V>(map);  
   }  
 
   @Override public synchronized final String toString() {  
       int accesses = hitCount + missCount;  
       int hitPercent = accesses != 0 ? (100 * hitCount / accesses) : 0;  
       return String.format("LruCache[maxSize=%d,hits=%d,misses=%d,hitRate=%d%%]",  
               maxSize, hitCount, missCount, hitPercent);  
   }  
}  
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,398评论 25 707
  • (一)荷之韵 蓬下红鳞呈祥瑞, 碧上添花绘今宵。 谁将藕衫画出艺? 扶平淤泥做天娇。 (二)欣荷 清风扶绿水,稚画...
    书海沉香阅读 338评论 1 3
  • 我们在培养习惯时,总是在最初的时候动力十足,可没过几天,就把原来信誓旦旦准备坚持的事情抛到脑后了。过段时间,回想这...
    老孟的晨思录阅读 658评论 0 1
  • 寄一盏风花雪月里,无醉不成眠。 吹取浮生掠影,正称红面。 盛夏时节,浅风寂兮。 在凉城以北,默然神伤。 只因回眸那...
    木易小行星阅读 274评论 0 2