更新时间:2026-05-22 GMT+08:00
分享

代码示例

以下代码仅为示例之一, 用户也可通过调用其他API方法实现获取设备的device_token接口,可参考租户实例串流前获取设备的device_token,用于串流前身份鉴权。

工程目录结构代码示例

工程目录结构代码示例最小化实现了云手机串流、断流的能力。

CloudPhoneController/
├── app/                        # 应用主模块
│   ├── libs/                   # 存放第三方库文件(如SDK的AAR文件)
│   │   └── cloudphone-sdk.aar  # 云手机SDK库文件
│   ├── src/                    # 源代码目录
│   │   ├── main/               # 主代码目录
│   │   │   ├── java/           # Java源代码目录
│   │   │   │   ├── com/
│   │   │   │   │   └── example/
│   │   │   │   │       └── cloudphonecontroller/
│   │   │   │   │           ├── MainActivity.java        # 主Activity,控制云手机连接/断开
│   │   │   │   │           ├── callback/                 # 回调处理目录
│   │   │   │   │           │   └── CloudPhoneCallBack.java # SDK回调处理器
│   │   │   │   │           ├── constant/                 # 常量定义目录
│   │   │   │   │           │   └── Constant.java         # 应用常量定义
│   │   │   │   │           ├── config/                 # 配置定义目录
│   │   │   │   │           │   └── Config.java         # 配置文件
│   │   │   │   │           ├── model/                    # 数据模型目录
│   │   │   │   │           │   ├── request/              # 请求模型
│   │   │   │   │           │   │   └── AuthRequest.java  # IAM鉴权请求模型
│   │   │   │   │           │   └── response/             # 响应模型
│   │   │   │   │           │       ├── AuthResponse.java # IAM鉴权响应模型
│   │   │   │   │           │       └── ApiResponse.java  # 业务接口响应模型
│   │   │   │   │           ├── network/                  # 网络请求目录
│   │   │   │   │           │   ├── ApiService.java       # 网络接口定义
│   │   │   │   │           │   ├── TokenManager.java     # Token管理器
│   │   │   │   │           │   └── service/              # 服务管理
│   │   │   │   │           │       ├── IAMServiceManager.java # IAM服务管理
│   │   │   │   │           │       └── KooPhoneServiceManager.java # 云手机服务管理
│   │   │   │   │           └── util/                     # 工具类目录
│   │   │   │   │               └── DataSubscriber.java   # 数据订阅者工具
│   │   │   │   │               └──DefaultSubscriber.java   # 数据订阅者抽象类
│   │   │   │   │           └── base/
│   │   │   │   │           │   └──ResponseBean.java   # 统一接口返回数据封装类
│   │   │   │   │           │   └──BaseEntity.java   # 公共基类
│   │   │   ├── res/            # 资源文件目录
│   │   │   │   ├── layout/      # 布局文件
│   │   │   │   │   ├── activity_main.xml  # 主界面布局
│   │   │   │   └── values/      # 资源值
│   │   │   │       └── strings.xml # 字符串资源
│   │   │   └── AndroidManifest.xml # Android应用清单文件
│   │   └── build.gradle        # 模块级构建配置
│   └── build.gradle            # 应用级构建配置
└── gradle.properties           # Gradle属性配置

