ReactNative源码解析-启动流程

栏目: JavaScript · 发布时间: 5年前

内容简介:在开始分析启动流程之前,我们先从混合开发流程入手,大致分两步。1.继承ReactActivity,并完成相关初始化工作。注意:

在开始分析启动流程之前,我们先从混合开发流程入手,大致分两步。

1.继承ReactActivity,并完成相关初始化工作。

public class MyReactActivity extends Activity implements DefaultHardwareBackBtnHandler {
    private ReactRootView mReactRootView;
    private ReactInstanceManager mReactInstanceManager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mReactRootView = new ReactRootView(this);
        mReactInstanceManager = ReactInstanceManager.builder()
                .setApplication(getApplication())
                .setCurrentActivity(this)
                .setBundleAssetName("index.android.bundle")
                .setJSMainModulePath("index")
                .addPackage(new MainReactPackage())
                .setUseDeveloperSupport(BuildConfig.DEBUG)
                .setInitialLifecycleState(LifecycleState.RESUMED)
                .build();
        // The string here (e.g. "MyReactNativeApp") has to match
        // the string in AppRegistry.registerComponent() in index.js
        mReactRootView.startReactApplication(mReactInstanceManager, "MyReactNativeApp", null);

        setContentView(mReactRootView);
    }

    @Override
    public void invokeDefaultOnBackPressed() {
        super.onBackPressed();
    }
    
    @Override
    protected void onPause() {
        super.onPause();
    
        if (mReactInstanceManager != null) {
            mReactInstanceManager.onHostPause(this);
        }
    }
    
    @Override
    protected void onResume() {
        super.onResume();
    
        if (mReactInstanceManager != null) {
            mReactInstanceManager.onHostResume(this, this);
        }
    }
    
    @Override
    protected void onDestroy() {
        super.onDestroy();
    
        if (mReactInstanceManager != null) {
            mReactInstanceManager.onHostDestroy(this);
        }
        if (mReactRootView != null) {
            mReactRootView.unmountReactApplication();
        }
    }
    
    @Override
     public void onBackPressed() {
        if (mReactInstanceManager != null) {
            mReactInstanceManager.onBackPressed();
        } else {
            super.onBackPressed();
        }
    }
}
复制代码

注意:

a.实际开发中,一般会写一个单例来获取mReactInstanceManager对象,多个ReactFragment or ReactActivity可以共享同一个mReactInstanceManager对象。

b.mReactRootView作为ReactActivity的根布局,是一个容器View,我们编写的react代码,最终就是被attach到这里的。

2.编写React代码

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

class HelloWorld extends React.Component {
  render() {
    return (
      <View style={styles.container}>
        <Text style={styles.hello}>Hello, World</Text>
      </View>
    );
  }
}
var styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
  },
  hello: {
    fontSize: 20,
    textAlign: 'center',
    margin: 10,
  },
});

AppRegistry.registerComponent('MyReactNativeApp', () => HelloWorld);
复制代码

完成以上2步,在增加一些配置操作,相信你应该能把一个demo run起来了。

二、启动流程

启动流程分以下几步:

1 在程序启动的时候,也就是ReactActivity的onCreate()函数中,我们会去创建一个ReactInstanceManager对象,ReactInstanceManager用于管理生命周期,管理ReactRootView,以及一些配置等。

2 ReactRootView作为应用的根视图,通过调用ReactRootView.startReactApplication()方法启动应用。

3 RN应用页面渲染前,需要先创建ReactContext的创建流程在,异步任务ReactContextInitAsyncTask负责来完成这个任务。

4 ReactContextInitAsyncTask在后台ReactContextInitAsyncTask.doInBackground()执行ReactContext的创建,创建ReactContext的过程中,会依据ReactPackage创建JavaScriptModuleRegistry与
NativeModuleRegistry注册表以及它们的管理类CatalystInstanceImpl,同时创建JS、Native与UI线程队列,并最终调用CatalystInstanceImpl.runJSBundle()去异步
加载JS Bundle文件。

5 后台任务执行完成后,在ReactContextInitAsyncTask.onPostExecute()会调用ReactInstanceManager.setupReactContext()设置创建好的ReactContext,并将
ReactRootView加载进来,并调用RN应用的JS入口APPRegistry来启动应用。

6 JS层找到已经注册的对应的启动组件,执行renderApplication()来渲染整个应用。
复制代码

按照初始化流程,我们从ReactActivity入手。通过查看源码我们发现,ReactActivity并没有做太多事情,大部分事情的交给了代理类ReactActivityDelegate来实现,那我们一起看看ReactActivityDelegate都干了什么。

public class ReactActivityDelegate {

  private final @Nullable Activity mActivity;
  private final @Nullable String mMainComponentName;

  private @Nullable ReactRootView mReactRootView;
  private @Nullable DoubleTapReloadRecognizer mDoubleTapReloadRecognizer;
  private @Nullable PermissionListener mPermissionListener;
  private @Nullable Callback mPermissionsCallback;

  protected ReactRootView createRootView() {
    return new ReactRootView(getContext());
  }

  protected ReactNativeHost getReactNativeHost() {
    return ((ReactApplication) getPlainActivity().getApplication()).getReactNativeHost();
  }

  public ReactInstanceManager getReactInstanceManager() {
    return getReactNativeHost().getReactInstanceManager();
  }

  protected void onCreate(Bundle savedInstanceState) {
    String mainComponentName = getMainComponentName();
    if (mainComponentName != null) {
      loadApp(mainComponentName);
    }
    mDoubleTapReloadRecognizer = new DoubleTapReloadRecognizer();
  }

  protected void loadApp(String appKey) {
    if (mReactRootView != null) {
      throw new IllegalStateException("Cannot loadApp while app is already running.");
    }
    mReactRootView = createRootView();
    mReactRootView.startReactApplication(
      getReactNativeHost().getReactInstanceManager(),
      appKey,
      getLaunchOptions());
    getPlainActivity().setContentView(mReactRootView);
  }

}
复制代码

可以发现,ReactActivityDelegate在创建时主要做了3件事:

