React Native原生功能集成

歡迎回到React Native學(xué)習(xí)之旅

在上一篇博客中,我們已經(jīng)學(xué)習(xí)了React Native的數(shù)據(jù)處理,包括網(wǎng)絡(luò)請求、本地存儲、狀態(tài)管理方案對比以及數(shù)據(jù)流最佳實踐?,F(xiàn)在,讓我們深入了解React Native的原生功能集成,這是構(gòu)建功能完整應(yīng)用的關(guān)鍵。

作為一名有安卓、TypeScript和Web基礎(chǔ)的開發(fā)者,你會發(fā)現(xiàn)React Native的原生功能集成既有熟悉的地方,也有一些獨特的特性。

一、原生模塊簡介

什么是原生模塊?

原生模塊是React Native中用于調(diào)用平臺特定功能的模塊,它們是用原生代碼(Java/Kotlin for Android, Objective-C/Swift for iOS)編寫的,可以通過JavaScript接口在React Native應(yīng)用中調(diào)用。

原生模塊的作用

  1. 訪問平臺特定功能:調(diào)用平臺特有的API,如相機(jī)、定位、傳感器等
  2. 性能優(yōu)化:對于計算密集型任務(wù),使用原生代碼可以獲得更好的性能
  3. 代碼復(fù)用:復(fù)用現(xiàn)有的原生代碼庫
  4. 平臺特定UI:實現(xiàn)平臺特定的UI組件

與安卓原生代碼的關(guān)系

在React Native中,安卓原生模塊是用Java或Kotlin編寫的,它們可以:

  1. 訪問安卓系統(tǒng)API
  2. 使用安卓第三方庫
  3. 與React Native JavaScript代碼通信

二、調(diào)用原生模塊

使用現(xiàn)有的原生模塊

React Native生態(tài)系統(tǒng)中有許多現(xiàn)成的原生模塊,我們可以通過npm安裝并使用它們。

安裝和使用步驟

  1. 安裝模塊:使用npm或yarn安裝原生模塊
npm install react-native-camera
  1. 鏈接模塊:對于較舊的React Native版本,需要手動鏈接原生模塊
react-native link react-native-camera

對于React Native 0.60及以上版本,會自動鏈接原生模塊。

  1. 配置權(quán)限:根據(jù)模塊的要求,在AndroidManifest.xml中添加必要的權(quán)限

  2. 在JavaScript中使用:導(dǎo)入并使用原生模塊

import { RNCamera } from 'react-native-camera';

// 使用RNCamera組件

常見的原生模塊

功能 原生模塊 說明
相機(jī) react-native-camera 訪問設(shè)備相機(jī)
定位 react-native-geolocation-service 獲取設(shè)備位置
存儲 react-native-fs 訪問文件系統(tǒng)
推送通知 react-native-firebase 集成推送通知
藍(lán)牙 react-native-ble-plx 藍(lán)牙通信
傳感器 react-native-sensors 訪問設(shè)備傳感器
分享 @react-native-community/share 分享內(nèi)容
網(wǎng)絡(luò)狀態(tài) @react-native-community/netinfo 監(jiān)控網(wǎng)絡(luò)狀態(tài)

三、權(quán)限管理

什么是權(quán)限?

權(quán)限是應(yīng)用訪問設(shè)備特定功能或數(shù)據(jù)的許可,如相機(jī)、定位、存儲等。在React Native中,我們需要正確處理權(quán)限,以確保應(yīng)用能夠正常運(yùn)行并尊重用戶隱私。

權(quán)限管理庫

使用react-native-permissions庫可以統(tǒng)一處理不同平臺的權(quán)限:

npm install react-native-permissions

基本使用

import React, { useState, useEffect } from 'react';
import { View, Text, Button, StyleSheet, Alert } from 'react-native';
import { check, request, PERMISSIONS, RESULTS } from 'react-native-permissions';

