# 加入白板房间 (/zh-CN/realtime-media/whiteboard/whiteboard-sdk/get-started/join-room-wb/android)

> For AI agents: see the complete documentation index at [llms.txt](/llms.txt).

声网互动白板房间提供丰富的编辑工具，支持展示静态图片、动态 PPT、H5 课件等多种格式的文件，不同终端用户能在白板上实时展示想法，轻松实现多人互动协作。本文介绍如何建立一个简单的项目，使用声网互动白板 SDK 加入房间并实现基础的白板功能。

## 技术原理 [#技术原理]

下图展示了加入声网互动白板房间的基本流程。

<DocsImage alt="互动白板快速开始流程图" src="https://web-cdn.agora.io/docs-portal/img/whiteboard/quick-start.svg" displayWidth="80%" />

当你的 App 客户端请求加入互动白板房间时：

1. 你的 App 服务端使用 SDK Token 向声网互动白板服务端发起创建房间的请求。
2. 成功创建房间后，声网互动白板服务端向你的 App 服务端返回新建房间的房间 UUID。
3. 你的 App 服务端使用房间 UUID 生成 room Token，并将房间 UUID 和 room Token 发送给 App 客户端。
4. App 客户端使用从声网控制台获取的 AppIdentifier 初始化互动白板 SDK 实例。
5. App 客户端调用 SDK 的方法并传入房间 UUID 和 room Token 以加入互动白板房间。

## 前提条件 [#前提条件]