关键文件实现代码示例:

  • 模块级构建配置app/build.gradle代码示例:
    apply plugin: 'com.android.application'
    
    
    android {
        compileSdkVersion 34
        buildToolsVersion "30.0.0"
        
        buildFeatures {        
             buildConfig true    
        }
    
        defaultConfig {
            applicationId "com.example.cloudphonecontroller"
            minSdkVersion 28
            targetSdkVersion 34
            versionCode 1
            versionName "1.0"
            renderscriptTargetApi 21
            renderscriptSupportModeEnabled true
            testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    
            // 配置BuildConfig字段
            buildConfigField "String", "INSTANCE_ID", "\"your_instance_id\""
            buildConfigField "String", "USER_ID", "\"43740902807\""
            buildConfigField "int", "SCREEN_WIDTH", "1920"
            buildConfigField "int", "SCREEN_HEIGHT", "1080"
            buildConfigField "int", "AUDIO_SAMPLE_RATE", "48000"
            buildConfigField "int", "AUDIO_CHANNEL_COUNT", "2"
        }
    
    
        buildTypes {
            release {
                minifyEnabled false
                proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
            }
        }
    
        compileOptions {        
                sourceCompatibility JavaVersion.VERSION_17         
                targetCompatibility JavaVersion.VERSION_17     
        }
    
        buildFeatures {        
                viewBinding true    
        }
    
        sourceSets {
            getByName("main") {
                jniLibs.srcDirs("libs")
            }
        }
    }
    
    ext {
        versions = [appcompat       : "1.7.1",
                    activity        : "1.9.3",
                    constraintlayout: "2.2.1",
                    material        : "1.7.0",
                    preference      : "1.1.1",
                    androidx_test   : "1.2.0",
                    junit           : "1.1.1",
                    espresso        : "3.2.0",
                    mockito         : "5.14.2",]
    }
    
    dependencies {
        implementation platform("org.jetbrains.kotlin:kotlin-bom:1.8.10")
        implementation fileTree(dir: 'libs', include: ['*.aar'])
        
    
        implementation "org.jetbrains.kotlin:kotlin-stdlib"
        implementation("androidx.appcompat:appcompat:${versions.appcompat}")
        implementation "com.google.android.material:material:${versions.material}"
    
        implementation "androidx.activity:activity:${versions.activity}" 
        implementation "androidx.constraintlayout:constraintlayout:${versions.constraintlayout}"
        // 测试相关
        testImplementation "junit:junit:4.13.2"
        androidTestImplementation "androidx.test.ext:junit:${versions.junit}"
        androidTestImplementation "androidx.test.espresso:espresso-core:${versions.espresso}"
        
        implementation "com.squareup.retrofit2:retrofit:2.9.0"
        implementation "com.squareup.retrofit2:converter-gson:2.9.0"
        implementation "com.squareup.retrofit2:adapter-rxjava2:2.9.0"
        implementation "io.reactivex.rxjava2:rxjava:2.2.21"
        implementation "io.reactivex.rxjava2:rxandroid:2.1.1"
        // 其他依赖
        implementation "com.fasterxml.jackson.core:jackson-databind:2.12.6"
        implementation 'org.greenrobot:eventbus:3.3.1'
        implementation "androidx.window:window:1.2.0"
        implementation "androidx.window:window-java:1.2.0" 
        // Lombok
        compileOnly "org.projectlombok:lombok:1.18.30"
        annotationProcessor "org.projectlombok:lombok:1.18.30"
        // Glide
        implementation "com.github.bumptech.glide:glide:4.16.0"
        annotationProcessor "com.github.bumptech.glide:compiler:4.16.0"
        implementation "com.github.bumptech.glide:okhttp3-integration:4.12.0"
    
        implementation "com.squareup.okhttp3:logging-interceptor:4.10.0"
        implementation "androidx.startup:startup-runtime:1.1.1"
    }
    
    configurations.all {
        resolutionStrategy {
            force "com.google.android.material:material:1.6.1"
            force "androidx.appcompat:appcompat:1.6.1"
        }
    }
  • Android应用清单文件AndroidManifest.xml代码示例:
    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools">
    
    
        <uses-permission android:name="android.permission.INTERNET" />
        <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
        <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
        <uses-permission android:name="android.permission.RECORD_AUDIO" />
        <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
        <uses-permission
            android:name="android.permission.QUERY_ALL_PACKAGES"
            tools:ignore="QueryAllPackagesPermission" />
    
    
        <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.CloudPhoneController"
            android:appComponentFactory="androidx.core.app.CoreComponentFactory"
            tools:replace="android:appComponentFactory,android:allowBackup">
    
    
            <activity
                android:name=".MainActivity"
                android:configChanges="orientation|screenSize"
                android:screenOrientation="portrait"
                android:exported="true">
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>
        </application>
    
    
    </manifest>
  • 主界面布局activity_main.xml代码示例:
    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
    
        <!-- 云手机画面容器 -->
        <FrameLayout
            android:id="@+id/containerf"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    
    
        <!-- 未连接时的遮罩层 -->
        <View
            android:id="@+id/black_overlay"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="#FF000000"
            android:alpha="1" />
    
    
        <!-- 控制按钮 -->
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:orientation="horizontal"
            android:padding="16dp"
            android:gravity="center">
    
    
            <Button
                android:id="@+id/btn_connect"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="连接云手机"
                android:textAllCaps="false"
                android:paddingHorizontal="24dp"
                android:paddingVertical="12dp"/>
    
    
            <Button
                android:id="@+id/btn_disconnect"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="断开连接"
                android:textAllCaps="false"
                android:paddingHorizontal="24dp"
                android:paddingVertical="12dp"
                android:layout_marginStart="16dp"/>
        </LinearLayout>
    
    
        <!-- 状态显示 -->
        <TextView
            android:id="@+id/tv_status"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:text="状态:未连接"
            android:textColor="@android:color/white"
            android:textSize="18sp" />
    
    
    
    
    </RelativeLayout>
  • 主Activity,控制云手机连接/断开MainActivity.java代码示例:
    package com.example.cloudphonecontroller;
    
    
    import static com.cloudphone.client.api.CloudPhoneConst.LAUNCH_KEY_ADDRESS;
    import static com.cloudphone.client.api.CloudPhoneConst.LAUNCH_KEY_PORT;
    import static com.cloudphone.client.api.CloudPhoneConst.LAUNCH_KEY_TOKEN;
    import static com.nbc.acsdk.adapter.IPlayer.HMTP_PLAYER;
    
    
    import android.os.Bundle;
    import android.text.TextUtils;
    import android.view.View;
    import android.widget.Button;
    import android.widget.TextView;
    import android.widget.Toast;
    
    
    import androidx.appcompat.app.AppCompatActivity;
    
    
    import com.cloudphone.client.api.CloudPhoneClient;
    import com.example.cloudphonecontroller.callback.CloudPhoneCallBack;
    import com.example.cloudphonecontroller.config.Config;
    import com.example.cloudphonecontroller.constant.Constant;
    import com.example.cloudphonecontroller.model.response.ApiResponse;
    import com.example.cloudphonecontroller.network.ApiService;
    import com.example.cloudphonecontroller.network.KooPhoneServiceManager;
    import com.example.cloudphonecontroller.network.TokenManager;
    import com.example.cloudphonecontroller.util.DataSusbcriber;
    import com.google.gson.Gson;
    import com.nbc.acsdk.widget.PlayerFragment;
    import com.nbc.utils.Log;
    
    
    import java.io.File;
    
    
    import io.reactivex.Observable;
    import io.reactivex.android.schedulers.AndroidSchedulers;
    import io.reactivex.schedulers.Schedulers;
    
    
    public class MainActivity extends AppCompatActivity {
    
    
        private static Gson gson;
        private View blackOverlay;
        private TextView tvStatus;
        private Button btnConnect, btnDisconnect;
        private PlayerFragment mFragment;
        private boolean isConnected = false;
    
    
        private static final String TAG = "MainActivity";
    
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            // 初始化SDK
            initCloudPhoneSDK();
            Log.info(TAG,"从这里开始 MainActivity onCreate");
            // 初始化UI
            initUI();
        }
    
    
        private void initCloudPhoneSDK() {
            // 初始化SDK
            CloudPhoneClient.init(this, null, true);
            // 设置播放渲染模式
            CloudPhoneClient.playerForceTextureView(false);
        }
    
    
        private void initUI() {
            //设置页面基本控制组件
            blackOverlay = findViewById(R.id.black_overlay);
            tvStatus = findViewById(R.id.tv_status);
            btnConnect = findViewById(R.id.btn_connect);
            btnDisconnect = findViewById(R.id.btn_disconnect);
    
    
            // 初始化PlayerFragment
            if (mFragment == null) {
                mFragment = new PlayerFragment();
                getFragmentManager()
                        .beginTransaction()
                        .add(R.id.containerf, mFragment, "player_fragment")
                        .commit();
                CloudPhoneClient.bindFragment(mFragment);
            }
    
    
            // 设置按钮监听
            btnConnect.setOnClickListener(v -> connectCloudPhone());
            btnDisconnect.setOnClickListener(v -> disconnectCloudPhone());
        }
    
    
        // 连接云手机功能
        private void connectCloudPhone() {
            // 检查连接状态
            if (isConnected) {
                showToast("已经连接");
                return;
            }
    
    
            tvStatus.setText("状态:正在获取鉴权信息...");
    
    
            // 在子线程中进行网络请求
            new Thread(() -> {
                try {
                    // 1.获取IAM Token
                    String token = TokenManager.fetchToken();
                    Log.info(TAG,"get token is : " + token);
    
    
                    if(Constant.INSTANCE_ID.isEmpty()){
                        this.runOnUiThread(() -> Toast.makeText(getApplicationContext(), "请在gradle.properties里填入 INSTANCE_ID", Toast.LENGTH_LONG).show());
                        return;
                    }
                    // 2.使用IAM Token获取设备连接信息
                    Observable<ApiResponse> deviceInfo = KooPhoneServiceManager
                            .createService(ApiService.class, token)
                            .getDeviceInfo(Constant.INSTANCE_ID);
    
    
                    // 3.切换到主线程处理结果
                    runOnUiThread(() -> {
                        deviceInfo.subscribeOn(Schedulers.io())
                                .observeOn(AndroidSchedulers.mainThread())
                                .subscribe(new DataSubscriber<ApiResponse>() {
                                    @Override
                                    public void onNext(ApiResponse response) {
                                        Log.info(TAG,"ApiResponse " + response.toString());
                                        if (response == null || response.getData() == null ||
                                                response.getData().getDevice_token() == null) {
                                            tvStatus.setText("状态:鉴权失败");
                                            showToast("获取的Token为null,请检查实例状态");
                                            return;
                                        }
    
    
                                        // 4.启动串流
                                        startStream(response);
                                        tvStatus.setText("状态:已连接");
                                        isConnected = true;
                                        blackOverlay.setVisibility(View.GONE);
                                        tvStatus.setVisibility(View.GONE);
                                    }
    
    
                                    @Override
                                    public void onError(Throwable e) {
                                        tvStatus.setText("状态:鉴权失败");
                                        showToast("获取Token失败: " + e.getMessage());
                                        Log.info(TAG,"获取Token失败: " + e.getMessage());
                                    }
                                });
                    });
                } catch (Exception e) {
                    runOnUiThread(() -> {
                        tvStatus.setText("状态:连接失败");
                        showToast("连接异常: " + e.getMessage());
                    });
                }
            }).start();
        }
    
    
        private void startStream(ApiResponse response) {
            Bundle bundle = createBundle(response);
            CloudPhoneCallBack callBack = CloudPhoneCallBack.getInstance();
            //云手机蓝牙模块初始化
            EmulationTransport.init(this);
            // 设置回调
            CloudPhoneClient.setPlayerCallback(callBack);
            CloudPhoneClient.setPlayerType(HMTP_PLAYER);
            // 启动串流
            CloudPhoneClient.start(mFragment, bundle);
        }
    
    
        private Bundle createBundle(ApiResponse authResponse)  {
            Bundle bundle = new Bundle();
    
    
            if (authResponse != null && null != authResponse.getData()) {
                ApiResponse.Data data = authResponse.getData();
                // 会话Token
                bundle.putString(LAUNCH_KEY_TOKEN, TextUtils.isEmpty(data.getDevice_token()) ? "" : data.getDevice_token());
                ApiResponse.Data.Resource resource = data.getResource();
                if (null != resource) {
                    ApiResponse.Data.Resource.Sdk sdk = resource.getSdk();
                    if (null != sdk) {
                        ApiResponse.Data.Resource.Sdk.External external = sdk.getExternal();
                        if (null != external) {
                            //服务地址
                            bundle.putString(LAUNCH_KEY_ADDRESS, external.getAddress());
                            //服务端口
                            bundle.putInt(LAUNCH_KEY_PORT, external.getAport());
                        }
                    }
                }
            }
            // 用户标识
            bundle.putString("userId", Config.USER_ID);
            // 自适应分辨率
            bundle.putBoolean("free_aspect", true);
            // 目标分辨率
            bundle.putInt("screen_width", Config.SCREEN_WIDTH);//任意分辨率宽
            bundle.putInt("screen_height", Config.SCREEN_HEIGHT);//任意分辨率高
            return bundle;
        }
    
    
        // 断开云手机连接
        private void disconnectCloudPhone() {
            if (!isConnected) {
                showToast("当前未连接");
                return;
            }
            // 停止串流
            CloudPhoneClient.stop();
            isConnected = false;
            tvStatus.setText("状态:未连接");
            showToast("已断开连接");
            blackOverlay.setVisibility(View.VISIBLE); // 显示黑色遮罩
            tvStatus.setVisibility(View.VISIBLE);
        }
    
    
        private void showToast(String message) {
            Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
        }
    }
  • SDK回调处理器CloudPhoneCallBack.java代码示例:
    package com.example.cloudphonecontroller.callback;
    
    
    import com.cloudphone.client.api.CloudPhoneClient;
    
    
    public class CloudPhoneCallBack implements CloudPhoneClient.Callback, CloudPhoneClient.Callback.AppCallback {
    
    
        private static volatile CloudPhoneCallBack instance;
    
    
        private CloudPhoneCallBack() {}
    
    
        public static CloudPhoneCallBack getInstance() {
            if (instance == null) {
                synchronized (CloudPhoneCallBack.class) {
                    if (instance == null) {
                        instance = new CloudPhoneCallBack();
                    }
                }
            }
            return instance;
        }
    
    
        @Override
        public void onSuccess() {
            // 串流建立成功
           EmulationTransport.getInstance().initTransport();
        }
    
    
        @Override
        public void onFailure(int code, String msg) {
            // 串流建立失败
        }
    }
  • 应用常量定义Constant.java代码示例:
    package com.example.cloudphonecontroller.constant;
    
    
    import com.example.cloudphonecontroller.BuildConfig;
    
    
    public class Constant {
        public static final String INSTANCE_ID = BuildConfig.INSTANCE_ID;
        public static final String USER_ID = BuildConfig.USER_ID;
    
    
        // IAM鉴权信息
        public static final String IAM_BASE_URL = "https://your-iam-endpoint.com/";
        public static final String USER_NAME = "your_username";
        public static final String USER_PASSWORD = "your_password";
        public static final String DOMAIN_NAME = "your_domain";
        public static final String PROJECT_NAME = "your_project";
    
    
        // 业务接口地址
        public static final String API_BASE_URL = "https://your-api-endpoint.com/";
    }
  • Token管理器TokenManager.java代码示例:
    package com.example.cloudphonecontroller.network;
    import com.example.cloudphonecontroller.network.ApiService;
    import com.example.cloudphonecontroller.base.ResponseBean;
    import com.example.cloudphonecontroller.constant.Constant;
    import com.example.cloudphonecontroller.model.request.AuthRequest;
    import com.example.cloudphonecontroller.model.response.AuthResponse;
    
    
    import java.io.IOException;
    
    
    import retrofit2.Response;
    
    
    
    
    public class TokenManager {
        private static String currentToken;
    
    
        // 同步获取 Token(需在子线程运行)
        public static synchronized String fetchToken() throws IOException, RuntimeException {
            AuthRequest authRequest = new AuthRequest();
            authRequest.auth = new AuthRequest.Auth();
            authRequest.auth.identity = new AuthRequest.Identity();
            authRequest.auth.identity.password = new AuthRequest.Password();
            authRequest.auth.identity.password.user = new AuthRequest.User();
            authRequest.auth.identity.password.user.domain = new AuthRequest.Domain();
    
    
            authRequest.auth.identity.password.user.domain.name = Constant.DOMAIN_NAME;
            authRequest.auth.identity.password.user.name = Constant.USER_NAME;
            authRequest.auth.identity.password.user.password = Constant.USER_PASSWORD;
            authRequest.auth.scope = new AuthRequest.Scope();
            authRequest.auth.scope.project = new AuthRequest.Project();
            authRequest.auth.scope.project.name = Constant.PROJECT_NAME;
            Response<ResponseBean<AuthResponse>> responseBeanResponse = IAMServiceManager.createService(ApiService.class).authenticate(authRequest, true).blockingFirst();
            currentToken = responseBeanResponse.headers().get("X-Subject-Token");
            return currentToken;
        }
    }
  • IAM服务管理IAMServiceManager.java代码示例:
    import com.example.cloudphonecontroller.constant.Constant;
    
    
    import java.util.Arrays;
    import java.util.Collections;
    import java.util.concurrent.TimeUnit;
    
    
    import okhttp3.OkHttpClient;
    import okhttp3.Protocol;
    import retrofit2.Retrofit;
    import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
    import retrofit2.converter.gson.GsonConverterFactory;
    
    
    public class IAMServiceManager {
        private static Retrofit retrofit = null;
        private static String baseUrl = Constant.IAM_BASE_URL;
    
    
        public static <T> T createService(Class<T> serviceClass) {
            Retrofit retrofit = initRetrofit();
            return retrofit.create(serviceClass);
        }
    
    
        private static synchronized Retrofit initRetrofit() {
            if (retrofit != null) {
                return retrofit;
            }
    
    
            OkHttpClient.Builder builder = new OkHttpClient.Builder();
            builder.connectTimeout(15, TimeUnit.SECONDS) // 连接超时时间设置
                    .readTimeout(15, TimeUnit.SECONDS) // 读取超时时间设置
                    .protocols(Collections.unmodifiableList(Arrays.asList(Protocol.HTTP_2, Protocol.HTTP_1_1)));
    
    
            // 初始化Retrofit
            retrofit = new Retrofit.Builder()
                    .client(builder.build())
                    .baseUrl(baseUrl)
                    .addConverterFactory(GsonConverterFactory.create())
                    .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                    .build();
    
    
            return retrofit;
    
    
        }
    }
  • 云手机服务管理KooPhoneServiceManager.java代码示例:
    import com.example.cloudphonecontroller.constant.Constant;
    
    
    import java.security.SecureRandom;
    import java.security.cert.CertificateException;
    import java.security.cert.X509Certificate;
    import java.util.Arrays;
    import java.util.Collections;
    import java.util.concurrent.TimeUnit;
    
    
    import javax.net.ssl.HostnameVerifier;
    import javax.net.ssl.SSLContext;
    import javax.net.ssl.SSLSession;
    import javax.net.ssl.SSLSocketFactory;
    import javax.net.ssl.TrustManager;
    import javax.net.ssl.X509TrustManager;
    
    
    import okhttp3.OkHttpClient;
    import okhttp3.Protocol;
    import okhttp3.Request;
    import retrofit2.Retrofit;
    import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
    import retrofit2.converter.gson.GsonConverterFactory;
    
    
    public class KooPhoneServiceManager {
        private static Retrofit retrofit = null;
        private static String baseUrl = Constant.API_BASE_URL;
    
    
        private static String m_iamtoken = "";
    
    
        public static <T> T createService(Class<T> serviceClass,String iamtoken) {
            m_iamtoken = iamtoken;
            retrofit = initRetrofit();
            return retrofit.create(serviceClass);
        }
    
    
        public static synchronized Retrofit initRetrofit() {
            if (retrofit != null) {
                return retrofit;
            }
    
    
            OkHttpClient.Builder builder = new OkHttpClient.Builder();
    
    
            try {
     
                final TrustManager[] trustAllCerts = new TrustManager[]{
                        new X509TrustManager() {
                            @Override
                            public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                            }
    
    
                            @Override
                            public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                            }
    
    
                            @Override
                            public X509Certificate[] getAcceptedIssuers() {
                                return new X509Certificate[]{};
                            }
                        }
                };
    
    
                // 初始化SSLContext
                SSLContext sslContext = SSLContext.getInstance("SSL");
                sslContext.init(null, trustAllCerts, new SecureRandom());
    
    
                // 获取SSLSocketFactory
                SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
    
    
                // 配置OkHttp使用自定义的SSL
                builder.sslSocketFactory(sslSocketFactory, (X509TrustManager) trustAllCerts[0]);
    
    
                // 跳过主机名验证
                builder.hostnameVerifier(new HostnameVerifier() {
                    @Override
                    public boolean verify(String hostname, SSLSession session) {
                        return true; // 信任所有主机名
                    }
                });
            } catch (Exception e) {
                e.printStackTrace();
            }
    
    
    
    
    
    
            builder.addInterceptor(chain -> {
                        Request original = chain.request();
                        Request.Builder requestBuilder = original.newBuilder();
                        if (!original.url().toString().contains("/login")) {
                            requestBuilder.header("x-auth-token", m_iamtoken);
                        }
                        return chain.proceed(requestBuilder.build());
                    })
                    .connectTimeout(15, TimeUnit.SECONDS) // 连接超时时间设置
                    .readTimeout(15, TimeUnit.SECONDS) // 读取超时时间设置
                    .protocols(Collections.unmodifiableList(Arrays.asList(Protocol.HTTP_2, Protocol.HTTP_1_1)));
    
    
            // 初始化Retrofit
            retrofit = new Retrofit.Builder()
                    .client(builder.build())
                    .baseUrl(baseUrl)
                    .addConverterFactory(GsonConverterFactory.create())
                    .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                    .build();
    
    
            return retrofit;
        }
    
    
    }
  • 数据订阅者工具DataSubscriber.java代码示例:
    import io.reactivex.disposables.Disposable;
    
    public abstract class DataSubscriber<T> extends DefaultSubscriber<T> {
    
    
        public DataSubcriber() {
            super();
        }
    
    
        @Override
        public void onSubscribe(Disposable d) {
    
    
        }
    
    
        @Override
        public void onComplete() {
        }
    
    
        @Override
        public void onError(Throwable e) {
        }
    
    
    }
  • 数据订阅者抽象类DefaultSubscriber.java代码示例:
    package com.example.cloudphonecontroller.util;
    
    import io.reactivex.Observer;
    
    public abstract class DefaultSubscriber<T> implements Observer<T> {
    
    }
  • 公共基类BaseEntity.Java代码示例:
    package com.example.cloudphonecontroller.base;
    
    import java.io.Serializable;
    
    public class BaseEntity implements Serializable {
    
    }
  • 统一接口返回数据封装类ResponseBean.java代码示例
    package com.example.cloudphonecontroller.base;
    
    public class ResponseBean<T> extends BaseEntity {
        private boolean success;
    
        private String code;
    
        private String msg;
    
        private T data;
    
        public String getCode() {
            return code;
        }
    
        public void setCode(String code) {
            this.code = code;
        }
    
        public String getMsg() {
            return msg;
        }
    
        public void setMsg(String msg) {
            this.msg = msg;
        }
    
        public T getData() {
            return data;
        }
    
        public void setData(T data) {
            this.data = data;
        }
    
        public boolean getSuccess() {
            return success;
        }
    
        public void setSuccess(boolean success) {
            this.success = success;
        }
    }

