Flutter筆記——FlutterActivity

2018-12-21修復(fù),F(xiàn)lutterActivity的頁(yè)面選擇錯(cuò)誤修改
自谷歌發(fā)布Flutter release版本幾天后才開(kāi)始學(xué)習(xí)Flutter,實(shí)在慚愧癣猾。在了解完一些基礎(chǔ)知識(shí)之后開(kāi)始嘗試將編寫(xiě)的簡(jiǎn)單Flutter module打包進(jìn)Android項(xiàng)目中慕蔚。本文章將嘗試過(guò)程中遇到的一些問(wèn)題和筆記記錄下來(lái)。
本篇文章只是閉門(mén)造車(chē)的結(jié)果巍佑,如有任何錯(cuò)誤很抱歉茴迁!請(qǐng)幫忙指出,多謝了

Android項(xiàng)目依賴(lài)Flutter項(xiàng)目

對(duì)于已有的Android項(xiàng)目來(lái)說(shuō)萤衰,將所有頁(yè)面都換成flutter頁(yè)面不太現(xiàn)實(shí)堕义,只能從一些簡(jiǎn)單的頁(yè)面入手逐個(gè)替換。

Flutter項(xiàng)目跟Android工程根文件夾是同級(jí)的脆栋,它不同于普通的Android module存在于Android工程根目錄下倦卖。在AndroidStudio中創(chuàng)建Flutter module,也并不會(huì)將該項(xiàng)目放到Android項(xiàng)目目錄中椿争,而是默認(rèn)選擇Android項(xiàng)目根目錄的同級(jí)目錄下怕膛。
在依賴(lài)Flutter module的時(shí)候,首先需要在項(xiàng)目的setting.gradle加入如下依賴(lài)

include ':app'
//加入下面配置
setBinding(new Binding([gradle: this]))
evaluate(new File(
        settingsDir.parentFile,
        'flutter_module/.android/include_flutter.groovy'
))

以上配置的Flutter module的位置是出于Android根目錄同級(jí)目錄下秦踪,如果Flutter module的路徑不同需要另外設(shè)置File函數(shù)的參數(shù)褐捻。編譯項(xiàng)目,會(huì)在Android項(xiàng)目下生成名為flutter的module椅邓,正常來(lái)說(shuō)該module不需要去修改代碼柠逞,只需要在app的build.gradle中依賴(lài)該fluttermodule即可。

dependencies {
    ...
    // 加入下面配置
    implementation project(':flutter')
}

自此景馁,完成Android項(xiàng)目對(duì)Flutter項(xiàng)目的依賴(lài)

FlutterActivity

除了FlutterActivity頁(yè)面板壮,也有FlutterFragmentActivity頁(yè)面,除了基類(lèi)不同裁僧,其他實(shí)現(xiàn)均一致个束。
在創(chuàng)建的Flutter項(xiàng)目的.andorid module中,只有一個(gè)類(lèi)聊疲,那就是MainActivity類(lèi)茬底。
其繼承自FlutterActivity,運(yùn)行該Flutter工程時(shí)获洲,Android項(xiàng)目的入口就是該MainActivity類(lèi)阱表。

MainActivity

該FlutterActivity類(lèi)是Flutter項(xiàng)目的頁(yè)面入口,F(xiàn)lutter為Android項(xiàng)目提供了FlutterView和FlutterFragment作為展示頁(yè)面,附著在Activity上面最爬。而FlutterActivity使用的便是FlutterView涉馁。
那么,從開(kāi)發(fā)的角度爱致,接下來(lái)引出幾個(gè)問(wèn)題烤送?

  1. 繼承FlutterActivity只能默認(rèn)進(jìn)入Flutter設(shè)定的首頁(yè)?
  2. Flutter頁(yè)面的生命周期如何管理糠悯?
  3. Flutter頁(yè)面與Android原生頁(yè)面之間如何通訊帮坚?
  4. Flutter頁(yè)面是如何繪制的?

查看源碼

查看FlutterActivity的類(lèi)聲明互艾,該類(lèi)實(shí)現(xiàn)了三個(gè)接口

public class FlutterActivity extends Activity implements 
Provider, PluginRegistry, ViewFactory {
  ...
}