const PermissionExample: React.FC = () => {
  const [cameraPermission, setCameraPermission] = useState<RESULTS>(RESULTS.UNAVAILABLE);

  useEffect(() => {
    checkCameraPermission();
  }, []);

  const checkCameraPermission = async () => {
    const result = await check(PERMISSIONS.ANDROID.CAMERA);
    setCameraPermission(result);
  };

  const requestCameraPermission = async () => {
    const result = await request(PERMISSIONS.ANDROID.CAMERA);
    setCameraPermission(result);
    
    if (result === RESULTS.GRANTED) {
      Alert.alert('Success', 'Camera permission granted');
    } else {
      Alert.alert('Error', 'Camera permission denied');
    }
  };

  return (
    <View style={styles.container}>
      <Text style={styles.title}>Permission Example</Text>
      <Text style={styles.status}>Camera Permission: {cameraPermission}</Text>
      
      {cameraPermission === RESULTS.DENIED && (
        <Button title="Request Camera Permission" onPress={requestCameraPermission} />
      )}
      
      {cameraPermission === RESULTS.GRANTED && (
        <Text style={styles.granted}>Camera permission is granted!</Text>
      )}
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    marginBottom: 20,
  },
  status: {
    fontSize: 16,
    marginBottom: 20,
  },
  granted: {
    fontSize: 16,
    color: 'green',
    marginTop: 20,
  },
});

export default PermissionExample;

權(quán)限最佳實踐

  1. 僅在需要時請求權(quán)限:不要在應(yīng)用啟動時請求所有權(quán)限
  2. 解釋為什么需要權(quán)限:在請求權(quán)限前,向用戶解釋為什么需要該權(quán)限
  3. 處理權(quán)限被拒絕的情況:如果權(quán)限被拒絕,提供替代方案
  4. 檢查權(quán)限狀態(tài):在使用需要權(quán)限的功能前,檢查權(quán)限狀態(tài)
  5. 遵循平臺指導(dǎo)原則:遵循各平臺的權(quán)限使用指導(dǎo)原則

四、相機(jī)功能集成

安裝相機(jī)庫

npm install react-native-camera

配置權(quán)限

android/app/src/main/AndroidManifest.xml中添加相機(jī)權(quán)限:

<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

基本使用

import React, { useState, useRef } from 'react';
import { View, Text, TouchableOpacity, StyleSheet, Alert } from 'react-native';
import { RNCamera } from 'react-native-camera';
import { check, request, PERMISSIONS, RESULTS } from 'react-native-permissions';

const CameraExample: React.FC = () => {
  const [hasPermission, setHasPermission] = useState(false);
  const cameraRef = useRef<RNCamera>(null);

  React.useEffect(() => {
    (async () => {
      const cameraPermission = await check(PERMISSIONS.ANDROID.CAMERA);
      
      if (cameraPermission === RESULTS.DENIED) {
        const result = await request(PERMISSIONS.ANDROID.CAMERA);
        setHasPermission(result === RESULTS.GRANTED);
      } else {
        setHasPermission(cameraPermission === RESULTS.GRANTED);
      }
    })();
  }, []);

  const takePicture = async () => {
    if (cameraRef.current) {
      const options = { quality: 0.5, base64: true };
      const data = await cameraRef.current.takePictureAsync(options);
      Alert.alert('Success', `Picture taken: ${data.uri}`);
    }
  };

  if (!hasPermission) {
    return (
      <View style={styles.container}>
        <Text style={{ textAlign: 'center' }}>No access to camera</Text>
      </View>
    );
  }

  return (
    <View style={styles.container}>
      <RNCamera
        ref={cameraRef}
        style={styles.preview}
        type={RNCamera.Constants.Type.back}
        flashMode={RNCamera.Constants.FlashMode.off}
        androidCameraPermissionOptions={{
          title: 'Permission to use camera',
          message: 'We need your permission to use your camera',
          buttonPositive: 'Ok',
          buttonNegative: 'Cancel',
        }}
      />
      <View style={styles.buttonContainer}>
        <TouchableOpacity style={styles.captureButton} onPress={takePicture}>
          <Text style={styles.captureText}>Take Picture</Text>
        </TouchableOpacity>
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  preview: {
    flex: 1,
    justifyContent: 'flex-end',
    alignItems: 'center',
  },
  buttonContainer: {
    flex: 0,
    flexDirection: 'row',
    justifyContent: 'center',
    marginBottom: 20,
  },
  captureButton: {
    backgroundColor: '#fff',
    borderRadius: 5,
    padding: 15,
    paddingHorizontal: 20,
    alignSelf: 'center',
  },
  captureText: {
    fontSize: 14,
    color: '#000',
  },
});

export default CameraExample;

五、定位功能集成

安裝定位庫

npm install react-native-geolocation-service

配置權(quán)限

android/app/src/main/AndroidManifest.xml中添加定位權(quán)限:

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />

基本使用

