ReactNative-調(diào)用原生開(kāi)啟新頁(yè)面-Android篇

開(kāi)啟新頁(yè)面-Andorid篇

最近在研究ReactNative,想用于新的項(xiàng)目開(kāi)發(fā),發(fā)現(xiàn)我們傳統(tǒng)的Android中開(kāi)Activity的方式?jīng)]有了,只能通過(guò)導(dǎo)航控制器來(lái)實(shí)現(xiàn),但是導(dǎo)航控制器本身又很難實(shí)現(xiàn)設(shè)計(jì)MM出的效果,故考慮自己寫(xiě)一個(gè)源生模塊來(lái)實(shí)現(xiàn)交互,然后導(dǎo)航控制器自定義就好了

前面

何謂開(kāi)啟新頁(yè)面呢?andorid中有兩種描述頁(yè)面的方式,一個(gè)是Activity,一種是Fragment
我這里的開(kāi)啟新頁(yè)面就是使用Intent的方式開(kāi)啟Activity
這里就是使用ReactNative(后稱(chēng)RN)開(kāi)啟新頁(yè)面

項(xiàng)目

項(xiàng)目地址
目前項(xiàng)目托管在oschina上,后續(xù)遷移到github

開(kāi)發(fā)環(huán)境

macos,用windows/linux的請(qǐng)自行探索相關(guān)的開(kāi)發(fā)步驟或者環(huán)境

node版本
npm版本
RN的版本
package.json 詳見(jiàn)截圖


屏幕快照 2017-07-14 上午8.31.48.png

使用WebStrom開(kāi)發(fā)js部分
AndroidStudio開(kāi)發(fā)Android的原生部分

思路分析

分析官網(wǎng)模塊的注入

首先肯定要先可以完成交互,再考慮如何去實(shí)現(xiàn)

官方文檔

android原生模塊
這里詳細(xì)解說(shuō)了如何使用js調(diào)用原生模塊

我們都知道ReactNative本身還是渲染js腳本來(lái)形成源生控件,而android必須要有一個(gè)Activity來(lái)作為載體

  1. 創(chuàng)建原生模塊
  2. 將原生模塊注入application
  3. 調(diào)用源生代碼

動(dòng)手寫(xiě)代碼

首先創(chuàng)建一個(gè)模塊

package com.sxwphone;


import android.app.Activity;

import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;

/**
 * Created by cai on 2017/7/13.
 */

public class StartNewHelper extends ReactContextBaseJavaModule {

    public StartNewHelper(ReactApplicationContext reactContext) {
        super(reactContext);
    }

    @ReactMethod
    public void startNewActivity(String name) {
        Activity activity = getCurrentActivity();
        if (activity instanceof StartNewActivity) {
            ((StartNewActivity) activity).startNewActivity(name);
        }
    }

    @Override
    public String getName() {
        return "startNew";
    }
}


首先是一個(gè)模塊,這個(gè)模塊的名字是startNew 也就是getName()中的返回值后面我們會(huì)用到它
這里吐槽自己一下,這類(lèi)名起的真爛,讓后續(xù)維護(hù)的人沒(méi)法用啊(實(shí)際項(xiàng)目中不要這樣隨意,否則會(huì)被罵死的)
這里的@ReactMethod標(biāo)識(shí)的方法startNewActivity(String name)就是后續(xù)js要用到的方法,這里記錄一下

關(guān)聯(lián)模塊

我們有了自己的模塊,得將它與項(xiàng)目關(guān)聯(lián)起來(lái)
首先需要?jiǎng)?chuàng)建一個(gè)ReactPackage,將模塊注入其中

package com.sxwphone;

import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;

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

public class ExampleReactPackage implements ReactPackage {
    @Override
    public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
      List<NativeModule> modules = new ArrayList<>();
      modules.add(new StartNewHelper(reactContext));
      return modules;
    }

    @Override
    public List<Class<? extends JavaScriptModule>> createJSModules() {
      return Collections.emptyList();
    }

    @Override
    public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
      return Collections.emptyList();
    }
}

這里在List<NativeModule> createNativeModules(ReactApplicationContext reactContext)中將module加到集合里

這里還需要將package加入到ReactNativeHost

package com.sxwphone;

import android.app.Application;

import com.facebook.react.ReactApplication;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage;
import com.facebook.react.shell.MainReactPackage;
import com.facebook.soloader.SoLoader;

import java.util.Arrays;
import java.util.List;