這三個(gè)接口作用如下

  • Provider:只有一個(gè)簡(jiǎn)單的方法试和,那就是getFlutterView()返回當(dāng)前Activity中的Flutter頁(yè)面
  • PluginRegistry:插件注冊(cè)相關(guān)的類(lèi),以后的文章再詳細(xì)講述
  • ViewFactory:該接口有三個(gè)方法纫普,分別是
    public interface ViewFactory {
          FlutterView createFlutterView(Context var1);
    
          FlutterNativeView createFlutterNativeView();
    
          boolean retainFlutterNativeView();
      }   
    
    1. FlutterView createFlutterView(Context context):該方法比較直觀阅悍,就是生成一個(gè)Flutter的頁(yè)面,供Activity展示昨稼。但是并沒(méi)有在源碼中找到引用它的地方节视。FlutterActivity的實(shí)現(xiàn)返回值是null。
    2. FlutterNativeView createFlutterNativeView():從字面意思是生成一個(gè)Flutter的原生View悦昵,但是并沒(méi)有在源碼中找到引用它的地方肴茄。FlutterActivity的實(shí)現(xiàn)返回值也是null晌畅。
    3. boolean retainFlutterNativeView():字面意思但指,保留Flutter原生頁(yè)面。是一個(gè)boolean類(lèi)型的值抗楔,但是并沒(méi)有在源碼中找到引用它的地方棋凳。FlutterActivity的實(shí)現(xiàn)返回值是false。
      通過(guò)查看FlutterActivity所繼承的三個(gè)接口连躏,我們并沒(méi)有找到FlutterActivity中直接生成FlutterView的線索剩岳,只能從實(shí)例變量中查找。

FlutterActivityDelegate

在進(jìn)行一番閱讀之后入热,發(fā)現(xiàn)該委派類(lèi)拍棕。在Android源碼中有很多使用委派模式的地方,該處也算是一個(gè)勺良。并且绰播,在FlutterActivity中,F(xiàn)lutterActivityDelegate對(duì)象會(huì)跟隨Activity的生命周期方法被調(diào)用同名方法尚困。查看FlutterActivityDelegate的源碼

  1. 構(gòu)造方法
    public FlutterActivityDelegate(Activity activity, FlutterActivityDelegate.ViewFactory viewFactory) {
        this.activity = (Activity)Preconditions.checkNotNull(activity);
        this.viewFactory = (FlutterActivityDelegate.ViewFactory)Preconditions.checkNotNull(viewFactory);
    }
    
    其構(gòu)造方法需要傳入一個(gè)Activity對(duì)象蠢箩,還有FlutterActivityDelegate.ViewFactory對(duì)象。但在上文已經(jīng)發(fā)現(xiàn)FlutterActivityDelegate.ViewFactory的方法并無(wú)引用的地方,這里只需要著重關(guān)注Activity對(duì)象就好了谬泌。
  2. 同名生命周期方法:查看FlutterActivityDelegate類(lèi)源碼滔韵,該類(lèi)定義了一些列對(duì)象Activity生命周期函數(shù)的同名函數(shù)。并分別運(yùn)行在FlutterActivity類(lèi)的對(duì)應(yīng)生命周期中掌实,由此可見(jiàn)Flutter頁(yè)面的生命周期是由該委托類(lèi)處理的
    • onCreate:該方法中實(shí)現(xiàn)了flutterView的生成陪蜻。查看代碼
        public void onCreate(Bundle savedInstanceState) {
            ...
            this.flutterView = this.viewFactory.createFlutterView(this.activity);
            if (this.flutterView == null) {
                FlutterNativeView nativeView = this.viewFactory.createFlutterNativeView();
                this.flutterView = new FlutterView(this.activity, (AttributeSet)null, nativeView);
                this.flutterView.setLayoutParams(matchParent);
                this.activity.setContentView(this.flutterView);
                this.launchView = this.createLaunchView();
                if (this.launchView != null) {
                    this.addLaunchView();
                }
            }
            if (!this.loadIntent(this.activity.getIntent())) {
                if (!this.flutterView.getFlutterNativeView().isApplicationRunning()) {
                    String appBundlePath = FlutterMain.findAppBundlePath(this.activity.getApplicationContext());
                    if (appBundlePath != null) {
                        FlutterRunArguments arguments = new FlutterRunArguments();
                        arguments.bundlePath = appBundlePath;
                        arguments.entrypoint = "main";
                        this.flutterView.runFromBundle(arguments);
                    }
                }
    
            }
        }
    
    從FlutterActivity實(shí)現(xiàn)的ViewFactory方法我們已經(jīng)得知,傳遞給委托類(lèi)FlutterActivityDelegate實(shí)例的ViewFactory并沒(méi)有生成FlutterView可供FlutterActivityDelegate使用贱鼻。所以只能繼續(xù)查看this.flutterView = new FlutterView(this.activity, (AttributeSet)null, nativeView)之后的代碼囱皿。
    這邊需要注意的是,即使getFlutterView返回具體的FlutterView對(duì)象忱嘹,Activity也不會(huì)去將返回的view設(shè)置到頁(yè)面內(nèi)容中的嘱腥。而是會(huì)通過(guò)loadIntent方法去讀取intent中傳遞過(guò)來(lái)的route的值,去跳轉(zhuǎn)到flutter項(xiàng)目中設(shè)定的route對(duì)應(yīng)頁(yè)面