关键代码示例

sample/
├── app/
│   ├── src/main/
│   │   ├── java/
│   │   │   └── com/
│   │   │       ├── cloud/keyfunction/
│   │   │       │   ├── MainActivity.java        # 主流程入口(初始化 + 串流启动)
│   │   │       │   └── Config.java              # 基础运行参数配置
│   │   │       │
│   │   │       └── KooCabin/
│   │   │           ├── api/
│   │   │           │   └── ApiService.java      # 接口定义(IAM / 业务接口)
│   │   │           │
│   │   │           ├── bean/
│   │   │           │   ├── request/
│   │   │           │   │   └── AuthRequest.java
│   │   │           │   └── response/
│   │   │           │       ├── AuthResponse.java
│   │   │           │       └── ApiResponse.java
│   │   │           │
│   │   │           ├── constant/
│   │   │           │   └── Constant.java
│   │   │           │
│   │   │           ├── iInterface/
│   │   │           │   └── CloudPhoneCallBack.java  # SDK回调
│   │   │           │
│   │   │           ├── manager/
│   │   │           │   ├── TokenManager.java
│   │   │           │   ├── IAMServiceManager.java
│   │   │           │   └── KooPhoneServiceManager.java
│   │   │           │
│   │   │           ├── subscriber/
│   │   │           │   ├── DataSubcriber.java
│   │   │           │   └── DefualtSubscriber.java
│   │   │           │
│   │   │           └── ui/base/
│   │   │               ├── BaseEntity.java
│   │   │               └── ResponseBean.java
│   │   │
│   │   ├── res/
│   │   │   ├── layout/
│   │   │   │   └── activity_main.xml
│   │   │   ├── drawable/
│   │   │   ├── mipmap*/
│   │   │   ├── values/
│   │   │   └── xml/
│   │   │
│   │   └── AndroidManifest.xml
│   │
│   ├── libs/               # SDK aar 包
│   └── build.gradle
│
├── build.gradle
├── settings.gradle
└── gradle.properties