import React, { useState, useEffect } from 'react';
import { View, Text, StyleSheet, Alert } from 'react-native';
import Geolocation from 'react-native-geolocation-service';
import { check, request, PERMISSIONS, RESULTS } from 'react-native-permissions';

const LocationExample: React.FC = () => {
  const [location, setLocation] = useState({ latitude: 0, longitude: 0 });
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    (async () => {
      const locationPermission = await check(PERMISSIONS.ANDROID.ACCESS_FINE_LOCATION);
      
      if (locationPermission === RESULTS.DENIED) {
        const result = await request(PERMISSIONS.ANDROID.ACCESS_FINE_LOCATION);
        if (result === RESULTS.GRANTED) {
          getLocation();
        } else {
          setError('Location permission denied');
        }
      } else if (locationPermission === RESULTS.GRANTED) {
        getLocation();
      }
    })();
  }, []);

  const getLocation = () => {
    Geolocation.getCurrentPosition(
      (position) => {
        setLocation({
          latitude: position.coords.latitude,
          longitude: position.coords.longitude,
        });
        setError(null);
      },
      (error) => {
        setError(`Error: ${error.code}, ${error.message}`);
      },
      {
        enableHighAccuracy: true,
        timeout: 15000,
        maximumAge: 10000,
      }
    );
  };

  return (
    <View style={styles.container}>
      <Text style={styles.title}>Location Example</Text>
      
      {error ? (
        <Text style={styles.error}>{error}</Text>
      ) : (
        <>
          <Text style={styles.locationText}>Latitude: {location.latitude}</Text>
          <Text style={styles.locationText}>Longitude: {location.longitude}</Text>
        </>
      )}
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    marginBottom: 20,
  },
  locationText: {
    fontSize: 16,
    marginBottom: 10,
  },
  error: {
    fontSize: 16,
    color: 'red',
  },
});

export default LocationExample;

六、與安卓原生代碼交互

創(chuàng)建原生模塊

1. 創(chuàng)建Java類

android/app/src/main/java/com/yourappname目錄下創(chuàng)建一個新的Java類:

// ToastModule.java
package com.yourappname;

import android.widget.Toast;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;

public class ToastModule extends ReactContextBaseJavaModule {

  private static ReactApplicationContext reactContext;

  ToastModule(ReactApplicationContext context) {
    super(context);
    reactContext = context;
  }

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

  @ReactMethod
  public void show(String message, int duration) {
    Toast.makeText(reactContext, message, duration).show();
  }
}

2. 創(chuàng)建包管理器

在同一目錄下創(chuàng)建包管理器類:

// CustomPackage.java
package com.yourappname;

import com.facebook.react.ReactPackage;
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 CustomPackage implements ReactPackage {

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

  @Override
  public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
    List<NativeModule> modules = new ArrayList<>();
    modules.add(new ToastModule(reactContext));
    return modules;
  }
}

3. 注冊包

MainApplication.java中注冊包:

// MainApplication.java
package com.yourappname;

import com.facebook.react.ReactActivity;
import com.facebook.react.ReactPackage;
import com.facebook.react.shell.MainReactPackage;
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.<ReactPackage>asList(
          new MainReactPackage(),
          new CustomPackage() // 添加這一行
      );
    }

    @Override
    protected String getJSMainModuleName() {
      return "index";
    }
  };

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

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

4. 在JavaScript中使用

// ToastExample.tsx
import React from 'react';
import { View, Text, Button, StyleSheet } from 'react-native';
import { NativeModules } from 'react-native';

const ToastExample: React.FC = () => {
  const showShortToast = () => {
    NativeModules.ToastModule.show('Hello from React Native!', 0); // 0 for short duration
  };

  const showLongToast = () => {
    NativeModules.ToastModule.show('Hello from React Native!', 1); // 1 for long duration
  };

  return (
    <View style={styles.container}>
      <Text style={styles.title}>Toast Example</Text>
      <Button title="Show Short Toast" onPress={showShortToast} />
      <Button title="Show Long Toast" onPress={showLongToast} />
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
    justifyContent: 'center',
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    marginBottom: 20,
    textAlign: 'center',
  },
});

export default ToastExample;

傳遞復(fù)雜數(shù)據(jù)

從JavaScript傳遞數(shù)據(jù)到原生