FlutterView

    public class FlutterView extends SurfaceView implements BinaryMessenger, TextureRegistry, AccessibilityStateChangeListener {
        public FlutterView(Context context, AttributeSet attrs, FlutterNativeView nativeView) {
            super(context, attrs);
            ...
            Activity activity = (Activity)this.getContext();
            if (nativeView == null) {
                this.mNativeView = new FlutterNativeView(activity.getApplicationContext());
            } else {
                this.mNativeView = nativeView;
            }
    
            this.mNativeView.attachViewAndActivity(this, activity);
            ...
        }
    }

我們可以看到FlutterView繼承自SurfaceView拘悦,在其構(gòu)造方法中齿兔。如果傳遞的FlutterNativeView如果為空,那將會(huì)重新創(chuàng)建一個(gè)默認(rèn)的FlutterNativeView础米。接著看

    public class FlutterNativeView implements BinaryMessenger {
        public FlutterNativeView(Context context) {
            this(context, false);
        }
    
        public FlutterNativeView(Context context, boolean isBackgroundView) {
            this.mNextReplyId = 1;
            this.mPendingReplies = new HashMap();
            this.mContext = context;
            this.mPluginRegistry = new FlutterPluginRegistry(this, context);
            this.attach(this, isBackgroundView);
            this.assertAttached();
            this.mMessageHandlers = new HashMap();
        }
    }

在這里我們可以看到FlutterNativeView實(shí)現(xiàn)了BinaryMessenger接口分苇,而B(niǎo)inaryMessenger是一個(gè)數(shù)據(jù)信息交流對(duì)象,其接口聲明如下

    public interface BinaryMessenger {
        /**
        *Sends a binary message to the Flutter application.
        *Parameters:
        *channel - the name String of the logical channel used for the message.
        *message - the message payload, a direct-allocated ByteBuffer with the message bytes between position zero and current position, or null.
        */
        void send(String var1, ByteBuffer var2);
        
         /**
         * Sends a binary message to the Flutter application, optionally expecting a reply.
         * Any uncaught exception thrown by the reply callback will be caught and logged.
         * <p>
         * Parameters:
         * channel - the name String of the logical channel used for the message.
         * message - the message payload, a direct-allocated ByteBuffer with the message bytes between position zero and current position, or null.
         * callback - a BinaryMessenger.BinaryReply callback invoked when the Flutter application responds to the message, possibly null.
         */
        void send(String var1, ByteBuffer var2, BinaryMessenger.BinaryReply var3);
    
        /**
         * Registers a handler to be invoked when the Flutter application sends a message to its host platform.
         * Registration overwrites any previous registration for the same channel name. Use a null handler to deregister.
         * <p>
         * If no handler has been registered for a particular channel, any incoming message on that channel will be handled silently by sending a null reply.
         * <p>
         * Parameters:
         * channel - the name String of the channel.
         * handler - a BinaryMessenger.BinaryMessageHandler to be invoked on incoming messages, or null.
         */
        void setMessageHandler(String var1, BinaryMessenger.BinaryMessageHandler var2);
    
        /**
         * Binary message reply callback. Used to submit a reply to an incoming message from Flutter. 
         * Also used in the dual capacity to handle a reply received from Flutter after sending a message.
         */
        public interface BinaryReply {
            /**
             * Handles the specified reply.
             * Parameters:
             * reply - the reply payload, a direct-allocated ByteBuffer or null. 
             * Senders of outgoing replies must place the reply bytes between position zero and current position. 
             * Reply receivers can read from the buffer directly.
             */
            void reply(ByteBuffer var1);
        }
        
        /**
         * Handler for incoming binary messages from Flutter.
         */
        public interface BinaryMessageHandler {
            /**
             * Handles the specified message.
             * Handler implementations must reply to all incoming messages, 
             * by submitting a single reply message to the given BinaryMessenger.BinaryReply. 
             * Failure to do so will result in lingering Flutter reply handlers. The reply may be submitted asynchronously.
             * <p>
             * Any uncaught exception thrown by this method will be caught by the messenger implementation and logged, 
             * and a null reply message will be sent back to Flutter.
             * <p>
             * Parameters:
             * message - the message ByteBuffer payload, possibly null.
             * reply - A BinaryMessenger.BinaryReply used for submitting a reply back to Flutter.
             */
            void onMessage(ByteBuffer var1, BinaryMessenger.BinaryReply var2);
        }
    }

