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; + } + } + /** * 判断是否为系统应用 *