1.创建ReactRootView   
2.调用mReactRootView的startReactApplication方法,继续执行应用启动流程   
3.调用setContentView方法,将mReactRootView作为ReactActivity的content view
复制代码

从中不难发现,ReactNative真正的核心就在ReactRootView,,ReactRootView本质上是一个FrameLayout,并没有什么神奇的魔法,好,接下来让我们看看跳进ReactRootView继续我们的启动流程分析。

public void startReactApplication(
      ReactInstanceManager reactInstanceManager,
      String moduleName,
      @Nullable Bundle initialProperties,
      @Nullable String initialUITemplate) {
    Systrace.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "startReactApplication");
    try {
      UiThreadUtil.assertOnUiThread();

      // TODO(6788889): Use POJO instead of bundle here, apparently we can't just use WritableMap
      // here as it may be deallocated in native after passing via JNI bridge, but we want to reuse
      // it in the case of re-creating the catalyst instance
      Assertions.assertCondition(
        mReactInstanceManager == null,
        "This root view has already been attached to a catalyst instance manager");

      mReactInstanceManager = reactInstanceManager;
      mJSModuleName = moduleName;
      mAppProperties = initialProperties;
      mInitialUITemplate = initialUITemplate;

      if (mUseSurface) {
        // TODO initialize surface here
      }

      if (!mReactInstanceManager.hasStartedCreatingInitialContext()) {
        mReactInstanceManager.createReactContextInBackground();
      }

      attachToReactInstanceManager();

    } finally {
      Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
    }
  }
复制代码

在这个方法中,主要创建了应用的上下文-ReactContext,同时把传入的参数赋给了成员变量。

我们来看看这4个参数:

ReactInstanceManager reactInstanceManager :管理React实例。  
String moduleName:模块的名字,对应ReactActivity.getMainComponentName()与AppRegistry.registerComponent()。   
Bundle initialProperties:Bundle类型的数据,可以通过这个参数在startActivity()时传递参数到JS层。  
String initialUITemplate:0.59版本并没有用到,可能是代码还没写完吧,尴尬。  
复制代码

继续我们的启动流程,ReactInstanceManager.createReactContextInBackground()。看名字好像是在后台线程创建上下文,我们跳进去看看。

@ThreadSafe
public class ReactInstanceManager {
    @ThreadConfined(UI)
  public void createReactContextInBackground() {
    Log.d(ReactConstants.TAG, "ReactInstanceManager.createReactContextInBackground()");
    Assertions.assertCondition(
        !mHasStartedCreatingInitialContext,
        "createReactContextInBackground should only be called when creating the react " +
            "application for the first time. When reloading JS, e.g. from a new file, explicitly" +
            "use recreateReactContextInBackground");

    mHasStartedCreatingInitialContext = true;
    recreateReactContextInBackgroundInner();
  }
  
  @ThreadConfined(UI)
  public void recreateReactContextInBackground() {
    Assertions.assertCondition(
        mHasStartedCreatingInitialContext,
        "recreateReactContextInBackground should only be called after the initial " +
            "createReactContextInBackground call.");
    recreateReactContextInBackgroundInner();
  }

  @ThreadConfined(UI)
  private void recreateReactContextInBackgroundInner() {
    Log.d(ReactConstants.TAG, "ReactInstanceManager.recreateReactContextInBackgroundInner()");
    PrinterHolder.getPrinter()
        .logMessage(ReactDebugOverlayTags.RN_CORE, "RNCore: recreateReactContextInBackground");
    UiThreadUtil.assertOnUiThread();

    if (mUseDeveloperSupport && mJSMainModulePath != null) {
      final DeveloperSettings devSettings = mDevSupportManager.getDevSettings();

      // If remote JS debugging is enabled, load from dev server.
      if (mDevSupportManager.hasUpToDateJSBundleInCache() &&
          !devSettings.isRemoteJSDebugEnabled()) {
        // If there is a up-to-date bundle downloaded from server,
        // with remote JS debugging disabled, always use that.
        onJSBundleLoadedFromServer(null);
        return;
      }

      if (!Systrace.isTracing(TRACE_TAG_REACT_APPS | TRACE_TAG_REACT_JS_VM_CALLS)) {
        if (mBundleLoader == null) {
          mDevSupportManager.handleReloadJS();
        } else {
          mDevSupportManager.isPackagerRunning(
              new PackagerStatusCallback() {
                @Override
                public void onPackagerStatusFetched(final boolean packagerIsRunning) {
                  UiThreadUtil.runOnUiThread(
                      new Runnable() {
                        @Override
                        public void run() {
                          if (packagerIsRunning) {
                            mDevSupportManager.handleReloadJS();
                          } else {
                            // If dev server is down, disable the remote JS debugging.
                            devSettings.setRemoteJSDebugEnabled(false);
                            recreateReactContextInBackgroundFromBundleLoader();
                          }
                        }
                      });
                }
              });
        }
        return;
      }
    }

    recreateReactContextInBackgroundFromBundleLoader();
  }

  @ThreadConfined(UI)
  private void recreateReactContextInBackgroundFromBundleLoader() {
    Log.d(
      ReactConstants.TAG,
      "ReactInstanceManager.recreateReactContextInBackgroundFromBundleLoader()");
    PrinterHolder.getPrinter()
        .logMessage(ReactDebugOverlayTags.RN_CORE, "RNCore: load from BundleLoader");
    recreateReactContextInBackground(mJavaScriptExecutorFactory, mBundleLoader);
  }
  