MainActivity.java

package com.cloud.keyfunction;


import static com.cloudapp.client.api.CloudAppConst.CLOUD_APP_EVENT_EXECUTOR;
import static com.cloudapp.client.api.CloudAppConst.CLOUD_APP_EVENT_INTERVAL_PROCESS_MS;
import static com.cloudapp.client.api.CloudAppConst.CLOUD_APP_HIGH_PERF_FRAME;
import static com.cloudapp.client.api.CloudAppConst.CLOUD_APP_LAUNCH_KEY_VENC_TYPE;
import static com.cloudapp.client.api.CloudAppConst.CLOUD_APP_LAUNCH_SENSOR_INTERVAL;
import static com.cloudapp.client.api.CloudAppConst.CLOUD_APP_TOUCH_INPUT_METHOD;
import static com.cloudapp.client.api.CloudAppConst.CLOUD_APP_USE_PROXY_SERVER;
import static com.cloudphone.client.api.CloudPhoneConst.CLOUD_APP_AUDIO_MAX_BUFFER_SIZE;
import static com.cloudphone.client.api.CloudPhoneConst.CLOUD_APP_HIDE_STREAM_AT_START_UP;
import static com.cloudphone.client.api.CloudPhoneConst.LAUNCH_KEY_ADDRESS;
import static com.cloudphone.client.api.CloudPhoneConst.CLOUD_APP_LAUNCH_KEY_AUDIO_ENCODE_TYPE;
import static com.cloudphone.client.api.CloudPhoneConst.LAUNCH_KEY_PORT;
import static com.cloudphone.client.api.CloudPhoneConst.CLOUD_APP_LAUNCH_KEY_PROFILE_LEVEL;
import static com.cloudphone.client.api.CloudPhoneConst.LAUNCH_KEY_TOKEN;
import static com.cloudphone.client.api.CloudPhoneConst.PROFILE_SPEED;
import static com.nbc.acsdk.adapter.IPlayer.HMTP_PLAYER;