- Android Studio
- Android SDK API level 21+
- 有效的声网[开发者账号](/zh-CN/introduction/quickstart)。
- 已在声网控制台[开启互动白板服务](/zh-CN/realtime-media/whiteboard/whiteboard-sdk/build/setup-and-access/enable-service)并获取[白板项目的 AppIdentifier 和 SDK Token](/zh-CN/realtime-media/whiteboard/whiteboard-sdk/build/setup-and-access/enable-service#获取互动白板项目的安全密钥)。

## 创建 Android 项目 [#创建-android-项目]

使用 Android Studio [创建一个 Android 项目](https://developer.android.google.cn/studio/projects/create-project)。

- 项目名设为 `WhiteBoard`。
- Package 名设为 `com.example.whiteboard`。
- Activity 类型选择 **Empty Activity**。
- **Minimum SDK** 选择 **API 21**。

## 添加权限 [#添加权限]

在 `AndroidManifest.xml` 文件中添加访问网络权限：

```xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.whiteboard">
 
    <uses-permission android:name="android.permission.INTERNET" />
 
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.WhiteBoard">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
```

## 获取 SDK [#获取-sdk]

打开根目录下的 `build.gradle` 进行以下配置：

```groovy
allprojects {
    repositories {
        google()
        mavenCentral()
        // 添加以下内容
        maven { url 'https://jitpack.io' }
        // 对于国内用户，如果 Gradle sync 失败，可以选择国内镜像，例如阿里云 Maven 镜像
        maven { url 'https://maven.aliyun.com/nexus/content/repositories/releases/'}
    }
}
```

然后打开 app 目录下的 `build.gradle` 进行如下配置：

```groovy
dependencies {
    // 当前正式版
    implementation 'com.github.netless-io:whiteboard-android:2.16.134'
}
```

> 关于声网互动白板 SDK 的最新版本，详见[发版说明](/zh-CN/realtime-media/whiteboard/whiteboard-sdk/reference/release-notes-wb)。

## 防止代码混淆 [#防止代码混淆]

在 `proguard-rules.pro` 文件中添加以下语句，防止代码混淆：

```proguard
# SDK model
-keep class com.herewhite.** { *; }
```

## 实现步骤 [#实现步骤]

现在，我们已经将声网互动白板 SDK 集成到项目中了。接下来我们要调用声网互动白板 SDK 提供的核心 API 实现基础的白板功能。

<a id="createroom"></a>
### 1. 创建房间 [#1-创建房间]

在 App 客户端加入互动白板房间前，你需要在 App 服务端调用互动白板服务端 RESTful API 创建一个房间。详见[创建房间（POST）](/zh-CN/api-reference/api-ref/whiteboard/restful/create-room)。

**请求示例**

你可以使用以下 Node.js 脚本发送请求：

:::info[信息]
使用 Node.js 发送 HTTP 请求前安装 `request` 模块。你可以运行 `npm install request` 安装。
:::

```javascript
var request = require("request");
var options = {
  method: "POST",
  url: "https://api.netless.link/v5/rooms",
  headers: {
    "token": "你的 SDK Token",
    "Content-Type": "application/json",
    "region": "cn-hz",
  },
};
request(options, function (error, response) {
  if (error) throw new Error(error);
  console.log(response.body);
});
```

如果方法调用成功，声网互动白板服务端将返回新建房间的信息，其中的 `uuid` 是房间的唯一标识，App 客户端加入房间时需要传入该参数。

**响应示例**

```json
{
    "uuid": "4a50xxxxxx796b", // 房间的 UUID
    "teamUUID": "RMmLxxxxxx15aw",
    "appUUID": "i54xxxxxx1AQ",
    "isRecord": true,
    "isBan": false,
    "createdAt": "2021-01-18T06:56:29.432Z",
    "limit": 0
}
```

<a id="roomtoken"></a>
### 2. 生成 Room Token [#2-生成-room-token]

创建房间并获取新建房间的 `uuid` 后，你需要在 App 服务端生成 Room Token 并下发给 App 客户端。当 App 客户端加入房间时，声网互动白板服务端会使用该 Token 对其鉴权。

你可以通过以下方式在 App 服务端生成 Room Token：

- 使用代码生成 Room Token，详见[在 App 服务端生成 Token](/zh-CN/realtime-media/whiteboard/whiteboard-sdk/build/setup-and-access/generate-token#在-app-服务端生成-token)。（推荐）
- 调用互动白板服务端 RESTful API 生成 Room Token，详见[生成 Room Token（POST）](/zh-CN/api-reference/api-ref/whiteboard/restful/generate-room-token)。

下面以调用 RESTful API 的方式为例介绍如何生成 Room Token。

**请求示例**

你可以使用以下 Node.js 脚本发送请求：

:::info[信息]
使用 Node.js 发送 HTTP 请求前安装 `request` 模块。你可以运行 `npm install request` 安装。
:::

```javascript
var request = require("request");
var options = {
  method: "POST",
  // 将 <房间的 UUID> 替换成你的房间 UUID
  url: "https://api.netless.link/v5/tokens/rooms/<房间的 UUID>",
  headers: {
    "token": "你的 SDK Token",
    "Content-Type": "application/json",
    "region": "cn-hz",
  },
  body: JSON.stringify({ lifespan: 3600000, role: "admin" }),
};
request(options, function (error, response) {
  if (error) throw new Error(error);
  console.log(response.body);
});
```

如果方法调用成功，声网互动白板服务端将返回生成的 Room Token。

**响应示例**

```javascript
"NETLESSROOM_YWs9XXXXXXXXXXXZWNhNjk" // Room Token
```

### 3. 创建 UI [#3-创建-ui]

修改 `activity_main.xml` 为如下内容，可以看到整个视图由一个白板页面填充，页面 `com.herewhite.sdk.WhiteboardView` 由互动白板 SDK 实现。

```xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">
 
    <com.herewhite.sdk.WhiteboardView
        android:id="@+id/white"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:visibility="visible" />
 
</LinearLayout>
```

### 4. 初始化 SDK 并加入房间 [#4-初始化-sdk-并加入房间]

编辑 `MainActivity.java`，实现从初始化 SDK 到加入房间的基本操作。你需要传入以下参数：

- `appId`：互动白板项目的 AppIdentifier。详见[获取 AppIdentifier](/zh-CN/realtime-media/whiteboard/whiteboard-sdk/build/setup-and-access/enable-service#获取互动白板项目的安全密钥)。
- `uuid`：房间的 UUID。详见[创建房间](#1-创建房间)。
- `uid`：用户的唯一标识符，字符串格式，长度不能超过 1024 字节。如果你使用 2.15.0 或之后版本的 SDK，必须传入该参数；如果你使用 2.15.0 之前版本的 SDK，则不需要传入该参数。
- `roomToken`：用于鉴权的 Room Token。生成该 Room Token 使用的房间 UUID 必须和上面的房间 UUID 一致。详见[生成 Room Token](#2-生成-room-token)。
- `region`：数据中心，必须和创建房间时设置的数据中心一致。

```java
package com.example.whiteboard;

import android.os.Bundle;
import android.util.Log;
import androidx.appcompat.app.AppCompatActivity;
import com.herewhite.sdk.Room;
import com.herewhite.sdk.RoomListener;
import com.herewhite.sdk.RoomParams;
import com.herewhite.sdk.WhiteSdk;
import com.herewhite.sdk.WhiteSdkConfiguration;
import com.herewhite.sdk.WhiteboardView;
import com.herewhite.sdk.domain.MemberState;
import com.herewhite.sdk.domain.Promise;
import com.herewhite.sdk.domain.Region;
import com.herewhite.sdk.domain.RoomPhase;
import com.herewhite.sdk.domain.RoomState;
import com.herewhite.sdk.domain.SDKError;
import com.herewhite.sdk.domain.WindowParams;

public class MainActivity extends AppCompatActivity {
    private WhiteboardView whiteboardView;
    private WhiteSdk whiteSdk;
    private Room room;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        whiteboardView = findViewById(R.id.white);
        WhiteSdkConfiguration config = new WhiteSdkConfiguration("APP_IDENTIFIER", true);
        config.setRegion(Region.cn);
        // 同一房间各端一致使用高性能绘制插件。
        config.setUseMultiViews(true);
        config.setEnableAppliancePlugin(true);
        whiteSdk = new WhiteSdk(whiteboardView, this, config);
        RoomParams params = new RoomParams("ROOM_UUID", "ROOM_TOKEN", "CLIENT_UID");
        params.setRegion(Region.cn);
        params.setWritable(true);
        params.setWindowParams(new WindowParams());
        RoomListener listener = new RoomListener() {
            @Override public void onPhaseChanged(RoomPhase phase) { }
            @Override public void onDisconnectWithError(Exception error) { }
            @Override public void onKickedWithReason(String reason) { }
            @Override public void onRoomStateChanged(RoomState modifyState) { }
            @Override public void onCanUndoStepsUpdate(long canUndoSteps) { }
            @Override public void onCanRedoStepsUpdate(long canRedoSteps) { }
            @Override public void onCatchErrorWhenAppendFrame(long userId, Exception error) { }
        };
        whiteSdk.joinRoom(params, listener, new Promise<Room>() {
            @Override public void then(Room joinedRoom) {
                room = joinedRoom;
                MemberState state = new MemberState();
                state.setCurrentApplianceName("pencil");
                state.setStrokeColor(new int[]{255, 0, 0});
                room.setMemberState(state);
            }
            @Override public void catchEx(SDKError error) {
                Log.e("Whiteboard", error.getMessage());
            }
        });
    }

    private boolean leaving;

    @Override
    public void onBackPressed() {
        if (leaving) return;
        if (whiteSdk == null) { finish(); return; }
        leaving = true;
        whiteSdk.leaveRoom(new Promise<Object>() {
            @Override public void then(Object completion) {
                // 仅通知退房完成；不是房间状态快照。
                room = null;
                whiteSdk.releaseRoom();
                finish(); // onDestroy 再销毁 View。
            }
            @Override public void catchEx(SDKError error) {
                leaving = false;
                Log.e("Whiteboard", "退出失败，请重试", error);
            }
        });
    }
  
    @Override
    protected void onDestroy() {
        // 正常退出已在 onBackPressed 等待；系统强制销毁不提供异步等待窗口。
        if (whiteboardView != null) whiteboardView.destroy();
        super.onDestroy();
    }
}
```

### 5. 接入多窗口 [#5-接入多窗口]

SDK 已通过内嵌 Bridge 集成 Window Manager。创建 `WhiteSdk` 前启用多 View，入房前将窗口配置传给 `RoomParams`；步骤 4 的完整示例已包含这些配置：

```java
import com.herewhite.sdk.domain.WindowParams;

// 创建 WhiteSdk 前设置。
config.setUseMultiViews(true);
// joinRoom 前设置；WindowParams 可进一步配置窗口显示。
params.setWindowParams(new WindowParams());
```

入房成功后，SDK 自动在 `WhiteboardView` 内挂载窗口管理器。窗口配置和课件控制见[统一页面控制](/zh-CN/realtime-media/whiteboard/whiteboard-sdk/build/extend-whiteboard/unified-page-control)。

### 6. 接入动态 PPT [#6-接入动态-ppt]

内嵌 Bridge 已注册 app-slide。业务服务端完成动态 PPT 转换后，在入房成功回调中，或其后的业务按钮中，用 `WindowAppParam.createSlideApp` 创建参数，再调用 `room.addApp`：

```java
import com.herewhite.sdk.domain.WindowAppParam;
import com.herewhite.sdk.domain.Promise;
import com.herewhite.sdk.domain.SDKError;

WindowAppParam slide = WindowAppParam.createSlideApp(
    "CONVERSION_TASK_UUID",
    "https://convertcdn.netless.link/dynamicConvert",
    "教学课件.pptx"
);
room.addApp(slide, new Promise<String>() {
    @Override public void then(String appId) {
        android.util.Log.i("Whiteboard", "动态 PPT 窗口 ID：" + appId);
    }
    @Override public void catchEx(SDKError error) {
        android.util.Log.e("Whiteboard", error.getMessage());
    }
});
```

第一个参数是转换任务 UUID，第二个参数是转换结果的资源地址前缀，末尾不带 `/`，不能填写 PPT 源文件地址。此工厂方法为窗口生成独立场景目录。创建 App 需要可写权限；房间中已存在的课件会同步显示，不要在每次入房时重复插入。返回窗口 ID 不表示课件资源和首帧全部加载完成。更多说明见[展示文件](/zh-CN/realtime-media/whiteboard/whiteboard-sdk/build/extend-whiteboard/courseware-integration)。

### 7. 开启新教具 [#7-开启新教具]

创建 `WhiteSdk` 前同时开启多 View 与新教具；步骤 4 的示例已开启：

```java
config.setUseMultiViews(true);
config.setEnableAppliancePlugin(true);
```

SDK 已内嵌 Appliance Plugin 与 Worker 资源，业务继续使用 `room.setMemberState` 等原生 API 切换 Pencil、Selector、Eraser、Shape、Text 等教具，无需额外安装或注册 Web 插件。同一房间各端应一致启用。

入房前可通过 `RoomParams.appliancePluginOptions` 设置教具插件参数。自定义教具、背景图和 Auto Shape 接入见[教具插件介绍与启用](/zh-CN/realtime-media/whiteboard/whiteboard-sdk/build/appliance-plugin/overview)。

`WhiteSdkConfiguration` 的初始化开关需在 SDK 创建前设置，入房后不能通过修改此对象启用新教具。SDK 初始化错误通过 `CommonCallback.sdkSetupFail` 处理。

## 编译并运行项目 [#编译并运行项目]

在 Android Studio 中编译并在模拟器或真机上运行项目。入房成功后可在白板上书写，业务按钮可通过步骤 6 的原生 API 打开动态 PPT。

## 配置远端参数 [#配置远端参数]

需要在入房前读取远端参数时，参见[动态配置](/zh-CN/realtime-media/whiteboard/whiteboard-sdk/build/extend-whiteboard/dynamic-config)。地址在创建 SDK 前设置，本地显式参数优先；读取失败回退本地参数。