  @ThreadConfined(UI)
  private void runCreateReactContextOnNewThread(final ReactContextInitParams initParams) {
    Log.d(ReactConstants.TAG, "ReactInstanceManager.runCreateReactContextOnNewThread()");
    UiThreadUtil.assertOnUiThread();
    synchronized (mAttachedReactRoots) {
      synchronized (mReactContextLock) {
        if (mCurrentReactContext != null) {
          tearDownReactContext(mCurrentReactContext);
          mCurrentReactContext = null;
        }
      }
    }

    mCreateReactContextThread =
        new Thread(
            null,
            new Runnable() {
              @Override
              public void run() {
                ReactMarker.logMarker(REACT_CONTEXT_THREAD_END);
                synchronized (ReactInstanceManager.this.mHasStartedDestroying) {
                  while (ReactInstanceManager.this.mHasStartedDestroying) {
                    try {
                      ReactInstanceManager.this.mHasStartedDestroying.wait();
                    } catch (InterruptedException e) {
                      continue;
                    }
                  }
                }
                // As destroy() may have run and set this to false, ensure that it is true before we create
                mHasStartedCreatingInitialContext = true;

                try {
                  Process.setThreadPriority(Process.THREAD_PRIORITY_DISPLAY);
                  ReactMarker.logMarker(VM_INIT);
                  final ReactApplicationContext reactApplicationContext =
                      createReactContext(
                          initParams.getJsExecutorFactory().create(),
                          initParams.getJsBundleLoader());

                  mCreateReactContextThread = null;
                  ReactMarker.logMarker(PRE_SETUP_REACT_CONTEXT_START);
                  final Runnable maybeRecreateReactContextRunnable =
                      new Runnable() {
                        @Override
                        public void run() {
                          if (mPendingReactContextInitParams != null) {
                            runCreateReactContextOnNewThread(mPendingReactContextInitParams);
                            mPendingReactContextInitParams = null;
                          }
                        }
                      };
                  Runnable setupReactContextRunnable =
                      new Runnable() {
                        @Override
                        public void run() {
                          try {
                            setupReactContext(reactApplicationContext);
                          } catch (Exception e) {
                            mDevSupportManager.handleException(e);
                          }
                        }
                      };

                  reactApplicationContext.runOnNativeModulesQueueThread(setupReactContextRunnable);
                  UiThreadUtil.runOnUiThread(maybeRecreateReactContextRunnable);
                } catch (Exception e) {
                  mDevSupportManager.handleException(e);
                }
              }
            },
            "create_react_context");
    ReactMarker.logMarker(REACT_CONTEXT_THREAD_START);
    mCreateReactContextThread.start();
  }
   private ReactApplicationContext createReactContext(
      JavaScriptExecutor jsExecutor,
      JSBundleLoader jsBundleLoader) {
    Log.d(ReactConstants.TAG, "ReactInstanceManager.createReactContext()");
    ReactMarker.logMarker(CREATE_REACT_CONTEXT_START, jsExecutor.getName());
    //在这里创建ReactContext,ReactApplicationContext继承自ReactContext
    final ReactApplicationContext reactContext = new ReactApplicationContext(mApplicationContext);
    //查看外部是否设置NativeModuleCallExceptionHandler,它是在ReactInstanceManagerBuilder构建ReactInstanceManager是传递进来的
    //如果设置了则使用外部NativeModuleCallExceptionHandler,如果没有设置则使用DevSupportManager。
    NativeModuleCallExceptionHandler exceptionHandler = mNativeModuleCallExceptionHandler != null
        ? mNativeModuleCallExceptionHandler
        : mDevSupportManager;
    reactContext.setNativeModuleCallExceptionHandler(exceptionHandler);

    NativeModuleRegistry nativeModuleRegistry = processPackages(reactContext, mPackages, false);
    //jsExecutor、nativeModuleRegistry、nativeModuleRegistry等各种参数处理好之后,开始构建CatalystInstanceImpl实例。
    CatalystInstanceImpl.Builder catalystInstanceBuilder = new CatalystInstanceImpl.Builder()
      .setReactQueueConfigurationSpec(ReactQueueConfigurationSpec.createDefault())
      .setJSExecutor(jsExecutor)
      .setRegistry(nativeModuleRegistry)
      .setJSBundleLoader(jsBundleLoader)
      .setNativeModuleCallExceptionHandler(exceptionHandler);

    ReactMarker.logMarker(CREATE_CATALYST_INSTANCE_START);
    // CREATE_CATALYST_INSTANCE_END is in JSCExecutor.cpp
    Systrace.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "createCatalystInstance");
    final CatalystInstance catalystInstance;
    try {
      catalystInstance = catalystInstanceBuilder.build();
    } finally {
      Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
      ReactMarker.logMarker(CREATE_CATALYST_INSTANCE_END);
    }
    if (mJSIModulePackage != null) {
      catalystInstance.addJSIModules(mJSIModulePackage
        .getJSIModules(reactContext, catalystInstance.getJavaScriptContextHolder()));
    }

    if (mBridgeIdleDebugListener != null) {
      catalystInstance.addBridgeIdleDebugListener(mBridgeIdleDebugListener);
    }
    if (Systrace.isTracing(TRACE_TAG_REACT_APPS | TRACE_TAG_REACT_JS_VM_CALLS)) {
      catalystInstance.setGlobalVariable("__RCTProfileIsProfiling", "true");
    }
    ReactMarker.logMarker(ReactMarkerConstants.PRE_RUN_JS_BUNDLE_START);
    Systrace.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "runJSBundle");
    //加载JS Bundle
    catalystInstance.runJSBundle();
    Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
    //关联ReacContext与CatalystInstance
    reactContext.initializeWithInstance(catalystInstance);


    return reactContext;
  }
  
   private NativeModuleRegistry processPackages(
    ReactApplicationContext reactContext,
    List<ReactPackage> packages,
    boolean checkAndUpdatePackageMembership) {
    //创建JavaModule注册表Builder,用来创建JavaModule注册表,JavaModule注册表将所有的JavaModule注册到CatalystInstance中。
    NativeModuleRegistryBuilder nativeModuleRegistryBuilder = new NativeModuleRegistryBuilder(
      reactContext,
      this);

    ReactMarker.logMarker(PROCESS_PACKAGES_START);

    // TODO(6818138): Solve use-case of native modules overriding
    synchronized (mPackages) {
      for (ReactPackage reactPackage : packages) {
        if (checkAndUpdatePackageMembership && mPackages.contains(reactPackage)) {
          continue;
        }
        Systrace.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "createAndProcessCustomReactPackage");
        try {
          if (checkAndUpdatePackageMembership) {
            mPackages.add(reactPackage);
          }
          // 递归处理我们在Application里注入的ReactPackage,处理的过程就是把各自的Module添加到对应的注册表中。
          processPackage(reactPackage, nativeModuleRegistryBuilder);
        } finally {
          Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
        }
      }
    }
    ReactMarker.logMarker(PROCESS_PACKAGES_END);

    ReactMarker.logMarker(BUILD_NATIVE_MODULE_REGISTRY_START);
    Systrace.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "buildNativeModuleRegistry");
    NativeModuleRegistry nativeModuleRegistry;
    try {
      //生成Java Module注册表
      nativeModuleRegistry = nativeModuleRegistryBuilder.build();
    } finally {
      Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
      ReactMarker.logMarker(BUILD_NATIVE_MODULE_REGISTRY_END);
    }

    return nativeModuleRegistry;
  }
}
复制代码