import android.app.Dialog;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils;


import com.KooCabin.subscriber.DataSubcriber;
import com.nbc.utils.Log;


import android.util.Base64;
import android.util.DisplayMetrics;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;


import androidx.activity.EdgeToEdge;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;


import com.KooCabin.api.ApiService;
import com.KooCabin.bean.response.ApiResponse;
import com.KooCabin.constant.Constant;
import com.KooCabin.iInterface.CloudPhoneCallBack;
import com.KooCabin.manager.KooPhoneServiceManager;
import com.KooCabin.manager.TokenManager;
import com.cloudphone.client.api.CloudPhoneClient;
import com.cloudphone.client.api.CloudPhoneConst;
import com.cloudphone.client.bean.AppInfo;
import com.cloudphone.client.bean.AppOperateResponse;
import com.cloudphone.client.bean.AppTypeEnum;
import com.cloudphone.client.bean.GetAppsReq;
import com.cloudphone.client.bean.GetAppsRsp;
import com.nbc.acsdk.adapter.IPlayer;
import com.nbc.acsdk.hmtp.HmtpPlayerWrapper;
import com.nbc.acsdk.media.play.AudioPlayerConfig;
import com.nbc.acsdk.widget.PlayerFragment;
import java.io.File;


import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;


import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;


public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";
    private ArrayList<Button> appButtonList = new ArrayList<>();
    private Map<Button,String> appButtonMap = new HashMap();
    private Map<Button,String> sysButtonMap = new HashMap();
    Button btnBackMain;
    Button btnCloseApp;
    Button btnTaskMng;
    Button btnExit;
    View frontView;
    private String currentRunningApp = "";
    PlayerFragment m_fragment = null;
    boolean streamStarted = false;
//    private Handler audioHandler;
    private ExecutorService threadPool;
    private CompositeDisposable disposables;
    private static MainActivity instance;
    private static final int AUDIO_DELAY_MS = 100;
    //是否是合一版本,串流时bundle参数不一样
    boolean isCombinedVersion = true;


    public static MainActivity getInstance() {
        return instance;
    }


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        EdgeToEdge.enable(this);
        setContentView(R.layout.activity_main);
        instance = this;
        // 初始化资源管理器