要命的是Flutter框架在Android中還沒(méi)有注釋可以看屁桑,只能從官網(wǎng)查看文檔医寿。
這是一個(gè)用于在Flutter和Native之間交換數(shù)據(jù)的接口類(lèi),已知FlutterView已經(jīng)實(shí)現(xiàn)了SurfaceView蘑斧,flutterNativeView負(fù)責(zé)FlutterView和Flutter之間的通訊靖秩,再使用Skia繪制頁(yè)面。

loadIntent

即使我們實(shí)現(xiàn)了getFlutterView方法竖瘾,F(xiàn)lutterActivityDelegate類(lèi)也不會(huì)將該flutterView 添加到Activity的content中的沟突,而是通過(guò)loadIntent方法去打開(kāi)對(duì)應(yīng)的頁(yè)面。loadIntent的代碼如下

    private boolean loadIntent(Intent intent) {
        String action = intent.getAction();
        if ("android.intent.action.RUN".equals(action)) {
            String route = intent.getStringExtra("route");
            String appBundlePath = intent.getDataString();
            if (appBundlePath == null) {
                appBundlePath = FlutterMain.findAppBundlePath(this.activity.getApplicationContext());
            }

            if (route != null) {
                this.flutterView.setInitialRoute(route);
            }

            if (!this.flutterView.getFlutterNativeView().isApplicationRunning()) {
                FlutterRunArguments args = new FlutterRunArguments();
                args.bundlePath = appBundlePath;
                args.entrypoint = "main";
                this.flutterView.runFromBundle(args);
            }

            return true;
        } else {
            return false;
        }
    }

可以得出捕传,只要打開(kāi)FlutterActivity頁(yè)面時(shí)候惠拭,通過(guò)Intent傳入一個(gè)key為route的字符串值,就可以跳轉(zhuǎn)到flutter項(xiàng)目中定義的對(duì)應(yīng)route值的頁(yè)面了庸论。
如果我們需要自己封裝帶有自定義屬性和動(dòng)作的FlutterFragmentActivity的子類(lèi)职辅,可以這樣子定義

/**
 * author: wangzh
 * create: 2018/12/20 19:46
 * description: flutter的基類(lèi)
 * version: 1.0
 */
public abstract class BaseFlutterActivity extends FlutterFragmentActivity implements LifecycleOwner {

    protected Lifecycle mLifecycle;

    private static final String ROUTE_ACTION ="android.intent.action.RUN";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        getIntent().putExtra("route", getTargetPage());
        mLifecycle = new LifecycleRegistry(this);
        super.onCreate(savedInstanceState);
        GeneratedPluginRegistrant.registerWith(this);
    }

    public static <P extends BaseFlutterActivity> void toPage(Context context, Class<P> target) {
        Intent intent = new Intent(context, target);
        intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
        intent.setAction(ROUTE_ACTION);
        intent.addCategory(Intent.CATEGORY_DEFAULT);
        context.startActivity(intent);
    }

    protected abstract String getTargetPage();

    @NonNull
    @Override
    public Lifecycle getLifecycle() {
        return mLifecycle;
    }
}

加入我們?cè)贔lutter項(xiàng)目中。定義了routelogin的頁(yè)面聂示,只需要這樣子打開(kāi)即可

/**
 * author: wangzh
 * create: 2018/12/20 19:44
 * description: 登錄頁(yè)面
 * version: 1.0
 */
public class LoginActivity extends BaseFlutterActivity {

    @Override
    protected String getTargetPage() {
        return "login";
    }
}
//打開(kāi)flutter中的loginPage
 BaseFlutterActivity.toPage(getContext(), LoginActivity.class);

總結(jié)

