feat: 改为MVVM

This commit is contained in:
2026-07-14 17:26:51 +08:00
parent 57f36cc8ae
commit 5c279d1ba6
7 changed files with 169 additions and 112 deletions

View File

@@ -17,7 +17,7 @@
android:theme="@style/Theme.MaterialComponents.DayNight"> android:theme="@style/Theme.MaterialComponents.DayNight">
<activity <activity
android:name=".MainActivity" android:name=".activity.main.MainActivity"
android:exported="true"> android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />

View File

@@ -1,4 +1,4 @@
package com.ttstd.controlled; package com.ttstd.controlled.activity.main;
import android.Manifest; import android.Manifest;
import android.content.ComponentName; import android.content.ComponentName;
@@ -8,39 +8,29 @@ import android.content.ServiceConnection;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.media.projection.MediaProjectionManager; import android.media.projection.MediaProjectionManager;
import android.os.Build; import android.os.Build;
import android.os.Bundle;
import android.os.IBinder; import android.os.IBinder;
import android.util.Log; import android.util.Log;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat; import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import com.ttstd.controlled.R;
import com.ttstd.controlled.base.BaseMvvmActivity;
import com.ttstd.controlled.databinding.ActivityMainBinding;
import com.ttstd.controlled.service.ScreenCaptureService; import com.ttstd.controlled.service.ScreenCaptureService;
import com.ttstd.controlled.webrtc.WebRtcClient; import com.ttstd.controlled.webrtc.WebRtcClient;
import org.webrtc.RendererCommon; import org.webrtc.RendererCommon;
import org.webrtc.SurfaceViewRenderer;
public class MainActivity extends AppCompatActivity { public class MainActivity extends BaseMvvmActivity<MainViewModel, ActivityMainBinding> {
private static final String TAG = "ControlledMain"; private static final String TAG = "ControlledMain";
private static final int REQUEST_MEDIA_PROJECTION = 1001; private static final int REQUEST_MEDIA_PROJECTION = 1001;
private static final int REQUEST_NOTIFICATION_PERMISSION = 1002; private static final int REQUEST_NOTIFICATION_PERMISSION = 1002;
private EditText etServerUrl;
private EditText etDeviceId;
private Button btnStart;
private Button btnStop;
private TextView tvStatus;
private SurfaceViewRenderer localView;
private boolean isServiceRunning = false; private boolean isServiceRunning = false;
private ScreenCaptureService screenCaptureService; private ScreenCaptureService screenCaptureService;
private boolean isBound = false; private boolean isBound = false;
@@ -63,28 +53,28 @@ public class MainActivity extends AppCompatActivity {
}; };
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected int getLayoutId() {
super.onCreate(savedInstanceState); return R.layout.activity_main;
setContentView(R.layout.activity_main); }
etServerUrl = findViewById(R.id.et_server_url);
etDeviceId = findViewById(R.id.et_device_id);
btnStart = findViewById(R.id.btn_start);
btnStop = findViewById(R.id.btn_stop);
tvStatus = findViewById(R.id.tv_status);
localView = findViewById(R.id.local_view);
@Override
protected void initView() {
// 默认服务器地址 // 默认服务器地址
etServerUrl.setText("ws://175.178.213.60:8088/ws/signal"); binding.etServerUrl.setText("ws://175.178.213.60:8088/ws/signal");
// 生成设备ID // 生成设备ID
// etDeviceId.setText("controlled_" + UUID.randomUUID().toString().substring(0, 8)); // binding.etDeviceId.setText("controlled_" + UUID.randomUUID().toString().substring(0, 8));
btnStart.setOnClickListener(v -> startScreenSharing()); binding.btnStart.setOnClickListener(v -> startScreenSharing());
btnStop.setOnClickListener(v -> stopScreenSharing()); binding.btnStop.setOnClickListener(v -> stopScreenSharing());
updateUI(false); updateUI(false);
} }
@Override
protected void initData() {
// Initialization logic if any
}
private void startScreenSharing() { private void startScreenSharing() {
// 检查通知权限 (Android 13+) // 检查通知权限 (Android 13+)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
@@ -112,8 +102,8 @@ public class MainActivity extends AppCompatActivity {
Intent serviceIntent = new Intent(this, ScreenCaptureService.class); Intent serviceIntent = new Intent(this, ScreenCaptureService.class);
serviceIntent.putExtra(ScreenCaptureService.EXTRA_RESULT_CODE, resultCode); serviceIntent.putExtra(ScreenCaptureService.EXTRA_RESULT_CODE, resultCode);
serviceIntent.putExtra(ScreenCaptureService.EXTRA_RESULT_DATA, data); serviceIntent.putExtra(ScreenCaptureService.EXTRA_RESULT_DATA, data);
serviceIntent.putExtra(ScreenCaptureService.EXTRA_SERVER_URL, etServerUrl.getText().toString()); serviceIntent.putExtra(ScreenCaptureService.EXTRA_SERVER_URL, binding.etServerUrl.getText().toString());
serviceIntent.putExtra(ScreenCaptureService.EXTRA_DEVICE_ID, etDeviceId.getText().toString()); serviceIntent.putExtra(ScreenCaptureService.EXTRA_DEVICE_ID, binding.etDeviceId.getText().toString());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(serviceIntent); startForegroundService(serviceIntent);
@@ -150,8 +140,8 @@ public class MainActivity extends AppCompatActivity {
} }
Intent serviceIntent = new Intent(this, ScreenCaptureService.class); Intent serviceIntent = new Intent(this, ScreenCaptureService.class);
stopService(serviceIntent); stopService(serviceIntent);
if (localView != null) { if (binding.localView != null) {
localView.release(); binding.localView.release();
isLocalViewInitialized = false; isLocalViewInitialized = false;
} }
updateUI(false); updateUI(false);
@@ -163,17 +153,17 @@ public class MainActivity extends AppCompatActivity {
WebRtcClient webRtcClient = screenCaptureService.getWebRtcClient(); WebRtcClient webRtcClient = screenCaptureService.getWebRtcClient();
if (webRtcClient != null && webRtcClient.getEglContext() != null) { if (webRtcClient != null && webRtcClient.getEglContext() != null) {
try { try {
localView.init(webRtcClient.getEglContext(), null); binding.localView.init(webRtcClient.getEglContext(), null);
localView.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FIT); binding.localView.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FIT);
localView.setMirror(false); binding.localView.setMirror(false);
webRtcClient.setLocalSink(localView); webRtcClient.setLocalSink(binding.localView);
isLocalViewInitialized = true; isLocalViewInitialized = true;
} catch (Exception e) { } catch (Exception e) {
Log.e(TAG, "Error initializing local view", e); Log.e(TAG, "Error initializing local view", e);
} }
} else { } else {
// 如果 webRtcClient 还没准备好稍后重试 // 如果 webRtcClient 还没准备好稍后重试
localView.postDelayed(this::setupLocalPreview, 500); binding.localView.postDelayed(this::setupLocalPreview, 500);
} }
} }
} }
@@ -185,18 +175,18 @@ public class MainActivity extends AppCompatActivity {
unbindService(serviceConnection); unbindService(serviceConnection);
isBound = false; isBound = false;
} }
if (localView != null) { if (binding != null && binding.localView != null) {
localView.release(); binding.localView.release();
isLocalViewInitialized = false; isLocalViewInitialized = false;
} }
} }
private void updateUI(boolean running) { private void updateUI(boolean running) {
isServiceRunning = running; isServiceRunning = running;
btnStart.setEnabled(!running); binding.btnStart.setEnabled(!running);
btnStop.setEnabled(running); binding.btnStop.setEnabled(running);
etServerUrl.setEnabled(!running); binding.etServerUrl.setEnabled(!running);
etDeviceId.setEnabled(!running); binding.etDeviceId.setEnabled(!running);
tvStatus.setText(running ? "状态: 运行中" : "状态: 已停止"); binding.tvStatus.setText(running ? "状态: 运行中" : "状态: 已停止");
} }
} }