@ReactMethod
public void processUserData(ReadableMap userData) {
  String name = userData.getString("name");
  int age = userData.getInt("age");
  boolean isActive = userData.getBoolean("isActive");
  
  // 處理數(shù)據(jù)
  Log.d("ToastModule", "Name: " + name + ", Age: " + age + ", Active: " + isActive);
}
const processUserData = () => {
  NativeModules.ToastModule.processUserData({
    name: "John Doe",
    age: 30,
    isActive: true,
  });
};

從原生返回數(shù)據(jù)到JavaScript

@ReactMethod
public void getUserData(Callback callback) {
  WritableMap userData = Arguments.createMap();
  userData.putString("name", "John Doe");
  userData.putInt("age", 30);
  userData.putBoolean("isActive", true);
  
  callback.invoke(null, userData);
}
const getUserData = () => {
  NativeModules.ToastModule.getUserData((error, userData) => {
    if (error) {
      console.error('Error:', error);
    } else {
      console.log('User data:', userData);
    }
  });
};

七、其他常見原生功能集成

存儲功能

使用react-native-fs訪問文件系統(tǒng):

npm install react-native-fs
import React from 'react';
import { View, Text, Button, StyleSheet } from 'react-native';
import RNFS from 'react-native-fs';

const StorageExample: React.FC = () => {
  const writeFile = async () => {
    try {
      const path = RNFS.DocumentDirectoryPath + '/test.txt';
      await RNFS.writeFile(path, 'Hello from React Native!', 'utf8');
      alert('File written successfully');
    } catch (error) {
      alert('Error writing file: ' + error);
    }
  };

  const readFile = async () => {
    try {
      const path = RNFS.DocumentDirectoryPath + '/test.txt';
      const content = await RNFS.readFile(path, 'utf8');
      alert('File content: ' + content);
    } catch (error) {
      alert('Error reading file: ' + error);
    }
  };

  return (
    <View style={styles.container}>
      <Text style={styles.title}>Storage Example</Text>
      <Button title="Write File" onPress={writeFile} />
      <Button title="Read File" onPress={readFile} />
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
    justifyContent: 'center',
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    marginBottom: 20,
    textAlign: 'center',
  },
});

export default StorageExample;

網(wǎng)絡(luò)狀態(tài)監(jiān)控

使用@react-native-community/netinfo監(jiān)控網(wǎng)絡(luò)狀態(tài):

npm install @react-native-community/netinfo
import React, { useState, useEffect } from 'react';
import { View, Text, StyleSheet } from 'react-native';
import NetInfo from '@react-native-community/netinfo';

const NetworkExample: React.FC = () => {
  const [networkState, setNetworkState] = useState('Unknown');

  useEffect(() => {
    const unsubscribe = NetInfo.addEventListener(state => {
      setNetworkState(
        state.isConnected 
          ? `Connected: ${state.type}` 
          : 'Disconnected'
      );
    });

    return () => unsubscribe();
  }, []);

  return (
    <View style={styles.container}>
      <Text style={styles.title}>Network Status</Text>
      <Text style={styles.status}>{networkState}</Text>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
    justifyContent: 'center',
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    marginBottom: 20,
    textAlign: 'center',
  },
  status: {
    fontSize: 18,
    textAlign: 'center',
  },
});

export default NetworkExample;

八、實際應(yīng)用示例

構(gòu)建一個包含多種原生功能的應(yīng)用

// App.tsx
import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { Ionicons } from '@expo/vector-icons';

// 導(dǎo)入功能模塊
import CameraExample from './screens/CameraExample';
import LocationExample from './screens/LocationExample';
import StorageExample from './screens/StorageExample';
import NetworkExample from './screens/NetworkExample';
import ToastExample from './screens/ToastExample';

const Tab = createBottomTabNavigator();