辗转反侧,最终通过runCreateReactContextOnNewThread开启了一个线程,然后调用createReactContext(JavaScriptExecutor jsExecutor,JSBundleLoader jsBundleLoader)创建了ReactContext。我们重点来看看传入的2给参数:

JavaScriptExecutor jsExecutor:当该类被加载时,它会自动去加载"reactnativejnifb.so"库,并会调用Native方
法initHybrid()初始化C++层RN与JSC通信的框架。

JSBundleLoader jsBundleLoader:缓存了JSBundle的信息,封装了上层加载JSBundle的相关接口,CatalystInstance通过其简介调用ReactBridge去加载JS文件,不同的场景会创建
不同的加载器,具体可以查看类JSBundleLoader。
复制代码

createReactContext->processPackages主要做了以下几件事:

1 创建JavaModule注册表并交由CatalystInstance管理。
3 处理ReactPackage,将JavaModule放进对应的注册表里。
3 创建CatalystInstance实例。
4 关联ReactContext与CatalystInstance,并加载JS Bundle。
复制代码

接着看catalystInstance.runJSBundle(),该方法的调用栈如下:

CatalystInstanceImpl.runJSBundle() -> JSBundleLoader.loadScript() ->CatalystInstanceImpl.loadScriptFromAssets()/loadScriptFromFile() -> CatalystInstanceImpl.jniLoadScriptFromAssets()/jniLoadScriptFromFile()
-> CatalystInstanceImpl::jniLoadScriptFromAssets()/jniLoadScriptFromFile() -> Instance::loadScriptFromString()/loadScriptFromFile()
-> NativeToJsBridge::loadApplication() -> JSCExecutor::loadApplicationScript()
复制代码

最终由C++中的JSCExecutor.cpp完成了JS Bundle的加载,核心逻辑都在JSCExecutor.cpp中,这一块的内容我们后续的文章在详细分析,我们先来看看CatalystInstanceImpl的创建流程。

public class CatalystInstanceImpl implements CatalystInstance {
    private CatalystInstanceImpl(
      final ReactQueueConfigurationSpec reactQueueConfigurationSpec,
      final JavaScriptExecutor jsExecutor,
      final NativeModuleRegistry nativeModuleRegistry,
      final JSBundleLoader jsBundleLoader,
      NativeModuleCallExceptionHandler nativeModuleCallExceptionHandler) {
    Log.d(ReactConstants.TAG, "Initializing React Xplat Bridge.");
    Systrace.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "createCatalystInstanceImpl");
    // Native方法,用来创建JNI相关状态,并返回mHybridData
    mHybridData = initHybrid();
    //创建三个核心线程:Native Modules Thread、JS Thread、UI Thread,都是通过Handler来管理的。
    mReactQueueConfiguration = ReactQueueConfigurationImpl.create(
        reactQueueConfigurationSpec,
        new NativeExceptionHandler());
    mBridgeIdleListeners = new CopyOnWriteArrayList<>();
    mNativeModuleRegistry = nativeModuleRegistry;
    // 创建JavaScriptModule注册表
    mJSModuleRegistry = new JavaScriptModuleRegistry();
    mJSBundleLoader = jsBundleLoader;
    mNativeModuleCallExceptionHandler = nativeModuleCallExceptionHandler;
    mNativeModulesQueueThread = mReactQueueConfiguration.getNativeModulesQueueThread();
    mTraceListener = new JSProfilerTraceListener(this);
    Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);

    Log.d(ReactConstants.TAG, "Initializing React Xplat Bridge before initializeBridge");
    Systrace.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "initializeCxxBridge");
    //Native方法,创建BridgeCallback实例,初始化Bridge。
    initializeBridge(
      new BridgeCallback(this),
      jsExecutor,
      mReactQueueConfiguration.getJSQueueThread(),
      mNativeModulesQueueThread,
      mNativeModuleRegistry.getJavaModules(this),
      mNativeModuleRegistry.getCxxModules());
    Log.d(ReactConstants.TAG, "Initializing React Xplat Bridge after initializeBridge");
    Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);

    mJavaScriptContextHolder = new JavaScriptContextHolder(getJavaScriptContext());
  }
  
  private native void initializeBridge(
      ReactCallback callback,
      JavaScriptExecutor jsExecutor,
      MessageQueueThread jsQueue,
      MessageQueueThread moduleQueue,
      Collection<JavaModuleWrapper> javaModules,
      Collection<ModuleHolder> cxxModules);
}
复制代码

从CatalystInstanceImpl的构建过程可以看出,CatalystInstanceImpl是个封装管理类,封装了各种注册表,以及初始化JNI,我们来看看最后初始化Bridge传入的6个参数:

ReactCallback callback:CatalystInstanceImpl的静态内部类ReactCallback,负责接口回调。
JavaScriptExecutor jsExecutor:JS执行器,将JS的调用传递给C++层。
MessageQueueThread jsQueue.getJSQueueThread():JS线程,通过mReactQueueConfiguration.getJSQueueThread()获得,mReactQueueConfiguration通过ReactQueueConfigurationSpec.createDefault()创建。
MessageQueueThread moduleQueue:Native线程,通过mReactQueueConfiguration.getNativeModulesQueueThread()获得,mReactQueueConfiguration通过ReactQueueConfigurationSpec.createDefault()创建。
Collection<JavaModuleWrapper> javaModules:java modules,来源于mJavaRegistry.getJavaModules(this)。
Collection<ModuleHolder> cxxModules):c++ modules,来源于mJavaRegistry.getCxxModules()。
复制代码