View File

@@ -0,0 +1,6 @@
package com.ttstd.controlled.activity.main;
import com.ttstd.controlled.base.BaseViewModel;
public class MainViewModel extends BaseViewModel {
}

View File

@@ -0,0 +1,45 @@
package com.ttstd.controlled.base;
import android.os.Bundle;
import androidx.annotation.LayoutRes;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.DataBindingUtil;
import androidx.databinding.ViewDataBinding;
import androidx.lifecycle.ViewModelProvider;
import java.lang.reflect.ParameterizedType;
public abstract class BaseMvvmActivity<VM extends BaseViewModel, DB extends ViewDataBinding> extends AppCompatActivity {
protected VM viewModel;
protected DB binding;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = DataBindingUtil.setContentView(this, getLayoutId());
binding.setLifecycleOwner(this);
initViewModel();
initView();
initData();
}
@LayoutRes
protected abstract int getLayoutId();
protected abstract void initView();
protected abstract void initData();
@SuppressWarnings("unchecked")
private void initViewModel() {
ParameterizedType type = (ParameterizedType) getClass().getGenericSuperclass();
if (type != null) {
Class<VM> viewModelClass = (Class<VM>) type.getActualTypeArguments()[0];
viewModel = new ViewModelProvider(this).get(viewModelClass);
}
}
}

