快速入門GreenDao框架并實現增刪改查案例

TimothyFuen 9年前發布 | 64K 次閱讀 Android Android開發 移動開發 greenDAO

大家的項目中不可避免的使用到SQLite,為此我們要花費心思編寫一個增刪改查框架。而一個好的ORM框架則能夠給我們帶來極大的方便,今天給大家講解一個非常火熱的ORM-GreenDao。

基本概念

GreenDao官網地址:http://greenrobot.org/greendao/

官網對GreenDao的介紹:

       greenDAO is an open source library for Android providing an easy-to-use interface to SQLite to help developers handle data efficiently – relieving developers from dealing with low-level database stuff and saving development time. SQLite is an awesome embedded relational database. Still, writing SQL and parsing query results are quite tedious and time-consuming tasks. greenDAO frees you from these by mapping Java objects to database tables (often called ORM). This way you can store, update, delete, and query for Java objects using a simple object oriented API.

簡單的說就是:greenDAO 是一個將對象映射到 SQLite 數據庫中的輕量且快速的 ORM 解決方案。

這里寫圖片描述

greenDAO 設計的主要目標

  • 一個精簡的庫
  • 性能最大化
  • 內存開銷最小化
  • 易于使用的 APIs
  • 對 Android 進行高度優化

greenDAO 設計的主要特點

  • greenDAO 性能遠遠高于同類的 ORMLite,具體測試結果可見官網
  • greenDAO 支持 protocol buffer(protobuf) 協議數據的直接存儲,如果你通過 protobuf
    協議與服務器交互,將不需要任何的映射。
  • 與 ORMLite 等使用注解方式的 ORM 框架不同,greenDAO 使用「Code
    generation」的方式,這也是其性能能大幅提升的原因。

Dao項目代碼生成

這里寫圖片描述

核心類介紹

這里寫圖片描述

DaoMaster:一看名字就知道它是Dao中的最大的官了。它保存了sqlitedatebase對象以及操作DAO classes(注意:不是對象)。其提供了一些創建和刪除table的靜態方法,其內部類OpenHelper和DevOpenHelper實現了SQLiteOpenHelper并創建數據庫的框架。
DaoSession:會話層。操作具體的DAO對象(注意:是對象),比如各種getter方法。
XXXDao:實際生成的某某DAO類,通常對應具體的java類,比如NoteDao等。其有更多的權限和方法來操作數據庫元素。
XXXEntity:持久的實體對象。通常代表了一個數據庫row的標準java properties。

了解了基本概念后我們開始動手完成一個增刪改查的項目案例。

實現過程

1.在 Android 工程中配置「greenDao Generator」模塊

在 .src/main 目錄下新建一個與 java 同層級的「java-gen」目錄,用于存放由 greenDAO 生成的 Bean、DAO、DaoMaster、DaoSession 等類。

這里寫圖片描述

這里寫圖片描述

配置 Android 工程(app)的 build.gradle,如圖分別添加 sourceSets 與 dependencies

這里寫圖片描述

sourceSets {
        main {
            java.srcDirs = ['src/main/java', 'src/main/java-gen']
        }
    }
compile 'de.greenrobot:greendao:1.3.7'

2.新建「greenDAO Generator」模塊 (純 Java 工程)

通過 File -> New -> New Module -> Java Library -> 填寫相應的包名與類名 -> Finish

這里寫圖片描述

這里寫圖片描述

配置 castielgreendaolb 工程的 build.gradle,添加 dependencie

這里寫圖片描述

注意主句話,配置輸出路徑

def outputDir = "../app/src/main/java-gen"

接著,編寫 CastielGreenDao類,注意: 我們的 Java 工程只有一個類,它的內容決定了「GreenDao Generator」的輸出,你可以在這個類中通過對象、關系等創建數據庫結構,下面我將以注釋的形式詳細講解代碼內容。

這里寫圖片描述

package com.castiel.dao;

import de.greenrobot.daogenerator.DaoGenerator;
import de.greenrobot.daogenerator.Entity;
import de.greenrobot.daogenerator.Schema;

