博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
React Native模块加载与原理分析
阅读量:5925 次
发布时间:2019-06-19

本文共 23708 字,大约阅读时间需要 79 分钟。

后面会陆续写一些文章分析React Native源码,今天分析下模块加载的过程,包括系统模块和用户自定义模块的加载。源码是基于0.19.1,加载的大概流程和0.54差别不大,所以小伙伴们也不用特别纠结。

首先先看下怎么自定义Java模块给JS调用。直接用的官方的ToastAndroid的例子了。 ###1.自定义模块 首先创建一个原生模块,原生模块是继承了ReactContextBaseJavaModule的Java类,它可以实现一些JavaScript所需的功能。

public class ToastModule extends ReactContextBaseJavaModule {  private static final String DURATION_SHORT_KEY = "SHORT";  private static final String DURATION_LONG_KEY = "LONG";  public ToastModule(ReactApplicationContext reactContext) {    super(reactContext);  }  @Override  public String getName() {    return "ToastExample";  }  @ReactMethod  public void show(String message, int duration) {    Toast.makeText(getReactApplicationContext(), message, duration).show();  }}复制代码

ReactContextBaseJavaModule要求派生类实现getName方法。这个函数用于返回一个字符串名字,这个名字在JavaScript端标记这个模块。这里我们把这个模块叫做ToastExample,这样就可以在JavaScript中通过React.NativeModules.ToastExample访问到这个模块。要导出一个方法给JavaScript使用,Java方法需要使用注解@ReactMethod。方法的返回类型必须为void。 这样模块就定义好了,接下来就是注册这个模块了。在Java层需要注册这个模块,在应用的Package类的createNativeModules方法中添加这个模块。如果模块没有被注册,它也无法在JavaScript中被访问到。

