diff --git a/app/build.gradle b/app/build.gradle
index 88146c2..47ab5f5 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -288,12 +288,6 @@ dependencies {
//Gson
implementation 'com.google.code.gson:gson:2.14.0'
implementation 'com.google.zxing:core:3.5.4'
- //生命周期管理
- implementation 'com.trello.rxlifecycle4:rxlifecycle:4.0.2'
- implementation 'com.trello.rxlifecycle4:rxlifecycle-android:4.0.2'
- implementation 'com.trello.rxlifecycle4:rxlifecycle-components:4.0.2'
- implementation 'com.trello.rxlifecycle4:rxlifecycle-components-preference:4.0.2'
- implementation 'com.trello.rxlifecycle4:rxlifecycle-android-lifecycle:4.0.2'
implementation 'com.jakewharton.rxbinding4:rxbinding:4.0.0'
diff --git a/app/src/main/java/com/ttstd/dialer/activity/settings/home/SettingsViewModel.java b/app/src/main/java/com/ttstd/dialer/activity/settings/home/SettingsViewModel.java
index 40de2ea..5fe0038 100644
--- a/app/src/main/java/com/ttstd/dialer/activity/settings/home/SettingsViewModel.java
+++ b/app/src/main/java/com/ttstd/dialer/activity/settings/home/SettingsViewModel.java
@@ -1,14 +1,25 @@
package com.ttstd.dialer.activity.settings.home;
-import com.ttstd.dialer.base.mvvm.BaseViewModel;
-import com.ttstd.dialer.bean.BaseResponse;
-import com.ttstd.dialer.network.OkHttpManager;
+import android.app.Application;
+import android.os.Build;
+
+import com.ttstd.dialer.base.mvvm.BaseAndroidViewModel;
+import com.ttstd.dialer.data.repository.SnRepository;
+import com.ttstd.dialer.mdm.DeviceManagerService;
import com.ttstd.dialer.utils.Logger;
-public class SettingsViewModel extends BaseViewModel {
+public class SettingsViewModel extends BaseAndroidViewModel {
+
+ private final SnRepository mRepository;
+
+ public SettingsViewModel(Application application) {
+ super(application);
+ mRepository = SnRepository.getInstance(application);
+ }
public void snRegister() {
- addDisposable(OkHttpManager.getInstance().getSnRegisterObservable()
+ addDisposable(mRepository.register(
+ DeviceManagerService.getInstance().getSerial(), Build.MODEL)
.subscribe(baseResponse -> Logger.e("snRegister", "onNext: " + baseResponse),
throwable -> Logger.e("snRegister", "onError: " + throwable.getMessage())));
}
diff --git a/app/src/main/java/com/ttstd/dialer/base/BaseDataBindingActivity.java b/app/src/main/java/com/ttstd/dialer/base/BaseDataBindingActivity.java
index f5b2c39..578bf54 100644
--- a/app/src/main/java/com/ttstd/dialer/base/BaseDataBindingActivity.java
+++ b/app/src/main/java/com/ttstd/dialer/base/BaseDataBindingActivity.java
@@ -6,16 +6,16 @@ import android.view.View;
import androidx.annotation.CallSuper;
import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.OnApplyWindowInsetsListener;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import com.ttstd.dialer.R;
-import com.ttstd.dialer.base.rx.BaseRxActivity;
import com.zackratos.ultimatebarx.ultimatebarx.java.UltimateBarX;
-public abstract class BaseDataBindingActivity extends BaseRxActivity {
+public abstract class BaseDataBindingActivity extends AppCompatActivity {
public BaseDataBindingActivity() {
super();
diff --git a/app/src/main/java/com/ttstd/dialer/base/BaseDialogFragment.java b/app/src/main/java/com/ttstd/dialer/base/BaseDialogFragment.java
index 0e0a7ff..8550c29 100644
--- a/app/src/main/java/com/ttstd/dialer/base/BaseDialogFragment.java
+++ b/app/src/main/java/com/ttstd/dialer/base/BaseDialogFragment.java
@@ -2,9 +2,9 @@ package com.ttstd.dialer.base;
import android.os.Bundle;
-import com.ttstd.dialer.base.rx.BaseRxDialogFragment;
+import androidx.fragment.app.DialogFragment;
-public abstract class BaseDialogFragment extends BaseRxDialogFragment {
+public abstract class BaseDialogFragment extends DialogFragment {
protected boolean isViewInitiated;
protected boolean isVisibleToUser;
diff --git a/app/src/main/java/com/ttstd/dialer/base/BaseFragment.java b/app/src/main/java/com/ttstd/dialer/base/BaseFragment.java
index 9bb4737..c8d470f 100644
--- a/app/src/main/java/com/ttstd/dialer/base/BaseFragment.java
+++ b/app/src/main/java/com/ttstd/dialer/base/BaseFragment.java
@@ -4,8 +4,7 @@ import android.os.Bundle;
import android.util.Log;
import androidx.annotation.Nullable;
-
-import com.ttstd.dialer.base.rx.BaseRxFragment;
+import androidx.fragment.app.Fragment;
/**
* Fragment 基类,提供标准的懒加载框架
@@ -20,7 +19,7 @@ import com.ttstd.dialer.base.rx.BaseRxFragment;
* 2. {@link #fetchData()} 每次可见都会回调,用于需要刷新的场景。
* 3. View 销毁重建后自动重置懒加载状态,保证重建后的页面能重新渲染。
*/
-public abstract class BaseFragment extends BaseRxFragment {
+public abstract class BaseFragment extends Fragment {
private static final String TAG = "BaseFragment";
diff --git a/app/src/main/java/com/ttstd/dialer/base/BaseService.java b/app/src/main/java/com/ttstd/dialer/base/BaseService.java
index a296241..757e0b2 100644
--- a/app/src/main/java/com/ttstd/dialer/base/BaseService.java
+++ b/app/src/main/java/com/ttstd/dialer/base/BaseService.java
@@ -1,43 +1,18 @@
package com.ttstd.dialer.base;
-import android.content.Intent;
+import androidx.lifecycle.LifecycleService;
-import androidx.lifecycle.Lifecycle;
-import androidx.lifecycle.LifecycleOwner;
-import androidx.lifecycle.LifecycleRegistry;
+/**
+ * Service 基类(基于 AndroidX {@link LifecycleService})。
+ *
+ * 通过继承 {@link LifecycleService} 自动获得:
+ * - {@link androidx.lifecycle.LifecycleOwner} 能力
+ * - 生命周期事件自动派发:onCreate → ON_CREATE,onStartCommand → ON_START / ON_RESUME,onDestroy → ON_DESTROY
+ * (其中 ON_START / ON_RESUME 满足 Service 中 LiveData 的触发条件,无需再手动修补)
+ * - 不再需要手动维护 {@link androidx.lifecycle.LifecycleRegistry} 或覆写生命周期方法派发事件
+ *
+ * 子类如需在生命周期回调中做处理,可直接覆写 onStartCommand / onDestroy 并调用 super。
+ */
+public abstract class BaseService extends LifecycleService {
-import com.ttstd.dialer.base.rx.BaseRxService;
-
-import org.jetbrains.annotations.NotNull;
-
-public abstract class BaseService extends BaseRxService implements LifecycleOwner {
-
- private final LifecycleRegistry lifecycleRegistry = new LifecycleRegistry(this);
-
- @NotNull
- @Override
- public Lifecycle getLifecycle() {
- return lifecycleRegistry;
- }
-
-
- @Override
- public void onCreate() {
- super.onCreate();
- lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);
- //修补在Service中LiveData不能触发
- lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
- }
-
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME);
- return super.onStartCommand(intent, flags, startId);
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY);
- }
}
diff --git a/app/src/main/java/com/ttstd/dialer/base/BaseTransparentActivity.java b/app/src/main/java/com/ttstd/dialer/base/BaseTransparentActivity.java
index 991062c..6b608d4 100644
--- a/app/src/main/java/com/ttstd/dialer/base/BaseTransparentActivity.java
+++ b/app/src/main/java/com/ttstd/dialer/base/BaseTransparentActivity.java
@@ -6,16 +6,16 @@ import android.view.View;
import androidx.annotation.CallSuper;
import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.OnApplyWindowInsetsListener;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import com.ttstd.dialer.R;
-import com.ttstd.dialer.base.rx.BaseRxActivity;
import com.zackratos.ultimatebarx.ultimatebarx.java.UltimateBarX;
-public abstract class BaseTransparentActivity extends BaseRxActivity {
+public abstract class BaseTransparentActivity extends AppCompatActivity {
public BaseTransparentActivity() {
super();
diff --git a/app/src/main/java/com/ttstd/dialer/base/rx/BaseRxActivity.java b/app/src/main/java/com/ttstd/dialer/base/rx/BaseRxActivity.java
deleted file mode 100644
index 02d7501..0000000
--- a/app/src/main/java/com/ttstd/dialer/base/rx/BaseRxActivity.java
+++ /dev/null
@@ -1,94 +0,0 @@
-package com.ttstd.dialer.base.rx;
-
-import android.os.Bundle;
-
-import androidx.annotation.CallSuper;
-import androidx.annotation.CheckResult;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.appcompat.app.AppCompatActivity;
-
-import com.trello.rxlifecycle4.LifecycleProvider;
-import com.trello.rxlifecycle4.LifecycleTransformer;
-import com.trello.rxlifecycle4.RxLifecycle;
-import com.trello.rxlifecycle4.android.ActivityEvent;
-import com.trello.rxlifecycle4.android.RxLifecycleAndroid;
-
-import io.reactivex.rxjava3.core.Observable;
-import io.reactivex.rxjava3.subjects.BehaviorSubject;
-
-/**
- * {@link com.trello.rxlifecycle4.components.RxActivity}
- * copied form RxActivity}
- */
-public abstract class BaseRxActivity extends AppCompatActivity implements LifecycleProvider {
- private final BehaviorSubject lifecycleSubject = BehaviorSubject.create();
-
- public BehaviorSubject getLifecycleSubject() {
- return lifecycleSubject;
- }
-
- @Override
- @NonNull
- @CheckResult
- public final Observable lifecycle() {
- return lifecycleSubject.hide();
- }
-
- @Override
- @NonNull
- @CheckResult
- public final LifecycleTransformer bindUntilEvent(@NonNull ActivityEvent event) {
- return RxLifecycle.bindUntilEvent(lifecycleSubject, event);
- }
-
- @Override
- @NonNull
- @CheckResult
- public final LifecycleTransformer bindToLifecycle() {
- return RxLifecycleAndroid.bindActivity(lifecycleSubject);
- }
-
- @Override
- @CallSuper
- protected void onCreate(@Nullable Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- lifecycleSubject.onNext(ActivityEvent.CREATE);
- }
-
- @Override
- @CallSuper
- protected void onStart() {
- super.onStart();
- lifecycleSubject.onNext(ActivityEvent.START);
- }
-
- @Override
- @CallSuper
- protected void onResume() {
- super.onResume();
- lifecycleSubject.onNext(ActivityEvent.RESUME);
- }
-
- @Override
- @CallSuper
- protected void onPause() {
- lifecycleSubject.onNext(ActivityEvent.PAUSE);
- super.onPause();
- }
-
- @Override
- @CallSuper
- protected void onStop() {
- lifecycleSubject.onNext(ActivityEvent.STOP);
- super.onStop();
- }
-
- @Override
- @CallSuper
- protected void onDestroy() {
- lifecycleSubject.onNext(ActivityEvent.DESTROY);
- super.onDestroy();
- }
-}
-
diff --git a/app/src/main/java/com/ttstd/dialer/base/rx/BaseRxDialogFragment.java b/app/src/main/java/com/ttstd/dialer/base/rx/BaseRxDialogFragment.java
deleted file mode 100644
index 20704f7..0000000
--- a/app/src/main/java/com/ttstd/dialer/base/rx/BaseRxDialogFragment.java
+++ /dev/null
@@ -1,123 +0,0 @@
-package com.ttstd.dialer.base.rx;
-
-import android.os.Bundle;
-import android.view.View;
-
-import androidx.annotation.CallSuper;
-import androidx.annotation.CheckResult;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.fragment.app.DialogFragment;
-
-import com.trello.rxlifecycle4.LifecycleProvider;
-import com.trello.rxlifecycle4.LifecycleTransformer;
-import com.trello.rxlifecycle4.RxLifecycle;
-import com.trello.rxlifecycle4.android.FragmentEvent;
-import com.trello.rxlifecycle4.android.RxLifecycleAndroid;
-
-import io.reactivex.rxjava3.core.Observable;
-import io.reactivex.rxjava3.subjects.BehaviorSubject;
-
-/**
- * {@link com.trello.rxlifecycle4.components.RxFragment}
- * copied form RxFragment}
- */
-public class BaseRxDialogFragment extends DialogFragment implements LifecycleProvider {
- private final BehaviorSubject lifecycleSubject = BehaviorSubject.create();
-
- public BehaviorSubject getLifecycleSubject() {
- return lifecycleSubject;
- }
-
- @Override
- @NonNull
- @CheckResult
- public final Observable lifecycle() {
- return lifecycleSubject.hide();
- }
-
- @Override
- @NonNull
- @CheckResult
- public final LifecycleTransformer bindUntilEvent(@NonNull FragmentEvent event) {
- return RxLifecycle.bindUntilEvent(lifecycleSubject, event);
- }
-
- @Override
- @NonNull
- @CheckResult
- public final LifecycleTransformer bindToLifecycle() {
- return RxLifecycleAndroid.bindFragment(lifecycleSubject);
- }
-
- @Override
- @CallSuper
- public void onAttach(android.app.Activity activity) {
- super.onAttach(activity);
- lifecycleSubject.onNext(FragmentEvent.ATTACH);
- }
-
- @Override
- @CallSuper
- public void onCreate(@Nullable Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- lifecycleSubject.onNext(FragmentEvent.CREATE);
- }
-
- @Override
- @CallSuper
- public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
- super.onViewCreated(view, savedInstanceState);
- lifecycleSubject.onNext(FragmentEvent.CREATE_VIEW);
- }
-
- @Override
- @CallSuper
- public void onStart() {
- super.onStart();
- lifecycleSubject.onNext(FragmentEvent.START);
- }
-
- @Override
- @CallSuper
- public void onResume() {
- super.onResume();
- lifecycleSubject.onNext(FragmentEvent.RESUME);
- }
-
- @Override
- @CallSuper
- public void onPause() {
- lifecycleSubject.onNext(FragmentEvent.PAUSE);
- super.onPause();
- }
-
- @Override
- @CallSuper
- public void onStop() {
- lifecycleSubject.onNext(FragmentEvent.STOP);
- super.onStop();
- }
-
- @Override
- @CallSuper
- public void onDestroyView() {
- lifecycleSubject.onNext(FragmentEvent.DESTROY_VIEW);
- super.onDestroyView();
- }
-
- @Override
- @CallSuper
- public void onDestroy() {
- lifecycleSubject.onNext(FragmentEvent.DESTROY);
- super.onDestroy();
- }
-
- @Override
- @CallSuper
- public void onDetach() {
- lifecycleSubject.onNext(FragmentEvent.DETACH);
- super.onDetach();
- }
-}
-
diff --git a/app/src/main/java/com/ttstd/dialer/base/rx/BaseRxFragment.java b/app/src/main/java/com/ttstd/dialer/base/rx/BaseRxFragment.java
deleted file mode 100644
index 26738d1..0000000
--- a/app/src/main/java/com/ttstd/dialer/base/rx/BaseRxFragment.java
+++ /dev/null
@@ -1,123 +0,0 @@
-package com.ttstd.dialer.base.rx;
-
-import android.os.Bundle;
-import android.view.View;
-
-import androidx.annotation.CallSuper;
-import androidx.annotation.CheckResult;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.fragment.app.Fragment;
-
-import com.trello.rxlifecycle4.LifecycleProvider;
-import com.trello.rxlifecycle4.LifecycleTransformer;
-import com.trello.rxlifecycle4.RxLifecycle;
-import com.trello.rxlifecycle4.android.FragmentEvent;
-import com.trello.rxlifecycle4.android.RxLifecycleAndroid;
-
-import io.reactivex.rxjava3.core.Observable;
-import io.reactivex.rxjava3.subjects.BehaviorSubject;
-
-/**
- * {@link com.trello.rxlifecycle4.components.RxFragment}
- * copied form RxFragment}
- */
-public class BaseRxFragment extends Fragment implements LifecycleProvider {
- private final BehaviorSubject lifecycleSubject = BehaviorSubject.create();
-
- public BehaviorSubject getLifecycleSubject() {
- return lifecycleSubject;
- }
-
- @Override
- @NonNull
- @CheckResult
- public final Observable lifecycle() {
- return lifecycleSubject.hide();
- }
-
- @Override
- @NonNull
- @CheckResult
- public final LifecycleTransformer bindUntilEvent(@NonNull FragmentEvent event) {
- return RxLifecycle.bindUntilEvent(lifecycleSubject, event);
- }
-
- @Override
- @NonNull
- @CheckResult
- public final LifecycleTransformer bindToLifecycle() {
- return RxLifecycleAndroid.bindFragment(lifecycleSubject);
- }
-
- @Override
- @CallSuper
- public void onAttach(android.app.Activity activity) {
- super.onAttach(activity);
- lifecycleSubject.onNext(FragmentEvent.ATTACH);
- }
-
- @Override
- @CallSuper
- public void onCreate(@Nullable Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- lifecycleSubject.onNext(FragmentEvent.CREATE);
- }
-
- @Override
- @CallSuper
- public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
- super.onViewCreated(view, savedInstanceState);
- lifecycleSubject.onNext(FragmentEvent.CREATE_VIEW);
- }
-
- @Override
- @CallSuper
- public void onStart() {
- super.onStart();
- lifecycleSubject.onNext(FragmentEvent.START);
- }
-
- @Override
- @CallSuper
- public void onResume() {
- super.onResume();
- lifecycleSubject.onNext(FragmentEvent.RESUME);
- }
-
- @Override
- @CallSuper
- public void onPause() {
- lifecycleSubject.onNext(FragmentEvent.PAUSE);
- super.onPause();
- }
-
- @Override
- @CallSuper
- public void onStop() {
- lifecycleSubject.onNext(FragmentEvent.STOP);
- super.onStop();
- }
-
- @Override
- @CallSuper
- public void onDestroyView() {
- lifecycleSubject.onNext(FragmentEvent.DESTROY_VIEW);
- super.onDestroyView();
- }
-
- @Override
- @CallSuper
- public void onDestroy() {
- lifecycleSubject.onNext(FragmentEvent.DESTROY);
- super.onDestroy();
- }
-
- @Override
- @CallSuper
- public void onDetach() {
- lifecycleSubject.onNext(FragmentEvent.DETACH);
- super.onDetach();
- }
-}
-
diff --git a/app/src/main/java/com/ttstd/dialer/base/rx/BaseRxService.java b/app/src/main/java/com/ttstd/dialer/base/rx/BaseRxService.java
deleted file mode 100644
index a374548..0000000
--- a/app/src/main/java/com/ttstd/dialer/base/rx/BaseRxService.java
+++ /dev/null
@@ -1,56 +0,0 @@
-package com.ttstd.dialer.base.rx;
-
-import android.app.Service;
-
-import androidx.annotation.CheckResult;
-import androidx.annotation.NonNull;
-
-import com.trello.rxlifecycle4.LifecycleProvider;
-import com.trello.rxlifecycle4.LifecycleTransformer;
-import com.trello.rxlifecycle4.RxLifecycle;
-import com.trello.rxlifecycle4.android.ActivityEvent;
-import com.trello.rxlifecycle4.android.RxLifecycleAndroid;
-
-import io.reactivex.rxjava3.core.Observable;
-import io.reactivex.rxjava3.subjects.BehaviorSubject;
-
-public abstract class BaseRxService extends Service implements LifecycleProvider {
- private final BehaviorSubject lifecycleSubject = BehaviorSubject.create();
-
- public BehaviorSubject getLifecycleSubject() {
- return lifecycleSubject;
- }
-
- @Override
- @NonNull
- @CheckResult
- public final Observable lifecycle() {
- return lifecycleSubject.hide();
- }
-
- @Override
- @NonNull
- @CheckResult
- public final LifecycleTransformer bindUntilEvent(@NonNull ActivityEvent event) {
- return RxLifecycle.bindUntilEvent(lifecycleSubject, event);
- }
-
- @Override
- @NonNull
- @CheckResult
- public final LifecycleTransformer bindToLifecycle() {
- return RxLifecycleAndroid.bindActivity(lifecycleSubject);
- }
-
- @Override
- public void onCreate() {
- super.onCreate();
- lifecycleSubject.onNext(ActivityEvent.CREATE);
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- lifecycleSubject.onNext(ActivityEvent.DESTROY);
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/ttstd/dialer/data/remote/SnRemoteDataSource.java b/app/src/main/java/com/ttstd/dialer/data/remote/SnRemoteDataSource.java
index a6d4b49..fe54284 100644
--- a/app/src/main/java/com/ttstd/dialer/data/remote/SnRemoteDataSource.java
+++ b/app/src/main/java/com/ttstd/dialer/data/remote/SnRemoteDataSource.java
@@ -2,14 +2,28 @@ package com.ttstd.dialer.data.remote;
import androidx.annotation.NonNull;
+import com.ttstd.dialer.bean.BaseResponse;
import com.ttstd.dialer.bean.DeveloperOptions;
+import com.ttstd.dialer.bean.req.SnHardwareInfoReq;
+import com.ttstd.dialer.bean.req.SnLocationReq;
+import com.ttstd.dialer.bean.req.SnNetworkInfoReq;
+import com.ttstd.dialer.bean.req.SnOtherInfoReq;
+import com.ttstd.dialer.bean.req.SnSecurityInfoReq;
+import com.ttstd.dialer.bean.req.SnSystemInfoReq;
import com.ttstd.dialer.network.OkHttpManager;
import com.ttstd.dialer.network.api.SnApi;
+import java.util.List;
+
import io.reactivex.rxjava3.core.Observable;
+import okhttp3.MultipartBody;
+import okhttp3.RequestBody;
/**
* 设备(SN)远程数据源(Data Layer - Remote DataSource)。
+ *
+ * 只负责发起网络请求并做基础解包,不做线程调度、不碰缓存。
+ * 所有线程调度与缓存策略由上层 {@link com.ttstd.dialer.data.repository.SnRepository} 统一处理。
*/
public class SnRemoteDataSource {
@@ -23,4 +37,89 @@ public class SnRemoteDataSource {
public Observable getDeveloperOptions() {
return ApiResponseMapper.unwrap(mApi.getDeveloperOptions());
}
+
+ /**
+ * 设备注册(sn + model)
+ */
+ @NonNull
+ public Observable register(@NonNull String sn, @NonNull String model) {
+ return mApi.register(sn, model);
+ }
+
+ /**
+ * 上传设备基本信息
+ */
+ @NonNull
+ public Observable> updateSystemInfo(@NonNull SnSystemInfoReq req) {
+ return mApi.updateSystemInfo(OkHttpManager.convertToRequestBodyjson(req));
+ }
+
+ /**
+ * 上传设备硬件信息
+ */
+ @NonNull
+ public Observable> updateHardwareInfo(@NonNull SnHardwareInfoReq req) {
+ return mApi.updateHardwareInfo(OkHttpManager.convertToRequestBodyjson(req));
+ }
+
+ /**
+ * 上传设备网络信息
+ */
+ @NonNull
+ public Observable> updateNetworkInfo(@NonNull SnNetworkInfoReq req) {
+ return mApi.updateNetworkInfo(OkHttpManager.convertToRequestBodyjson(req));
+ }
+
+ /**
+ * 上传设备安全信息
+ */
+ @NonNull
+ public Observable> updateSecurityInfo(@NonNull SnSecurityInfoReq req) {
+ return mApi.updateSecurityInfo(OkHttpManager.convertToRequestBodyjson(req));
+ }
+
+ /**
+ * 上传设备其他信息
+ */
+ @NonNull
+ public Observable> updateOtherInfo(@NonNull SnOtherInfoReq req) {
+ return mApi.updateOtherInfo(OkHttpManager.convertToRequestBodyjson(req));
+ }
+
+ /**
+ * 上传定位信息
+ */
+ @NonNull
+ public Observable> uploadLocation(@NonNull SnLocationReq req) {
+ return mApi.uploadLocation(OkHttpManager.convertToRequestBodyjson(req));
+ }
+
+ /**
+ * 上传截图(或拍照文件)
+ */
+ @NonNull
+ public Observable uploadScreenshot(@NonNull MultipartBody.Part part) {
+ return mApi.uploadScreenshot(part);
+ }
+
+ /**
+ * 上传已安装应用列表
+ */
+ @NonNull
+ public Observable uploadInstallApks(@NonNull RequestBody body) {
+ return mApi.uploadInstallApks(body);
+ }
+
+ /**
+ * 上传应用图标(全局图标库)
+ */
+ @NonNull
+ public Observable uploadAppIcon(@NonNull String packageName,
+ String appName,
+ @NonNull MultipartBody.Part part) {
+ return mApi.uploadAppIcon(
+ OkHttpManager.convertToRequestBody(packageName),
+ OkHttpManager.convertToRequestBody(appName == null ? "" : appName),
+ part);
+ }
}
diff --git a/app/src/main/java/com/ttstd/dialer/data/repository/SnRepository.java b/app/src/main/java/com/ttstd/dialer/data/repository/SnRepository.java
index 0b69d23..87c9e22 100644
--- a/app/src/main/java/com/ttstd/dialer/data/repository/SnRepository.java
+++ b/app/src/main/java/com/ttstd/dialer/data/repository/SnRepository.java
@@ -4,7 +4,14 @@ import android.content.Context;
import androidx.annotation.NonNull;
+import com.ttstd.dialer.bean.BaseResponse;
import com.ttstd.dialer.bean.DeveloperOptions;
+import com.ttstd.dialer.bean.req.SnHardwareInfoReq;
+import com.ttstd.dialer.bean.req.SnLocationReq;
+import com.ttstd.dialer.bean.req.SnNetworkInfoReq;
+import com.ttstd.dialer.bean.req.SnOtherInfoReq;
+import com.ttstd.dialer.bean.req.SnSecurityInfoReq;
+import com.ttstd.dialer.bean.req.SnSystemInfoReq;
import com.ttstd.dialer.data.NetworkBoundResource;
import com.ttstd.dialer.data.Resource;
import com.ttstd.dialer.data.cache.CacheManager;
@@ -13,6 +20,9 @@ import com.ttstd.dialer.data.remote.SnRemoteDataSource;
import java.lang.reflect.Type;
import io.reactivex.rxjava3.core.Observable;
+import io.reactivex.rxjava3.schedulers.Schedulers;
+import okhttp3.MultipartBody;
+import okhttp3.RequestBody;
/**
* 设备(SN)配置仓库(Data Layer - Repository)。
@@ -86,4 +96,98 @@ public class SnRepository {
}
}.asObservable();
}
+
+ // ==================== 设备上报类(写操作,fire-and-forget)====================
+
+ /**
+ * 设备注册(sn + model)。调用方按需 subscribe。
+ */
+ @NonNull
+ public Observable register(@NonNull String sn, @NonNull String model) {
+ return mRemoteDataSource.register(sn, model)
+ .subscribeOn(Schedulers.io());
+ }
+
+ /**
+ * 上传设备基本信息
+ */
+ @NonNull
+ public Observable> updateSystemInfo(@NonNull SnSystemInfoReq req) {
+ return mRemoteDataSource.updateSystemInfo(req)
+ .subscribeOn(Schedulers.io());
+ }
+
+ /**
+ * 上传设备硬件信息
+ */
+ @NonNull
+ public Observable> updateHardwareInfo(@NonNull SnHardwareInfoReq req) {
+ return mRemoteDataSource.updateHardwareInfo(req)
+ .subscribeOn(Schedulers.io());
+ }
+
+ /**
+ * 上传设备网络信息
+ */
+ @NonNull
+ public Observable> updateNetworkInfo(@NonNull SnNetworkInfoReq req) {
+ return mRemoteDataSource.updateNetworkInfo(req)
+ .subscribeOn(Schedulers.io());
+ }
+
+ /**
+ * 上传设备安全信息
+ */
+ @NonNull
+ public Observable> updateSecurityInfo(@NonNull SnSecurityInfoReq req) {
+ return mRemoteDataSource.updateSecurityInfo(req)
+ .subscribeOn(Schedulers.io());
+ }
+
+ /**
+ * 上传设备其他信息
+ */
+ @NonNull
+ public Observable> updateOtherInfo(@NonNull SnOtherInfoReq req) {
+ return mRemoteDataSource.updateOtherInfo(req)
+ .subscribeOn(Schedulers.io());
+ }
+
+ /**
+ * 上传定位信息
+ */
+ @NonNull
+ public Observable> uploadLocation(@NonNull SnLocationReq req) {
+ return mRemoteDataSource.uploadLocation(req)
+ .subscribeOn(Schedulers.io());
+ }
+
+ /**
+ * 上传截图 / 拍照文件
+ */
+ @NonNull
+ public Observable uploadScreenshot(@NonNull MultipartBody.Part part) {
+ return mRemoteDataSource.uploadScreenshot(part)
+ .subscribeOn(Schedulers.io());
+ }
+
+ /**
+ * 上传已安装应用列表(body 由调用方构建为 JSON RequestBody)
+ */
+ @NonNull
+ public Observable uploadInstallApks(@NonNull RequestBody body) {
+ return mRemoteDataSource.uploadInstallApks(body)
+ .subscribeOn(Schedulers.io());
+ }
+
+ /**
+ * 上传应用图标(全局图标库)
+ */
+ @NonNull
+ public Observable uploadAppIcon(@NonNull String packageName,
+ String appName,
+ @NonNull MultipartBody.Part part) {
+ return mRemoteDataSource.uploadAppIcon(packageName, appName, part)
+ .subscribeOn(Schedulers.io());
+ }
}
diff --git a/app/src/main/java/com/ttstd/dialer/db/contact/ContactInfo.java b/app/src/main/java/com/ttstd/dialer/db/contact/ContactInfo.java
index cc78ca8..dcf8d27 100644
--- a/app/src/main/java/com/ttstd/dialer/db/contact/ContactInfo.java
+++ b/app/src/main/java/com/ttstd/dialer/db/contact/ContactInfo.java
@@ -203,7 +203,7 @@ public class ContactInfo implements Serializable {
public boolean equals(@Nullable Object obj) {
if (obj instanceof ContactInfo) {
return Objects.equals(((ContactInfo) obj).phoneNumber, phoneNumber)
- || ((ContactInfo) obj).id == id;
+ && ((ContactInfo) obj).id == id;
}
return false;
}
diff --git a/app/src/main/java/com/ttstd/dialer/manager/AppManager.java b/app/src/main/java/com/ttstd/dialer/manager/AppManager.java
index 33273f1..78624a6 100644
--- a/app/src/main/java/com/ttstd/dialer/manager/AppManager.java
+++ b/app/src/main/java/com/ttstd/dialer/manager/AppManager.java
@@ -200,6 +200,8 @@ public class AppManager {
updatePositionSync();
}
reorderAppPositionsSync();
+ // 初始化时清理已失效的启动页
+ clearInvalidSplash();
notifyCompleted(true, "应用列表处理完成");
} catch (Exception e) {
Logger.e(TAG, "异步处理失败", e);
@@ -490,9 +492,63 @@ public class AppManager {
}
}
+ // app 安装信息变化后,清理已失效的启动页
+ clearInvalidSplash();
+
LiveDataBus.get().send(LiveDataAction.ACTION_UPDATE_APPS, TAG);
}
+ // ============================================================
+ // 启动页(开屏页)清理
+ // ============================================================
+
+ /**
+ * 清除数据库中不存在的启动页。
+ *
+ * 遍历数据库中的所有 AppInfo(每条记录对应一个启动组件 ComponentName),
+ * 通过 PackageManager 校验组件是否仍可解析。当应用被卸载、更新导致 Activity
+ * 类名变化或被禁用时,对应的 ComponentName 将失效,此时从数据库删除该记录。
+ *
+ * 在每次初始化以及 app 安装信息变化(updateApp)时调用。
+ *
+ * @return 本次删除的失效启动页数量
+ */
+ public int clearInvalidSplash() {
+ if (mAppRepository == null) return 0;
+ try {
+ List allApps = mAppRepository.getAllApp();
+ if (allApps == null || allApps.isEmpty()) return 0;
+
+ List toDelete = new ArrayList<>();
+ for (AppInfo app : allApps) {
+ if (app == null) continue;
+ ComponentName componentName = app.getComponentName();
+ // 组件为空、或组件已不存在(Activity 被卸载/重命名/禁用)则标记为删除
+ if (componentName == null || !ApkUtils.isComponentExist(mContext, componentName)) {
+ toDelete.add(app);
+ }
+ }
+
+ if (toDelete.isEmpty()) return 0;
+
+ for (AppInfo app : toDelete) {
+ try {
+ mAppRepository.delete(app);
+ Logger.i(TAG, "清除失效启动页: " + (app.getComponentName() != null
+ ? app.getComponentName().flattenToShortString() : "null"));
+ } catch (Exception e) {
+ Logger.e(TAG, "删除失效启动页失败: " + app.getPackageName(), e);
+ }
+ }
+ Logger.i(TAG, "共清除 " + toDelete.size() + " 条失效启动页");
+ LiveDataBus.get().send(LiveDataAction.ACTION_UPDATE_APPS, TAG);
+ return toDelete.size();
+ } catch (Exception e) {
+ Logger.e(TAG, "清除失效启动页异常", e);
+ return 0;
+ }
+ }
+
// 重构getAllApp方法,复用现有处理逻辑
/**
@@ -831,6 +887,8 @@ public class AppManager {
@Override
public void run() {
manager.reorderAppPositionsSync();
+ // 初始化时清理已失效的启动页
+ manager.clearInvalidSplash();
manager.notifyCompleted(true, "应用列表处理完成");
}
}).exceptionally(new java.util.function.Function() {
diff --git a/app/src/main/java/com/ttstd/dialer/manager/ContactManager.java b/app/src/main/java/com/ttstd/dialer/manager/ContactManager.java
index 25c6db8..b52f8a3 100644
--- a/app/src/main/java/com/ttstd/dialer/manager/ContactManager.java
+++ b/app/src/main/java/com/ttstd/dialer/manager/ContactManager.java
@@ -3,13 +3,10 @@ package com.ttstd.dialer.manager;
import android.content.Context;
import android.util.Log;
-import com.trello.rxlifecycle4.RxLifecycle;
-import com.trello.rxlifecycle4.android.ActivityEvent;
-import com.ttstd.dialer.bean.BaseResponse;
+import com.ttstd.dialer.data.Resource;
+import com.ttstd.dialer.data.repository.ContactDataRepository;
import com.ttstd.dialer.db.contact.ContactInfo;
import com.ttstd.dialer.db.contact.ContactRepository;
-import com.ttstd.dialer.network.BaseObserver;
-import com.ttstd.dialer.network.OkHttpManager;
import com.ttstd.dialer.utils.Logger;
import java.util.HashMap;
@@ -25,7 +22,6 @@ import io.reactivex.rxjava3.core.ObservableOnSubscribe;
import io.reactivex.rxjava3.core.Observer;
import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.rxjava3.schedulers.Schedulers;
-import io.reactivex.rxjava3.subjects.BehaviorSubject;
public class ContactManager {
private static final String TAG = "ContactManager";
@@ -57,84 +53,51 @@ public class ContactManager {
/**
* 获取联系人列表(优先从网络获取,失败则从本地数据库获取)
+ *
+ * 统一走 {@link ContactDataRepository} 的多级缓存 + SWR:命中缓存秒出,
+ * 网络失败自动降级 Room 本地数据。旧逻辑在 Manager 层直连 OkHttpManager,
+ * 现收敛到 Data Layer(Repository),Manager 仅做回调转发。
*
- * @param lifecycle RxLifecycle 生命周期绑定
- * @param callback 回调接口
+ * @param callback 回调接口
*/
- public void getContacts(BehaviorSubject lifecycle, ContactCallback callback) {
+ public void getContacts(ContactCallback callback) {
Logger.e(TAG, "开始获取联系人列表");
- OkHttpManager.getInstance().getContactListObservable(lifecycle)
- .compose(RxLifecycle.bindUntilEvent(lifecycle, ActivityEvent.DESTROY))
- .subscribe(new BaseObserver>>() {
+ ContactDataRepository.getInstance(mContext).getContactList(false)
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(new Observer>>() {
@Override
- public void onSuccess(BaseResponse> listBaseResponse) {
- Log.e(TAG, "网络请求成功: " + listBaseResponse);
- if (listBaseResponse.isSuccess()) {
- List networkContacts = listBaseResponse.getData();
- if (networkContacts != null && !networkContacts.isEmpty()) {
- // 在子线程中处理数据库同步
- Observable.create(new ObservableOnSubscribe>() {
- @Override
- public void subscribe(@NonNull ObservableEmitter> emitter) throws Throwable {
- try {
- // 同步网络和本地数据
- syncContacts(networkContacts);
- // 返回排序后的联系人列表
- List sortedContacts = networkContacts.stream()
- .sorted((c1, c2) -> Integer.compare(c1.getPosition(), c2.getPosition()))
- .collect(Collectors.toList());
- emitter.onNext(sortedContacts);
- emitter.onComplete();
- } catch (Exception e) {
- emitter.onError(e);
- }
- }
- })
- .subscribeOn(Schedulers.io())
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe(new Observer>() {
- @Override
- public void onSubscribe(@NonNull Disposable d) {
- Logger.e(TAG, "开始处理联系人数据");
- }
+ public void onSubscribe(@NonNull Disposable d) {
+ Logger.e(TAG, "开始获取联系人");
+ }
- @Override
- public void onNext(@NonNull List contactInfos) {
- Logger.e(TAG, "联系人数据处理完成,数量: " + contactInfos.size());
- if (callback != null) {
- callback.onSuccess(contactInfos);
- }
- }
-
- @Override
- public void onError(@NonNull Throwable e) {
- Logger.e(TAG, "处理联系人数据失败: " + e.getMessage());
- if (callback != null) {
- callback.onFailure(e);
- }
- }
-
- @Override
- public void onComplete() {
- Logger.e(TAG, "联系人数据处理完成");
- }
- });
- } else {
- // 网络返回空数据,从本地获取
- getLocalContacts(callback);
+ @Override
+ public void onNext(@NonNull Resource> resource) {
+ if (resource.data != null) {
+ Logger.e(TAG, "联系人数据获取成功,数量: " + resource.data.size());
+ if (callback != null) {
+ callback.onSuccess(resource.data);
+ }
+ } else if (resource.isError()) {
+ Logger.e(TAG, "联系人获取失败: " + resource.message);
+ if (callback != null) {
+ callback.onFailure(new Exception(resource.message));
}
- } else {
- // 网络请求业务失败,从本地获取
- getLocalContacts(callback);
}
}
@Override
- public void onFailure(Throwable e) {
- Log.e(TAG, "网络请求失败: " + e.getMessage());
- // 网络请求失败,从本地获取
- getLocalContacts(callback);
+ public void onError(@NonNull Throwable e) {
+ Logger.e(TAG, "联系人获取异常: " + e.getMessage());
+ if (callback != null) {
+ callback.onFailure(e);
+ }
+ }
+
+ @Override
+ public void onComplete() {
+ Logger.e(TAG, "联系人获取完成");
}
});
}
diff --git a/app/src/main/java/com/ttstd/dialer/manager/ContactSyncManager.java b/app/src/main/java/com/ttstd/dialer/manager/ContactSyncManager.java
index fbaad48..4e2438f 100644
--- a/app/src/main/java/com/ttstd/dialer/manager/ContactSyncManager.java
+++ b/app/src/main/java/com/ttstd/dialer/manager/ContactSyncManager.java
@@ -2,10 +2,10 @@ package com.ttstd.dialer.manager;
import android.content.Context;
+import com.ttstd.dialer.data.repository.ContactDataRepository;
import com.ttstd.dialer.db.contact.ContactInfo;
import com.ttstd.dialer.db.contact.ContactRepository;
import com.ttstd.dialer.db.contact.SyncStatus;
-import com.ttstd.dialer.network.OkHttpManager;
import com.ttstd.dialer.utils.Logger;
import java.util.List;
@@ -19,11 +19,13 @@ public class ContactSyncManager {
private static final String TAG = "ContactSyncManager";
private static volatile ContactSyncManager INSTANCE;
- private ContactRepository mRepository;
- private CompositeDisposable mDisposable;
+ private final ContactRepository mRepository;
+ private final ContactDataRepository mDataRepository;
+ private final CompositeDisposable mDisposable;
private ContactSyncManager(Context context) {
mRepository = new ContactRepository(context);
+ mDataRepository = ContactDataRepository.getInstance(context);
mDisposable = new CompositeDisposable();
}
@@ -39,7 +41,8 @@ public class ContactSyncManager {
}
public void syncPendingContacts() {
- Observable.fromCallable(() -> {
+ // 订阅结果加入 CompositeDisposable,dispose() 时可统一取消
+ mDisposable.add(Observable.fromCallable(() -> {
List pendingContacts = mRepository.getUnsyncedContacts();
Logger.d(TAG, "查询到 " + pendingContacts.size() + " 个待同步联系人");
return pendingContacts;
@@ -53,21 +56,22 @@ public class ContactSyncManager {
.subscribe(
result -> Logger.d(TAG, "同步完成: " + result),
throwable -> Logger.e(TAG, "同步流程异常: " + throwable.getMessage())
- );
+ ));
}
private Observable syncSingleContactObservable(ContactInfo contactInfo) {
- return OkHttpManager.getInstance().getContactInsertObservable(contactInfo)
+ // 统一走 Data Layer(Repository → RemoteDataSource),不再直连 OkHttpManager
+ return mDataRepository.addContact(contactInfo)
.subscribeOn(Schedulers.io())
- .flatMap(baseResponse -> {
- if (baseResponse.isSuccess()) {
- Long onlineId = baseResponse.getData();
- return saveSuccessSyncStatus(contactInfo.getId(), onlineId)
+ .flatMap(result -> {
+ if (result.online) {
+ // 服务端插入成功,更新同步状态并记录在线 id
+ return saveSuccessSyncStatus(contactInfo.getId(), result.id)
.map(v -> "成功: " + contactInfo.getName());
- } else {
- return saveFailedSyncStatus(contactInfo.getId())
- .flatMap(v -> Observable.error(new Exception("业务失败: " + baseResponse.getMsg())));
}
+ // 网络不可用降级到本地保存,标记为失败待后续重试
+ return saveFailedSyncStatus(contactInfo.getId())
+ .flatMap(v -> Observable.error(new Exception("离线降级保存: " + contactInfo.getName())));
})
.onErrorResumeNext(throwable -> {
return saveFailedSyncStatus(contactInfo.getId())
diff --git a/app/src/main/java/com/ttstd/dialer/network/OkHttpManager.java b/app/src/main/java/com/ttstd/dialer/network/OkHttpManager.java
index b21b71c..4a2dd69 100644
--- a/app/src/main/java/com/ttstd/dialer/network/OkHttpManager.java
+++ b/app/src/main/java/com/ttstd/dialer/network/OkHttpManager.java
@@ -1,26 +1,11 @@
package com.ttstd.dialer.network;
import android.content.Context;
-import android.os.Build;
import android.util.Log;
import androidx.annotation.NonNull;
import com.google.gson.Gson;
-import com.trello.rxlifecycle4.RxLifecycle;
-import com.trello.rxlifecycle4.android.ActivityEvent;
-import com.ttstd.dialer.bean.BaseResponse;
-import com.ttstd.dialer.bean.DeveloperOptions;
-import com.ttstd.dialer.bean.req.SnHardwareInfoReq;
-import com.ttstd.dialer.bean.req.SnLocationReq;
-import com.ttstd.dialer.bean.req.SnNetworkInfoReq;
-import com.ttstd.dialer.bean.req.SnOtherInfoReq;
-import com.ttstd.dialer.bean.req.SnSecurityInfoReq;
-import com.ttstd.dialer.bean.req.SnSystemInfoReq;
-import com.ttstd.dialer.db.contact.ContactInfo;
-import com.ttstd.dialer.mdm.DeviceManagerService;
-import com.ttstd.dialer.network.api.ContactApi;
-import com.ttstd.dialer.network.api.SnApi;
import com.ttstd.dialer.network.interceptor.AuthInterceptor;
import com.ttstd.dialer.utils.Logger;
import com.ttstd.iconloader.utils.FileUtils;
@@ -28,13 +13,8 @@ import com.ttstd.iconloader.utils.FileUtils;
import org.jetbrains.annotations.NotNull;
import java.io.File;
-import java.util.List;
import java.util.concurrent.TimeUnit;
-import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
-import io.reactivex.rxjava3.core.Observable;
-import io.reactivex.rxjava3.schedulers.Schedulers;
-import io.reactivex.rxjava3.subjects.BehaviorSubject;
import okhttp3.Cache;
import okhttp3.ConnectionPool;
import okhttp3.Dispatcher;
@@ -50,6 +30,12 @@ import retrofit2.converter.gson.GsonConverterFactory;
/**
* OkHttp + Retrofit 管理类
* 单例模式 + 避免内存泄漏 + 增强异常处理 + 提升扩展性
+ *
+ * 职责收敛为「网络基础设施」:
+ * - 创建并持有 OkHttpClient / Retrofit
+ * - 通过 {@link #getApiService(Class)} 提供 Retrofit 接口实例
+ * - 提供 RequestBody / MultipartBody 转换工具
+ *
线程调度由 Data Layer(Repository / RemoteDataSource)统一负责,不再在此层处理。
*/
public class OkHttpManager {
private static final String TAG = "OkHttpManager";
@@ -121,8 +107,8 @@ public class OkHttpManager {
// 1. 构建调度器(控制并发)
Dispatcher dispatcher = new Dispatcher();
- dispatcher.setMaxRequests(1); // 全局最大并发
- dispatcher.setMaxRequestsPerHost(1); // 单Host最大并发
+// dispatcher.setMaxRequests(1); // 全局最大并发
+// dispatcher.setMaxRequestsPerHost(1); // 单Host最大并发
// 2. 构建缓存(增强异常处理)
Cache cache = createCache();
@@ -179,8 +165,6 @@ public class OkHttpManager {
}
Cache cache = new Cache(cacheDir, CACHE_SIZE_BYTES);
- // 验证缓存是否可用(避免创建了但不可用的情况)
-// cache.getCacheDirectory().getFreeSpace();
return cache;
} catch (SecurityException e) {
Log.e(TAG, "No permission to create cache directory", e);
@@ -248,6 +232,13 @@ public class OkHttpManager {
}
}
+ /**
+ * 获取 Retrofit API 接口实例(Data Layer 通过它创建 RemoteDataSource)
+ */
+ public T getApiService(Class tClass) {
+ return mRetrofit.create(tClass);
+ }
+
public static MultipartBody.Part getBodyPart(String type, String name, File file) {
MediaType mediaType = MediaType.Companion.parse(type);
RequestBody fileBody = RequestBody.Companion.create(file, mediaType);
@@ -284,148 +275,4 @@ public class OkHttpManager {
RequestBody requestBody = RequestBody.create(MediaType.parse("text/plain"), String.valueOf(param));
return requestBody;
}
-
-
- /**
- * 统一线程调度 + 生命周期管理
- *
- * @param observable 接口Observable
- * @param provider 生命周期管理者(Activity/Fragment)
- * @return 处理后的Observable
- */
- public Observable schedule(Observable observable, BehaviorSubject provider) {
- return observable
- // 子线程执行请求
- .subscribeOn(Schedulers.io())
- // 主线程回调
- .observeOn(AndroidSchedulers.mainThread())
- // 生命周期管理:页面销毁自动取消请求,防止内存泄漏
- .compose(RxLifecycle.bindUntilEvent(provider, ActivityEvent.DESTROY));
- }
-
- public T getApiService(Class tClass) {
- return mRetrofit.create(tClass);
- }
-
- public Observable schedule(Observable observable) {
- return observable
- // 子线程执行请求
- .subscribeOn(Schedulers.io())
- // 主线程回调
- .observeOn(AndroidSchedulers.mainThread());
- }
-
- public Observable getSnRegisterObservable(BehaviorSubject provider) {
- return schedule(getApiService(SnApi.class).register(DeviceManagerService.getInstance().getSerial(), Build.MODEL), provider);
- }
-
- /**
- * 设备注册(由 ViewModel 通过 addDisposable 管理订阅生命周期)
- */
- public Observable getSnRegisterObservable() {
- return schedule(getApiService(SnApi.class).register(DeviceManagerService.getInstance().getSerial(), Build.MODEL));
- }
-
- /**
- * 上传设备基本信息
- */
- public Observable> getUpdateSystemInfoObservable(SnSystemInfoReq req) {
- RequestBody body = convertToRequestBodyjson(req);
- return schedule(getApiService(SnApi.class).updateSystemInfo(body));
- }
-
- /**
- * 上传设备硬件信息
- */
- public Observable> getUpdateHardwareInfoObservable(SnHardwareInfoReq req) {
- RequestBody body = convertToRequestBodyjson(req);
- return schedule(getApiService(SnApi.class).updateHardwareInfo(body));
- }
-
- /**
- * 上传设备网络信息
- */
- public Observable> getUpdateNetworkInfoObservable(SnNetworkInfoReq req) {
- RequestBody body = convertToRequestBodyjson(req);
- return schedule(getApiService(SnApi.class).updateNetworkInfo(body));
- }
-
- /**
- * 上传设备安全信息
- */
- public Observable> getUpdateSecurityInfoObservable(SnSecurityInfoReq req) {
- RequestBody body = convertToRequestBodyjson(req);
- return schedule(getApiService(SnApi.class).updateSecurityInfo(body));
- }
-
- /**
- * 上传设备其他信息
- */
- public Observable> getUpdateOtherInfoObservable(SnOtherInfoReq req) {
- RequestBody body = convertToRequestBodyjson(req);
- return schedule(getApiService(SnApi.class).updateOtherInfo(body));
- }
-
- public Observable getUploadScreenshotObservable(MultipartBody.Part part) {
- return schedule(getApiService(SnApi.class).uploadScreenshot(part));
- }
-
- public Observable getUploadLocationObservable(SnLocationReq locationReq, BehaviorSubject provider) {
- return schedule(getApiService(SnApi.class).uploadLocation(convertToRequestBodyjson(locationReq)), provider);
- }
-
- public Observable getUploadLocationObservable(SnLocationReq locationReq) {
- return schedule(getApiService(SnApi.class).uploadLocation(convertToRequestBodyjson(locationReq)));
- }
-
- public Observable> getDeveloperOptionsObservable(BehaviorSubject provider) {
- return schedule(getApiService(SnApi.class).getDeveloperOptions(), provider);
- }
-
- public Observable> getDeveloperOptionsObservable() {
- return schedule(getApiService(SnApi.class).getDeveloperOptions());
- }
-
- public Observable getUploadInstallApksObservable(RequestBody body) {
- return schedule(getApiService(SnApi.class).uploadInstallApks(body));
- }
-
- /**
- * 上传应用图标(全局图标库,packageName/appName 作为 form 字段,file 作为文件 part)
- *
- * 注意:Retrofit 的 @Part 不允许传 null,appName 为空时统一转为空串,
- * 由服务端按「未上报」处理(沿用已有名称)。
- */
- public Observable getUploadAppIconObservable(String packageName, String appName, MultipartBody.Part part) {
- MediaType textPlain = MediaType.Companion.parse("text/plain");
- RequestBody packageNameBody = RequestBody.Companion.create(packageName, textPlain);
- RequestBody appNameBody = RequestBody.Companion.create(appName == null ? "" : appName, textPlain);
- return schedule(getApiService(SnApi.class).uploadAppIcon(packageNameBody, appNameBody, part));
- }
-
- public Observable>> getContactListObservable(BehaviorSubject provider) {
- return schedule(getApiService(ContactApi.class).getContactList(), provider);
- }
-
- public Observable> getContactInsertObservable(ContactInfo contactInfo, BehaviorSubject provider) {
- return schedule(getApiService(ContactApi.class).insertContact(convertToRequestBodyjson(contactInfo)), provider);
- }
-
- public Observable> getContactInsertObservable(ContactInfo contactInfo) {
- return schedule(getApiService(ContactApi.class).insertContact(convertToRequestBodyjson(contactInfo)));
- }
-
- public Observable> getContactFormObservable(long id, BehaviorSubject provider) {
- return schedule(getApiService(ContactApi.class).getContactForm(id), provider);
- }
-
- public Observable> getContactUpdateObservable(long id, ContactInfo contactInfo, BehaviorSubject provider) {
- return schedule(getApiService(ContactApi.class).updateContact(id, convertToRequestBodyjson(contactInfo)), provider);
- }
-
- public Observable> getContactDeleteObservable(long id, BehaviorSubject provider) {
- return schedule(getApiService(ContactApi.class).deleteContact(id), provider);
- }
-
-
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/com/ttstd/dialer/network/api/SnApi.java b/app/src/main/java/com/ttstd/dialer/network/api/SnApi.java
index 581f510..6a17e94 100644
--- a/app/src/main/java/com/ttstd/dialer/network/api/SnApi.java
+++ b/app/src/main/java/com/ttstd/dialer/network/api/SnApi.java
@@ -78,7 +78,7 @@ public interface SnApi {
);
@POST(UrlConstants.UPLOAD_LOCATION)
- Observable uploadLocation(
+ Observable> uploadLocation(
@Body RequestBody body
);
diff --git a/app/src/main/java/com/ttstd/dialer/push/PushExecutor.java b/app/src/main/java/com/ttstd/dialer/push/PushExecutor.java
index cd81baf..8dd15a4 100644
--- a/app/src/main/java/com/ttstd/dialer/push/PushExecutor.java
+++ b/app/src/main/java/com/ttstd/dialer/push/PushExecutor.java
@@ -87,12 +87,16 @@ public class PushExecutor {
*/
private List mApkInstalledInfos;
+ private SnRepository mSnRepository;
+
private PushExecutor(Context context) {
if (context == null) {
throw new RuntimeException("Context is NULL");
}
this.mContext = context;
this.mPackageManager = mContext.getPackageManager();
+ // 数据访问统一走 Data Layer(Repository),避免直连 OkHttpManager
+ this.mSnRepository = SnRepository.getInstance(mContext);
}
public static void init(Context context) {
@@ -203,7 +207,7 @@ public class PushExecutor {
public Observable apply(List apkInstalledInfos) throws Throwable {
mApkInstalledInfos = apkInstalledInfos;
RequestBody body = OkHttpManager.convertToRequestBodyjson(apkInstalledInfos);
- return OkHttpManager.getInstance().getUploadInstallApksObservable(body);
+ return mSnRepository.uploadInstallApks(body);
}
})
.subscribeOn(Schedulers.io())
@@ -279,8 +283,7 @@ public class PushExecutor {
return Observable.empty();
}
MultipartBody.Part part = OkHttpManager.getBodyPart("image/png", "file", iconFile);
- return OkHttpManager.getInstance()
- .getUploadAppIconObservable(packageName, apkInfo.getAppName(), part)
+ return mSnRepository.uploadAppIcon(packageName, apkInfo.getAppName(), part)
// getUploadAppIconObservable 内部已 observeOn(mainThread),
// 这里必须切回 IO 线程,否则下面的 Room 写入会发生在主线程
.observeOn(Schedulers.io())
@@ -323,33 +326,33 @@ public class PushExecutor {
*/
private void uploadAllDeviceInfo() {
DeviceInfoCollector collector = new DeviceInfoCollector(mContext);
- OkHttpManager http = OkHttpManager.getInstance();
// defer + fromCallable 保证采集动作发生在订阅时的 IO 线程,而非当前线程
+ // 上报统一走 Data Layer(SnRepository → SnRemoteDataSource),不再直连 OkHttpManager
Observable> system = Observable
.fromCallable(collector::collectSystemInfo)
.subscribeOn(Schedulers.io())
- .flatMap(http::getUpdateSystemInfoObservable);
+ .flatMap(mSnRepository::updateSystemInfo);
Observable> hardware = Observable
.fromCallable(collector::collectHardwareInfo)
.subscribeOn(Schedulers.io())
- .flatMap(http::getUpdateHardwareInfoObservable);
+ .flatMap(mSnRepository::updateHardwareInfo);
Observable> network = Observable
.fromCallable(collector::collectNetworkInfo)
.subscribeOn(Schedulers.io())
- .flatMap(http::getUpdateNetworkInfoObservable);
+ .flatMap(mSnRepository::updateNetworkInfo);
Observable> security = Observable
.fromCallable(collector::collectSecurityInfo)
.subscribeOn(Schedulers.io())
- .flatMap(http::getUpdateSecurityInfoObservable);
+ .flatMap(mSnRepository::updateSecurityInfo);
Observable> other = Observable
.fromCallable(collector::collectOtherInfo)
.subscribeOn(Schedulers.io())
- .flatMap(http::getUpdateOtherInfoObservable);
+ .flatMap(mSnRepository::updateOtherInfo);
Observable.mergeDelayError(
Observable.just(system, hardware, network, security, other))
@@ -395,7 +398,7 @@ public class PushExecutor {
if (integer == 0) {
// 截图成功,上传文件
MultipartBody.Part part = OkHttpManager.getBodyPart("image/png", "file", new File(filepath));
- return OkHttpManager.getInstance().getUploadScreenshotObservable(part)
+ return mSnRepository.uploadScreenshot(part)
.subscribeOn(Schedulers.io()); // 确保上传操作在子线程执行
} else {
// 截图失败,返回一个空的Observable
@@ -436,7 +439,7 @@ public class PushExecutor {
.flatMap(integer -> {
if (integer == 0) {
MultipartBody.Part part = OkHttpManager.getBodyPart("image/jpeg", "file", file);
- return OkHttpManager.getInstance().getUploadScreenshotObservable(part)
+ return mSnRepository.uploadScreenshot(part)
.subscribeOn(Schedulers.io());
} else {
return Observable.empty();
diff --git a/app/src/main/java/com/ttstd/dialer/service/main/MainService.java b/app/src/main/java/com/ttstd/dialer/service/main/MainService.java
index 9a862b3..9b25540 100644
--- a/app/src/main/java/com/ttstd/dialer/service/main/MainService.java
+++ b/app/src/main/java/com/ttstd/dialer/service/main/MainService.java
@@ -27,6 +27,7 @@ import com.ttstd.dialer.activity.main.MainActivity;
import com.ttstd.dialer.base.BaseService;
import com.ttstd.dialer.bean.req.SnLocationReq;
import com.ttstd.dialer.config.CommonConfig;
+import com.ttstd.dialer.data.repository.SnRepository;
import com.ttstd.dialer.manager.ContactSyncManager;
import com.ttstd.dialer.manager.MqttManager;
import com.ttstd.dialer.utils.ApkUtils;
@@ -69,6 +70,7 @@ public class MainService extends BaseService implements NetworkUtils.OnNetworkSt
@Nullable
@Override
public IBinder onBind(Intent intent) {
+ super.onBind(intent);
return null;
}
@@ -81,8 +83,8 @@ public class MainService extends BaseService implements NetworkUtils.OnNetworkSt
// 初始化ViewModel(绑定上下文+View回调)
mViewModel = new MainServiceModel();
- // 绑定Service生命周期(关键:让请求跟随生命周期)
- mViewModel.setLifecycleSubject(getLifecycleSubject());
+ // 注入 Repository(数据访问统一走 Data 层,避免 ViewModel 直接触网)
+ mViewModel.setSnRepository(SnRepository.getInstance(this));
LiveEventBus.get(CommonConfig.LOCATION_CHANGED_KEY, SnLocationReq.class)
.observeSticky(this, new Observer() {
diff --git a/app/src/main/java/com/ttstd/dialer/service/main/MainServiceModel.java b/app/src/main/java/com/ttstd/dialer/service/main/MainServiceModel.java
index 94f91ef..3ad50be 100644
--- a/app/src/main/java/com/ttstd/dialer/service/main/MainServiceModel.java
+++ b/app/src/main/java/com/ttstd/dialer/service/main/MainServiceModel.java
@@ -1,47 +1,36 @@
package com.ttstd.dialer.service.main;
-import android.text.TextUtils;
import android.util.Log;
import androidx.lifecycle.ViewModel;
-import com.trello.rxlifecycle4.LifecycleTransformer;
-import com.trello.rxlifecycle4.RxLifecycle;
-import com.trello.rxlifecycle4.android.ActivityEvent;
import com.ttstd.dialer.bean.BaseResponse;
import com.ttstd.dialer.bean.req.SnLocationReq;
+import com.ttstd.dialer.data.repository.SnRepository;
import com.ttstd.dialer.gson.GsonUtils;
-import com.ttstd.dialer.network.BaseObserver;
-import com.ttstd.dialer.network.OkHttpManager;
-import io.reactivex.rxjava3.subjects.BehaviorSubject;
+import io.reactivex.rxjava3.disposables.CompositeDisposable;
+import io.reactivex.rxjava3.observers.DisposableObserver;
public class MainServiceModel extends ViewModel {
private static final String TAG = "MainServiceModel";
- private BehaviorSubject lifecycleSubject;
+ private final CompositeDisposable mDisposables = new CompositeDisposable();
+ private SnRepository mSnRepository;
- // 设置Service生命周期Subject
- public void setLifecycleSubject(BehaviorSubject lifecycleSubject) {
- this.lifecycleSubject = lifecycleSubject;
- }
-
- // 绑定请求到Service销毁事件(自动取消请求)
- private LifecycleTransformer bindToLifecycle() {
- if (lifecycleSubject == null) {
- throw new IllegalStateException("请先设置LifecycleSubject!");
- }
- return RxLifecycle.bindUntilEvent(lifecycleSubject, ActivityEvent.DESTROY);
+ public void setSnRepository(SnRepository snRepository) {
+ this.mSnRepository = snRepository;
}
@Override
protected void onCleared() {
+ // ViewModel 销毁(Service 停止)时自动取消进行中的上报,避免内存泄漏
+ if (!mDisposables.isDisposed()) {
+ mDisposables.clear();
+ }
super.onCleared();
- // 清空生命周期引用,防止内存泄漏
- this.lifecycleSubject = null;
}
-
public void uploadLocation(SnLocationReq locationReq) {
// 空校验:位置信息缺失时不发起请求,避免后端收到无效数据
if (locationReq == null) {
@@ -49,19 +38,26 @@ public class MainServiceModel extends ViewModel {
return;
}
Log.e(TAG, "uploadLocation: " + GsonUtils.toJSONString(locationReq));
- OkHttpManager.getInstance().getUploadLocationObservable(locationReq)
- .compose(bindToLifecycle())
- .subscribe(new BaseObserver() {
+ if (mSnRepository == null) {
+ Log.e(TAG, "uploadLocation: SnRepository 未注入,跳过");
+ return;
+ }
+ mDisposables.add(mSnRepository.uploadLocation(locationReq)
+ .subscribeWith(new DisposableObserver>() {
@Override
- public void onSuccess(BaseResponse baseResponse) {
- Log.e("uploadLocation", "onSuccess: " + baseResponse);
+ public void onNext(BaseResponse baseResponse) {
+ Log.e("uploadLocation", "onNext: " + baseResponse);
}
@Override
- public void onFailure(Throwable e) {
- Log.e("uploadLocation", "onFailure: " + e.getMessage());
+ public void onError(Throwable e) {
+ Log.e("uploadLocation", "onError: " + e.getMessage());
}
- });
+ @Override
+ public void onComplete() {
+ Log.e("uploadLocation", "onComplete");
+ }
+ }));
}
}
diff --git a/app/src/main/java/com/ttstd/dialer/utils/ApkUtils.java b/app/src/main/java/com/ttstd/dialer/utils/ApkUtils.java
index e5c5c56..b7ea523 100644
--- a/app/src/main/java/com/ttstd/dialer/utils/ApkUtils.java
+++ b/app/src/main/java/com/ttstd/dialer/utils/ApkUtils.java
@@ -16,6 +16,7 @@ import android.net.Uri;
import android.os.Build;
import android.text.TextUtils;
+import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import com.kongzue.dialogx.dialogs.PopTip;
@@ -37,6 +38,24 @@ public class ApkUtils {
}
}
+ /**
+ * 判断指定组件(Activity)是否仍然存在且可被解析。
+ * 应用卸载、更新导致 Activity 类名变化或被禁用时,均会返回 false。
+ *
+ * @param context 上下文
+ * @param componentName 待校验的组件
+ * @return true 表示组件仍存在
+ */
+ public static boolean isComponentExist(Context context, @Nullable ComponentName componentName) {
+ if (context == null || componentName == null) return false;
+ try {
+ ActivityInfo info = context.getPackageManager().getActivityInfo(componentName, 0);
+ return info != null;
+ } catch (PackageManager.NameNotFoundException e) {
+ return false;
+ }
+ }
+
/**
* 判断是否为系统应用
*