public class CastielGreenDao {
    public static void main(String args[]) throws Exception {
        // 創建了一個用于添加實體(Entity)的模式(Schema)對象。
        // 兩個參數分別代表:數據庫版本號與自動生成代碼的包路徑。
        Schema schema = new Schema(1, "greendao");
        schema.setDefaultJavaPackageDao("com.castiel.dao");

        // 一旦你擁有了一個 Schema 對象后,你便可以使用它添加實體(Entities)了。
        addNote(schema);

        // 最后我們將使用 DAOGenerator 類的 generateAll() 方法自動生成代碼,此處你需要根據自己的情況更改輸出目錄(既之前創建的 java-gen)。
        new DaoGenerator().generateAll(schema, args[0]);
    }

    /** * @param schema */
    private static void addNote(Schema schema) {
        // 一個實體(類)就關聯到數據庫中的一張表,此處表名為「Student」(既類名)
        Entity note = schema.addEntity("Student");
        // 你也可以重新給表命名
        // note.setTableName("Student2");

        // greenDAO 會自動根據實體類的屬性值來創建表字段,并賦予默認值
        // 接下來你便可以設置表中的字段:
        // 與在 Java 中使用駝峰命名法不同,默認數據庫中的命名是使用大寫和下劃線來分割單詞的。
        note.addIdProperty();
        note.addStringProperty("sName").notNull();
        note.addStringProperty("sAge");
        note.addStringProperty("sSex");
        note.addStringProperty("sClass");
    }
}

3.生成 DAO 文件(數據庫)

執行 generator 工程,如一切正常,你將會在控制臺看到如下日志,并且在主工程「java-gen」下會發現生成了DaoMaster、DaoSession、StudentDao、Student共4個類文件。

greenDAO Generator
Copyright 2011-2013 Markus Junginger, greenrobot.de. Licensed under GPL V3.
This program comes with ABSOLUTELY NO WARRANTY
Processing schema version 1...
Written C:\Users\huangshuai\AndroidStudioProjects\CastielGreenDao\app\src\main\java-gen\com\castiel\dao\StudentDao.java
Written C:\Users\huangshuai\AndroidStudioProjects\CastielGreenDao\app\src\main\java-gen\greendao\Student.java
Written C:\Users\huangshuai\AndroidStudioProjects\CastielGreenDao\app\src\main\java-gen\com\castiel\dao\DaoMaster.java
Written C:\Users\huangshuai\AndroidStudioProjects\CastielGreenDao\app\src\main\java-gen\com\castiel\dao\DaoSession.java
Processed 1 entities in 153ms

BUILD SUCCESSFUL

構建Android項目實現增刪改查

先來張項目結構全景圖

這里寫圖片描述

再來張項目效果圖

這里寫圖片描述

在官網中,提供了一段核心初始化代碼,推薦將該段代碼放在Application中

helper = new DaoMaster.DevOpenHelper( this, "notes-db", null);
db = helper.getWritableDatabase();
daoMaster = new DaoMaster(db);
daoSession = daoMaster.newSession();
noteDao = daoSession.getNoteDao();

即:在自己的Application中先創建了一個SQLiteOpenHelper并創建連接到一個具體數據庫;再根據具體的datebase創建一個master對象用于;最后通過master創建一個數據庫的會話操作。

package com.castiel.dao;

import android.app.Application;
import android.database.sqlite.SQLiteDatabase;

/** * Created by huangshuai on 2016/5/11. * Email:huangshuai@wooyun.org */
public class BaseApplication extends Application {
    public DaoSession daoSession;
    public SQLiteDatabase db;
    public DaoMaster.DevOpenHelper helper;
    public DaoMaster daoMaster;

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

    private void setupDatabase() {
        // 通過 DaoMaster 的內部類 DevOpenHelper,你可以得到一個便利的 SQLiteOpenHelper 對象。
        // 可能你已經注意到了,你并不需要去編寫「CREATE TABLE」這樣的 SQL 語句,因為 greenDAO 已經幫你做了。
        // 注意:默認的 DaoMaster.DevOpenHelper 會在數據庫升級時,刪除所有的表,意味著這將導致數據的丟失。
        // 所以,在正式的項目中,你還應該做一層封裝,來實現數據庫的安全升級。
        helper = new DaoMaster.DevOpenHelper(this, "student", null);
        db = helper.getWritableDatabase();
        // 注意:該數據庫連接屬于 DaoMaster,所以多個 Session 指的是相同的數據庫連接。
        daoMaster = new DaoMaster(db);
        daoSession = daoMaster.newSession();
    }