我们接着看,CatalystInstanceImpl.runJSBundle()

@Override
  public void runJSBundle() {
    Log.d(ReactConstants.TAG, "CatalystInstanceImpl.runJSBundle()");
    Assertions.assertCondition(!mJSBundleHasLoaded, "JS bundle was already loaded!");
    // incrementPendingJSCalls();
    //调用加载器加载JS Bundle,不同情况下加载器不同。
    mJSBundleLoader.loadScript(CatalystInstanceImpl.this);

    synchronized (mJSCallsPendingInitLock) {

      // Loading the bundle is queued on the JS thread, but may not have
      // run yet.  It's safe to set this here, though, since any work it
      // gates will be queued on the JS thread behind the load.
      mAcceptCalls = true;

      for (PendingJSCall function : mJSCallsPendingInit) {
        function.call(this);
      }
      mJSCallsPendingInit.clear();
      mJSBundleHasLoaded = true;
    }

    // This is registered after JS starts since it makes a JS call
    Systrace.registerListener(mTraceListener);
  }
复制代码

接着调用JSBundleLoader.loadScript

public abstract class JSBundleLoader {
    /**
   * This loader is recommended one for release version of your app. In that case local JS executor
   * should be used. JS bundle will be read from assets in native code to save on passing large
   * strings from java to native memory.
   */
  // 从assets文件加载
  public static JSBundleLoader createAssetLoader(
      final Context context,
      final String assetUrl,
      final boolean loadSynchronously) {
    return new JSBundleLoader() {
      @Override
      public String loadScript(JSBundleLoaderDelegate delegate) {
        delegate.loadScriptFromAssets(context.getAssets(), assetUrl, loadSynchronously);
        return assetUrl;
      }
    };
  }

  /**
   * This loader loads bundle from file system. The bundle will be read in native code to save on
   * passing large strings from java to native memory.
   */
  public static JSBundleLoader createFileLoader(final String fileName) {
    return createFileLoader(fileName, fileName, false);
  }
  // 从文件加载
  public static JSBundleLoader createFileLoader(
      final String fileName,
      final String assetUrl,
      final boolean loadSynchronously) {
    return new JSBundleLoader() {
      @Override
      public String loadScript(JSBundleLoaderDelegate delegate) {
        delegate.loadScriptFromFile(fileName, assetUrl, loadSynchronously);
        return fileName;
      }
    };
  }

  /**
   * This loader is used when bundle gets reloaded from dev server. In that case loader expect JS
   * bundle to be prefetched and stored in local file. We do that to avoid passing large strings
   * between java and native code and avoid allocating memory in java to fit whole JS bundle in it.
   * Providing correct {@param sourceURL} of downloaded bundle is required for JS stacktraces to
   * work correctly and allows for source maps to correctly symbolize those.
   */
  //从缓存加载
  public static JSBundleLoader createCachedBundleFromNetworkLoader(
      final String sourceURL,
      final String cachedFileLocation) {
    return new JSBundleLoader() {
      @Override
      public String loadScript(JSBundleLoaderDelegate delegate) {
        try {
          delegate.loadScriptFromFile(cachedFileLocation, sourceURL, false);
          return sourceURL;
        } catch (Exception e) {
          throw DebugServerException.makeGeneric(e.getMessage(), e);
        }
      }
    };
  }

  /**
   * This loader is used to load delta bundles from the dev server. We pass each delta message to
   * the loader and process it in C++. Passing it as a string leads to inefficiencies due to memory
   * copies, which will have to be addressed in a follow-up.
   * @param nativeDeltaClient
   */
  // 从dev server加载
  public static JSBundleLoader createDeltaFromNetworkLoader(
    final String sourceURL,
    final NativeDeltaClient nativeDeltaClient) {
    return new JSBundleLoader() {
      @Override
      public String loadScript(JSBundleLoaderDelegate delegate) {
        try {
          delegate.loadScriptFromDeltaBundle(sourceURL, nativeDeltaClient, false);
          return sourceURL;
        } catch (Exception e) {
          throw DebugServerException.makeGeneric(e.getMessage(), e);
        }
      }
    };
  }

  /**
   * This loader is used when proxy debugging is enabled. In that case there is no point in fetching
   * the bundle from device as remote executor will have to do it anyway.
   */
  // debug时加载
  public static JSBundleLoader createRemoteDebuggerBundleLoader(
      final String proxySourceURL,
      final String realSourceURL) {
    return new JSBundleLoader() {
      @Override
      public String loadScript(JSBundleLoaderDelegate delegate) {
        delegate.setSourceURLs(realSourceURL, proxySourceURL);
        return realSourceURL;
      }
    };
  }

  /** Loads the script, returning the URL of the source it loaded. */
  public abstract String loadScript(JSBundleLoaderDelegate delegate);
}
复制代码

接下来我们以CatalystInstanceImpl.loadScriptFromAssets()举例。

@Override
  public void loadScriptFromAssets(AssetManager assetManager, String assetURL, boolean loadSynchronously) {
    mSourceURL = assetURL;
    jniLoadScriptFromAssets(assetManager, assetURL, loadSynchronously);
  }
复制代码

可以看出该方法最终调用Native方法jniLoadScriptFromAssets去加载JS Bundle,来到CatalystInstanceImpl.cpp。

void CatalystInstanceImpl::jniLoadScriptFromAssets(
    jni::alias_ref<JAssetManager::javaobject> assetManager,
    const std::string& assetURL,
    bool loadSynchronously) {
  const int kAssetsLength = 9;  // strlen("assets://");
  // 获取source js Bundle的路径名
  auto sourceURL = assetURL.substr(kAssetsLength);
  //获取AssetManager对象。
  auto manager = extractAssetManager(assetManager);
  //读取JS Bundle里的内容。
  auto script = loadScriptFromAssets(manager, sourceURL);
  // 判断是不是unbundle命令打包,build.gradle默认里是bundle打包方式。
  if (JniJSModulesUnbundle::isUnbundle(manager, sourceURL)) {
    auto bundle = JniJSModulesUnbundle::fromEntryFile(manager, sourceURL);
    auto registry = RAMBundleRegistry::singleBundleRegistry(std::move(bundle));
    instance_->loadRAMBundle(
      std::move(registry),
      std::move(script),
      sourceURL,
      loadSynchronously);
    return;
  } else {
    //bundle命令打包走此流程
    instance_->loadScriptFromString(std::move(script), sourceURL, loadSynchronously);
  }
}