//        audioHandler = new Handler(Looper.getMainLooper());
        threadPool = Executors.newFixedThreadPool(3);
        disposables = new CompositeDisposable();
        // 初始化SDK
        initCloudPhoneSDK();
        Log.info(TAG,"从这里开始 MainActivity onCreate");
        // 初始化UI
        initUI();
        // 开始连接云手机
        fetchTokenAndStartSteam();
    }
    private void initCloudPhoneSDK() {
        CloudPhoneClient.init(this, null, true);
        CloudPhoneClient.playerForceTextureView(false);
        CloudPhoneClient.setLogFolder(this.getExternalFilesDir("log").getPath() + File.separator);
    }


    private void initUI() {
        frontView = findViewById(R.id.black_overlay);


        findAppButtonAndAdd2Map();
        findSysButtonAndAdd2Map();
        setSysButtonClick();


        if (m_fragment == null) {
            m_fragment = new PlayerFragment();
            getFragmentManager()
                    .beginTransaction()
                    .add(R.id.containerf, m_fragment, "player_fragment")
                    .commit();
            CloudPhoneClient.bindFragment(m_fragment);
        }


        setAppButtonVisible(false);
    }
    @Override
    protected void onStart() {
        super.onStart();
        Log.info(TAG,"MainActivity onStart");
    }


    @Override
    protected void onStop() {
        super.onStop();
        Log.info(TAG,"MainActivity onStop");
    }


    @Override
    protected void onPause() {
        super.onPause();
        Log.info(TAG,"MainActivity onPause");
    }


    @Override
    protected void onResume() {
        super.onResume();
        Log.info(TAG,"MainActivity onResume");
    }


    @Override
    protected void onDestroy() {
        Log.info(TAG,"MainActivity.onDestroy");
        // 清理所有资源
        cleanupResources();
        super.onDestroy();
    }


    /**
     * 找到各app按钮并添加到Map中
     */
    void findAppButtonAndAdd2Map(){
        Button btnStartApp1 = findViewById(R.id.btnStartApp1);
        Button btnStartApp2 = findViewById(R.id.btnStartApp2);
        Button btnStartApp3 = findViewById(R.id.btnStartApp3);
        Button btnStartApp4 = findViewById(R.id.btnStartApp4);
        Button btnStartApp5 = findViewById(R.id.btnStartApp5);
        Button btnStartApp6 = findViewById(R.id.btnStartApp6);
        Button btnStartApp7 = findViewById(R.id.btnStartApp7);
        Button btnStartApp8 = findViewById(R.id.btnStartApp8);
        Button btnStartApp9 = findViewById(R.id.btnStartApp9);
        Button btnStartApp10 = findViewById(R.id.btnStartApp10);
        Button btnStartApp11 = findViewById(R.id.btnStartApp11);
        Button btnStartApp12 = findViewById(R.id.btnStartApp12);
        Button btnStartApp13 = findViewById(R.id.btnStartApp13);
        Button btnStartApp14 = findViewById(R.id.btnStartApp14);
        Button btnStartApp15 = findViewById(R.id.btnStartApp15);
        Button btnStartApp16 = findViewById(R.id.btnStartApp16);
        Button btnStartApp17 = findViewById(R.id.btnStartApp17);
        Button btnStartApp18 = findViewById(R.id.btnStartApp18);
        Button btnStartApp19 = findViewById(R.id.btnStartApp19);
        Button btnStartApp20 = findViewById(R.id.btnStartApp20);
        Button btnStartApp21 = findViewById(R.id.btnStartApp21);
        Button btnTop = findViewById(R.id.btn_top);
        btnTop.setText("顶部留空                  当前使用的实例ID: "+Constant.INSTANCE_ID);


        appButtonList.add(btnStartApp1);
        appButtonList.add(btnStartApp2);
        appButtonList.add(btnStartApp3);
        appButtonList.add(btnStartApp4);
        appButtonList.add(btnStartApp5);
        appButtonList.add(btnStartApp6);
        appButtonList.add(btnStartApp7);
        appButtonList.add(btnStartApp8);
        appButtonList.add(btnStartApp9);
        appButtonList.add(btnStartApp10);
        appButtonList.add(btnStartApp11);
        appButtonList.add(btnStartApp12);
        appButtonList.add(btnStartApp13);
        appButtonList.add(btnStartApp14);
        appButtonList.add(btnStartApp15);
        appButtonList.add(btnStartApp16);
        appButtonList.add(btnStartApp17);
        appButtonList.add(btnStartApp18);
        appButtonList.add(btnStartApp19);
        appButtonList.add(btnStartApp20);
        appButtonList.add(btnStartApp21);
    }




    /**
     * 找到返回主屏、关闭应用、重启云机、退出云机按钮 并添加到Map中
     */
    void findSysButtonAndAdd2Map(){
        btnBackMain = findViewById(R.id.btnBackMain);
        btnCloseApp = findViewById(R.id.btnCloseApp);
        btnTaskMng= findViewById(R.id.btnTaskMng);
        btnExit = findViewById(R.id.btnExit);
        sysButtonMap.put(btnBackMain,"");
        sysButtonMap.put(btnCloseApp,"");
        sysButtonMap.put(btnTaskMng,"");
        sysButtonMap.put(btnExit,"");
    }




    /**
     * 设置单击各app按钮后的触发的事件
     */
    void setAppButtonClick() {
        for (Map.Entry<Button, String> entry : appButtonMap.entrySet()) {
            final Button button = entry.getKey();
            final String pkgname = entry.getValue();
            button.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    launchAppWithAudio(pkgname);
                }
            });
        }
    }
    /**
     * 设置launchAppWithAudio
     */
    private void launchAppWithAudio(String packageName) {
        Log.info(TAG, "调用SDK 启动应用<" + packageName + ">");
        CloudPhoneClient.startApp(packageName);
        currentRunningApp = packageName;
//        startAudio();
        CloudPhoneClient.switchStreamType(CloudPhoneConst.CLOUD_APP_SWITCH_STREAM_TYPE.BOTH);
        showFragment();
    }


    /**
     * 设置单击返回主屏、关闭应用、重启云机、退出云机按钮后的触发的事件
     */
    void setSysButtonClick(){
        btnBackMain.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.info(TAG,"返回主屏 按钮被单击了");
                CloudPhoneClient.sendKeyEvent(KeyEvent.KEYCODE_HOME);
                CloudPhoneClient.switchStreamType(CloudPhoneConst.CLOUD_APP_SWITCH_STREAM_TYPE.AUDIO);
                showAppWall();
            }
        });


        btnCloseApp.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.info(TAG,"关闭应用 按钮被单击了 "+currentRunningApp);
                CloudPhoneClient.switchStreamType(CloudPhoneConst.CLOUD_APP_SWITCH_STREAM_TYPE.DATA);
                if(!currentRunningApp.isEmpty())
                {
                    CloudPhoneClient.closeApp(currentRunningApp);
                }else{
                    Log.error(TAG,"currentRunningApp isEmpty ");
                }
                showAppWall();
            }
        });




        btnTaskMng.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.info(TAG,"触发任务管理器");
                GetAppsReq getAppsReq = new GetAppsReq();
                getAppsReq.setAppType(AppTypeEnum.THIRD_APP);
                getAppsReq.setNeedIcon(true);
                getAppsReq.setPageNum(1);
                getAppsReq.setPageSize(20);
                getAppsReq.setQuality(70);
                CloudPhoneClient.getRunningApps(getAppsReq);
            }
        });




        btnExit.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.info(TAG,"退出云机 ");
                System.exit(0);
            }
        });
    }






    /**
     * //展示应用墙界面
     */
    private void showAppWall(){
        frontView.setVisibility(View.VISIBLE);
        setAppButtonVisible(true);
        findViewById(R.id.btn_top).setVisibility(View.VISIBLE);
        Log.info(TAG,"显示应用墙页面");
    }


    /**
     * //展示串流界面
     */
    private void showFragment(){
        frontView.setVisibility(View.INVISIBLE);
        setAppButtonVisible(false);
        findViewById(R.id.btn_top).setVisibility(View.INVISIBLE);
        Log.info(TAG,"显示串流页面");
    }


    void setAppButtonVisible(boolean b){
        for(int i=0;i<appButtonList.size();i++)
        {
            appButtonList.get(i).setVisibility(View.INVISIBLE);
        }
        for (Map.Entry<Button, String> entry : appButtonMap.entrySet()) {
            final Button button = entry.getKey();
            if(b){
                button.setVisibility(View.VISIBLE);
            }else{
                button.setVisibility(View.INVISIBLE);
            }
        }
    }


    private void fetchTokenAndStartSteam() {
        threadPool.execute(() -> {
            try {
                if(Constant.INSTANCE_ID.isEmpty()){
                    this.runOnUiThread(() -> Toast.makeText(getApplicationContext(), "请在gradle.properties里填入 INSTANCE_ID", Toast.LENGTH_LONG).show());
                    return;
                }
                String result= TokenManager.fetchToken(); //拿到IAMtoken
                if(result.equals(""))
                {
                    Log.error(TAG, "拿到IAMtoken 为空 ");
                }


                Observable<ApiResponse> deviceInfo = KooPhoneServiceManager.createService(ApiService.class,result).getDeviceInfo(Constant.INSTANCE_ID); //拿到DeviceToken
                executeAuthTokenAndStartStream(deviceInfo);
            } catch (Exception e) {
                Log.error(TAG, "Token initialization failed", e);
            }
        });
    }




    private void executeAuthTokenAndStartStream(Observable<ApiResponse> deviceInfo) {
        // 订阅authToken接口
        deviceInfo.subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new DataSubcriber<ApiResponse>() {
                    @Override
                    public void onNext(ApiResponse authResponse) {
                        if(authResponse.getData()==null||authResponse.getData().getDevice_token()==null||authResponse.getData().getResource()==null||
                                authResponse.getData().getResource().getRtc().getIce_signaling().getSignaling_url()==null)
                        {
                            MainActivity.this.runOnUiThread(() -> Toast.makeText(getApplicationContext(), "获取的Token等信息为null 请检查云机状态", Toast.LENGTH_LONG).show());
                            return;
                        }
                        Log.info(TAG,"S1------------- 成功获取token");
                        startStream(authResponse);
                    }


                    @Override
                    public void onError(Throwable e) {
                        Log.info(TAG,"onError "+e.toString());
                        MainActivity.this.runOnUiThread(() -> Toast.makeText(getApplicationContext(), "获取Token失败 请检查网络配置及云机ID是否正确  "+e.toString(), Toast.LENGTH_LONG).show());
                    }


                    @Override
                    public void onComplete() {


                    }
                });
    }


    public void startStream(ApiResponse authResponseResponseBean) {
        Bundle bundle = createBundle(authResponseResponseBean);
        CloudPhoneCallBack callBack = CloudPhoneCallBack.getInstance();
        CloudPhoneClient.setPlayerType(HMTP_PLAYER);
        CloudPhoneClient.enableAudioKeeping(true);
        CloudPhoneClient.setPlayerCallback(callBack);
        CloudPhoneClient.setAppCallback(callBack);
        CloudPhoneClient.start(null, bundle); //合一版本 使用null
        Log.info(TAG,"调用SDK 初始化数据通道串流");
    }


    /**
     * 创建重连所需的Bundle(需根据项目实际逻辑实现)
     */
    private Bundle createBundle(ApiResponse authResponse) {
        Bundle bundle = new Bundle();
        if (authResponse != null && null != authResponse.getData()) {
            ApiResponse.Data data = authResponse.getData();
            bundle.putString(LAUNCH_KEY_TOKEN, TextUtils.isEmpty(data.getDevice_token()) ? "" : data.getDevice_token());
            ApiResponse.Data.Resource resource = data.getResource();
            if (null != resource) {
                ApiResponse.Data.Resource.Sdk sdk = resource.getSdk();
                if (null != sdk) {
                    ApiResponse.Data.Resource.Sdk.External external = sdk.getExternal();
                    if (null != external) {
                        bundle.putString(LAUNCH_KEY_ADDRESS, external.getAddress());
                        bundle.putInt(LAUNCH_KEY_PORT, external.getAport());
                        
                    }
                }
            }
        }
        bundle.putInt(CLOUD_APP_LAUNCH_KEY_VENC_TYPE, 0);
        bundle.putInt(CLOUD_APP_LAUNCH_KEY_PROFILE_LEVEL, PROFILE_SPEED);
        bundle.putString("userId", Config.USER_ID);
        bundle.putBoolean(CLOUD_APP_HIDE_STREAM_AT_START_UP, true);
        bundle.putBoolean("free_aspect", true);
        bundle.putInt("screen_width", Config.SCREEN_WIDTH);//任意分辨率宽
        bundle.putInt("screen_height", Config.SCREEN_HEIGHT);//任意分辨率高




        //合一版本
        if(isCombinedVersion)
        {
            bundle.putInt(CLOUD_APP_EVENT_EXECUTOR, 1); //ignition策略
            bundle.putInt(CLOUD_APP_TOUCH_INPUT_METHOD,1);//触控方式
            bundle.putInt(CLOUD_APP_EVENT_INTERVAL_PROCESS_MS,4);//mright间隔
            bundle.putBoolean(CLOUD_APP_USE_PROXY_SERVER,false);//代理
            bundle.putBoolean(CLOUD_APP_HIGH_PERF_FRAME,false);//补帧策略
            bundle.putInt(CLOUD_APP_LAUNCH_SENSOR_INTERVAL,1000);//传感器间隔再把这坨加上
            //aac需求
            bundle.putInt(CLOUD_APP_AUDIO_MAX_BUFFER_SIZE, 19200*8);
            bundle.putInt(CLOUD_APP_LAUNCH_KEY_AUDIO_ENCODE_TYPE, 0); // OPUS - 0 / PCM - 1
        }


        return  bundle;
    }




    public void setStreamStarted(){
        if(!streamStarted){
            Log.info(TAG, "首次streamStarted 调getInstalledApps尝试获取应用列表");
            GetAppsReq getAppsReq = new GetAppsReq();
            getAppsReq.setAppType(AppTypeEnum.THIRD_APP);
            getAppsReq.setNeedIcon(false);
            getAppsReq.setPageNum(1);
            getAppsReq.setPageSize(20);
            getAppsReq.setQuality(85);
            CloudPhoneClient.getInstalledApps(getAppsReq);
        }
        streamStarted = true;
    }


    public void updateAppList(AppOperateResponse<GetAppsRsp> appOperateResponse) {
        Log.info(TAG, "处理已安装应用列表 显示按钮");
        int count = appOperateResponse.getData().getTotalCount();
        for(int i=0;i<count&&i<20;i++) //超20个会报错,先仅支持20个,后续扩展
        {
            AppInfo ai = appOperateResponse.getData().getAppList().get(i);
            Log.info(TAG, "包名及应用名  "+ai.getPackageName() + "  "+ai.getAppName());
            appButtonList.get(i).setText(ai.getAppName());
            appButtonMap.put(appButtonList.get(i),ai.getPackageName());
        }
        this.setAppButtonClick();
        showAppWall();
    }




    //触发任务管理,获取到当前正在运行的应用列表后,进行显示
    public void setRunningApps(AppOperateResponse<GetAppsRsp> appOperateResponse){
        if (!appOperateResponse.getData().getAppList().isEmpty()) {
            Log.info("onGetRunningApps", "遍历返回的list size=" +appOperateResponse.getData().getAppList().size());
            for(int i=0;i<appOperateResponse.getData().getAppList().size();i++)
            {
                Log.info("onGetRunningApps", " appName: "+appOperateResponse.getData().getAppList().get(i).getAppName()
                        +" packageName: "+appOperateResponse.getData().getAppList().get(i).getPackageName()
                        + " iconBytes: "+appOperateResponse.getData().getAppList().get(i).getIconBytes().length()
                );
            }
        }
        showImages(appOperateResponse);
    }




    private void showImages(AppOperateResponse<GetAppsRsp> appOperateResponse) {
        // 创建全屏Dialog
        Dialog dialog = new Dialog(this, android.R.style.Theme_Black_NoTitleBar_Fullscreen);


        // 创建垂直布局
        LinearLayout mainLayout = new LinearLayout(this);
        mainLayout.setOrientation(LinearLayout.VERTICAL);
        mainLayout.setBackgroundColor(0xFF000000);


        // 添加关闭按钮
        Button closeBtn = new Button(this);
        closeBtn.setText("← 任务管理");
        closeBtn.setOnClickListener(v -> dialog.dismiss());


        // 设置按钮样式
        closeBtn.setTextColor(0xFFFFFFFF);
        closeBtn.setBackgroundColor(0xFFFF0000);
        LinearLayout.LayoutParams btnParams = new LinearLayout.LayoutParams(
                LinearLayout.LayoutParams.WRAP_CONTENT,
                LinearLayout.LayoutParams.WRAP_CONTENT
        );
        btnParams.setMargins(0, 0, 0, 20);
        closeBtn.setLayoutParams(btnParams);


        mainLayout.addView(closeBtn);


        // 创建两行水平布局
        LinearLayout row1 = new LinearLayout(this);
        row1.setOrientation(LinearLayout.HORIZONTAL);
        LinearLayout row2 = new LinearLayout(this);
        row2.setOrientation(LinearLayout.HORIZONTAL);


        // 设置行布局参数
        LinearLayout.LayoutParams rowParams = new LinearLayout.LayoutParams(
                LinearLayout.LayoutParams.MATCH_PARENT,
                0
        );
        rowParams.weight = 1;
        rowParams.setMargins(0, 0, 0, 10);


        row1.setLayoutParams(rowParams);
        row2.setLayoutParams(rowParams);


        // 获取屏幕宽度
        DisplayMetrics displayMetrics = new DisplayMetrics();
        getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
        int screenWidth = displayMetrics.widthPixels;


        // 计算图片宽度(一行3个,减去间距后除以3)
        int imageWidth = (screenWidth - 4 * 20) / 3; // 4个间距,每个20dp
        // 根据16:10的比例计算高度
        int imageHeight = (int) (imageWidth * 10.0f / 16.0f);


// 应用名数组
        String[] appNames = new String[appOperateResponse.getData().getAppList().size()];
        for(int i=0;i<appNames.length;i++)
        {
            appNames[i] = appOperateResponse.getData().getAppList().get(i).getAppName();
        }


        // 引用图标Base64数组(假设是小的引用图标)
//        String[] iconBase64Images = {"图标1base64", "图标2base64", "图标3base64",
//                "图标4base64", "图标5base64", "图标6base64"};


        String[] iconBase64Images = new String[appOperateResponse.getData().getAppList().size()];
        for(int i=0;i<iconBase64Images.length;i++)
        {
            iconBase64Images[i] = appOperateResponse.getData().getAppList().get(i).getIconBytes();
        }


        // 添加你的图片(最多6张)
//        String[] base64Images = {"图片1base64", "图片2base64", "图片3base64",
//                "图片4base64", "图片5base64", "图片6base64"};


        String[] base64Images = new String[appOperateResponse.getData().getAppList().size()];
        for(int i=0;i<base64Images.length;i++)
        {
            base64Images[i] = appOperateResponse.getData().getAppList().get(i).getScreenshot();
        }


        for (int i = 0; i < Math.min(base64Images.length, 6); i++) {
            try {
                // 为每个应用创建垂直布局
                LinearLayout appItem = new LinearLayout(this);
                appItem.setOrientation(LinearLayout.VERTICAL);


                // 设置应用项布局参数
                LinearLayout.LayoutParams appItemParams = new LinearLayout.LayoutParams(
                        LinearLayout.LayoutParams.WRAP_CONTENT,
                        LinearLayout.LayoutParams.WRAP_CONTENT
                );
                appItemParams.setMargins(10, 0, 10, 0);
                appItem.setLayoutParams(appItemParams);


                // 1. 创建顶部水平布局(引用图标 + 应用名 + 关闭按钮)
                LinearLayout topBar = new LinearLayout(this);
                topBar.setOrientation(LinearLayout.HORIZONTAL);
                topBar.setLayoutParams(new LinearLayout.LayoutParams(
                        LinearLayout.LayoutParams.MATCH_PARENT,
                        LinearLayout.LayoutParams.WRAP_CONTENT
                ));
                topBar.setPadding(5, 5, 5, 5);


                // 1.1 引用图标
                ImageView iconView = new ImageView(this);
                String iconBase64 = iconBase64Images[i];
                if (iconBase64.contains(",")) {
                    iconBase64 = iconBase64.split(",")[1];
                }
                byte[] iconBytes = Base64.decode(iconBase64, Base64.DEFAULT);
                Bitmap iconBitmap = BitmapFactory.decodeByteArray(iconBytes, 0, iconBytes.length);
                iconView.setImageBitmap(iconBitmap);


                // 设置图标大小
                int iconSize = 30; // 30dp
                LinearLayout.LayoutParams iconParams = new LinearLayout.LayoutParams(
                        dpToPx(iconSize), dpToPx(iconSize)
                );
                iconParams.setMargins(0, 0, dpToPx(5), 0);
                iconView.setLayoutParams(iconParams);
                topBar.addView(iconView);


                // 1.2 应用名
                TextView appNameView = new TextView(this);
                appNameView.setText(appNames[i]);
                appNameView.setTextColor(0xFFFFFFFF);
                appNameView.setTextSize(12);
                appNameView.setGravity(Gravity.CENTER_VERTICAL);


                LinearLayout.LayoutParams nameParams = new LinearLayout.LayoutParams(
                        0, LinearLayout.LayoutParams.WRAP_CONTENT
                );
                nameParams.weight = 1; // 占据剩余空间
                nameParams.setMargins(0, 0, dpToPx(5), 0);
                appNameView.setLayoutParams(nameParams);
                topBar.addView(appNameView);


                // 1.3 关闭按钮 - 修改高度
                Button closeAppBtn = new Button(this);
                closeAppBtn.setText("结束应用");
                closeAppBtn.setTextSize(10);
                closeAppBtn.setTextColor(0xFFFFFFFF);
                closeAppBtn.setBackgroundColor(0xFFFF4444);
                closeAppBtn.setPadding(dpToPx(5), dpToPx(2), dpToPx(5), dpToPx(2));


                // 设置关闭按钮的布局参数,高度设置为原来一半
                LinearLayout.LayoutParams closeBtnParams = new LinearLayout.LayoutParams(
                        LinearLayout.LayoutParams.WRAP_CONTENT,
                        dpToPx(20)  // 高度改为原来的一半(图标是30dp,一半是15dp)
                );
                closeBtnParams.gravity = Gravity.CENTER_VERTICAL;  // 垂直居中
                closeAppBtn.setLayoutParams(closeBtnParams);


                // 为关闭按钮设置单击事件
                int finalI = i;
                closeAppBtn.setOnClickListener(v -> {
                    // 结束应用的逻辑
                    Toast.makeText(this, "结束应用: " + appNames[finalI], Toast.LENGTH_SHORT).show();
                    // 这里可以添加实际结束应用的代码
                });


                topBar.addView(closeAppBtn);


                appItem.addView(topBar);


                // 2. 创建主图片
                String cleanBase64 = base64Images[i];
                if (cleanBase64.contains(",")) {
                    cleanBase64 = cleanBase64.split(",")[1];
                }


                byte[] bytes = Base64.decode(cleanBase64, Base64.DEFAULT);
                Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);


                ImageView iv = new ImageView(this);
                iv.setImageBitmap(bitmap);
                iv.setScaleType(ImageView.ScaleType.CENTER_CROP);


                // 为主图片添加单击事件
                int finalI1 = i;
                iv.setOnClickListener(v -> {
                    // 处理主图片单击事件
                    Toast.makeText(this, "单击了: " + appNames[finalI1], Toast.LENGTH_SHORT).show();
                    // 这里可以添加单击后的操作,比如跳转到应用详情等
                });


                // 设置图片布局参数
                LinearLayout.LayoutParams imgParams = new LinearLayout.LayoutParams(
                        imageWidth, imageHeight
                );
                imgParams.setMargins(0, dpToPx(5), 0, 0);
                imgParams.gravity = Gravity.CENTER_HORIZONTAL;
                iv.setLayoutParams(imgParams);


                appItem.addView(iv);


                // 添加到对应的行
                if (i < 3) {
                    row1.addView(appItem);
                } else {
                    row2.addView(appItem);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }


        // 添加行布局到主布局
        mainLayout.addView(row1);
        mainLayout.addView(row2);


        dialog.setContentView(mainLayout);
        dialog.show();
    }


    // dp转px的工具方法
    private int dpToPx(int dp) {
        return (int) (dp * getResources().getDisplayMetrics().density);
    }


    private void cleanupResources() {
        // 停止云手机
        if (streamStarted) {
            try {
                CloudPhoneClient.stop();
            } catch (Exception e) {
                Log.error(TAG, "停止云手机失败",e);
            }
        }


        // 解绑Fragment
        if (m_fragment != null) {
            try {
                getFragmentManager().beginTransaction()
                        .remove(m_fragment)
                        .commitAllowingStateLoss();
                m_fragment = null;
                Log.info(TAG,"Fragment已移除");
            } catch (Exception e) {
                Log.error(TAG, "解绑Fragment失败",e);
            }
        }


        // 关闭线程池
        if (threadPool != null) {
            threadPool.shutdown();
            try {
                if (!threadPool.awaitTermination(500, TimeUnit.MILLISECONDS)) {
                    threadPool.shutdownNow();
                }
            } catch (InterruptedException e) {
                threadPool.shutdownNow();
            }
        }


        // 清理RxJava
        if (disposables != null && !disposables.isDisposed()) {
            disposables.clear();
            disposables.dispose();
        }


        // 清理集合
        appButtonList.clear();
        appButtonMap.clear();
        sysButtonMap.clear();
    }
}

相关文档