android O版本 设置(Settings)模块总结--设置的启动界面选择

本文深入解析Android系统的设置模块,从SettingsActivity的启动流程到不同界面的加载机制,包括主界面、内部类界面及SubSettings界面的布局差异。揭示了设置模块如何通过类名判断加载不同布局,以及Fragment在界面跳转中的作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

之前一直在公司做设置模块的功能修改,现在转到了其它模块,所以就总结一下吧,有不足之处,还请指正.

设置是整个android系统的重要应用,涉及的都是系统功能,本文只是对其结构进行解析,功能控制等不做说明

(1)设置的界面选择

设置里面的Activity乍一看和常规应用的Activity有很大不同,但是原理都是一样的,不过设置为了更方便的区分和获取信息,在AndroidManifest.xml文件中添加了许多的属性,这可能导致很多人看着头疼,但是这也是设置的精髓所在.

 

设置的启动界面: O/packages/apps/Settings/src/com/android/settings/Settings.java

Settings的父类是SettingsActivity,而且其中有很多继承SettingsActivity的内部类,这个地方是很有意思,后续再说,先看下SettingsActivity.java

O/packages/apps/Settings/src/com/android/settings/SettingsActivity.java

SettingsActivity的父类是SettingsDrawerActivity,而SettingsDrawerActivity是在SettingLib中定义

O/frameworks/base/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java

SettingsDrawerActivity名称是沿用N版本的名字,但是侧滑栏功能在O版本上已经移除了,google在这个地方偷了下懒.

这父子俩的onCreate():

  SettingsDrawerActivity
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //主要是布局设置
        super.setContentView(R.layout.settings_with_drawer);
        mContentHeaderContainer = (FrameLayout) findViewById(R.id.content_header_container);

        Toolbar toolbar = (Toolbar) findViewById(R.id.action_bar);
        if (theme.getBoolean(android.R.styleable.Theme_windowNoTitle, false)) {
            toolbar.setVisibility(View.GONE);
            return;
        }
        setActionBar(toolbar);
    }


SettingsActivity
    @Override
    protected void onCreate(Bundle savedState) {
        super.onCreate(savedState);
       ...... 
        //获取的类名,此处获取的可能是在Settings.java中的内部类,或者就是Settings.java

        // Should happen before any call to getIntent()
        getMetaData();
        // Getting Intent properties can only be done after the super.onCreate(...)
        final String initialFragmentName = intent.getStringExtra(EXTRA_SHOW_FRAGMENT);
        ......
        final ComponentName cn = intent.getComponent();
        final String className = cn.getClassName();
        //mIsShowingDashboard会根据不同的类名布置不同的布局
        mIsShowingDashboard = className.equals(Settings.class.getName());

        // This is a "Sub Settings" when:
        // - this is a real SubSettings
        // - or :settings:show_fragment_as_subsetting is passed to the Intent
        final boolean isSubSettings = this instanceof SubSettings ||
                intent.getBooleanExtra(EXTRA_SHOW_FRAGMENT_AS_SUBSETTING, false);

        // If this is a sub settings, then apply the SubSettings Theme for the ActionBar content
        // insets
        //设置二级界面的主题
        if (isSubSettings) {
            setTheme(R.style.Theme_SubSettings);
        }
        //布局不同的布置,主要是为了区分一级界面
        setContentView(mIsShowingDashboard ? R.layout.settings_main_dashboard : R.layout.settings_main_prefs);

        mContent = (ViewGroup) findViewById(R.id.main_content);

        getFragmentManager().addOnBackStackChangedListener(this);
       ......
        //跳转到制定的界面
        launchSettingFragment(initialFragmentName, isSubSettings, intent);

       ......
    }

在SettingsActivity的onCreate中去其实会有三个不同布局的加载方向,以下是重点了:

Settings , Settings的内部类 , SubSettings

(a)Settings ---主界面(一级界面)

onCreate函数中会根据类名加载不同的布局,而布尔值:mIsShowingDashboard 就是当前是否为一级界面的判断

        mIsShowingDashboard = className.equals(Settings.class.getName());
        setContentView(mIsShowingDashboard ?
                R.layout.settings_main_dashboard : R.layout.settings_main_prefs);

 

settings_main_dashboard.xml就很简单了只是一个FrameLayout

 

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
             android:id="@+id/main_content"
             android:layout_height="match_parent"
             android:layout_width="match_parent"
             />