public class AnExampleReactPackage implements ReactPackage { @Override public List
createViewManagers(ReactApplicationContext reactContext) { return Collections.emptyList(); } @Override public List
createNativeModules( ReactApplicationContext reactContext) { List
modules = new ArrayList<>(); modules.add(new ToastModule(reactContext)); return modules; }复制代码

这个package需要在MainApplication.java文件的getPackages方法中提供。这个文件位于你的react-native应用文件夹的android目录中。具体路径是: android/app/src/main/java/com/your-app-name/MainApplication.java.

public class MainApplication extends Application implements ReactApplication {    private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {        @Override        public boolean getUseDeveloperSupport() {            return BuildConfig.DEBUG;        }        @Override        protected List
getPackages() { return Arrays.
asList( new MainReactPackage(), new AnExampleReactPackage() ); } @Override protected String getJSMainModuleName() { return "index.android"; } @Nullable @Override protected String getBundleAssetName() { return "index.android.bundle"; } }; @Override public ReactNativeHost getReactNativeHost() { return mReactNativeHost; } @Override public void onCreate() { super.onCreate(); MultiDex.install(this); ... }}复制代码

这样就完成了模块的注册工作了,可以看出来框架的封装做的很好,可以很好的扩展组件的定义和注册。

不知道大家有没有疑问,反正我在刚做RN的时候做完上面的步骤后是有疑问的,模块怎么被加载的?RN的界面和Android原生的界面比如Activity有什么关系? 首先来看下,RN的界面和Android原生的界面比如Activity有什么关系? ###2.ReactActivity 大家都知道,在原生Android中肯定是有一个MainActivity,然后再onCreate中setContentView塞入我们在xml中定义的布局文件,这样界面就能被系统给绘制出来。那么RN的所有界面其实也可以放到一个布局文件中,只不过现在布局文件不是xml了,而是js写的文件了,然后把这个布局文件给到一个Activity中,空口无凭,去扒一扒源码。

首先在开发RN程序的时候要新建一个Activity继承于ReactActivity,比如下面这样:

public class MainActivity extends ReactActivity implements DefaultHardwareBackBtnHandler {        /**         * Returns the name of the main component registered from JavaScript.         * This is used to schedule rendering of the component.         */        @Override        protected String getMainComponentName() {            return "TEST";        }            @Override        protected void onCreate(Bundle savedInstanceState) {            super.onCreate(savedInstanceState);        }            @Override        public void invokeDefaultOnBackPressed() {            super.onBackPressed();        }    }复制代码

首先继承ReactActivity,把生命周期交给系统进行管理,其他工作通过Delegate完成。

看下ReactActivity,其中getMainComponentName方法需要进行重写,为什么需要重写?后面在代码里面分析,这里先略过。

/** * Base Activity for React Native applications. */public abstract class ReactActivity extends Activity    implements DefaultHardwareBackBtnHandler, PermissionAwareActivity {  private final ReactActivityDelegate mDelegate;  protected ReactActivity() {    mDelegate = createReactActivityDelegate();  }  /**   * Returns the name of the main component registered from JavaScript.   * This is used to schedule rendering of the component.   * e.g. "MoviesApp"   */  protected @Nullable String getMainComponentName() {    return null;  }  /**   * Called at construction time, override if you have a custom delegate implementation.   */  protected ReactActivityDelegate createReactActivityDelegate() {    return new ReactActivityDelegate(this, getMainComponentName());  }  @Override  protected void onCreate(Bundle savedInstanceState) {    super.onCreate(savedInstanceState);    mDelegate.onCreate(savedInstanceState);  }  ...}复制代码

getMainComponentName返回的名称需要和Android入口文件index.android.js里面注册的组件名称一致:

class TEST extends Component {    render() {        return 
; }}AppRegistry.registerComponent('TEST', () => TEST);复制代码

其实到这里可以知道要渲染的组件就是TEST这个Component(RN中的组件,可以简单对等于View)。

接着返回去看ReactActivity,生命周期里面都调用ReactActivityDelegate:

protected void onCreate(Bundle savedInstanceState) {        boolean needsOverlayPermission = false;        //如果Android版本是M(23)也就是Android 6.0系统,在调式模式下需要弹窗获取权限        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();      }          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);      }复制代码

1.mMainComponentName就是前面重写方法需要返回的,如果复写了就会进入loadApp,这就是前面需要复写该方法的原因喽

3.loadApp进行三个很重要的工作,

  • 创建ReactRootView,这个是RN 的自定义根View,可以监听屏幕尺寸的变化,拦截Touch事件然后进行发送给JS层进行处理。最终会把这个View塞给activity的setContentView。看下官方的定义:
Default root view for catalyst apps. Provides the ability to listen for size changes so that a UI manager can re-layout its elements. It delegates handling touch events for itself and child views and sending those events to JS by using JSTouchDispatcher. This view is overriding {@link   ViewGroup#onInterceptTouchEvent} method in order to be notified about the events for all of its children and it's also overriding {@link ViewGroup#requestDisallowInterceptTouchEvent} to make sure that {@link ViewGroup#onInterceptTouchEvent} will get events even when some child view start intercepting it. In case when no child view is interested in handling some particular touch event this view's {@link View#onTouchEvent} will still return true in order to be notified about all subsequent touch events related to that gesture (in case when JS code want to handle that gesture复制代码
  • 创建ReactContext
Schedule rendering of the react component rendered by the JS application from thegiven JS* module (@{param moduleName}) using provided {@param reactInstanceManager} to attach to the* JS context of that manager.复制代码
  • setContentView(ReactRootView)

到这里就明白主要工作在ReactActivityDelegate中的loadApp,首先构造根View-createRootView,然后构造ReactContext上下文,渲染appKey-- mMainComponentName这里就是在index.android.js中注册的入口模块,之后将该View塞给Activity,最终Android系统将其显示到屏幕上。

###3.RN框架加载Package 接下来看看Package怎么加载到RN框架中。

首先回到前面ReactActivityDelegate中的loadApp会拿到MainApplication中的ReactNativeHost:

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);  }protected ReactNativeHost getReactNativeHost() {    return ((ReactApplication) getPlainActivity().getApplication()).getReactNativeHost();  }复制代码

loadApp会调用getReactNativeHost().getReactInstanceManager(),接着看看ReactInstanceManager是什么,跟到ReactNativeHost中,第一次会构建ReactInstanceManager,用来管理CatalystInstance,CatalystInstance是Java/C++/JS三方通信的总管:

/**   * Get the current {@link ReactInstanceManager} instance, or create one.   */  public ReactInstanceManager getReactInstanceManager() {    if (mReactInstanceManager == null) {      mReactInstanceManager = createReactInstanceManager();    }    return mReactInstanceManager;  }protected ReactInstanceManager createReactInstanceManager() {    ReactInstanceManagerBuilder builder = ReactInstanceManager.builder()      .setApplication(mApplication)      .setJSMainModulePath(getJSMainModuleName())      .setUseDeveloperSupport(getUseDeveloperSupport())      .setRedBoxHandler(getRedBoxHandler())      .setJavaScriptExecutorFactory(getJavaScriptExecutorFactory())      .setUIImplementationProvider(getUIImplementationProvider())      .setInitialLifecycleState(LifecycleState.BEFORE_CREATE);    for (ReactPackage reactPackage : getPackages()) {      builder.addPackage(reactPackage);    }    String jsBundleFile = getJSBundleFile();    if (jsBundleFile != null) {      builder.setJSBundleFile(jsBundleFile);    } else {      builder.setBundleAssetName(Assertions.assertNotNull(getBundleAssetName()));    }    return builder.build();  }复制代码

从上面可以看到用到了建造者模式构造 ReactInstanceManager:

/** * Builder class for {@link ReactInstanceManager} */public class ReactInstanceManagerBuilder {  private final List
mPackages = new ArrayList<>(); public ReactInstanceManagerBuilder addPackage(ReactPackage reactPackage) { mPackages.add(reactPackage); return this; } public ReactInstanceManagerBuilder addPackages(List
reactPackages) { mPackages.addAll(reactPackages); return this; } /** * Instantiates a new {@link ReactInstanceManagerImpl}. * Before calling {@code build}, the following must be called: *
    *
  • {@link #setApplication} *
  • {@link #setJSBundleFile} or {@link #setJSMainModuleName} *
*/ public ReactInstanceManager build() { return new ReactInstanceManagerImpl( Assertions.assertNotNull( mApplication, "Application property has not been set with this builder"), mJSBundleFile, mJSMainModuleName, mPackages, mUseDeveloperSupport, mBridgeIdleDebugListener, Assertions.assertNotNull(mInitialLifecycleState, "Initial lifecycle state was not set"), mUIImplementationProvider, mNativeModuleCallExceptionHandler); }}复制代码

到这里系统定义的和我们自定义的ReactPackage都加载到ReactInstanceManageImpl中的mPackages中。 再返回前面ReactRootView startReactApplication,会调用mReactInstanceManager.createReactContextInBackground

//com/facebook/react/ReactRootViewpublic void startReactApplication(      ReactInstanceManager reactInstanceManager,      String moduleName,      @Nullable Bundle initialProperties) {    Systrace.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "startReactApplication");    try {      UiThreadUtil.assertOnUiThread();      if (!mReactInstanceManager.hasStartedCreatingInitialContext()) {        mReactInstanceManager.createReactContextInBackground();      }      attachToReactInstanceManager();    } finally {      Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);    }  }复制代码

接着到ReactInstanceManagerImpl中:

//com/facebool/react/ReactInstanceManagerImpl@ThreadConfined(UI)  public void createReactContextInBackground() {	......    mHasStartedCreatingInitialContext = true;    recreateReactContextInBackgroundInner();  }@ThreadConfined(UI)  private void recreateReactContextInBackgroundInner() {    ......    UiThreadUtil.assertOnUiThread();    if (mUseDeveloperSupport && mJSMainModulePath != null &&      !Systrace.isTracing(TRACE_TAG_REACT_APPS | TRACE_TAG_REACT_JSC_CALLS)) {      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();      } else 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();  }复制代码

最后都会回调:

//com/facebool/react/ReactInstanceManagerImplprivate void recreateReactContextInBackground(      JavaScriptExecutor.Factory jsExecutorFactory,      JSBundleLoader jsBundleLoader) {    UiThreadUtil.assertOnUiThread();    ReactContextInitParams initParams =        new ReactContextInitParams(jsExecutorFactory, jsBundleLoader);    if (!mIsContextInitAsyncTaskRunning) {      // No background task to create react context is currently running, create and execute one.      ReactContextInitAsyncTask initTask = new ReactContextInitAsyncTask();      initTask.execute(initParams);      mIsContextInitAsyncTaskRunning = true;    } else {      // Background task is currently running, queue up most recent init params to recreate context      // once task completes.      mPendingReactContextInitParams = initParams;    }  }复制代码

会启动一个AsyncTask来构造ReactContext:

//com/facebool/react/ReactInstanceManagerImpl/*   * Task class responsible for (re)creating react context in the background. These tasks can only   * be executing one at time, see {@link #recreateReactContextInBackground()}.   */  private final class ReactContextInitAsyncTask extends      AsyncTask
> { @Override protected void onPreExecute() { if (mCurrentReactContext != null) { tearDownReactContext(mCurrentReactContext); mCurrentReactContext = null; } } @Override protected Result
doInBackground(ReactContextInitParams... params) { Assertions.assertCondition(params != null && params.length > 0 && params[0] != null); try { JavaScriptExecutor jsExecutor = params[0].getJsExecutorFactory().create(); return Result.of(createReactContext(jsExecutor, params[0].getJsBundleLoader())); } catch (Exception e) { // Pass exception to onPostExecute() so it can be handled on the main thread return Result.of(e); } } @Override protected void onPostExecute(Result
result) { try { setupReactContext(result.get()); } catch (Exception e) { mDevSupportManager.handleException(e); } finally { mIsContextInitAsyncTaskRunning = false; } ...... } }复制代码

在doInBackground中构造ReactContext:

  • 构造ReactContext
  • 加载CoreModulesPackage,系统的核心模块
  • 加载用户自定义Packages
  • 构造NativeModuleRegistry,管理暴露给JS层的Java本地模块
  • 构造JavaScriptModulesConfig,存储JS层暴露给Java调用的JS模块
  • 构造CatalystInstanceImpl,三方总管
  • runJSBundle
/**       * @return instance of {@link ReactContext} configured a {@link CatalystInstance} set       */      private ReactApplicationContext createReactContext(          JavaScriptExecutor jsExecutor,          JSBundleLoader jsBundleLoader) {        mSourceUrl = jsBundleLoader.getSourceUrl();        NativeModuleRegistry.Builder nativeRegistryBuilder = new NativeModuleRegistry.Builder();        JavaScriptModulesConfig.Builder jsModulesBuilder = new JavaScriptModulesConfig.Builder();            //构造ReactContext        ReactApplicationContext reactContext = new ReactApplicationContext(mApplicationContext);        if (mUseDeveloperSupport) {          reactContext.setNativeModuleCallExceptionHandler(mDevSupportManager);        }            //加载CoreModulesPackage        Systrace.beginSection(            Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,            "createAndProcessCoreModulesPackage");        try {          CoreModulesPackage coreModulesPackage =              new CoreModulesPackage(this, mBackBtnHandler, mUIImplementationProvider);          processPackage(coreModulesPackage, reactContext, nativeRegistryBuilder, jsModulesBuilder);        } finally {          Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);        }            //加载用户自定义Packages        for (ReactPackage reactPackage : mPackages) {          Systrace.beginSection(              Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,              "createAndProcessCustomReactPackage");          try {            processPackage(reactPackage, reactContext, nativeRegistryBuilder, jsModulesBuilder);          } finally {            Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);          }        }            //构造NativeModuleRegistry        Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "buildNativeModuleRegistry");        NativeModuleRegistry nativeModuleRegistry;        try {           nativeModuleRegistry = nativeRegistryBuilder.build();        } finally {          Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);        }            //构造JavaScriptModulesConfig        Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "buildJSModuleConfig");        JavaScriptModulesConfig javaScriptModulesConfig;        try {          javaScriptModulesConfig = jsModulesBuilder.build();        } finally {          Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);        }            ...        //构造CatalystInstanceImpl        CatalystInstanceImpl.Builder catalystInstanceBuilder = new CatalystInstanceImpl.Builder()            .setCatalystQueueConfigurationSpec(CatalystQueueConfigurationSpec.createDefault())            .setJSExecutor(jsExecutor)            .setRegistry(nativeModuleRegistry)            .setJSModulesConfig(javaScriptModulesConfig)            .setJSBundleLoader(jsBundleLoader)            .setNativeModuleCallExceptionHandler(exceptionHandler);            Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "createCatalystInstance");        CatalystInstance catalystInstance;        try {          catalystInstance = catalystInstanceBuilder.build();        } finally {          Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);        }            ......        reactContext.initializeWithInstance(catalystInstance);            //runJSBundle        Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "runJSBundle");        try {          catalystInstance.runJSBundle();        } finally {          Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);        }            ReactMarker.logMarker("CREATE_REACT_CONTEXT_END");        return reactContext;      }    //加载模块    private void processPackage(          ReactPackage reactPackage,          ReactApplicationContext reactContext,          NativeModuleRegistry.Builder nativeRegistryBuilder,          JavaScriptModulesConfig.Builder jsModulesBuilder) {        for (NativeModule nativeModule : reactPackage.createNativeModules(reactContext)) {          nativeRegistryBuilder.add(nativeModule);        }        for (Class
jsModuleClass : reactPackage.createJSModules()) { jsModulesBuilder.add(jsModuleClass); } }复制代码

CoreModulesPackage

封装了一些系统的核心模块,有NativeModules和JSModules

class CoreModulesPackage implements ReactPackage{  @Override  public List
createNativeModules( ReactApplicationContext catalystApplicationContext) { ...... return Arrays.
asList( new AnimationsDebugModule( catalystApplicationContext, mReactInstanceManager.getDevSupportManager().getDevSettings()), new AndroidInfoModule(), new DeviceEventManagerModule(catalystApplicationContext, mHardwareBackBtnHandler), new ExceptionsManagerModule(mReactInstanceManager.getDevSupportManager()), new Timing(catalystApplicationContext), new SourceCodeModule( mReactInstanceManager.getSourceUrl(), mReactInstanceManager.getDevSupportManager().getSourceMapUrl()), uiManagerModule, new DebugComponentOwnershipModule(catalystApplicationContext)); } @Override public List
> createJSModules() { return Arrays.asList( DeviceEventManagerModule.RCTDeviceEventEmitter.class, JSTimersExecution.class, RCTEventEmitter.class, RCTNativeAppEventEmitter.class, AppRegistry.class, com.facebook.react.bridge.Systrace.class, DebugComponentOwnershipModule.RCTDebugComponentOwnership.class); }}public interface ReactPackage { /** * @param reactContext react application context that can be used to create modules * @return list of native modules to register with the newly created catalyst instance */ List
createNativeModules(ReactApplicationContext reactContext); /** * @return list of JS modules to register with the newly created catalyst instance. * * IMPORTANT: Note that only modules that needs to be accessible from the native code should be * listed here. Also listing a native module here doesn't imply that the JS implementation of it * will be automatically included in the JS bundle. */ List
> createJSModules(); /** * @return a list of view managers that should be registered with {@link UIManagerModule} */ List
createViewManagers(ReactApplicationContext reactContext);}复制代码

到这里就把所有的Java模块和JS模块传递给了CatalystInstanceImpl,这个是三方的中转模块,调用逻辑在这里中转。再之后就是Java<-->JS双方通信的原理了,后面专门的文章进行分析。

###4.总结 整个流程其实是:

ReactActivity--->  ReactActivityDelegate--->         createRootView->          getReactNativeHost->          createReactInstanceManager->ReactInstanceManagerImpl                  recreateReactContextInBackground->                     1.构造ReactContext->                     2.加载CoreModulesPackage->                     3.加载用户自定义Packages->                     4.构造CatalystInstanceImpl->                     5.runJSBundle          setContentView(mReactRootView)复制代码

React Native框架就是套一个自定义的根View-ReactRootView,在这个自定义View中创建ReactInstanceManager,ReactInstanceManager会构造ReactContext(包括JS运行环境和JSBundleLoader),同时加载系统和用户定义的Package,构造三方通信的中转站CatalystInstanceImpl,然后解释执行JSBundle,最后getPlainActivity().setContentView(mReactRootView)塞给Activity这样RN界面就显示到屏幕上了,而这个Activity的生命周期其实还是交给Android进行管理的。

后面会接着分析RN中Java与JS的通信原理,感兴趣的小伙伴们欢迎关注。

如果文章内容能够帮到你,帮忙点赞哈。

欢迎关注

转载地址:http://wlavx.baihongyu.com/

你可能感兴趣的文章
[译] 所有你需要知道的关于完全理解 Node.js 事件循环及其度量
查看>>
脚本监控网络状态,输出日志并归档(V2)
查看>>
(六十九)复合语句
查看>>
Cisco ASA 5505配置详解
查看>>
我的友情链接
查看>>
java读取某个文件夹下的所有文件
查看>>
设计模式:装饰者
查看>>
CentOS minimal+VirtualBox 设置桥接DHCP网络连接
查看>>
OC高效率52之不要使用dispatch_get_current_queue
查看>>
Quartz2D
查看>>
栈与queue
查看>>
对于java我的看法
查看>>
Java Web中实现Servlet的方式
查看>>
74LVC245AD技术资料
查看>>
第三方库之 - SVProgressHUD
查看>>
struts-自定义标签
查看>>
ZHYcms开源内容管理系统源码阅读
查看>>
11个让你吃惊的 Linux 终端命令
查看>>
Caused by: java.lang.IllegalArgumentException: error at ::0 can't find referenced pointcut
查看>>
MySQL与MongoDB的操作对比
查看>>