Unity 與 Android 互調(diào)用

Unity 項目中一些需要訪問安卓操作系統(tǒng)的功能缓艳,比如獲取電量校摩,wifi 狀態(tài)等,需要 Unity 啟動安卓系統(tǒng)的 BroadcastReceiver 監(jiān)聽狀態(tài)阶淘,并在狀態(tài)更新后通知到 Unity 界面衙吩。這就需要一種 Unity 與 Android 互相調(diào)用的機制,直觀地看就是 C# 與 Java 互相調(diào)用的方法舶治。

有 Unity 與 Android 互相調(diào)用需求的項目需要在兩個開發(fā)環(huán)境中同時進行分井,創(chuàng)建兩個工程车猬,這時就涉及到如何將兩個工程連接起來,有兩種方式來連接:

  • Android 工程生成 aar/jar 文件尺锚,復(fù)制到 Unity 工程中珠闰,最終使用 Unity 的 Build 機制生成 apk。
  • Unity 工程將所有內(nèi)容和代碼導(dǎo)出為一個 Android gradle 項目瘫辩,然后使用 Android Studio 打開項目進行開發(fā)伏嗜,最終使用 Android Studio 打包 apk。

對比一下兩者的優(yōu)缺點:

Unity 使用 jar/aar 庫 Unity 導(dǎo)出 gradle 項目
Unity 與 Android 依賴性 Unity 只依賴 Android 庫文件伐厌,分割清晰承绸,需要同步的文件只有庫文件 Android 依賴 Unity 導(dǎo)出的場景數(shù)據(jù),需要同步的文件太多
開發(fā)調(diào)試速度 Android 庫文件比較小挣轨,調(diào)試較快 Unity 工程較大军熏,同步較慢,調(diào)試周期長
Build機制 Unity 內(nèi)置的 Android Build 機制卷扮,類似于 eclipse 編譯 Android 項目 Android Studio gradle
Build靈活性 較差荡澎,無法深度定制,庫有依賴時需要將全部依賴顯式拷貝到 Unity 工程中 非常自由晤锹,可以使用最新的 Android Build 機制
如何打包apk Unity Build 機制直接打包 Android Studio 打包

本項目使用的是第一種方法摩幔,因為這個項目中 Unity 工程特別大,導(dǎo)出 Unity 工程的代價太大鞭铆。但也遇到了庫文件依賴問題或衡,不過由于依賴項不是很多,可以手動解決车遂。以下是解決思路:

  • 運行 gradle task dependencies封断,可以在 “Gradle projects” 窗口中目標項目的 help 目錄中找到,這個 task 會打印出樹形結(jié)構(gòu)的依賴關(guān)系艰额。
  • 將所有的依賴項單獨下載到本地澄港,放到 Unity 工程中。

從這兩個步驟可以看出柄沮,如果依賴層次比較少回梧、數(shù)量比較少,還是可以接受的祖搓,但如果有大量深層的依賴就會變得特別麻煩狱意。

查看依賴樹

Unity 調(diào)用 Android

Unity官方文檔說明需要通過Plugin的方式調(diào)用Java代碼,但實際上不需要引入任何Plugin就可以調(diào)用Java代碼拯欧。只是一般情況下需要調(diào)用的都是封裝好的庫详囤,這時才需要將 jar 或者 aar 放到 Unity 項目中,然后通過 C# 來訪問其中的內(nèi)容。

jar 或者 aar 文件可以放在Unity任意目錄下藏姐,為了方便管理隆箩,都放在了 Assets/Plugins/Android 目錄下。

C# 調(diào)用 Java 方法羔杨,獲取 Java 字段

C# 調(diào)用 Java 的底層原理是使用JNI調(diào)用捌臊,Unity已經(jīng)提供了很方便的接口:

  • 創(chuàng)建對象:C#中使用 AndroidJavaObject 類封裝 Java 對象,new 一個 AndroidJavaObject 對象相當(dāng)于調(diào)用對應(yīng)的 Java 對象的構(gòu)造函數(shù)兜材。借助 C# 可變參數(shù)列表理澎,可以給 Java 對象的構(gòu)造函數(shù)傳遞任意數(shù)量的參數(shù)。
// 第一個參數(shù)是 Java 類的完整包名曙寡,剩下的其他參數(shù)會傳遞給構(gòu)造方法糠爬。
AndroidJavaObject jo = new AndroidJavaObject("java.lang.String", "some_string"); 
  • 調(diào)用對象方法:使用 AndroidJavaObject 類的 Call 方法,有泛型與非泛型的兩個版本举庶。
