Android開(kāi)發(fā)(54) AIDL示例

背景

最近在考慮項(xiàng)目重構(gòu)的時(shí)候临梗,考慮將項(xiàng)目拆分成兩個(gè)APK胞得,一個(gè)用于數(shù)據(jù)服務(wù)荧止,一個(gè)用于UI展示。
數(shù)據(jù)服務(wù)APK向自己編寫(xiě)APK提供數(shù)據(jù),同時(shí)也可以向第三方提供數(shù)據(jù)跃巡∥:牛考慮使用這樣的方式代替向第三方提供jar形式的sdk包。
如果拆分成多個(gè)APK素邪,不得不考慮 進(jìn)程間通信(IPC)的問(wèn)題外莲。Android提供了一種IPC的實(shí)現(xiàn),就是AIDL.

在學(xué)習(xí)AIDL時(shí)編寫(xiě)示例形成本文兔朦。放在Github的demo項(xiàng)目中偷线。可以在下面的地址下載到源代碼
github: https://github.com/vir56k/demo/tree/master/aidlDemo

什么是AIDL

AIDL (Android Interface Definition Language沽甥, Android接口定義語(yǔ)言)
在不同的進(jìn)程(應(yīng)用)之間進(jìn)行數(shù)據(jù)交換淋昭,就要約定 之間的通信接口。

從面向?qū)ο蟮慕嵌葋?lái)看安接,接口設(shè)計(jì)要考慮狀態(tài)和行為翔忽。一般來(lái)說(shuō),接口定義的內(nèi)容分為:
1.方法操作(描述行為)
2.參數(shù)(描述狀態(tài)盏檐,數(shù)據(jù)的類(lèi)型歇式,數(shù)據(jù)的載體/實(shí)體)

AIDL是一種IDL,它有特有的語(yǔ)法描述胡野。我們需要編寫(xiě)一個(gè)AIDL文件作為約定材失。它的語(yǔ)法非常類(lèi)似java語(yǔ)法。
它支持基礎(chǔ)數(shù)據(jù)類(lèi)型硫豆,比如 int,String,float等龙巨。
它支持實(shí)體類(lèi),必須是實(shí)現(xiàn)了Parcelable接口熊响,支持序列化旨别。

AIDL通過(guò)服務(wù)綁定的方式來(lái)使用。你需要定義一個(gè)service,傳遞一個(gè) IBinder對(duì)象汗茄。這個(gè) IBinder對(duì)象具有我們需要的方法秸弛。
拿到這個(gè)對(duì)象后執(zhí)行具體方法。

AIDL分為 服務(wù)端和客戶(hù)端
服務(wù)端即服務(wù)提供著洪碳,提供可操作的方法和數(shù)據(jù)递览。
客戶(hù)端即調(diào)用者,使用方法和數(shù)據(jù)瞳腌。

什么時(shí)候適合使用AIDL:
官方文檔建議只有你允許客戶(hù)端從不同的應(yīng)用程序?yàn)榱诉M(jìn)程間的通信而去訪(fǎng)問(wèn)你的service绞铃,以及想在你的service處理多線(xiàn)程。

步驟說(shuō)明

服務(wù)端開(kāi)發(fā)步驟如下:

1.定義一個(gè)AIDL文件
2.實(shí)現(xiàn)描述的接口嫂侍,編寫(xiě)service
3.如果有實(shí)體類(lèi)儿捧,需要提供實(shí)體類(lèi)(jar包形式)

客戶(hù)端

1.拿到AIDL文件
2.綁定服務(wù)冷离,獲得接口持有對(duì)象。

示例

服務(wù)端開(kāi)發(fā)

1.聲明AIDL文件

Android提供的特殊的文件夾來(lái)放置AIDL文件纯命,位于 src/mian/aidl 文件夾下西剥。
由于java類(lèi)/接口是有 package(命名空間)的。我們需要定義命名空間亿汞,一般和文件位置一致瞭空。
在這里,我們?cè)?src/mian/aidl 文件夾下疗我,創(chuàng)建package咆畏,名稱(chēng)為:com.example.myserver。
對(duì)應(yīng)文件夾路徑為src/mian/aidl/com/example/myserver吴裤,我們?cè)谶@個(gè)文件下建立我們的aidl文件旧找,內(nèi)容如下:

IRemoteService.aidl

    package com.example.myserver;
    import com.example.myserver.Entity;
    import com.example.myserver.IMyCallback;
    
    // Declare any non-default types here with import statements
    
    interface IRemoteService {
    
        void doSomeThing(int anInt,String aString);
    
        void addEntity(in Entity entity);
    
        List<Entity> getEntity();
    
    }

Entity.aidl,這個(gè)是實(shí)體類(lèi) 麦牺,它還需要對(duì)應(yīng)一個(gè)java class文件

// Entity.aidl
package com.example.myserver;

parcelable Entity;

2.實(shí)現(xiàn)接口,編寫(xiě)service

在src/java文件夾寫(xiě)下 MyService class,集成服務(wù)Service類(lèi).在mainifest文件中注冊(cè)這個(gè)服務(wù)類(lèi)钮蛛。
如果你的aidl描述文件編寫(xiě)無(wú)誤的話(huà),android studio 會(huì)自動(dòng)幫你生成一些輔助類(lèi)剖膳,你可以在下面的目錄找到:

build/generated/source/debug

在這個(gè)文件夾下回自動(dòng)生成有 IRemoteService類(lèi)魏颓,和它的子類(lèi) IRemoteService.Stub類(lèi)及其他。感興趣的同學(xué)可以讀讀吱晒。

IRemoteService.Stub是一個(gè)根文件甸饱,它是一個(gè)抽象類(lèi)。下面代碼演示了仑濒,一個(gè) IRemoteService.Stub 的匿名類(lèi)的實(shí)現(xiàn)叹话。
在這個(gè)服務(wù)類(lèi)的 public IBinder onBind(Intent intent) 方法中,我們r(jià)eturn 一個(gè) IRemoteService.Stub 的匿名類(lèi)實(shí)現(xiàn)墩瞳。

在客戶(hù)綁定到這個(gè)服務(wù)的時(shí)候驼壶,將可以獲得到這個(gè)實(shí)現(xiàn)的一個(gè)實(shí)例,調(diào)用它的方法矗烛。

代碼如下

package com.example.myserver;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.support.annotation.Nullable;
import android.util.Log;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by zhangyunfei on 16/10/12.
 */
public class MyService extends Service {
    public static final String TAG = "MyService";

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.e(TAG, String.format("on bind,intent = %s", intent.toString()));
        return binder;
    }


    private final IRemoteService.Stub binder = new IRemoteService.Stub() {
        public static final String TAG = "IRemoteService.Stub";
        private List<Entity> data = new ArrayList<Entity>();

        @Override
        public void doSomeThing(int anInt, String aString) throws RemoteException {
            Log.d(TAG, String.format("收到:%s, %s", anInt, aString));
        }

        @Override
        public void addEntity(Entity entity) throws RemoteException {
            Log.d(TAG, String.format("收到:entity = %s", entity));
            data.add(entity);
        }

        @Override
        public List<Entity> getEntity() throws RemoteException {
            return data;
        }


    };
}

3.編寫(xiě)實(shí)體類(lèi)

我們上面提到辅柴,接口的參數(shù)可以是實(shí)體類(lèi)。我們?cè)谇懊娑x了一個(gè)entity.aidl瞭吃,它里面寫(xiě)了一句

     parcelable Entity;  

這么一句話(huà)指明它需要關(guān)聯(lián)到一個(gè)具體的實(shí)體類(lèi)。我們需要在src/java文件夾編寫(xiě)這么一個(gè)類(lèi)的實(shí)現(xiàn)涣旨,必須實(shí)現(xiàn)parcelable接口歪架。
注意我們要先建立package,這個(gè) package要和aidl接口聲明里的一致。
android studio為我們方便的提供自動(dòng)生成parcelable實(shí)現(xiàn)的快捷鍵霹陡,在mac下是 command+空格和蚪。實(shí)現(xiàn)后的代碼如下:

package com.example.myserver;

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

/**
 * Created by zhangyunfei on 16/10/12.
 */
public class Entity implements Parcelable {
    int age;
    String name;

    public Entity() {
    }

    public Entity(int age, String name) {
        this.age = age;
        this.name = name;
    }

    protected Entity(Parcel in) {
        age = in.readInt();
        name = in.readString();
    }

    public static final Creator<Entity> CREATOR = new Creator<Entity>() {
        @Override
        public Entity createFromParcel(Parcel in) {
            return new Entity(in);
        }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

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

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(age);
        dest.writeString(name);
    }

    @Override
    public String toString() {
        return String.format("age=%s, name=%s", age, name);
    }
}

客戶(hù)端開(kāi)發(fā) - 調(diào)用AIDL接口

再開(kāi)始之前止状,我們可以新建一個(gè)app來(lái)做演示.步驟如下:

1.獲得AIDL,放到項(xiàng)目中

我們先拿到AIDL描述文件才用使用,將AIDL文件放到aidl文件夾下攒霹。android studio 自動(dòng)生成根文件類(lèi)怯疤。
獲得實(shí)體類(lèi)Entity.class 放入到項(xiàng)目中。

2.在activity中調(diào)用

在它的 MainActivity 下綁定服務(wù)

     Intent intent = new Intent();
        intent.setAction("com.example.REMOTE.myserver");
        intent.setPackage("com.example.myserver");
        bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);

指定服務(wù)的名稱(chēng)催束,bindService方法中需要傳入一個(gè) ServiceConnection對(duì)象集峦。
我們寫(xiě)一個(gè)ServiceConnection的匿名類(lèi),在它的onServiceConnected方法中抠刺,獲得 aidl定義的接口持有類(lèi)塔淤。

                    iRemoteService = IRemoteService.Stub.asInterface(service);

還記得剛剛編寫(xiě)服務(wù)類(lèi)返回的 binder嗎,在這里獲得的就是那個(gè)binder示例速妖。我們可以通過(guò)對(duì)這個(gè)示例進(jìn)行 轉(zhuǎn)型 后的對(duì)象來(lái)調(diào)用 接口定義的方法高蜂。

3.調(diào)用接口方法

通過(guò) iRemoteService.addEntity(entity) 方法,我們可以操作具體的實(shí)體罕容,傳入實(shí)體類(lèi)作為參數(shù)备恤。

     if (!mBound) {
                    alert("未連接到遠(yuǎn)程服務(wù)");
                    return;
                }
                try {
                    Entity entity = new Entity(1, "zhang");
                    if (iRemoteService != null)
                        iRemoteService.addEntity(entity);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }

完整代碼如下:

package com.example.zhangyunfei.myapplication;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Toast;

import com.example.myserver.Entity;
import com.example.myserver.IMyCallback;
import com.example.myserver.IRemoteService;

import java.util.List;

public class MainActivity extends AppCompatActivity {