在之后的界面跳转即 launchSettingFragment(initialFragmentName, isSubSettings, intent)函数中又做出了区分:

    void launchSettingFragment(String initialFragmentName, boolean isSubSettings, Intent intent) {
        if (!mIsShowingDashboard && initialFragmentName != null) {
            ......
        } else {
            // No UP affordance if we are displaying the main Dashboard
            mDisplayHomeAsUpEnabled = false;
            // Show Search affordance
            mDisplaySearch = true;
            mInitialTitleResId = R.string.dashboard_title;
            //此处是真正显示一级界面的操作
            switchToFragment(DashboardSummary.class.getName(), null /* args */, false, false,
                mInitialTitleResId, mInitialTitle, false);
        }
    }

在launchSettingFragment函数中正式加载了设置的一级界面DashboardSummary.java,DashboardSummary是一个Fragment,而内部布局是使用的R.layout.dashboard,dashboard.xml包含了一个自定义的RecyclerView类FocusRecyclerView,此处就不再详细说明了.

(b)Settings的内部类

Settings的内部类的启动一版都是通过activity 中的action属性启动的,而判断的依据也是通过mIsShowingDashboard,加载的布局为R.layout.settings_main_prefs,而settings_main_prefs.xml比dashboard.xml来说就增加了一个switchBar:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:layout_height="match_parent"
              android:layout_width="match_parent">

    <LinearLayout
            android:orientation="vertical"
            android:layout_height="0px"
            android:layout_width="match_parent"
            android:layout_weight="1">

        <com.android.settings.widget.SwitchBar android:id="@+id/switch_bar"
                  android:layout_height="?android:attr/actionBarSize"
                  android:layout_width="match_parent"
                  android:background="@drawable/switchbar_background"
                  android:theme="?attr/switchBarTheme"
                />

        <FrameLayout
                android:id="@+id/main_content"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                />

    </LinearLayout>

    <RelativeLayout /><!--没怎么研究过,此处代码就省略了-->

</LinearLayout>

Settings的内部类的主体显示内容依然是一个Fragment,而这个Fragment已经在AndroidManifest.xml中定义好了.拿StorageDashboardActivity为例:

        <activity android:name=".Settings$StorageDashboardActivity"
                android:label="@string/storage_settings"
                android:icon="@drawable/ic_settings_storage"
                android:taskAffinity="com.android.settings"
                android:parentActivityName="Settings">
            <intent-filter android:priority="1">
                <action android:name="android.settings.INTERNAL_STORAGE_SETTINGS" />
                <action android:name="android.settings.MEMORY_CARD_SETTINGS" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.VOICE_LAUNCH" />
            </intent-filter>
            <intent-filter android:priority="5">
                <action android:name="com.android.settings.action.SETTINGS" />
            </intent-filter>
            <meta-data android:name="com.android.settings.category"
                android:value="com.android.settings.category.ia.homepage" />
            <meta-data android:name="com.android.settings.title"
                android:resource="@string/storage_usb_settings" />
            <meta-data android:name="com.android.settings.FRAGMENT_CLASS"
                android:value="com.android.settings.deviceinfo.StorageSettings" />
            <meta-data android:name="com.android.settings.PRIMARY_PROFILE_CONTROLLED"
                android:value="true" />
        </activity>

这个地方很容易会让人迷糊的,主要是里面的属性太多,看第一眼估计就会晕掉,这些属性是在SettingLib中的O/frameworks/base/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java有用到的,后续有时间的话可以总结一下,TileUtils主要目录加载的工具类.

言归正传,内部类StorageDashboardActivity的Fragment显示内容为:com.android.settings.FRAGMENT_CLASS,即com.android.settings.deviceinfo.StorageSettings
(c)SubSettings

和内部类就很相似了,不过是做了主题的切换:

        if (isSubSettings) {
            setTheme(R.style.Theme_SubSettings);
        }

SubSettings的启动方式是很隐蔽的这里把启动的顺序贴下,各位有时间的话可以自己研究一下:

android.support.v7.preference.Preference$1.onClick()
android.support.v7.preference.Preference.performClick()
com.android.settings.fuelgauge.PowerUsageSummary.onPreferenceTreeClick()
com.android.settings.dashboard.DashboardFragment.onPreferenceTreeClick()
android.support.v14.preference.PreferenceFragment.onPreferenceTreeClick()
com.android.settings.SettingsActivity.onPreferenceStartFragment()
com.android.settings.SettingsActivity.startPreferencePanel()
com.android.settings.Utils.startWithFragment()
com.android.settings.Utils.onBuildStartFragmentIntent()

 