在閱讀完FlutterActivity的部分源碼以后域携,得出了以上幾個(gè)問(wèn)題的答案。

  1. 打開(kāi)FlutterActivity頁(yè)面時(shí)候催什,通過(guò)Intent傳入一個(gè)key為route的字符串值涵亏,就可以跳轉(zhuǎn)到flutter項(xiàng)目中定義的對(duì)應(yīng)route值的頁(yè)面宰睡。
  2. 在FlutterActivityDelegate委托類(lèi)里,實(shí)現(xiàn)了對(duì)FlutterActivity和Flutter頁(yè)面生命周期的管理
  3. HelloFlutter——MethodChannel(Native&Flutter數(shù)據(jù)交互)
  4. FlutterView繼承了SurfaceView气筋,使用FlutterNativeView在Android和Flutter之間作為通訊的橋梁拆内,之后調(diào)用Skia框架繪制頁(yè)面。這也是其與RN和其他依賴(lài)于WebView的混合開(kāi)發(fā)的框架不同的根源宠默。
    本篇文章只是閉門(mén)造車(chē)的結(jié)果麸恍,如有任何錯(cuò)誤很抱歉!請(qǐng)幫忙指出搀矫,多謝了
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末抹沪,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子瓤球,更是在濱河造成了極大的恐慌融欧,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,627評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件卦羡,死亡現(xiàn)場(chǎng)離奇詭異噪馏,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)绿饵,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,180評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)欠肾,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人拟赊,你說(shuō)我怎么就攤上這事刺桃。” “怎么了吸祟?”我有些...
    開(kāi)封第一講書(shū)人閱讀 169,346評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵瑟慈,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我欢搜,道長(zhǎng)封豪,這世上最難降的妖魔是什么谴轮? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 60,097評(píng)論 1 300
  • 正文 為了忘掉前任炒瘟,我火速辦了婚禮,結(jié)果婚禮上第步,老公的妹妹穿的比我還像新娘疮装。我一直安慰自己,他們只是感情好粘都,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,100評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布廓推。 她就那樣靜靜地躺著,像睡著了一般翩隧。 火紅的嫁衣襯著肌膚如雪樊展。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,696評(píng)論 1 312
  • 那天,我揣著相機(jī)與錄音专缠,去河邊找鬼雷酪。 笑死,一個(gè)胖子當(dāng)著我的面吹牛涝婉,可吹牛的內(nèi)容都是我干的哥力。 我是一名探鬼主播,決...
    沈念sama閱讀 41,165評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼墩弯,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼吩跋!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起渔工,我...
    開(kāi)封第一講書(shū)人閱讀 40,108評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤锌钮,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后引矩,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體轧粟,經(jīng)...
    沈念sama閱讀 46,646評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,709評(píng)論 3 342
  • 正文 我和宋清朗相戀三年脓魏,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了兰吟。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,861評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡茂翔,死狀恐怖混蔼,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情珊燎,我是刑警寧澤惭嚣,帶...
    沈念sama閱讀 36,527評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站悔政,受9級(jí)特大地震影響晚吞,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜谋国,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,196評(píng)論 3 336
  • 文/蒙蒙 一槽地、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧芦瘾,春花似錦捌蚊、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,698評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至祷愉,卻和暖如春窗宦,著一層夾襖步出監(jiān)牢的瞬間赦颇,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,804評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工赴涵, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留沐扳,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,287評(píng)論 3 379
  • 正文 我出身青樓句占,卻偏偏與公主長(zhǎng)得像沪摄,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子纱烘,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,860評(píng)論 2 361

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

  • rljs by sennchi Timeline of History Part One The Cognitiv...
    sennchi閱讀 7,350評(píng)論 0 10
  • 本文重點(diǎn)介紹應(yīng)用程序的啟動(dòng)過(guò)程杨拐,應(yīng)用程序的啟動(dòng)過(guò)程實(shí)際上就是應(yīng)用程序中的默認(rèn)Activity的啟動(dòng)過(guò)程,本文將詳細(xì)...
    天宇sonny閱讀 405評(píng)論 1 0
  • 女兒兩歲了擂啥,最近休息帶她時(shí)哄陶,發(fā)現(xiàn)一個(gè)狀況重復(fù)發(fā)生:當(dāng)有其他小朋友搶走她手里的玩具時(shí),女兒不會(huì)有任何反抗哺壶,而是噘著嘴...
    多多余兒閱讀 419評(píng)論 0 0
  • Chrome應(yīng)該是很多人都喜歡用的瀏覽器屋吨,特別是玩前端的同學(xué)。Chrome上的各種插件也是很好用的山宾。對(duì)于Chrom...
    吃土的小此方閱讀 583評(píng)論 0 6
  • 只打甜蜜區(qū)的球——精進(jìn)自己的可執(zhí)行至扰,可以做好的方面。組織中補(bǔ)短板是為了組織更加的好资锰,但是個(gè)人成長(zhǎng)的話更多的需要發(fā)展...
    B型血兔子007閱讀 196評(píng)論 0 0