public class MainApplication extends Application implements ReactApplication {

    private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
        @Override
        public boolean getUseDeveloperSupport() {
            return BuildConfig.DEBUG;
        }

        @Override
        protected List<ReactPackage> getPackages() {
            return Arrays.asList(new MainReactPackage(), new ExampleReactPackage());
        }
    };

    @Override
    public ReactNativeHost getReactNativeHost() {
        return mReactNativeHost;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        SoLoader.init(this, /* native exopackage */ false);
    }
}


這個(gè)是application的代碼,其中有一個(gè)ReactNativieHost,將我們的ExampleReactPackage加入到List<ReactPackage> getPackages()創(chuàng)建的集合中,這樣我們就完成了Native模塊的注入


js調(diào)用

到了這里我們就可以通過(guò)js調(diào)用到方法了

/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 * @flow
 */

import React, { Component } from 'react';
import {
  AppRegistry, Button,
  StyleSheet,
  Text,
  View
} from 'react-native';
import {NativeModules} from 'react-native';


export default class sxwphone extends Component {
  render() {
    return (
      <View style={styles.container}>
        <Text style={styles.welcome}>
          Welcome to React Native!
        </Text>
        <Text style={styles.instructions}>
          To get started, edit index.android.js
        </Text>
        <Text style={styles.instructions}>
          Double tap R on your keyboard to reload,{'\n'}
          Shake or press menu button for dev menu
        </Text>
        <Button title={'點(diǎn)擊'} onPress={() => this.newPage()}/>
      </View>
    );
  }

  newPage() {
    var startHelper = NativeModules.startNew;
    startHelper.startNewActivity("abc")
  }

        // newPage() {
        //
        // }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },
  welcome: {
    fontSize: 20,
    textAlign: 'center',
    margin: 10,
  },
  instructions: {
    textAlign: 'center',
    color: '#333333',
    marginBottom: 5,
  },
});

AppRegistry.registerComponent('sxwphone', () => sxwphone);
AppRegistry.registerComponent('abc', () => sxwphone);

這里我是比較懶,沒(méi)有寫(xiě)多個(gè)模塊,等于是同一個(gè)頁(yè)面,只是開(kāi)在不同的兩個(gè)Activity上了
這個(gè)時(shí)候運(yùn)行應(yīng)用

Activity

package com.sxwphone;

/**
 * Created by cai on 2017/7/13.
 */

public interface StartNewActivity {
    void startNewActivity(String name);

    String getMainComponentName();
}

MainActivity:

    

    @Override
    public void startNewActivity(String name) {
        Intent intent = new Intent(this, NewActivity.class);
        intent.putExtra(NewActivity.NAME, name);
        startActivity(intent);
    }

NewActivity:

@Nullable
    @Override
    public String getMainComponentName() {
        if (getIntent() == null) {
            return null;
        }
        String name = getIntent().getStringExtra(NAME);
        if (name == null || name.isEmpty()) {
            finish();
            return "";
        }
        return name;
    }

這里我重寫(xiě)了getMainComponentName()方法,不直接返回字符串了,返回一個(gè)從上個(gè)頁(yè)面?zhèn)鱽?lái)的值也就是abc
這樣應(yīng)該可以調(diào)用到對(duì)應(yīng)的模塊了吧

接下來(lái)運(yùn)行吧

運(yùn)行

運(yùn)行andorid,發(fā)現(xiàn)崩潰了,崩潰了....

查下原因:

NoFountActivity
哦哦 沒(méi)注冊(cè)Activity啊 打開(kāi)AndoridManifest.xml注冊(cè)下

這個(gè)時(shí)候以為結(jié)束了?太天真了!!!

發(fā)現(xiàn)這時(shí)候新頁(yè)面打開(kāi)了,咋是空白一片呢?
這個(gè)時(shí)候就要考慮問(wèn)題出在哪里了呢

解決方案

我們MainActivity直接返回模塊名就成功了,為啥這里不成功呢,Intent的傳遞一定沒(méi)錯(cuò),那么錯(cuò)在哪里呢
這個(gè)時(shí)候應(yīng)該想如何去解決這樣的問(wèn)題了,為啥會(huì)空白一片呢
我們考慮是不是調(diào)用時(shí)機(jī)出現(xiàn)了問(wèn)題呢?

查看Android代碼