// 泛型版本执隧,目的是指定返回值的類型
int hash = jo.Call<int>("hashCode");
// 非泛型版本,處理返回值是void的情況灯变。
jo.Call("aMethodReturnVoid"); // String中沒有返回void的簡單方法殴玛。。添祸。
  • 獲取類,主要用于獲取靜態(tài)字段或調(diào)用靜態(tài)方法寻仗,常用來獲取 UnityPlayer刃泌。
// 傳入類的完整包名
AndroidJavaClass jc = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
  • 獲取靜態(tài)字段,只有泛型版本署尤,因為不會有void類型的字段耙替。。曹体。
AndroidJavaObject jo = jc.GetStatic<AndroidJavaObject>("currentActivity"); 

設(shè)置字段俗扇、獲取對象字段、調(diào)用靜態(tài)方法的代碼類似箕别,略铜幽。

類型映射

調(diào)用 Java 方法時,直接將 C# 變量/常量 傳遞給 Java 方法串稀,會自動處理 C# 類型到 Java 類型的轉(zhuǎn)換除抛。通過 C# 泛型,可以指定 Java 方法的返回值類型母截,也就是將 Java 類型轉(zhuǎn)換為了 C# 類型到忽。C# 類型與 Java 類型是可以自動轉(zhuǎn)換的,規(guī)則如下:

Java 類型 C# 類型
基本類型清寇,比如 int, boolean 對應(yīng)的值類型 int, bool
String string
數(shù)組類型 數(shù)組類型 (不能是多維數(shù)組)
其他繼承自 Object 的類型 AndroidJavaObject

Android 調(diào)用 Unity

從 Android 端并不能直接調(diào)用 Unity 腳本喘漏,而是通過消息發(fā)送或者接口回調(diào)的方式护蝶。

消息發(fā)送方式

消息發(fā)送是一個非常簡單的調(diào)用機制,建立在一個發(fā)消息的接口之上:

// objectName: Unity 對象的名稱
// methodName: Unity 對象綁定的腳本方法名
// message: 自定義消息
UnityPlayer.UnitySendMessage(String objectName, String methodName, String message);

做一下簡單的封裝:

import com.unity3d.player.UnityPlayer;
public class UnityPlayerCallback {
    public final String objectName;
    public final String methodName;
    public UnityPlayerCallback(String objectName, String methodName) {
        this.objectName = objectName;
        this.methodName = methodName;
    }
    public void invoke(String message) {
        UnityPlayer.UnitySendMessage(objectName, methodName, message);
    }
}

發(fā)送消息需要知道 Unity 對象的名稱和方法名翩迈,而這些信息在 Android 端是不知道的滓走,也不應(yīng)該寫死在 Java 代碼里。因為 Unity 腳本相對于 Android 代碼是上層客戶代碼帽馋,調(diào)用的是 Android 庫文件提供的功能搅方,庫文件是不應(yīng)該知道使用它的客戶代碼的任何具體信息的。

正確的做法是通過某種方式將這些信息注入到庫中绽族,最簡單地姨涡,使用 C# 調(diào)用 Java 端的代碼將這兩個字符串保存到 Java 對象中。

下面的示例規(guī)定了一個簡單的消息格式:消息=類型/數(shù)據(jù)吧慢。