    public DaoSession getDaoSession() {
        return daoSession;
    }

    public SQLiteDatabase getDb() {
        return db;
    }

}

Activity具體實現

package com.castiel.dao;

import android.app.Activity;
import android.app.Dialog;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.Toast;

import java.util.List;

import de.greenrobot.dao.query.Query;
import greendao.Student;

/** * Created by huangshuai on 2016/5/11. * Email:huangshuai@wooyun.org * GreenDao增刪改查實例 */
public class CastielActivity extends Activity implements StudentAdapter.AdapterEnterCallBack{
    // 初始化組件
    public static final String TAG = "WY";
    private List<Student> listStudent;
    private ListView list;
    private EditText etQureyName;
    StudentAdapter studentAdapter;
    private Button btnAdd,btnQurey;
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main_dao);
        list = (ListView) findViewById(R.id.dao_list);
        btnAdd = (Button) findViewById(R.id.btn_add);
        btnQurey = (Button) findViewById(R.id.btn_query);
        etQureyName = (EditText) findViewById(R.id.edit_query);
        listStudent = getStudentDao().loadAll();// 查詢全部數據操作
        studentAdapter = new StudentAdapter(CastielActivity.this,listStudent);

        list.setAdapter(studentAdapter);
        studentAdapter.setCallback(this);

        btnAdd.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                dialogAdd();
            }
        });

        btnQurey.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String curCondition = etQureyName.getText().toString().trim();
                if (TextUtils.isEmpty(curCondition)) {
                    Toast.makeText(CastielActivity.this,"查詢條件不能為空",Toast.LENGTH_SHORT).show();
                } else {
                    // Query 類代表了一個可以被重復執行的查詢
                    Query query = getStudentDao().queryBuilder()
                            .where(StudentDao.Properties.SName.eq(curCondition))
                            .build();
                    // 查詢結果以 List 返回
                    List students = query.list();
                    Toast.makeText(CastielActivity.this,"有" + students.size() + "個條件符合",Toast.LENGTH_SHORT).show();
                }
                etQureyName.setText("");// 查詢后重置條件
            }
        });
    }

    /** * 通過 BaseApplication 類提供的 getDaoSession() 獲取具體 Dao * @return */
    private StudentDao getStudentDao() {
        return ((BaseApplication) this.getApplicationContext()).getDaoSession().getStudentDao();
    }

    /** * 通過 BaseApplication 類提供的 getDb() 獲取具體 db * @return */
    private SQLiteDatabase getDb() {
        return ((BaseApplication) this.getApplicationContext()).getDb();
    }

    /** * 添加操作彈窗 */
    protected void dialogAdd() {
        final Dialog dataDialog = new Dialog(CastielActivity.this,R.style.myDialogTheme);
        LayoutInflater curInfnfalater = LayoutInflater.from(this);
        View view = curInfnfalater.inflate(R.layout.my_dialog, null);
        Button b_ok,b_cancle;
        final EditText etName,etSex,etClass,etAge;
        etName = (EditText) view.findViewById(R.id.edit_name);
        etSex = (EditText) view.findViewById(R.id.edit_sex);
        etClass = (EditText) view.findViewById(R.id.edit_class);
        etAge = (EditText) view.findViewById(R.id.edit_age);


        b_ok = (Button) view.findViewById(R.id.btn_ok);
        b_ok.setOnClickListener(new Button.OnClickListener() {
            public void onClick(View v) {
                Student ss = new Student(null,etName.getText().toString().trim(),etAge.getText().toString().trim(),etSex.getText().toString().trim(),etClass.getText().toString().trim());
                getStudentDao().insert(ss);
                studentAdapter.addData(ss);
                dataDialog.dismiss();
            }
        });
        b_cancle = (Button) view.findViewById(R.id.btn_cancel);
        b_cancle.setOnClickListener(new Button.OnClickListener() {
            public void onClick(View v) {
                dataDialog.dismiss();
            }
        });


        dataDialog.setContentView(view);
        dataDialog.show();
    }

    /** * 修改操作彈窗 */
    protected void dialogModify(final Student curBean) {
        final Dialog dataDialog = new Dialog(CastielActivity.this,R.style.myDialogTheme);
        LayoutInflater curInfnfalater = LayoutInflater.from(this);
        View view = curInfnfalater.inflate(R.layout.my_dialog, null);
        Button b_ok,b_cancle;
        final EditText etName,etSex,etClass,etAge;
        etName = (EditText) view.findViewById(R.id.edit_name);
        etSex = (EditText) view.findViewById(R.id.edit_sex);
        etClass = (EditText) view.findViewById(R.id.edit_class);
        etAge = (EditText) view.findViewById(R.id.edit_age);
        etName.setText(curBean.getSName());
        etSex.setText(curBean.getSSex());
        etClass.setText(curBean.getSClass());
        etAge.setText(curBean.getSAge());

        b_ok = (Button) view.findViewById(R.id.btn_ok);
        b_ok.setOnClickListener(new Button.OnClickListener() {
            public void onClick(View v) {
                Student ss = new Student(curBean.getId(),etName.getText().toString().trim(),etAge.getText().toString().trim(),etSex.getText().toString().trim(),etClass.getText().toString().trim());
                getStudentDao().deleteByKey(curBean.getId());
                getStudentDao().insert(ss);
                studentAdapter.setData(getStudentDao().loadAll());
                dataDialog.dismiss();
            }
        });
        b_cancle = (Button) view.findViewById(R.id.btn_cancel);
        b_cancle.setOnClickListener(new Button.OnClickListener() {
            public void onClick(View v) {
                dataDialog.dismiss();
            }
        });


        dataDialog.setContentView(view);
        dataDialog.show();
    }

    /**菜單彈窗 */
    protected void dialogMenu(final Student curBean) {
        final Dialog dataDialog = new Dialog(CastielActivity.this,R.style.myDialogTheme);
        LayoutInflater curInfnfalater = LayoutInflater.from(this);
        View view = curInfnfalater.inflate(R.layout.item_dialog, null);
        Button b_modify,b_del;

        b_modify = (Button) view.findViewById(R.id.btn_modify);
        b_modify.setOnClickListener(new Button.OnClickListener() {
            public void onClick(View v) {
                dialogModify(curBean);
                dataDialog.dismiss();
            }
        });
        b_del = (Button) view.findViewById(R.id.btn_del);
        b_del.setOnClickListener(new Button.OnClickListener() {
            public void onClick(View v) {
                // 根據Id刪除對應數據
                getStudentDao().deleteByKey(curBean.getId());
                studentAdapter.setData(getStudentDao().loadAll());
                dataDialog.dismiss();
            }
        });


        dataDialog.setContentView(view);
        dataDialog.show();
    }

    @Override
    public void onEnterClick(Student bean) {
        dialogMenu(bean);
    }
}