打開(kāi)MainActivity,發(fā)現(xiàn)MainActivity是繼承自ReactActivity
這時(shí)候打開(kāi)ReactActivity發(fā)現(xiàn)是直接繼承自Activity的,那么具體實(shí)現(xiàn)就在這里了
onCreate方法中有一個(gè)delegate,我們發(fā)現(xiàn)所有的Activity生命周期方法都和delegate關(guān)聯(lián)了
具體實(shí)現(xiàn)查看下delegate 是ReactActivityDelegate

ReactActivityDelegate

private final @Nullable String mMainComponentName;

public ReactActivityDelegate(Activity activity, @Nullable String mainComponentName) {
    mActivity = activity;
    mMainComponentName = mainComponentName;
    mFragmentActivity = null;
  }

  public ReactActivityDelegate(
    FragmentActivity fragmentActivity,
    @Nullable String mainComponentName) {
    mFragmentActivity = fragmentActivity;
    mMainComponentName = mainComponentName;
    mActivity = null;
  }

protected void onCreate(Bundle savedInstanceState) {
    boolean needsOverlayPermission = false;
    if (getReactNativeHost().getUseDeveloperSupport() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
      // Get permission to show redbox in dev builds.
      if (!Settings.canDrawOverlays(getContext())) {
        needsOverlayPermission = true;
        Intent serviceIntent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getContext().getPackageName()));
        FLog.w(ReactConstants.TAG, REDBOX_PERMISSION_MESSAGE);
        Toast.makeText(getContext(), REDBOX_PERMISSION_MESSAGE, Toast.LENGTH_LONG).show();
        ((Activity) getContext()).startActivityForResult(serviceIntent, REQUEST_OVERLAY_PERMISSION_CODE);
      }
    }

    if (mMainComponentName != null && !needsOverlayPermission) {
      loadApp(mMainComponentName);
    }
    mDoubleTapReloadRecognizer = new DoubleTapReloadRecognizer();
  }

這里會(huì)發(fā)現(xiàn)我們?cè)贛ainActivity中實(shí)現(xiàn)的mMainComponentName就是被用到這里了

if (mMainComponentName != null && !needsOverlayPermission) {
      loadApp(mMainComponentName);
    }

而且這個(gè)是一個(gè)final字段,我們不能改寫(xiě),而這個(gè)name又是在Activity的構(gòu)造方法中傳入的
作為多年的Android程序員,我們知道Activity這個(gè)東西是由ActivityThread創(chuàng)建的,這個(gè)時(shí)候Intent還沒(méi)生效呢呢,而構(gòu)造方法又必然首先運(yùn)行,所以運(yùn)行順序是

Activity 構(gòu)造方法->Activity.getMainComponentName()->null

這里如果就這樣運(yùn)行的話(huà),無(wú)論如何也只能獲得空,我們必須讓loadApp可以運(yùn)行,且componentName不是空
這里牽扯到兩種寫(xiě)法,我在onCreate前調(diào)用Intent,獲取到Component的名字,通過(guò)暴力反射的方式,修改名稱(chēng),這里可以這樣寫(xiě),但是我想了一下,放棄了,反射影響效率,而且代碼不優(yōu)雅,所以考慮使用別的方案解決

這里查看下loadApp的調(diào)用,發(fā)現(xiàn)onActivityResult()中也有執(zhí)行

public void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (getReactNativeHost().hasInstance()) {
      getReactNativeHost().getReactInstanceManager()
        .onActivityResult(getPlainActivity(), requestCode, resultCode, data);
    } else {
      // Did we request overlay permissions?
      if (requestCode == REQUEST_OVERLAY_PERMISSION_CODE && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        if (Settings.canDrawOverlays(getContext())) {
          if (mMainComponentName != null) {
            loadApp(mMainComponentName);
          }
          Toast.makeText(getContext(), REDBOX_PERMISSION_GRANTED_MESSAGE, Toast.LENGTH_LONG).show();
        }
      }
    }
  }

這里是為什么呢?
這就牽扯到Android6.0的運(yùn)行時(shí)權(quán)限了
這里如果檢查到運(yùn)行時(shí)權(quán)限沒(méi)通過(guò),就需要到activityResult中再執(zhí)行加載界面的代碼

MyReactActivityDelegate

既然我們無(wú)法復(fù)寫(xiě)final的方法,那就需要我們創(chuàng)建自己的Delegate
繼承ReactActivityDelegate


public class MyReactActivityDelegate extends ReactActivityDelegate {
    private final Activity activity;
    private final String firstMainComponentName;