// Java 代碼
public class Downloader {
    private UnityPlayerCallback mUnityCallback;
    public void setDownloadCallback(String objectName, String methodName) {
        mUnityCallback = new UnityPlayerCallback(objectName, methodName);
    }
    ...
    void onDownloaded(File file, String url) {
        if (mUnityCallback != null) {
            mUnityCallback.invoke("downloaded/" + file.getName());
        }
    }
}
// C# 腳本:
void OnStart()
{
    AndroidJavaObject downloader = new AndroidJavaObject("my.package.Downloader");
    downloader.Call("setDownloadCallback", gameObject.name, "OnJavaMessage");
}
void OnJavaMessage(string message)
{
    // 這里解析 message涛漂,例:"download/filename.txt"
    if (message.StartsWith("downloaded/")
    {
        // 處理下載完成的邏輯...
    }
}

由于這種方式比較粗糙,而且繞不開消息處理方法检诗,如果有多個回調(diào)方法匈仗、傳遞的數(shù)據(jù)比較復(fù)雜,就需要定義復(fù)雜的消息傳遞格式逢慌。

接口調(diào)用方式

這種方法使用起來比較自然悠轩,按照 Java 的風(fēng)格定義好 Java 的回調(diào)接口,然后在 C# 腳本中通過繼承 AndroidJavaProxy 類來實現(xiàn)這個 Java 的接口攻泼。通過 Java 側(cè)提供的回調(diào)設(shè)置方法將實現(xiàn)了接口的 C# 對象設(shè)置給 Java 代碼火架,就完成了 Java 設(shè)置 C# 回調(diào)的過程。

下面舉例說明這個方法:

Java 代碼定義一個下載工具類忙菠,使用一個下載進度和狀態(tài)接口通知調(diào)用者:

// 回調(diào)接口
public interface DownloadListener {
    void onProgress(String name, int progress);
    void onDownloaded(String name);
    void onError(String name, String message);
}
// 下載工具類
public class DownloadHelper {
    public void download(String url, String name) {...}
    public void setDownloadListener(DownloadListener listener) {...}
}

C# 代碼同樣定義一個同名的 DownloadHelper 類何鸡,用來封裝對 Java 對象的調(diào)用:

public class DownloadHelper {
    // 定義 C# 端的接口,對外隱藏 Java 相關(guān)代碼
    public interface IDownloadListener {
        void OnProgress(string name, int progress);
        void OnDownloaded(string name);
        void OnError(string name, string message);
    }
    // 定義個 Adapter 來適配 AndroidJavaProxy 對象和 IDownloadListener
    private class ListenerAdapter : AndroidJavaProxy {
        private readonly IDownloadListener listener;
        public ListenerAdapter(IDownloadListener listener) : base("my.package.DownloadListener") {
            this.listener = listener;
        }
        // 繼承自 AndroidJavaProxy 的對象可以直接按照 Java 中的方法簽名
        // 寫出對應(yīng)的 C# 方法牛欢,參數(shù)類型遵循上文提到的數(shù)據(jù)類型轉(zhuǎn)換規(guī)則骡男。
        // 當(dāng) Java 調(diào)用接口方法時,對應(yīng)的 C# 方法會自動調(diào)用傍睹,非常方便隔盛。
        void onProgress(string name, int progress) {
            listener.OnProgress(name, progress);
        }
        void onDownloaded(string name) {
            listener.OnDownloaded(name);
        }
        void onError(string name, string message) {
            listener.OnError(name, message);
        }
    }
    private readonly AndroidJavaObject javaObject;
    private ListenerAdapter listenerAdapter;
    public DownloadHelper() {
        javaObject = new AndroidJavaObject("my.package.DownloadHelper", DefaultDirectory);
    }
    public void SetDownloadListener(IDownloadListener listener) {
        if (listener != null) {
            listenerAdapter = new ListenerAdapter(listener);
            javaObject.Call("setDownloadListener", listenerAdapter);
        } else {
            listenerAdapter = null;
            javaObject.Call("setDownloadListener", null);
        }
    }
    public void Download(string url, string name) {
        javaObject.Call("download", url, name);
    }
    // 初始化下載目錄
    private static string DefaultDirectory;
    static DownloadHelper() {
        AndroidJavaClass jc = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
        AndroidJavaObject jo = jc.GetStatic<AndroidJavaObject>("currentActivity");
        string path = jo.Call<AndroidJavaObject>("getExternalFilesDir", "videos").Call<string>("getCanonicalPath");
        DefaultDirectory = path;
    }
}

使用的時候,直接使用 C# DownloadHelper 類配合 DownloadHelper.IDownloadListener 即可焰望。

后記:
第二種實現(xiàn)的方式交給寫 Unity 腳本的同事后發(fā)現(xiàn)一個問題:由于下載模塊的回調(diào)是在安卓UI線程執(zhí)行的骚亿,這個線程并不是 Unity 的主線程,回調(diào)到 Unity 環(huán)境中不能執(zhí)行各種對象的操作熊赖。因此需要通知 Unity 主線程并在其中執(zhí)行回調(diào)来屠。

C# 代碼修改如下,使用了 Loom 類,有點類似于安卓的 Handler 俱笛,可以參考這篇文章 Unity Loom 插件使用

private class ListenerAdapter : AndroidJavaProxy {
    ...
    void onProgress(string name, int progress) {
        Loom.QueueOnMainThread((param) => {
            listener.OnProgress(name, progress);
        }, null);
    }
    void onDownloaded(string name) {
        Loom.QueueOnMainThread((param) => {
            listener.OnDownloaded(name);
        }, null);
    }
    void onError(string name, string message) {
        Loom.QueueOnMainThread((param) => {
            listener.OnError(name, message);
        }, null);
    }
}

如何直接獲得安卓廣播

雖然可以在安卓層使用 BroadcastReceiver 接收廣播捆姜,并通過自定義的方法傳遞給 C# 層。但如果能在 C# 端直接接收就更方便了迎膜,于是后來又寫了一個通用的廣播接收層泥技。

先來看一下如何使用這個廣播接收層,設(shè)計這個層的主要目的有兩個:一是能直接在 C# 代碼中注冊安卓廣播磕仅,另一個是使用的代碼要足夠簡單珊豹。

先上使用的代碼:

public class BTHolder : MonoBehaviour, UnityBroadcastHelper.IBroadcastListener {
    UnityBroadcastHelper helper;
    void Start() {
        if (helper == null) {
            helper = UnityBroadcastHelper.Register(
                new string[] { "some_action_string" }, this);
        }
    }
    void OnReceive(string action, Dictionary<string, string> dictionary) {
        // handle the broadcast
    }
}

可以看到使用廣播需要4個步驟:

  1. 實現(xiàn) UnityBroadcastHelper.IBroadcastListener 接口。
  2. 定義一個 UnityBroadcastHelper 對象并初始化榕订。
  3. 在方法 void OnReceive(string action, Dictionary<string, string> dictionary) 中自定義廣播處理代碼店茶。
  4. 在合適的時機調(diào)用 helper.Stop() 停止監(jiān)聽廣播。

可以看出與 Java 代碼中自定義 BroadcastReceiver 幾乎是相同的步驟劫恒,下面分析一下原理贩幻。

  1. 先使用一個 Java 對象 UnityBroadcastHelper 來持有 BroadcastReceiver,再通過 Java 代碼注冊到 Context 中两嘴。
  2. 再使用上文提到的接口方式將 UnityBroadcastHelper.BroadcastListener 映射為 C# 中的 UnityBroadcastHelper.IBroadcastListener丛楚。這樣在 Java 端接收到廣播時調(diào)用 C# 端的接口,就可以通知 C# 廣播已經(jīng)接收到憔辫。
  3. 最后使用數(shù)據(jù)獲取接口將廣播中的數(shù)據(jù)趣些,也就是保存 Extra 的 Bundle,映射為 C# 中的 Dictionary螺垢,傳遞給 OnReceive 方法喧务,方便 C# 使用。這里為了簡單把所有類型的數(shù)據(jù)都映射為了 string 類型枉圃,這個映射比較繁瑣,有需要可以再寫詳細一些庐冯。

Java 代碼:

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;

import com.unity3d.player.UnityPlayer;

import java.util.LinkedList;
import java.util.Queue;

public class UnityBroadcastHelper {
    private static final String TAG = "UnityBroadcastHelper";
    public interface BroadcastListener {
        void onReceive(String action);
    }
    private final BroadcastListener listener;
    private Queue<String[]> keysQueue = new LinkedList<>();
    private Queue<String[]> valuesQueue = new LinkedList<>();
    public UnityBroadcastHelper(String[] actions, BroadcastListener listener) {
        MyLog.d(TAG, "UnityBroadcastHelper: actions: " + actions);
        MyLog.d(TAG, "UnityBroadcastHelper: listener: " + listener);
        this.listener = listener;
        IntentFilter intentFilter = new IntentFilter();
        for (String action : actions) {
            intentFilter.addAction(action);
        }
        Context context = UnityPlayer.currentActivity;
        if (context == null) {
            return;
        }
        context.registerReceiver(broadcastReceiver, intentFilter);
    }
    public boolean hasKeyValue() {
        return !keysQueue.isEmpty();
    }
    public String[] getKeys() {
        return keysQueue.peek();
    }
    public String[] getValues() {
        return valuesQueue.peek();
    }
    public void pop() {
        keysQueue.poll();
        valuesQueue.poll();
    }
    public void stop() {
        Context context = UnityPlayer.currentActivity;
        if (context == null) {
            return;
        }
        context.unregisterReceiver(broadcastReceiver);
    }
    private BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            MyLog.d(TAG, "UnityBroadcastHelper: action: " + action);
            Bundle bundle = intent.getExtras();
            if (bundle == null) {
                bundle = new Bundle();
            }
            int n = bundle.size();
            String[] keys = new String[n];
            String[] values = new String[n];
            int i = 0;
            for (String key : bundle.keySet()) {
                keys[i] = key;
                Object value = bundle.get(key);
                values[i] = value != null ? value.toString() : null;
                MyLog.d(TAG, "UnityBroadcastHelper: key[" + i + "]: " + key);
                MyLog.d(TAG, "UnityBroadcastHelper: value[" + i + "]: " + value);
                i++;
            }

            keysQueue.offer(keys);
            valuesQueue.offer(values);
            listener.onReceive(action);
        }
    };
}

