安卓服务与多线程

Android 服务与多线程编程详解

一、服务基本概念

服务(Service)是Android中实现程序后台运行的重要组件,它适合执行不需要与用户交互且需要长期运行的任务。服务有以下特点:

  1. 独立于UI:服务运行不依赖于任何用户界面,即使程序被切换到后台也能正常运行
  2. 进程依赖:服务依赖于创建它的应用程序进程,当应用进程被杀死时,所有依赖该进程的服务都会停止
  3. 主线程运行:服务不会自动开启线程,所有代码默认在主线程执行,需要手动创建子线程处理耗时任务

二、Android多线程编程

Android的多线程编程与Java基本相同,创建线程的方式也类似:

java 复制代码
class MyThread extends Thread {
    @Override
    public void run() {
        // 线程执行的代码
    }
}

// 启动线程
new MyThread().start();

但需要注意Android的UI线程模型:

1. UI线程模型

Android采用单线程模型处理UI操作,主线程(也称为UI线程)负责:

  • 绘制和更新界面
  • 处理用户交互事件
  • 执行Activity生命周期回调方法

2. 子线程更新UI的问题

直接在子线程中更新UI会导致异常:

java 复制代码
// 错误示例:子线程更新UI
new Thread(() -> {
    textView.setText("更新内容"); // 会抛出CalledFromWrongThreadException
}).start();

这是因为Android的UI组件不是线程安全的,所有UI操作必须在主线程执行。

三、异步消息处理机制

Android提供了异步消息处理机制来解决子线程与UI线程通信的问题,主要由4个部分组成:

  1. Message:线程间传递的消息,可携带少量数据
  2. Handler:消息的处理者,负责发送和处理消息
  3. MessageQueue:消息队列,存放所有通过Handler发送的消息
  4. Looper:消息循环器,不断从MessageQueue中取出消息并分发给Handler

1. 完整使用示例

java 复制代码
public class MainActivity extends AppCompatActivity {
    public static final int UPDATE_TEXT = 1;
    private TextView textView;
    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case UPDATE_TEXT:
                    textView.setText("更新后的内容");
                    break;
                // 可以处理更多消息类型
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        textView = findViewById(R.id.textView);
        Button button = findViewById(R.id.button);
        button.setOnClickListener(v -> {
            // 创建子线程执行耗时操作
            new Thread(() -> {
                // 模拟耗时操作
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                
                // 发送消息到主线程更新UI
                Message message = new Message();
                message.what = UPDATE_TEXT;
                handler.sendMessage(message);
            }).start();
        });
    }
}

2. 更简洁的实现方式 - Runnable

除了使用Message,还可以直接传递Runnable对象:

java 复制代码
// 在主线程创建Handler
Handler handler = new Handler(Looper.getMainLooper());

// 在子线程中
new Thread(() -> {
    // 耗时操作...
    
    // 在主线程执行UI更新
    handler.post(() -> {
        textView.setText("更新后的内容");
    });
}).start();

四、服务详解

1. 定义服务

服务需要继承Service类并实现关键方法:

java 复制代码
public class MyService extends Service {
    private static final String TAG = "MyService";
    
    @Override
    public void onCreate() {
        super.onCreate();
        Log.d(TAG, "服务创建了");
        // 初始化操作
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d(TAG, "服务启动了");
        // 执行后台任务
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d(TAG, "服务销毁了");
        // 释放资源
    }

    @Override
    public IBinder onBind(Intent intent) {
        // 返回Binder对象用于Activity与Service通信
        return null;
    }
}

2. 服务的生命周期

  • onCreate():服务第一次创建时调用
  • onStartCommand():每次通过startService()启动服务时调用
  • onBind():通过bindService()绑定服务时调用
  • onUnbind():所有客户端解除绑定时调用
  • onDestroy():服务销毁时调用

3. 启动和停止服务

启动服务

java 复制代码
Intent intent = new Intent(this, MyService.class);
startService(intent); // 启动服务

停止服务

java 复制代码
Intent intent = new Intent(this, MyService.class);
stopService(intent); // 停止服务

绑定服务

java 复制代码
private ServiceConnection connection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        // 获取Binder对象
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        // 服务异常断开
    }
};

Intent intent = new Intent(this, MyService.class);
bindService(intent, connection, Context.BIND_AUTO_CREATE);

解除绑定

java 复制代码
unbindService(connection);

五、活动与服务通信

1. 使用Binder实现通信

  1. 在Service中创建Binder子类:
java 复制代码
public class MyService extends Service {
    private DownloadBinder mBinder = new DownloadBinder();
    
    class DownloadBinder extends Binder {
        public void startDownload() {
            Log.d(TAG, "开始下载");
        }
        