关于unbundle命令

<unbundle命令,使用方式和bundle命令完全相同。unbundle命令是在bundle命令的基础上增加了一项功能,除了生成整合JS文件index.android.bundle外,还会
生成各个单独的未整合JS文件(但会被优化),全部放在js-modules目录下,同时会生成一个名为UNBUNDLE的标识文件,一并放在其中。UNBUNDLE标识文件的前4个字节
固定为0xFB0BD1E5,用于加载前的校验。
复制代码

接着会调用Instance.cpp的loadScriptFromString()方法去解析JS Bundle里的内容。

void Instance::loadScriptFromString(std::unique_ptr<const JSBigString> string,
                                    std::string sourceURL,
                                    bool loadSynchronously) {
  SystraceSection s("Instance::loadScriptFromString", "sourceURL",
                    sourceURL);
  if (loadSynchronously) {
    loadApplicationSync(nullptr, std::move(string), std::move(sourceURL));
  } else {
    loadApplication(nullptr, std::move(string), std::move(sourceURL));
  }
}
复制代码

进一步调用NativeToJsBridge.cpp的loadApplication()方法,它的实现如下所示:

void NativeToJsBridge::loadApplication(
    std::unique_ptr<RAMBundleRegistry> bundleRegistry,
    std::unique_ptr<const JSBigString> startupScript,
    std::string startupScriptSourceURL) {

  runOnExecutorQueue(
      [this,
       bundleRegistryWrap=folly::makeMoveWrapper(std::move(bundleRegistry)),
       startupScript=folly::makeMoveWrapper(std::move(startupScript)),
       startupScriptSourceURL=std::move(startupScriptSourceURL)]
        (JSExecutor* executor) mutable {
    auto bundleRegistry = bundleRegistryWrap.move();
    if (bundleRegistry) {
      executor->setBundleRegistry(std::move(bundleRegistry));
    }
    try {
      //Java中的JSCJavaScriptExecutor对应。它的实例在JSIExecutor.cpp中实现。
      executor->loadApplicationScript(std::move(*startupScript),
                                      std::move(startupScriptSourceURL));
    } catch (...) {
      m_applicationScriptHasFailure = true;
      throw;
    }
  });
}
复制代码

进一步调用JSIExecutor.cpp的loadApplicationScript()方法。

void JSIExecutor::loadApplicationScript(
    std::unique_ptr<const JSBigString> script,
    std::string sourceURL) {
  SystraceSection s("JSIExecutor::loadApplicationScript");

  // TODO: check for and use precompiled HBC

  runtime_->global().setProperty(
      *runtime_,
      "nativeModuleProxy",
      Object::createFromHostObject(
          *runtime_, std::make_shared<NativeModuleProxy>(*this)));

  runtime_->global().setProperty(
      *runtime_,
      "nativeFlushQueueImmediate",
      Function::createFromHostFunction(
          *runtime_,
          PropNameID::forAscii(*runtime_, "nativeFlushQueueImmediate"),
          1,
          [this](
              jsi::Runtime &,
              const jsi::Value &,
              const jsi::Value *args,
              size_t count) {
            if (count != 1) {
              throw std::invalid_argument(
                  "nativeFlushQueueImmediate arg count must be 1");
            }
            callNativeModules(args[0], false);
            return Value::undefined();
          }));

  runtime_->global().setProperty(
      *runtime_,
      "nativeCallSyncHook",
      Function::createFromHostFunction(
          *runtime_,
          PropNameID::forAscii(*runtime_, "nativeCallSyncHook"),
          1,
          [this](
              jsi::Runtime &,
              const jsi::Value &,
              const jsi::Value *args,
              size_t count) { return nativeCallSyncHook(args, count); }));

  if (runtimeInstaller_) {
    runtimeInstaller_(*runtime_);
  }

  bool hasLogger(ReactMarker::logTaggedMarker);
  std::string scriptName = simpleBasename(sourceURL);
  if (hasLogger) {
    ReactMarker::logTaggedMarker(
        ReactMarker::RUN_JS_BUNDLE_START, scriptName.c_str());
  }
  // //解释执行JS
  runtime_->evaluateJavaScript(
      std::make_unique<BigStringBuffer>(std::move(script)), sourceURL);
  flush();
  if (hasLogger) {
    ReactMarker::logMarker(ReactMarker::CREATE_REACT_CONTEXT_STOP);
    ReactMarker::logTaggedMarker(
        ReactMarker::RUN_JS_BUNDLE_STOP, scriptName.c_str());
  }
}