C# 代碼:

using System.Collections.Generic;
using UnityEngine;
public class UnityBroadcastHelper {
    public interface IBroadcastListener {
        void OnReceive(string action, Dictionary<string, string> dictionary);
    }
    private class ListenerAdapter : AndroidJavaProxy {
        readonly IBroadcastListener listener;
        readonly UnityBroadcastHelper helper;
        public ListenerAdapter(IBroadcastListener listener, UnityBroadcastHelper helper) : base("UnityBroadcastHelper$BroadcastListener") {
            this.listener = listener;
            this.helper = helper;
        }
        void onReceive(string action) {
            AndroidJavaObject javaObject = helper.javaObject;
            if (!javaObject.Call<bool>("hasKeyValue")) {
                return;
            }
            string[] keys = javaObject.Call<string[]>("getKeys");
            string[] values = javaObject.Call<string[]>("getValues");
            javaObject.Call("pop");
            Dictionary<string, string> dictionary = new Dictionary<string, string>();
            Debug.Log("onReceive: dictionary: " + dictionary);
            int n = keys.Length;
            for (int i = 0; i < n; i++) {
                dictionary[keys[i]] = values[i];
            }
            listener.OnReceive(action, dictionary);
        }
    }
    private readonly AndroidJavaObject javaObject;
    private UnityBroadcastHelper(string[] actions, IBroadcastListener listener) {
        ListenerAdapter adapter = new ListenerAdapter(listener, this);
        javaObject = new AndroidJavaObject("UnityBroadcastHelper", actions, adapter);
    }
    public static UnityBroadcastHelper Register(string[] actions, IBroadcastListener listener) {
        return new UnityBroadcastHelper(actions, listener);
    }
    public void Stop() {
        javaObject.Call("stop");
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末孽亲,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子展父,更是在濱河造成了極大的恐慌返劲,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件栖茉,死亡現(xiàn)場離奇詭異篮绿,居然都是意外死亡,警方通過查閱死者的電腦和手機吕漂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進店門亲配,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事吼虎∪郑” “怎么了?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵思灰,是天一觀的道長玷犹。 經(jīng)常有香客問我,道長洒疚,這世上最難降的妖魔是什么歹颓? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮油湖,結(jié)果婚禮上巍扛,老公的妹妹穿的比我還像新娘。我一直安慰自己肺魁,他們只是感情好电湘,可當(dāng)我...
    茶點故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著鹅经,像睡著了一般寂呛。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上瘾晃,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天贷痪,我揣著相機與錄音,去河邊找鬼蹦误。 笑死劫拢,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的强胰。 我是一名探鬼主播舱沧,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼偶洋!你這毒婦竟也來了熟吏?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤玄窝,失蹤者是張志新(化名)和其女友劉穎牵寺,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體恩脂,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡帽氓,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了俩块。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片黎休。...
    茶點故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡浓领,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出奋渔,到底是詐尸還是另有隱情镊逝,我是刑警寧澤,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布嫉鲸,位于F島的核電站撑蒜,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏玄渗。R本人自食惡果不足惜座菠,卻給世界環(huán)境...
    茶點故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望藤树。 院中可真熱鬧浴滴,春花似錦、人聲如沸岁钓。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽屡限。三九已至品嚣,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間钧大,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工啊央, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留眶诈,地道東北人瓜饥。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓球拦,卻偏偏與公主長得像,于是被迫代替她去往敵國和親拦键。 傳聞我的和親對象是個殘疾皇子芬为,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,724評論 2 354

推薦閱讀更多精彩內(nèi)容

  • 2017年4月5日 農(nóng)歷三月初九 星期三 天氣萄金,陰小雨 F說,我是家里的老小媚朦,我出生的時候他們年齡大了氧敢,在我成長的...
    張寧psy閱讀 584評論 0 3
  • 昨晚下了一夜的雨。天晴了询张。
    胎記閱讀 153評論 0 0
  • 風(fēng) 婆娑了你的臉龐 沙場寂寥 不比京都十里 刀柄割裂的青袍 沾染了熱的血 融化了遍地的冰涼 一片姹紫嫣紅 孤傲的身...
    愛吃的胡蘿卜閱讀 158評論 0 3