export default function App() {
  return (
    <NavigationContainer>
      <Tab.Navigator
        screenOptions={({ route }) => ({
          tabBarIcon: ({ focused, color, size }) => {
            let iconName: keyof typeof Ionicons.glyphMap;

            if (route.name === 'Camera') {
              iconName = focused ? 'camera' : 'camera-outline';
            } else if (route.name === 'Location') {
              iconName = focused ? 'location' : 'location-outline';
            } else if (route.name === 'Storage') {
              iconName = focused ? 'folder' : 'folder-outline';
            } else if (route.name === 'Network') {
              iconName = focused ? 'wifi' : 'wifi-outline';
            } else if (route.name === 'Toast') {
              iconName = focused ? 'alert-circle' : 'alert-circle-outline';
            } else {
              iconName = 'help-circle-outline';
            }

            return <Ionicons name={iconName} size={size} color={color} />;
          },
          tabBarActiveTintColor: 'tomato',
          tabBarInactiveTintColor: 'gray',
        })}
      >
        <Tab.Screen name="Camera" component={CameraExample} options={{ title: '相機(jī)' }} />
        <Tab.Screen name="Location" component={LocationExample} options={{ title: '定位' }} />
        <Tab.Screen name="Storage" component={StorageExample} options={{ title: '存儲' }} />
        <Tab.Screen name="Network" component={NetworkExample} options={{ title: '網(wǎng)絡(luò)' }} />
        <Tab.Screen name="Toast" component={ToastExample} options={{ title: '原生交互' }} />
      </Tab.Navigator>
    </NavigationContainer>
  );
}

九、最佳實踐和注意事項

最佳實踐

  1. 優(yōu)先使用現(xiàn)有庫:盡量使用成熟的第三方庫,而不是自己編寫原生模塊
  2. 模塊化設(shè)計:將原生功能封裝成獨立的模塊,提高代碼可維護(hù)性
  3. 錯誤處理:合理處理原生模塊的錯誤,提供友好的用戶提示
  4. 權(quán)限管理:遵循權(quán)限管理的最佳實踐,保護(hù)用戶隱私
  5. 性能優(yōu)化:對于頻繁調(diào)用的原生功能,考慮使用批處理或緩存
  6. 測試:在不同設(shè)備和平臺上測試原生功能

注意事項

  1. 平臺差異:不同平臺(Android/iOS)的原生功能實現(xiàn)可能不同,需要進(jìn)行適配
  2. 版本兼容性:原生模塊可能與特定版本的React Native不兼容,需要注意版本匹配
  3. 權(quán)限變更:Android和iOS的權(quán)限模型可能會隨著系統(tǒng)版本更新而變化,需要及時適配
  4. 內(nèi)存管理:原生模塊可能會持有內(nèi)存,需要正確管理生命周期
  5. 線程安全:原生模塊的調(diào)用可能在不同線程中執(zhí)行,需要注意線程安全
  6. 構(gòu)建問題:添加新的原生模塊后,可能需要重新構(gòu)建應(yīng)用

調(diào)試技巧

  1. 使用Logcat:在Android Studio中使用Logcat查看原生日志
  2. 使用React Native Debugger:調(diào)試JavaScript代碼
  3. 使用try-catch:捕獲和處理原生模塊的錯誤
  4. 簡化測試:創(chuàng)建最小化的測試用例來調(diào)試原生功能

十、與安卓原生開發(fā)的對比

功能 安卓原生開發(fā) React Native開發(fā) 說明
權(quán)限請求 ActivityCompat.requestPermissions() react-native-permissions React Native使用統(tǒng)一的API處理權(quán)限
相機(jī)訪問 Camera API/Camera2 API react-native-camera React Native提供更簡潔的API
定位獲取 FusedLocationProviderClient react-native-geolocation-service React Native封裝了原生定位API
文件操作 File API react-native-fs React Native提供跨平臺的文件操作API
原生模塊 直接調(diào)用 NativeModules React Native通過Bridge調(diào)用原生模塊
UI組件 XML布局 JSX React Native使用JSX構(gòu)建UI

十一、下一步學(xué)習(xí)計劃

在掌握了React Native的原生功能集成后,我們將在后續(xù)的博客中學(xué)習(xí):

  1. 性能優(yōu)化
  2. 測試與調(diào)試
  3. 應(yīng)用發(fā)布

總結(jié)

React Native的原生功能集成是構(gòu)建功能完整應(yīng)用的關(guān)鍵。通過本文的學(xué)習(xí),你應(yīng)該已經(jīng)掌握了:

  1. 原生模塊的基本概念和使用方法
  2. 權(quán)限管理的最佳實踐
  3. 常見原生功能(相機(jī)、定位、存儲等)的集成
  4. 與安卓原生代碼的交互方法
  5. 實際應(yīng)用示例和注意事項

作為一名有安卓基礎(chǔ)的開發(fā)者,你可以利用已有的知識,快速掌握React Native的原生功能集成。在接下來的學(xué)習(xí)中,我們將通過實際示例來進(jìn)一步鞏固這些概念,并學(xué)習(xí)更多高級特性。

祝你學(xué)習(xí)愉快!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容