void JSIExecutor::flush() {
  SystraceSection s("JSIExecutor::flush");
  if (flushedQueue_) {
    callNativeModules(flushedQueue_->call(*runtime_), true);
    return;
  }

  // When a native module is called from JS, BatchedBridge.enqueueNativeCall()
  // is invoked.  For that to work, require('BatchedBridge') has to be called,
  // and when that happens, __fbBatchedBridge is set as a side effect.
  Value batchedBridge =
      runtime_->global().getProperty(*runtime_, "__fbBatchedBridge");
  // So here, if __fbBatchedBridge doesn't exist, then we know no native calls
  // have happened, and we were able to determine this without forcing
  // BatchedBridge to be loaded as a side effect.
  if (!batchedBridge.isUndefined()) {
    // If calls were made, we bind to the JS bridge methods, and use them to
    // get the pending queue of native calls.
    //绑定bridge
    bindBridge();
    //把JS层相关通信数据通过flushedQUeue()
    //返回给callNativeModules
    callNativeModules(flushedQueue_->call(*runtime_), true);
  } else if (delegate_) {
    // If we have a delegate, we need to call it; we pass a null list to
    // callNativeModules, since we know there are no native calls, without
    // calling into JS again.  If no calls were made and there's no delegate,
    // nothing happens, which is correct.
    callNativeModules(nullptr, true);
  }
  
  void JSIExecutor::bindBridge() {
  std::call_once(bindFlag_, [this] {
    SystraceSection s("JSIExecutor::bindBridge (once)");
    Value batchedBridgeValue =
        runtime_->global().getProperty(*runtime_, "__fbBatchedBridge");
    if (batchedBridgeValue.isUndefined()) {
      throw JSINativeException(
          "Could not get BatchedBridge, make sure your bundle is packaged correctly");
    }

    Object batchedBridge = batchedBridgeValue.asObject(*runtime_);
    callFunctionReturnFlushedQueue_ = batchedBridge.getPropertyAsFunction(
        *runtime_, "callFunctionReturnFlushedQueue");
    invokeCallbackAndReturnFlushedQueue_ = batchedBridge.getPropertyAsFunction(
        *runtime_, "invokeCallbackAndReturnFlushedQueue");
    flushedQueue_ =
        batchedBridge.getPropertyAsFunction(*runtime_, "flushedQueue");
    callFunctionReturnResultAndFlushedQueue_ =
        batchedBridge.getPropertyAsFunction(
            *runtime_, "callFunctionReturnResultAndFlushedQueue");
  });
}

void JSIExecutor::callNativeModules(const Value &queue, bool isEndOfBatch) {
  SystraceSection s("JSIExecutor::callNativeModules");
  // If this fails, you need to pass a fully functional delegate with a
  // module registry to the factory/ctor.
  CHECK(delegate_) << "Attempting to use native modules without a delegate";
#if 0 // maybe useful for debugging
  std::string json = runtime_->global().getPropertyAsObject(*runtime_, "JSON")
    .getPropertyAsFunction(*runtime_, "stringify").call(*runtime_, queue)
    .getString(*runtime_).utf8(*runtime_);
#endif
  //m_delegate为JsToNativeBridge对象。
  delegate_->callNativeModules(
      *this, dynamicFromValue(*runtime_, queue), isEndOfBatch);
}

}
复制代码

m_flushedQueueJS支线的是MessageQueue.js的flushedQueue()方法,此时JS已经被加载到队列中,等待 Java 层来驱动它。加载完JS后,返回reactApplicationContext,我们继续跟进它的实现。

@ThreadConfined(UI)
  private void runCreateReactContextOnNewThread(final ReactContextInitParams initParams) {
    Log.d(ReactConstants.TAG, "ReactInstanceManager.runCreateReactContextOnNewThread()");
    UiThreadUtil.assertOnUiThread();
    synchronized (mAttachedReactRoots) {
      synchronized (mReactContextLock) {
        if (mCurrentReactContext != null) {
          tearDownReactContext(mCurrentReactContext);
          mCurrentReactContext = null;
        }
      }
    }

    mCreateReactContextThread =
        new Thread(
            null,
            new Runnable() {
              @Override
              public void run() {
                ReactMarker.logMarker(REACT_CONTEXT_THREAD_END);
                synchronized (ReactInstanceManager.this.mHasStartedDestroying) {
                  while (ReactInstanceManager.this.mHasStartedDestroying) {
                    try {
                      ReactInstanceManager.this.mHasStartedDestroying.wait();
                    } catch (InterruptedException e) {
                      continue;
                    }
                  }
                }
                // As destroy() may have run and set this to false, ensure that it is true before we create
                mHasStartedCreatingInitialContext = true;

                try {
                  Process.setThreadPriority(Process.THREAD_PRIORITY_DISPLAY);
                  ReactMarker.logMarker(VM_INIT);
                  final ReactApplicationContext reactApplicationContext =
                      createReactContext(
                          initParams.getJsExecutorFactory().create(),
                          initParams.getJsBundleLoader());

                  mCreateReactContextThread = null;
                  ReactMarker.logMarker(PRE_SETUP_REACT_CONTEXT_START);
                  final Runnable maybeRecreateReactContextRunnable =
                      new Runnable() {
                        @Override
                        public void run() {
                          if (mPendingReactContextInitParams != null) {
                            runCreateReactContextOnNewThread(mPendingReactContextInitParams);
                            mPendingReactContextInitParams = null;
                          }
                        }
                      };
                  Runnable setupReactContextRunnable =
                      new Runnable() {
                        @Override
                        public void run() {
                          try {
                            setupReactContext(reactApplicationContext);
                          } catch (Exception e) {
                            mDevSupportManager.handleException(e);
                          }
                        }
                      };

                  reactApplicationContext.runOnNativeModulesQueueThread(setupReactContextRunnable);
                  UiThreadUtil.runOnUiThread(maybeRecreateReactContextRunnable);
                } catch (Exception e) {
                  mDevSupportManager.handleException(e);
                }
              }
            },
            "create_react_context");
    ReactMarker.logMarker(REACT_CONTEXT_THREAD_START);
    mCreateReactContextThread.start();
  }
复制代码

接着调用 setupReactContext(reactApplicationContext);