列表相關Adapter

package com.castiel.dao;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.LinearLayout;
import android.widget.TextView;

import java.util.List;

import greendao.Student;

/** * Created by huangshuai on 2016/5/11. * Email:huangshuai@wooyun.org * Student列表的Adapter */
public class StudentAdapter extends BaseAdapter {
    private AdapterEnterCallBack callback;
    private LayoutInflater inflater;
    private List<Student> list_student;

    public StudentAdapter(Context context, List<Student> list_student) {
        this.inflater = LayoutInflater.from(context);
        this.list_student = list_student;
    }

    @Override
    public int getCount() {
        return list_student.size();
    }

    @Override
    public Object getItem(int position) {
        return list_student.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(final int position, View convertView, ViewGroup parent) {
        StudentViewHodler sv;
        if (convertView == null) {
            sv = new StudentViewHodler();
            convertView = inflater.inflate(R.layout.list_item, null);
            sv.uAge = (TextView) convertView.findViewById(R.id.tv_age);
            sv.uName = (TextView) convertView.findViewById(R.id.tv_name);
            sv.uSex = (TextView) convertView.findViewById(R.id.tv_sex);
            sv.uClass = (TextView) convertView.findViewById(R.id.tv_class);
            sv.litem = (LinearLayout) convertView.findViewById(R.id.ll_item);

            convertView.setTag(sv);
        } else {
            sv = (StudentViewHodler) convertView.getTag();
        }

        sv.uSex.setText(list_student.get(position).getSSex());
        sv.uName.setText(list_student.get(position).getSName());
        sv.uAge.setText(list_student.get(position).getSAge());
        sv.uClass.setText(list_student.get(position).getSClass());
        sv.litem.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (null != callback) {
                    callback.onEnterClick(list_student.get(position));
                }
            }
        });
        return convertView;
    }

    public void addData(Student data) {
        if (data != null) {
            this.list_student.add(data);
            notifyDataSetChanged();
        }
    }

    public void setData(List<Student> data) {
        if (data != null) {
            list_student.clear();
            list_student.addAll(data);
            notifyDataSetChanged();
        }
    }

    public class StudentViewHodler {
        TextView uName;
        TextView uSex;
        TextView uAge;
        TextView uClass;
        LinearLayout litem;
    }

    public interface AdapterEnterCallBack {
        void onEnterClick(Student bean);
    }

    public void setCallback(AdapterEnterCallBack callback) {
        this.callback = callback;
    }

}

