Android多线程编程之Service

二、Android多线程编程

1.线程的基本用法

2.在子线程中更新UI

3.解析异步消息处理机制

 Android中异步消息处理主要由4个部分组成:Message、Handler、MessageQueue和Looper。

Message

Message是在线程之间传递的消息,可以在内部携带少量的信息,用于在不同线程之间传递数据。

一般有wha字段、用来携带整形数据的args1、args2和携带Object对象的obj字段。

Handler

Handler是处理者的意思,主要用于发送和处理消息。发送消息一般是使用Handler的sendessage()方法、post()方法等,发出的消息最终会传递到Handler的handleMessage()方法中。

MessageQueue

MessageQueue是消息队列的意思,主要用于存放所有通过Handler发送的消息。这部分消息会一直存在于消息队列中,的呢古代被处理。每个线程只有一个MessageQueue对象。

Looper

Looper是每个线程中的MessageQueue的管家,调用Looper的loop()方法后,就会进入一个ie无限循环中,然后每当发现MessageQueue中存在一条消息时,就会将它取出,并传递到Handler的handleMessage()方法中。每个线程只会有一个Looper对象。

异步消息处理的整个流程:

       首先需要在主线程中创建一个Handler对象,并重写handleMessage()方法。当子线程中需要进行UI操作时,就创建一个Message对象,并通过Handler将这条消息发送出去。之后这条消息会被添加到MessageQueue的队列中等待被处理,而Looper则会一直尝试从MessageQueue中取出待处理消息,最后分发回Handler的handleMessage()方法中。如果Handler的构造函数中我们传入了Looper.getMainLooper(),handleMessaeg()方法中的代码就会在主线程中运行。如下所示:

 

三、Service的基本用法

1.定义一个Service

new->Service->Service,会弹出如下窗口:

Exported属性表示是否将这个Service暴露给外部其他程序访问,Enabled属性表示是否启用这个Service 。

      每个Service中最常用的3个方法:

      onCreate()方法会在Service创建的时候调用,onStartCommand()方法会在每次Service启动的时候调用,onDestroy()方法会在Service销毁的时候调用。如下所示:

public class BaseService extends Service {
    public BaseService() {
    }

    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
    }
}

2.启动和停止Service

       Context类中定义了startService()方法和stopService()方法,直接在Activity中调用这两个方法,就可以启动和停止Service 。如下所示,定义了两个按钮分别设置启动和停止:

final Intent intent1 = new Intent(this,BaseService.class);

mStartServiceBtn.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        startService(intent1);
    }
});

mStopServiceBtn.setOnClickListener(new View.OnClickListener() {
     @Override
     public void onClick(View view) {
         stopService(intent1);
     }
});

注:onCreate()方法和onStartCommand()方法的区别:

onCreate()方法是在Service第一次创建的时候调用的,而onStartCommand()方法则在每次启动Service的时候都会调用。为了验证这个说法,可以连续多点击几次startService的按钮,加入日志打印即可看出。

3.Activity和Service进行通信

       虽然Service是在Activity里启动的,但是在启动了Service之后,Acitivity与Service基本就没有什么关系了,调用Service的onCreate()方法和onStartCommand()方法后,Service就会一直处于运行状态,但具体运行的是什么逻辑,Activity就无法控制了。

      比如说,如果希望在Service里提供一个下载功能,然后在Activity中决定何时开始下载,并可以岁时查看下载进度。实现这个功能的思路是创建一个专门的Binder对象来对下载功能进行管理,如下所示:

public class BaseService extends Service {

    private Binder mBinder = new DownloadBinder();

    static class DownloadBinder extends Binder {

        public void startDownload() {

        }

        public int getProgress() {
            return 0;
        }

    }

}

 然后在Activity中设置两个按钮分别用于绑定和取消绑定Service,当一个Activity和Service绑定了之后,就可以调用Service里的binder提供的方法了,如下所示:

public class AndroidThreadTestActivity extends AppCompatActivity {

    private Button mStartServiceBtn;
    private Button mStopServiceBtn;
    private BaseService.DownloadBinder mDownloadBinder = new BaseService.DownloadBinder();

    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            mDownloadBinder.startDownload();
            mDownloadBinder.getProgress();
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {

        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_thread);
        mStartServiceBtn = findViewById(R.id.startServiceBtn);
        mStopServiceBtn = findViewById(R.id.stopServiceBtn);

        final Intent intent1 = new Intent(this,BaseService.class);
        mStartServiceBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                bindService(intent1,connection, Context.BIND_AUTO_CREATE);
            }
        });
        mStopServiceBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                unbindService(connection);
            }
        });

    }

}

 onServiceConnected()方法会在Activity与Service成功绑定的时候调用,而onServiceDsiconnected()方法只有在Service的创建进程崩溃或者被杀掉的时候才会调用。

通过调用onServiceConnected()方法,可以获取DownloadBinder实例,然后调用任何public方法。

bindService()方法将Acitivity和Service进行绑定,该方法接收3个参数,第一个参数是Intent对象,第二个对象是ServiceConnection实例,第三个参数是标志位,BIND_AUTO_CREATE表示在Activity和Service进行绑定后自动创建Service,调用onCreate()方法,但是onStartCommand()方法不会执行。

四、Service的生命周期

1.启动Service

       一旦在项目的任何位置调用了Context的startService()方法,相应的Service就会自动启动,并回调onStartCommand()方法。如果这个Service之前还没有创建国,onCreate()方法会先于onStartCommand()方法执行。Service启动了之后会一直保持运行状态,直到stopService()或stopSelf()方法被调用,或者被系统回收。

        注:理论上来说,每调用一次startService()方法,onStartCommand()就会执行一次,但实际上每个Service只会存在一个实例。所以不管你调用了多少次startService()方法,只需调用一次stopService()或stopSelf()方法,Service就会停止。