到这里为止,设置的界面加载启动基本上已经总结完了,看起来还是很简单的,但是这只是启动而已,内部的数据加载,数据与View的绑定其实还有很多东西,后续有时间的话会继续总结.

 

package com.example.text; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.media.projection.MediaProjectionManager; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; import android.provider.Settings; import android.util.Log; import android.view.View; import android.widget.Button; import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; import com.example.text.AI.FloatingBallService; import com.example.text.Class.goods; import com.example.text.SQL.DatabaseHelper; import com.example.text.SQL.Goods.Goods; import com.example.text.SQL.Goods.GoodsItem; import com.example.text.utils.Http; import java.io.IOException; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import okhttp3.Call; import okhttp3.Callback; import okhttp3.Response; import android.Manifest; import android.widget.Toast; public class MainActivity extends AppCompatActivity { private static final int REQUEST_STORAGE_PERMISSION = 1; private DatabaseHelper dbHelper; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // EdgeToEdge.enable(this); setContentView(R.layout.activity_main); // 检查存储权限 if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { // 权限未开启,请求用户授权 ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_STORAGE_PERMISSION); } else { // 权限已开启,继续执行创建数据库的操作 createDatabase(); } if (!Settings.canDrawOverlays(this)) { Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName())); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent); return; // 如果没有权限,直接退出服务 } //创建数据库 dbHelper = new DatabaseHelper(this); Intent captureIntent = ((MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE)) .createScreenCaptureIntent(); startActivityForResult(captureIntent, 101); Button button=findViewById(R.id.button2); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (Settings.canDrawOverlays(MainActivity.this)) { startService(new Intent(MainActivity.this, FloatingBallService.class)); // ✅ 自动退到后台(模拟 Home 键) Intent homeIntent = new Intent(Intent.ACTION_MAIN); homeIntent.addCategory(Intent.CATEGORY_HOME); homeIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(homeIntent); } else { // 引导用户授权 Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName())); startActivity(intent); } } }); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == 101 && resultCode == RESULT_OK) { Intent service = new Intent(this, FloatingBallService.class); service.putExtra("code", resultCode); service.putExtra("data", data); startService(service); } } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == REQUEST_STORAGE_PERMISSION) { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { // 用户授予了权限,继续执行创建数据库的操作 createDatabase(); } else { // 用户拒绝了权限,提示用户 Log.e("MainActivity", "存储权限被拒绝"); Toast.makeText(this, "存储权限被拒绝,无法继续操作", Toast.LENGTH_SHORT).show(); } } } private void createDatabase() { // 创建数据库 dbHelper = new DatabaseHelper(this); Log.d("MainActivity", "数据库创建成功"); } } 2025-07-18 09:17:38.018 2569-2630 MiEvent system_server E report:open miev fail, EventId 901003004 -t 1752801458 -paraList {"ActivityName":"com.example.text.MainActivity","Duration":10126,"PackageName":"com.example.text"} ---------------------------- PROCESS ENDED (10638) for package com.example.text ---------------------------- 2025-07-18 09:17:40.058 4285-5371 ActivityManagerWrapper com.miui.home E getRecentTasks: mainTaskId=10275 userId=0 windowMode=1 baseIntent=Intent { act=android.intent.action.MAIN flag=270532608 cmp=ComponentInfo{com.example.text/com.example.text.MainActivity} } 2025-07-18 09:17:40.416 1425-1425 vendor.qti...al-service ven...qti.hardware.perf-hal-service E ExtendedPerfBoost: extended_perfboost_verification() 738: Ok, set boost params. hintid=4225 hinttype=1 prio=3, clientpid=2569 packagename=com.example.text params=boost:1 cpu0:1804800 cpu4:2496000 cpu7:2592000 cpumask:ff 2025-07-18 09:17:40.459 10872-10872 Zygote usap64 E process_name_ptr:10872 com.example.text 2025-07-18 09:17:40.523 27603-3228 SuggestManager com.miui.securitycenter.remote E openApp name = com.example.text 2025-07-18 09:17:40.534 27603-28288 AntiFraud com.miui.securitycenter.remote E hookDetectUnsafeAppStart error, android.content.pm.PackageManager$NameNotFoundException: com.example.text at android.app.ApplicationPackageManager.getApplicationInfoAsUser(ApplicationPackageManager.java:515) at android.app.ApplicationPackageManager.getApplicationInfo(ApplicationPackageManager.java:498) at android.app.ApplicationPackageManager.getApplicationInfo(ApplicationPackageManager.java:492) at w2.b.f(Unknown Source:12) at w2.b$c.onForegroundInfoChanged(Unknown Source:21) at com.miui.gamebooster.mutiwindow.b$a.onForegroundInfoChanged(Unknown Source:152) at miui.process.IForegroundInfoListener$Stub.onTransact(IForegroundInfoListener.java:86) at android.os.Binder.execTransactInternal(Binder.java:1351) at android.os.Binder.execTransact(Binder.java:1282) 2025-07-18 09:17:40.546 8279-10833 SuggestManager com.miui.securitycenter.remote E openApp name = com.example.text 2025-07-18 09:17:40.549 4285-5371 ActivityManagerWrapper com.miui.home E getRecentTasks: mainTaskId=10276 userId=0 windowMode=1 baseIntent=Intent { act=android.intent.action.MAIN flag=268435456 cmp=ComponentInfo{com.example.text/com.example.text.MainActivity} } ---------------------------- PROCESS STARTED (10872) for package com.example.text ----------------------------
最新发布
07-19
<think>我们正在解决一个Android应用中的问题,具体涉及MainActivity.java中的权限请求、数据库创建和FloatingBallService启动逻辑。目标是通过修改代码避免崩溃并提升稳定性。 1. 权限请求:需要确保在运行时请求必要的权限,特别是Android 6.0(API 23)及以上版本的危险权限。 2. 数据库创建:确保数据库创建或访问时不会因为文件路径、权限等问题导致崩溃。 3. FloatingBallService启动:作为前台服务启动时,需要处理Android 8.0(API 26)及以上版本的前台服务限制,并避免因服务未正确启动而导致的崩溃。 我们将分别对这三个方面进行优化: 一、权限请求优化 在MainActivity中,我们需要在适当的时机请求权限,并处理权限请求的结果。通常,在应用启动时(如onCreate)检查所需权限,如果缺少则请求。 二、数据库创建优化 使用SQLiteOpenHelper来管理数据库创建和版本升级,确保数据库操作不会在主线程中进行(避免ANR),并正确处理可能发生的异常。 三、FloatingBallService启动优化 启动前台服务需要提供一个通知,并且从Android 9(API 28)开始,还需要在前台服务中声明FOREGROUND_SERVICE权限。同时,要确保服务在Android 8.0及以上版本使用startForegroundService()启动,然后立即在服务中调用startForeground()。 下面我们分别给出代码示例: 一、权限请求 假设我们需要请求悬浮窗权限(SYSTEM_ALERT_WINDOW)和存储权限(WRITE_EXTERNAL_STORAGE)。注意:SYSTEM_ALERT_WINDOW是特殊权限,需要单独处理。 步骤: 1. 检查权限 2. 如果没有权限,则请求 3. 处理权限请求结果 二、数据库创建 使用SQLiteOpenHelper,并在后台线程中执行数据库操作。 三、启动FloatingBallService 注意:从Android 8.0开始,启动前台服务需要先调用startForegroundService(),然后在服务中5秒内调用startForeground()。 具体代码修改如下:</think>### 优化 Android MainActivity 权限请求、数据库创建与服务启动逻辑 #### 1. **权限请求优化** **问题**:Android 6.0+ 需要运行时权限请求,直接调用敏感操作会导致崩溃 **解决方案**:分阶段请求权限并处理拒绝场景 ```java // MainActivity.java private static final int PERMISSION_REQUEST_CODE = 100; private static final String[] REQUIRED_PERMS = { Manifest.permission.SYSTEM_ALERT_WINDOW, // 悬浮窗权限 Manifest.permission.READ_EXTERNAL_STORAGE // 存储权限 }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 阶段1:检查基础权限 if (checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { requestPermissions(REQUIRED_PERMS, PERMISSION_REQUEST_CODE); } else { initDatabase(); // 权限已授予才初始化数据库 } } // 处理权限请求结果 @Override public void onRequestPermissionsResult(int code, String[] perms, int[] results) { super.onRequestPermissionsResult(code, perms, results); if (code == PERMISSION_REQUEST_CODE) { if (results.length > 0 && results[0] == PackageManager.PERMISSION_GRANTED) { initDatabase(); } else { // 优雅处理权限拒绝 new AlertDialog.Builder(this) .setTitle("需要权限") .setMessage("存储权限是必要功能,请前往设置开启") .setPositiveButton("去设置", (d, w) -> openAppSettings()) .show(); } } } // 打开应用设置界面 private void openAppSettings() { Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); intent.setData(Uri.parse("package:" + getPackageName())); startActivity(intent); } ``` #### 2. **数据库创建优化** **问题**:主线程直接创建数据库可能导致 ANR **解决方案**:异步初始化 + 异常处理 ```java // MainActivity.java private void initDatabase() { // 使用 AsyncTask 避免主线程阻塞 new AsyncTask<Void, Void, Boolean>() { @Override protected Boolean doInBackground(Void... voids) { try { AppDatabase db = Room.databaseBuilder( getApplicationContext(), AppDatabase.class, "app_db" ).build(); return true; } catch (Exception e) { Log.e("DB_ERROR", "数据库创建失败", e); return false; } } @Override protected void onPostExecute(Boolean success) { if (success) { startFloatingService(); // 数据库就绪后启动服务 } else { Toast.makeText(MainActivity.this, "数据库初始化失败", Toast.LENGTH_LONG).show(); } } }.execute(); } ``` #### 3. **悬浮球服务启动优化** **问题**:Android 8.0+ 需前台服务通知,直接启动可能被系统限制 **解决方案**:兼容性封装 + 服务状态检查 ```java // MainActivity.java private void startFloatingService() { Intent serviceIntent = new Intent(this, FloatingBallService.class); // 检查服务是否已在运行 if (!isServiceRunning(FloatingBallService.class)) { try { // Android 8.0+ 使用 startForegroundService if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { startForegroundService(serviceIntent); } else { startService(serviceIntent); } } catch (SecurityException e) { Log.e("SERVICE_ERROR", "服务启动权限不足", e); handleServiceStartFailure(); } } } // 检查服务运行状态 private boolean isServiceRunning(Class<?> serviceClass) { ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE); for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) { if (serviceClass.getName().equals(service.service.getClassName())) { return true; } } return false; } private void handleServiceStartFailure() { // 1. 检查悬浮窗权限 if (!Settings.canDrawOverlays(this)) { Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName())); startActivityForResult(intent, OVERLAY_PERMISSION_CODE); } // 2. 检查电池优化白名单 else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { Intent intent = new Intent(); intent.setAction(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS); startActivity(intent); } } ``` #### 4. **完整生命周期整合** ```java // MainActivity.java @Override protected void onResume() { super.onResume(); // 返回时重新检查权限状态 if (checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { startFloatingService(); } } // 处理悬浮窗权限返回结果 @Override protected void onActivityResult(int code, int result, Intent data) { super.onActivityResult(code, result, data); if (code == OVERLAY_PERMISSION_CODE && Settings.canDrawOverlays(this)) { startFloatingService(); // 权限授予后重试 } } ``` #### 5. **关键配置补充** **AndroidManifest.xml 必须声明**: ```xml <!-- 前台服务权限 --> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <!-- 悬浮窗权限 --> <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> <!-- 服务声明 --> <service android:name=".FloatingBallService" android:foregroundServiceType="mediaPlayback" <!-- 按实际类型选择 --> android:enabled="true" android:exported="false" /> <!-- 非公开服务 --> ``` #### 6. **稳定性增强技巧** 1. **数据库防御编程**: ```java @Database(entities = {User.class}, version = 1, exportSchema = false) public abstract class AppDatabase extends RoomDatabase { private static volatile AppDatabase INSTANCE; public static AppDatabase getInstance(Context context) { if (INSTANCE == null) { synchronized (AppDatabase.class) { if (INSTANCE == null) { INSTANCE = Room.databaseBuilder( context.getApplicationContext(), AppDatabase.class, "app_db" ).fallbackToDestructiveMigration() // 崩溃时重建 .build(); } } } return INSTANCE; } } ``` 2. **服务启动重试机制**: ```java private void safeStartService(Intent intent) { try { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { startForegroundService(intent); } else { startService(intent); } } catch (IllegalStateException e) { // 后台限制时延迟重试 new Handler(Looper.getMainLooper()).postDelayed(() -> safeStartService(intent), 2000); } } ``` 3. **内存泄漏预防**: ```java @Override protected void onDestroy() { // 解绑所有回调引用 floatingBallView.setCallback(null); super.onDestroy(); } ``` > **最佳实践**:在 `Application` 类中初始化全局组件,`MainActivity` 只负责 UI 交互和权限处理[^1]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值