private void setupReactContext(final ReactApplicationContext reactContext) {
    Log.d(ReactConstants.TAG, "ReactInstanceManager.setupReactContext()");
    ReactMarker.logMarker(PRE_SETUP_REACT_CONTEXT_END);
    ReactMarker.logMarker(SETUP_REACT_CONTEXT_START);
    Systrace.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "setupReactContext");
    synchronized (mAttachedReactRoots) {
      synchronized (mReactContextLock) {
        mCurrentReactContext = Assertions.assertNotNull(reactContext);
      }

      CatalystInstance catalystInstance =
          Assertions.assertNotNull(reactContext.getCatalystInstance());
      // Native Java module的初始化
      catalystInstance.initialize();
      mDevSupportManager.onNewReactContextCreated(reactContext);
      mMemoryPressureRouter.addMemoryPressureListener(catalystInstance);
      //复位生命周期
      moveReactContextToCurrentLifecycleState();

      ReactMarker.logMarker(ATTACH_MEASURED_ROOT_VIEWS_START);
      //遍历ReactRootView
      for (ReactRoot reactRoot : mAttachedReactRoots) {
        //遍历ReactRootView
        attachRootViewToInstance(reactRoot);
      }
      ReactMarker.logMarker(ATTACH_MEASURED_ROOT_VIEWS_END);
    }

    ReactInstanceEventListener[] listeners =
      new ReactInstanceEventListener[mReactInstanceEventListeners.size()];
    final ReactInstanceEventListener[] finalListeners =
        mReactInstanceEventListeners.toArray(listeners);

    UiThreadUtil.runOnUiThread(
        new Runnable() {
          @Override
          public void run() {
            for (ReactInstanceEventListener listener : finalListeners) {
              listener.onReactContextInitialized(reactContext);
            }
          }
        });
    Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
    ReactMarker.logMarker(SETUP_REACT_CONTEXT_END);
    reactContext.runOnJSQueueThread(
        new Runnable() {
          @Override
          public void run() {
            Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
            ReactMarker.logMarker(CHANGE_THREAD_PRIORITY, "js_default");
          }
        });
    reactContext.runOnNativeModulesQueueThread(
        new Runnable() {
          @Override
          public void run() {
            Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
          }
        });
  }
复制代码

reactRootView的attach操作。

private void attachRootViewToInstance(final ReactRoot reactRoot) {
    Log.d(ReactConstants.TAG, "ReactInstanceManager.attachRootViewToInstance()");
    Systrace.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "attachRootViewToInstance");
    UIManager uiManagerModule = UIManagerHelper.getUIManager(mCurrentReactContext, reactRoot.getUIManagerType());

    @Nullable Bundle initialProperties = reactRoot.getAppProperties();
    // 将ReactRootView作为根布局
    final int rootTag = uiManagerModule.addRootView(
      reactRoot.getRootViewGroup(),
      initialProperties == null ?
            new WritableNativeMap() : Arguments.fromBundle(initialProperties),
        reactRoot.getInitialUITemplate());
    reactRoot.setRootViewTag(rootTag);
    //启动流程入口
    reactRoot.runApplication();
    Systrace.beginAsyncSection(
      TRACE_TAG_REACT_JAVA_BRIDGE,
      "pre_rootView.onAttachedToReactInstance",
      rootTag);
    UiThreadUtil.runOnUiThread(
        new Runnable() {
          @Override
          public void run() {
            Systrace.endAsyncSection(
                TRACE_TAG_REACT_JAVA_BRIDGE, "pre_rootView.onAttachedToReactInstance", rootTag);
            reactRoot.onStage(ReactStage.ON_ATTACH_TO_INSTANCE);
          }
        });
    Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
  }
复制代码

reactRoot.runApplication(),我们跟进去看一下。

@Override
  public void runApplication() {
      Systrace.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "ReactRootView.runApplication");
      try {
        if (mReactInstanceManager == null || !mIsAttachedToInstance) {
          return;
        }

        ReactContext reactContext = mReactInstanceManager.getCurrentReactContext();
        if (reactContext == null) {
          return;
        }

        CatalystInstance catalystInstance = reactContext.getCatalystInstance();
        String jsAppModuleName = getJSModuleName();

        if (mUseSurface) {
          // TODO call surface's runApplication
        } else {

          if (mWasMeasured) {
            updateRootLayoutSpecs(mWidthMeasureSpec, mHeightMeasureSpec);
          }

          WritableNativeMap appParams = new WritableNativeMap();
          appParams.putDouble("rootTag", getRootViewTag());
          @Nullable Bundle appProperties = getAppProperties();
          if (appProperties != null) {
            appParams.putMap("initialProps", Arguments.fromBundle(appProperties));
          }
          if (getUIManagerType() == FABRIC) {
            appParams.putBoolean("fabric", true);
          }

          mShouldLogContentAppeared = true;

          catalystInstance.getJSModule(AppRegistry.class).runApplication(jsAppModuleName, appParams);
        }
      } finally {
        Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
      }
  }
复制代码

ReactInstanceManager.attachMeasuredRootViewToInstance()最终进入了RN应用的启动流程入口,调用catalystInstance.getJSModule(AppRegistry.class).runApplication(jsAppModuleName, appParams)。 AppRegistry.class是JS层暴露给Java层的接口方法。它的真正实现在AppRegistry.js里,AppRegistry.js是运行所有RN应用的JS层入口,我们来看看它的实现:

//上面代码最终调用的就是这个函数
  runApplication(appKey: string, appParameters: any): void {
    const msg =
      'Running application "' + appKey + '" with appParams: ' +
      JSON.stringify(appParameters) + '. ' +
      '__DEV__ === ' + String(__DEV__) +
      ', development-level warning are ' + (__DEV__ ? 'ON' : 'OFF') +
      ', performance optimizations are ' + (__DEV__ ? 'OFF' : 'ON');
    infoLog(msg);
    BugReporting.addSource('AppRegistry.runApplication' + runCount++, () => msg);
    invariant(
      runnables[appKey] && runnables[appKey].run,
      'Application ' + appKey + ' has not been registered.\n\n' +
      'Hint: This error often happens when you\'re running the packager ' +
      '(local dev server) from a wrong folder. For example you have ' +
      'multiple apps and the packager is still running for the app you ' +
      'were working on before.\nIf this is the case, simply kill the old ' +
      'packager instance (e.g. close the packager terminal window) ' +
      'and start the packager in the correct app folder (e.g. cd into app ' +
      'folder and run \'npm start\').\n\n' +
      'This error can also happen due to a require() error during ' +
      'initialization or failure to call AppRegistry.registerComponent.\n\n'
    );
    runnables[appKey].run(appParameters);
  },
复制代码

到这里就会去调用JS进行组件渲染,再通过Java层的UIManagerModule将JS组件转换为Android组件,最终显示在ReactRootView上,下期我们再见。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

The Photoshop Anthology

The Photoshop Anthology

Corrie Haffly / SitePoint Pty. Ltd. / 2006 / USD 39.95

The Photoshop Anthology is full-color, question-and-answer book for Web Designers who want to use Photoshop to build Websites and create better looking web graphics more effectively. The book covers: ......一起来看看 《The Photoshop Anthology》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器