2.建立连接

       另外,还可以调用Context的bindService()来获取一个Service的持久连接,这时就会回调Service中的onBind()方法。同样,如果这个Service还没有创建过,onCreae()方法会先于onBind()方法执行。之后,可以获取到onBind()方法里返回的IBinder对象的实例,这样就可以和Service进行通信了。只要连接没有断开,Service就会一直保持运行状态,直到被系统回收。

3.销毁

       当调用了startService()方法后,再去调用stopService()方法。这时Service中的onDestroy()方法就会执行,表示Service已经销毁了。同样的,当调用了bindService()方法后,再去调用unbindService()方法,onDestroy()方法也会执行。

       注:如果对一个Service既调用了startService()方法,又调用了bindService()方法,根据Android系统的机制,一个Service只要被启动或者被绑定了之后,就会处于运行状态,必须要让以上两种条件都不满足,Service才能被销毁。所以,这种情况必须同时调用stopService()和unbindService()方法,onDestroy()方法才会执行。

五、Service的更多技巧

1.使用前台Service

       从Android8.0系统开始,只有当应用保持在前台可见状态的情况下,Service才能保证稳定运行,一旦应用进入后台之后,Service随时都有可能被系统回收。如果希望Service能够一直保持运行状态,可以考虑使用前台Service。前台Service和普通Service最大的区别在于,一直会有一个正在运行的图标在系统的状态栏显示,下拉状态栏后可以看到更加详细的信息,类似于通知的效果。

       由于状态栏中一直有一个正在运行的图标,相当于我们的应用以另外一种形式保持在前台可见状态,所以系统不会倾向于回收前台Service。另外,用户也可以通过下拉状态栏清楚地指导当前什么应用正在与兴宁,因此也不存在某些恶意应用长期在后台偷偷占用收集资源的情况。

       使用前台Servcie,关键是调用了startForeground()方法,让Service变成一个前台Service,并在系统状态栏显示出来。这个方法接受两个参数:第一个参数是通知的id,类似notify方法的第一个参数;第二个参数是构建的Notification对象。如下所示:

@RequiresApi(api = Build.VERSION_CODES.O)
    @Override
    public void onCreate() {
        super.onCreate();
        Log.d(TAG,"onCreate executed");
        NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        NotificationChannel channel = new NotificationChannel("my-service","前台Service通知", NotificationManager.IMPORTANCE_DEFAULT);
        manager.createNotificationChannel(channel);
        Intent intent = new Intent(this, MainActivity.class);
        PendingIntent pi = PendingIntent.getActivity(this,0,intent,0);
        Notification notification = new NotificationCompat.Builder(this,"my_service")
                .setContentTitle("This is a content title")
                .setContentText("This is content text")
                .setContentIntent(pi)
                .build();
        startForeground(1,notification);
    }

        从Android9.0系统开始,使用前台Servcie必须在AndroidManifest.xml文件中进行权限声明。如下所示:

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

       以前台Service的模式启动服务后,在系统状态栏会显示一个通知图表,即使退出应用程序,Service也会一直处于运行状态,而且不用担心会被系统回收。Service对应的通知也会一直显示在状态栏上面。如果不希望程序一直运行,也可以选择手动杀掉应用,这样Service就会跟着一起停止运行了。

2.使用IntentService

       Service中的代码都是默认运行主线程当中的,如果直接在Service里处理一些耗时的逻辑,就很容易出现ANR的情况。

       所以一般情况下,应该在Service的每个具体的方法里开启一个子线程,然后在这里处理那些耗时的逻辑。但是这种Service一旦启动,就会一直处于运行状态,必须调用stopService()或stopSelf()反法,或者被系统回收,Service才会停止。实现一个Service在执行完毕后自动停止的功能,如下所示:

@Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                // TODO: 21-9-25 处理具体的逻辑
                stopSelf();
            }
        }).start();
        return super.onStartCommand(intent, flags, startId);
    }

上面这种写法并不复杂,但是有些时候总会忘记开启线程,或者忘记调用stopSelf()方法。为了可以简单地创建一个异步的、会自动停止的Service,Andriod专门提供了一个IntentService类,这个类就很好地解决了这些问题。如下所示:

public class MyIntentService extends IntentService {

    @Override
    protected void onHandleIntent(Intent intent) {
        //打印当前线程的id
        Log.d(TAG,"Thread id is: " + Thread.currentThread().getName());
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d(TAG,"onDestroy executed");
    }
}

为了验证这一说法,可以在UI设计一个按钮,用于启动IntentService,并打印线程id,如下所示:

mStartIntentServiceBtn = findViewById(R.id.startIntentServiceBtn);

final Intent intent = new Intent(this, MyIntentService.class);

mStartIntentServiceBtn.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
         Log.d(TAG, "Thread id is: " + Thread.currentThread().getName());
         startService(intent);
    }
});

在xml布局文件中设置:

<Button
        android:id="@+id/startIntentServiceBtn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Start IntentService"/>

注意别忘了在AndroidManifest.xml中声明,一般来说,使用AndroidStudio来创建一个Servcie,声明都会自动创建:

<service
            android:name=".MyIntentService"
            android:exported="false"></service>

最终的验证这里就不展示了,可以想象,IntentService的线程name和主线程的肯定不一样,并且,IntentService的onDestroy()方法也会触发。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值