        public int getProgress() {
            Log.d(TAG, "获取下载进度");
            return 0;
        }
    }
    
    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }
}
  1. 在Activity中绑定服务并获取Binder:
java 复制代码
private MyService.DownloadBinder downloadBinder;

private ServiceConnection connection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        downloadBinder = (MyService.DownloadBinder) service;
        downloadBinder.startDownload();
        downloadBinder.getProgress();
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
    }
};

// 绑定服务
Intent intent = new Intent(this, MyService.class);
bindService(intent, connection, Context.BIND_AUTO_CREATE);

2. 服务的销毁规则

必须同时调用stopService()unbindService()才能完全销毁服务:

  • 如果只调用stopService(),但仍有客户端绑定,服务不会销毁
  • 如果只调用unbindService(),但服务是通过startService()启动的,服务也不会销毁

六、前台服务

前台服务比普通服务具有更高的优先级,系统更不容易杀死它,适合执行重要任务。

1. 创建前台服务

  1. 在AndroidManifest.xml中声明权限:
xml 复制代码
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<!-- Android 13+ 需要 -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
  1. 在Service中创建通知并启动前台服务:
java 复制代码
@Override
public void onCreate() {
    super.onCreate();
    
    // 创建通知渠道(Android 8.0+)
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        NotificationChannel channel = new NotificationChannel(
            "channel_id", 
            "Channel Name", 
            NotificationManager.IMPORTANCE_DEFAULT
        );
        NotificationManager manager = getSystemService(NotificationManager.class);
        manager.createNotificationChannel(channel);
    }
    
    // 创建PendingIntent
    Intent intent = new Intent(this, MainActivity.class);
    PendingIntent pendingIntent = PendingIntent.getActivity(
        this, 
        0, 
        intent, 
        PendingIntent.FLAG_IMMUTABLE
    );
    
    // 创建通知
    Notification notification = new NotificationCompat.Builder(this, "channel_id")
        .setContentTitle("前台服务")
        .setContentText("服务正在运行")
        .setSmallIcon(R.drawable.ic_notification)
        .setContentIntent(pendingIntent)
        .build();
    
    // 启动前台服务
    startForeground(1, notification); // 1是通知ID
}
  1. 启动前台服务:
java 复制代码
// Android 9.0+ 需要使用startForegroundService()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    startForegroundService(intent);
} else {
    startService(intent);
}

2. Android 13+的通知权限

从Android 13开始,需要动态申请通知权限:

java 复制代码
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
    ActivityCompat.requestPermissions(
        this,
        new String[]{Manifest.permission.POST_NOTIFICATIONS},
        1
    );
}

七、IntentService

IntentService是Service的子类,它解决了Service不能自动创建线程的问题:

  1. 自动创建子线程处理任务
  2. 任务执行完毕后自动停止服务

1. 创建IntentService

java 复制代码
public class MyIntentService extends IntentService {
    public MyIntentService() {
        super("MyIntentService"); // 指定线程名称
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        // 在这里执行耗时操作
        // 这个方法运行在子线程中
    }
}

2. 使用IntentService

java 复制代码
Intent intent = new Intent(this, MyIntentService.class);
startService(intent); // 启动服务,会自动创建线程执行onHandleIntent()

八、总结

  1. 服务:适合执行后台任务,但需要注意生命周期管理和资源释放
  2. 多线程:Android UI操作必须在主线程执行,耗时操作应在子线程执行
  3. 通信机制:使用Handler、Binder等方式实现线程间和Activity-Service间通信
  4. 前台服务:适合重要任务,需要创建通知并处理权限问题
  5. IntentService:简化了Service的使用,自动处理线程和生命周期

通过合理使用服务和多线程技术,可以构建出响应迅速、功能完善的Android应用。

相关推荐
xiangxiongfly9151 小时前
Android 圆形和圆角矩形总结
android·圆形·圆角·imageview
幻雨様7 小时前
UE5多人MOBA+GAS 45、制作冲刺技能
android·ue5
Jerry说前后端8 小时前
Android 数据可视化开发:从技术选型到性能优化
android·信息可视化·性能优化
Meteors.9 小时前
Android约束布局(ConstraintLayout)常用属性
android
alexhilton10 小时前
玩转Shader之学会如何变形画布
android·kotlin·android jetpack
whysqwhw14 小时前
安卓图片性能优化技巧
android
风往哪边走14 小时前
自定义底部筛选弹框
android
Yyyy48215 小时前
MyCAT基础概念
android
Android轮子哥15 小时前
尝试解决 Android 适配最后一公里
android
雨白16 小时前
OkHttp 源码解析:enqueue 非同步流程与 Dispatcher 调度
android