    private static final String REDBOX_PERMISSION_GRANTED_MESSAGE =
            "Overlay permissions have been granted.";

    public MyReactActivityDelegate(Activity activity, @Nullable String mainComponentName) {
        super(activity, mainComponentName);
        this.activity = activity;
        firstMainComponentName = mainComponentName;
    }

    public MyReactActivityDelegate(FragmentActivity fragmentActivity, @Nullable String mainComponentName) {
        super(fragmentActivity, mainComponentName);
        this.activity = fragmentActivity;
        firstMainComponentName = mainComponentName;
    }

    private boolean isLoadApp = false;

    public boolean isLoadApp() {
        return isLoadApp;
    }

    @Override
    protected void loadApp(String appKey) {
        if (activity instanceof StartNewActivity) {
            if (isLoadApp()) {
                return;
            }
            String mainComponentName = ((StartNewActivity) activity).getMainComponentName();
            super.loadApp(mainComponentName);
            isLoadApp = true;
        } else {
            super.loadApp(appKey);
        }
    }

    private static final String REDBOX_PERMISSION_MESSAGE =
            "Overlay permissions needs to be granted in order for react native apps to run in dev mode";

    private static final int REQUEST_OVERLAY_PERMISSION_CODE = 1111;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        String mMainComponentName = null;
        if (activity instanceof StartNewActivity) {
            mMainComponentName = ((StartNewActivity) activity).getMainComponentName();
        }

        boolean needsOverlayPermission = false;
        if (getReactNativeHost().getUseDeveloperSupport() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            // Get permission to show redbox in dev builds.
            if (!Settings.canDrawOverlays(activity)) {
                needsOverlayPermission = true;
            }
        }

        if (mMainComponentName != null && !needsOverlayPermission) {
            loadApp(mMainComponentName);
            return;
        }

        super.onCreate(savedInstanceState);
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        String mMainComponentName = null;
        if (activity instanceof StartNewActivity) {
            mMainComponentName = ((StartNewActivity) activity).getMainComponentName();
        }

        if (getReactNativeHost().hasInstance()) {
            getReactNativeHost().getReactInstanceManager()
                    .onActivityResult((Activity) getContext(), requestCode, resultCode, data);
        } else {
            // Did we request overlay permissions?
            if (requestCode == REQUEST_OVERLAY_PERMISSION_CODE && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                if (Settings.canDrawOverlays(getContext())) {
                    if (firstMainComponentName != null) {
                        loadApp(firstMainComponentName);
                    } else if (mMainComponentName != null) {
                        loadApp(mMainComponentName);
                    }
                    Toast.makeText(getContext(), REDBOX_PERMISSION_GRANTED_MESSAGE, Toast.LENGTH_LONG).show();
                }
            }
        }
    }

    protected Context getContext() {
        return activity;
    }
}

這里將activity和原始的componentName都作為成員變量寫(xiě)了下來(lái),方便后面的調(diào)用
在onCreate的時(shí)候檢查權(quán)限和當(dāng)時(shí)的方法中獲取的名字,如果不是空,則loadApp
activityResult中同理,如果最初的name不為空,則加載最初的名字,如果為空,則繼續(xù)判斷方法中獲取的名字

接著修改NewActivity

package com.sxwphone;

import android.content.Intent;

import com.facebook.react.ReactActivity;
import com.facebook.react.ReactActivityDelegate;

import javax.annotation.Nullable;

/**
 * Created by cai on 2017/7/13.
 */

public class NewActivity extends ReactActivity implements StartNewActivity {

    public static final String NAME = "_name";

    @Override
    protected ReactActivityDelegate createReactActivityDelegate() {
        return new MyReactActivityDelegate(this, getMainComponentName());
    }

    @Nullable
    @Override
    public String getMainComponentName() {
        if (getIntent() == null) {
            return null;
        }
        String name = getIntent().getStringExtra(NAME);
        if (name == null || name.isEmpty()) {
            finish();
            return "";
        }
        return name;
    }

    @Override
    public void startNewActivity(String name) {
        Intent intent = new Intent(this, NewActivity.class);
        intent.putExtra(NewActivity.NAME, name);
        startActivity(intent);
    }
}

運(yùn)行

發(fā)現(xiàn)點(diǎn)擊按鈕就可以調(diào)用到新模塊了
修改完的最終代碼查看 項(xiàng)目地址

總結(jié)