View File

@@ -0,0 +1,10 @@
package com.ttstd.controlled.base;
import androidx.lifecycle.ViewModel;
public class BaseViewModel extends ViewModel {
@Override
protected void onCleared() {
super.onCleared();
}
}

View File

@@ -21,7 +21,7 @@ import android.view.WindowManager;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationCompat;
import com.ttstd.controlled.MainActivity; import com.ttstd.controlled.activity.main.MainActivity;
import com.ttstd.controlled.input.InputCommandHandler; import com.ttstd.controlled.input.InputCommandHandler;
import com.ttstd.controlled.signaling.SignalMessage; import com.ttstd.controlled.signaling.SignalMessage;
import com.ttstd.controlled.signaling.WebSocketClient; import com.ttstd.controlled.signaling.WebSocketClient;

View File

@@ -1,75 +1,81 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <layout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_height="match_parent" xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical" tools:context=".activity.main.MainActivity">
android:padding="24dp">
<TextView <LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp"
android:text="WebRTC 被控端"
android:textSize="24sp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="信令服务器地址:"
android:textSize="14sp" />
<EditText
android:id="@+id/et_server_url"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="match_parent"
android:layout_marginBottom="16dp" android:orientation="vertical"
android:hint="ws://175.178.213.60:8088/ws/signal" android:padding="24dp">
android:inputType="textUri" />
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="设备ID:" android:layout_marginBottom="24dp"
android:textSize="14sp" /> android:text="WebRTC 被控端"
android:textSize="24sp"
android:textStyle="bold" />
<EditText <TextView
android:id="@+id/et_device_id" android:layout_width="wrap_content"
android:layout_width="match_parent" android:layout_height="wrap_content"
android:layout_height="wrap_content" android:text="信令服务器地址:"
android:layout_marginBottom="24dp" android:textSize="14sp" />
android:hint="设备ID"
android:inputType="text"
android:text="981964879" />
<TextView <EditText
android:id="@+id/tv_status" android:id="@+id/et_server_url"
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="24dp" android:layout_marginBottom="16dp"
android:text="状态: 已停止" android:hint="ws://175.178.213.60:8088/ws/signal"
android:textSize="16sp" android:inputType="textUri" />
android:textStyle="bold" />
<Button <TextView
android:id="@+id/btn_start" android:layout_width="wrap_content"
android:layout_width="match_parent" android:layout_height="wrap_content"
android:layout_height="wrap_content" android:text="设备ID:"
android:layout_marginBottom="8dp" android:textSize="14sp" />
android:text="开始屏幕共享" />
<Button <EditText
android:id="@+id/btn_stop" android:id="@+id/et_device_id"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="16dp" android:layout_marginBottom="24dp"
android:enabled="false" android:hint="设备ID"
android:text="停止屏幕共享" /> android:inputType="text"
android:text="981964879" />
<org.webrtc.SurfaceViewRenderer <TextView
android:id="@+id/local_view" android:id="@+id/tv_status"
android:layout_width="match_parent" android:layout_width="wrap_content"
android:layout_height="0dp" android:layout_height="wrap_content"
android:layout_weight="1" /> android:layout_marginBottom="24dp"
android:text="状态: 已停止"
android:textSize="16sp"
android:textStyle="bold" />
</LinearLayout> <Button
android:id="@+id/btn_start"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:text="开始屏幕共享" />
<Button
android:id="@+id/btn_stop"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:enabled="false"
android:text="停止屏幕共享" />
<org.webrtc.SurfaceViewRenderer
android:id="@+id/local_view"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
</LinearLayout>
</layout>