    private boolean mBound = false;
    private IRemoteService iRemoteService;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        findViewById(R.id.btnAdd).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (!mBound) {
                    alert("未連接到遠(yuǎn)程服務(wù)");
                    return;
                }
                try {
                    Entity entity = new Entity(1, "zhang");
                    if (iRemoteService != null)
                        iRemoteService.addEntity(entity);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }


        });

        findViewById(R.id.btnList).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (!mBound) {
                    alert("未連接到遠(yuǎn)程服務(wù)");
                    return;
                }
                if (iRemoteService != null) {
                    try {
                        List<Entity> entityList = iRemoteService.getEntity();

                        StringBuilder sb = new StringBuilder("當(dāng)前數(shù)量:" + entityList.size() + "\r\n");
                        for (int i = 0; i < entityList.size(); i++) {
                            sb.append(i + ": ");
                            sb.append(entityList.get(i) == null ? "" : entityList.get(i).toString());
                            sb.append("\n");
                        }
                        alert(sb.toString());
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                }
            }
        });

        findViewById(R.id.btnCallback).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (!mBound) {
                    alert("未連接到遠(yuǎn)程服務(wù)");
                    return;
                }
                try {
                    if (iRemoteService != null) {
                        final String para = "canshu";
                        iRemoteService.asyncCallSomeone(para, new IMyCallback.Stub() {
                            @Override
                            public void onSuccess(String aString) throws RemoteException {
                                alert(String.format("發(fā)送: %s, 回調(diào): %s", para, aString));
                            }
                        });
                    }
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        });
    }

    private void alert(String str) {
        Toast.makeText(this, str, 0).show();
    }

    @Override
    protected void onStart() {
        super.onStart();
        if (!mBound) {
            attemptToBindService();
        }
    }

    @Override
    protected void onStop() {
        super.onStop();
        if (mBound) {
            unbindService(mServiceConnection);
            mBound = false;
        }
    }


    /**
     * 嘗試與服務(wù)端建立連接
     */
    private void attemptToBindService() {
        Intent intent = new Intent();
        intent.setAction("com.example.REMOTE.myserver");
        intent.setPackage("com.example.myserver");
        bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
    }


    private ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.e(getLocalClassName(), "service connected");
            iRemoteService = IRemoteService.Stub.asInterface(service);
            mBound = true;

            if (iRemoteService != null) {
                try {
                    iRemoteService.doSomeThing(0, "anything string");
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.e(getLocalClassName(), "service disconnected");
            mBound = false;
        }
    };
}

回調(diào)

在A(yíng)IDL中,有時(shí)候需要實(shí)現(xiàn)回調(diào)锦秒,傳入一個(gè)回調(diào)callbak烘跺,或者listener類(lèi)。如何實(shí)現(xiàn)呢脂崔?

1.編寫(xiě)回調(diào)類(lèi)aidl文件

IMyCallback類(lèi)具有一個(gè) onSuccess回調(diào)方法
IMyCallback.aidl滤淳,這個(gè)文件里描述一個(gè)回調(diào)接口

// IMyCallback.aidl
package com.example.myserver;

// Declare any non-default types here with import statements

interface IMyCallback {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    void onSuccess(String aString);
}

2.聲明方法,以回調(diào)類(lèi)作為參數(shù)砌左,示例:

IRemoteService.aidl

    package com.example.myserver;
    import com.example.myserver.Entity;
    import com.example.myserver.IMyCallback;
    
    // Declare any non-default types here with import statements
    
    interface IRemoteService {
   
        void asyncCallSomeone( String para, IMyCallback callback);
    }

3.實(shí)現(xiàn)方法脖咐,發(fā)起回調(diào)通知

發(fā)起回調(diào)有點(diǎn)類(lèi)似廣播的方式,示例:

                @Override
        public void asyncCallSomeone(String para, IMyCallback callback) throws RemoteException {
            RemoteCallbackList<IMyCallback> remoteCallbackList = new RemoteCallbackList<>();
            remoteCallbackList.register(callback);
            final int len = remoteCallbackList.beginBroadcast();
            for (int i = 0; i < len; i++) {
                remoteCallbackList.getBroadcastItem(i).onSuccess(para + "_callbck");
            }
            remoteCallbackList.finishBroadcast();
        }

我們需要一個(gè) RemoteCallbackList 集合類(lèi)汇歹,把 要回調(diào)的類(lèi)的示例callback示例放到這集合內(nèi)屁擅。調(diào)用這個(gè)集合類(lèi)RemoteCallbackList的下面兩個(gè)方法:
beginBroadcast 開(kāi)始廣播,finishBroadcast 結(jié)束廣播产弹,配合使用派歌。

4.客戶(hù)端調(diào)用示例:

客戶(hù)端在獲得接口操作對(duì)象后,傳入回調(diào)類(lèi)痰哨,示例:

        try {
                if (iRemoteService != null) {
                    final String para = "canshu";
                    iRemoteService.asyncCallSomeone(para, new IMyCallback.Stub() {
                        @Override
                        public void onSuccess(String aString) throws RemoteException {
                            alert(String.format("發(fā)送: %s, 回調(diào): %s", para, aString));
                        }
                    });
                }
            } catch (RemoteException e) {
                e.printStackTrace();
            }