布局文件實現

item_dialog.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="60dp" android:background="@android:color/white" android:orientation="horizontal">

    <Button  android:id="@+id/btn_del" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="刪除" />

    <Button  android:id="@+id/btn_modify" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="修改" />
</LinearLayout>

list_item.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/ll_item" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal">

    <TextView  android:id="@+id/tv_name" android:layout_width="0dp" android:layout_height="24dp" android:layout_weight="1" android:textSize="22sp" android:text="姓名" />

    <TextView  android:id="@+id/tv_age" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:textSize="22sp" android:text="年齡"/>

    <TextView  android:id="@+id/tv_sex" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:textSize="22sp" android:text="性別"/>

    <TextView  android:id="@+id/tv_class" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:textSize="22sp" android:text="班級"/>

</LinearLayout>

main_dao.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical">

    <LinearLayout  android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal">

        <Button  android:id="@+id/btn_add" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="增加數據" />

        <Button  android:id="@+id/btn_query" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="姓名查詢" />

        <EditText  android:id="@+id/edit_query" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:hint="輸入姓名" android:textColor="@android:color/black" />
    </LinearLayout>

    <LinearLayout  android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal">

        <TextView  android:id="@+id/tv_name" android:layout_width="0dp" android:layout_height="24dp" android:layout_weight="1" android:textSize="18sp" android:text="姓名" />

        <TextView  android:id="@+id/tv_age" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:textSize="18sp" android:text="年齡"/>

        <TextView  android:id="@+id/tv_sex" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:textSize="18sp" android:text="性別"/>

        <TextView  android:id="@+id/tv_class" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:textSize="18sp" android:text="班級"/>

    </LinearLayout>


    <ListView  android:id="@+id/dao_list" android:layout_width="match_parent" android:layout_height="wrap_content"/>
</LinearLayout>

my_dialog.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@android:color/white" android:orientation="vertical">

    <LinearLayout  android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical">

        <LinearLayout  android:layout_width="match_parent" android:layout_height="wrap_content">

            <LinearLayout  android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1">

                <TextView  android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:gravity="center" android:text="姓名:" />

                <EditText  android:id="@+id/edit_name" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:textColor="@android:color/black" />
            </LinearLayout>

            <LinearLayout  android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1">

                <TextView  android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:gravity="center" android:text="性別:" />

                <EditText  android:id="@+id/edit_sex" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:textColor="@android:color/black" />
            </LinearLayout>
        </LinearLayout>

        <LinearLayout  android:layout_width="match_parent" android:layout_height="wrap_content">

            <LinearLayout  android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1">

                <TextView  android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:gravity="center" android:text="班級:" />

                <EditText  android:id="@+id/edit_class" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:textColor="@android:color/black" />
            </LinearLayout>

            <LinearLayout  android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1">

                <TextView  android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:gravity="center" android:text="年齡:" />

                <EditText  android:id="@+id/edit_age" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:textColor="@android:color/black" />
            </LinearLayout>
        </LinearLayout>

    </LinearLayout>

    <LinearLayout  android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal">

        <Button  android:id="@+id/btn_ok" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="確認" />

        <Button  android:id="@+id/btn_cancel" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="取消" />
    </LinearLayout>
</LinearLayout>

運行程序,執行增刪改查

這里寫圖片描述

這里寫圖片描述

項目源碼鏈接:http://download.csdn.net/detail/mynameishuangshuai/9518322

參考鏈接:http://itangqi.me/2015/07/26/orm-greendao-summary/

via: http://blog.csdn.net/mynameishuangshuai/article/details/51386402

 本文由用戶 TimothyFuen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!