這個(gè)項(xiàng)目沒(méi)用多長(zhǎng)時(shí)間就完成了,但是可以說(shuō)初窺了RN和android的交互,RN的模塊如何注入,其中牽扯到了一些android的知識(shí),RN的一部分知識(shí),可能對(duì)于RN+android老手來(lái)說(shuō)沒(méi)什么,但是我想對(duì)于RN新手或者前端轉(zhuǎn)android的人來(lái)說(shuō)還算可以看看的文章

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市欧引,隨后出現(xiàn)的幾起案子外驱,更是在濱河造成了極大的恐慌世曾,老刑警劉巖喉钢,帶你破解...
    沈念sama閱讀 219,110評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件橘券,死亡現(xiàn)場(chǎng)離奇詭異榨崩,居然都是意外死亡七问,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,443評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)棒假,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)溯职,“玉大人,你說(shuō)我怎么就攤上這事帽哑∶站疲” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,474評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵妻枕,是天一觀的道長(zhǎng)僻族。 經(jīng)常有香客問(wèn)我,道長(zhǎng)屡谐,這世上最難降的妖魔是什么述么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,881評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮愕掏,結(jié)果婚禮上度秘,老公的妹妹穿的比我還像新娘。我一直安慰自己饵撑,他們只是感情好剑梳,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,902評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著滑潘,像睡著了一般垢乙。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上语卤,一...
    開(kāi)封第一講書(shū)人閱讀 51,698評(píng)論 1 305
  • 那天追逮,我揣著相機(jī)與錄音酪刀,去河邊找鬼。 笑死钮孵,一個(gè)胖子當(dāng)著我的面吹牛骂倘,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播油猫,決...
    沈念sama閱讀 40,418評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼稠茂,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼柠偶!你這毒婦竟也來(lái)了情妖?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,332評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤诱担,失蹤者是張志新(化名)和其女友劉穎毡证,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體蔫仙,經(jīng)...
    沈念sama閱讀 45,796評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡料睛,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,968評(píng)論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了摇邦。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片恤煞。...
    茶點(diǎn)故事閱讀 40,110評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖施籍,靈堂內(nèi)的尸體忽然破棺而出居扒,到底是詐尸還是另有隱情,我是刑警寧澤丑慎,帶...
    沈念sama閱讀 35,792評(píng)論 5 346
  • 正文 年R本政府宣布喜喂,位于F島的核電站,受9級(jí)特大地震影響竿裂,放射性物質(zhì)發(fā)生泄漏玉吁。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,455評(píng)論 3 331
  • 文/蒙蒙 一腻异、第九天 我趴在偏房一處隱蔽的房頂上張望进副。 院中可真熱鬧,春花似錦悔常、人聲如沸影斑。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,003評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)鸥昏。三九已至,卻和暖如春姐帚,著一層夾襖步出監(jiān)牢的瞬間吏垮,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,130評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留膳汪,地道東北人唯蝶。 一個(gè)月前我還...
    沈念sama閱讀 48,348評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像遗嗽,于是被迫代替她去往敵國(guó)和親粘我。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,047評(píng)論 2 355

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

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,173評(píng)論 25 707
  • 這篇文章針對(duì)于對(duì)rn有些基礎(chǔ)的同學(xué)痹换,沒(méi)有基礎(chǔ)的同學(xué)可以先了解一下rn以后再看這篇文章征字。要想深入理解 React N...
    小草33閱讀 2,049評(píng)論 1 5
  • ¥開(kāi)啟¥ 【iAPP實(shí)現(xiàn)進(jìn)入界面執(zhí)行逐一顯】 〖2017-08-25 15:22:14〗 《//首先開(kāi)一個(gè)線(xiàn)程,因...
    小菜c閱讀 6,426評(píng)論 0 17
  • 最近剛從舊公司離職娇豫,為面試在做準(zhǔn)備匙姜,因?yàn)槠綍r(shí)開(kāi)發(fā)CV大法用得比較多,很多基礎(chǔ)知識(shí)掌握得不是很牢靠以及很多工具框架只...
    黎清海閱讀 2,196評(píng)論 1 19
  • 網(wǎng)上類(lèi)似的文章很多冯痢,這里只是做個(gè)記錄氮昧!也希望對(duì)大家有所幫助! 總體來(lái)說(shuō)安裝比較簡(jiǎn)單浦楣,主要問(wèn)題還在天朝網(wǎng)絡(luò)上袖肥!這里假...
    TerryXi閱讀 1,776評(píng)論 0 1