參考

谷歌官方文檔

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末胶果,一起剝皮案震驚了整個(gè)濱河市缅帘,隨后出現(xiàn)的幾起案子遭京,更是在濱河造成了極大的恐慌嚣鄙,老刑警劉巖白指,帶你破解...
    沈念sama閱讀 216,496評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件凭峡,死亡現(xiàn)場(chǎng)離奇詭異铲觉,居然都是意外死亡森缠,警方通過(guò)查閱死者的電腦和手機(jī)祝闻,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)甘苍,“玉大人尝蠕,你說(shuō)我怎么就攤上這事≡赝ィ” “怎么了看彼?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,632評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀(guān)的道長(zhǎng)昧捷。 經(jīng)常有香客問(wèn)我闲昭,道長(zhǎng),這世上最難降的妖魔是什么靡挥? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,180評(píng)論 1 292
  • 正文 為了忘掉前任序矩,我火速辦了婚禮,結(jié)果婚禮上跋破,老公的妹妹穿的比我還像新娘簸淀。我一直安慰自己,他們只是感情好毒返,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布租幕。 她就那樣靜靜地躺著,像睡著了一般拧簸。 火紅的嫁衣襯著肌膚如雪劲绪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,165評(píng)論 1 299
  • 那天盆赤,我揣著相機(jī)與錄音贾富,去河邊找鬼。 笑死牺六,一個(gè)胖子當(dāng)著我的面吹牛颤枪,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播淑际,決...
    沈念sama閱讀 40,052評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼畏纲,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了春缕?” 一聲冷哼從身側(cè)響起盗胀,我...
    開(kāi)封第一講書(shū)人閱讀 38,910評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎淡溯,沒(méi)想到半個(gè)月后读整,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,324評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡咱娶,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評(píng)論 2 332
  • 正文 我和宋清朗相戀三年米间,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片膘侮。...
    茶點(diǎn)故事閱讀 39,711評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡屈糊,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出琼了,到底是詐尸還是另有隱情逻锐,我是刑警寧澤,帶...
    沈念sama閱讀 35,424評(píng)論 5 343
  • 正文 年R本政府宣布雕薪,位于F島的核電站昧诱,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏所袁。R本人自食惡果不足惜盏档,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望燥爷。 院中可真熱鬧蜈亩,春花似錦、人聲如沸前翎。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,668評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)港华。三九已至道川,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間立宜,已是汗流浹背冒萄。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,823評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留赘理,地道東北人宦言。 一個(gè)月前我還...
    沈念sama閱讀 47,722評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像商模,于是被迫代替她去往敵國(guó)和親奠旺。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評(píng)論 2 353

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

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,075評(píng)論 25 707
  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理施流,服務(wù)發(fā)現(xiàn)响疚,斷路器,智...
    卡卡羅2017閱讀 134,651評(píng)論 18 139
  • Jianwei's blog 首頁(yè) 分類(lèi) 關(guān)于 歸檔 標(biāo)簽 巧用Android多進(jìn)程瞪醋,微信忿晕,微博等主流App都在用...
    justCode_閱讀 5,915評(píng)論 1 23
  • 1.1單位階躍函數(shù)定義 階躍函數(shù)表示信號(hào)在0時(shí)刻從0變?yōu)?,其中包含了極限的概念银受,暫時(shí)不考慮0時(shí)刻信號(hào)的值践盼。 1....
    板撒閱讀 22,096評(píng)論 1 3
  • 第十一個(gè)公主鸦采。 昨晚畫(huà)了一半,由于快到十二點(diǎn)了咕幻,所以決定今天繼續(xù)畫(huà)渔伯。 今天是堅(jiān)持五點(diǎn)半起床計(jì)劃的第四天。 我要加油...
    千股的土豆閱讀 202評(píng)論 5 3