Android第一行代码--Service(四大组件之一)

Android第一行代码-Service

Android支持后台功能,这使得应用程序即使在关闭的情况下仍然可以在后台继续运行。

服务是什么

非常适合执行那些不需要和用户交互而且需要长时间运行的任务。

服务并不是运行在一个独立的进程中,而是依赖于创建服务时所在的应用程序进程。当某个应用程序进程被杀掉时,所有依赖于该进程的服务也会停止。

服务并不会自动开启线程,所有的代码都是默认运行在主线程中,也就是说,我们需要在服务的内部手动创建子线程,并且在这里执行具体的任务,否则就有可能出现主线程被阻塞的情况。

Android多线程编程

线程的基本用法

  • 1、继承Thread类,实现run方法
  • 2、实现Runnable接口,实现里面的run方法。

在子线程中更新UI

Android的UI和其他许多GUI库一样,也是线程不安全的。有就是说,如果想要更新应用程序里的UI元素,则必须在主线程中进行,否则就会出现异常。

package com.example.androidthreadtest;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private TextView text;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        text = (TextView) findViewById(R.id.text);
        Button button = (Button) findViewById(R.id.change_text);
        button.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.change_text:
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        text.setText("Nice to meet you");
                    }
                }).start();
                break;
            default:
                break;
        }
    }
}

android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
当运行上面的代码时会报错,这是因为在子线程中更新UI导致的。

但是有时候必须要在子线程中执行一些耗时任务,然后根据任务的执行结果来更新相应的UI控件,这该怎么解决呢?

对于这种情况,Android提供了一套异步消息处理机制,完美解决了在子线程中进行UI操作的问题。

通过异步消息处理机制解决在子线程中更新UI导致的报错问题
package com.example.androidthreadtest;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private TextView text;
    public static final int UPDATE_TEXT = 1;

    private Handler handler = new Handler(){
        @Override
        public void handleMessage(@NonNull Message msg) {
            switch (msg.what){
                case UPDATE_TEXT:
                    text.setText("Nice to meet you");
                    break;
                default:
                    break;
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        text = (TextView)findViewById(R.id.text);
        Button changeText = (Button)findViewById(R.id.change_text);
        changeText.setOnClickListener(this);

    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.change_text:
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        Message message = new Message();
                        message.what = UPDATE_TEXT;
                        handler.sendMessage(message);
                    }
                }).start();
                break;
            default:
                break;
        }
    }
}

解析异步消息处理机制

Android中的异步消息处理主要分为四个部分:

  • 1、Message
  • 2、Handler
  • 3、MessageQueue
  • 4、Looper
1、Message(传递消息)

用于在线程间传递消息,可以携带少量的信息,用于在不同线程之间交换数据。除此之外,Message还有arg1和arg2字段来携带一些整型数据,obj属性来携带一些Object对象。

2、Handler(处理者,用于发送和处理消息,发出的消息最终会传递到Handler的handleMessage方法中)

用于发送和处理消息。发送消息一般是使用sendMessage方法,而发出的消息经过一系列辗转处理后,最终会传递到Handler的handleMessage方法中。

3、MessageQueue(消息队列)

用于存放Handler发送的消息。这部分消息会一直存在于消息队列中,等待被处理,每个线程只会有一个MessageQueue对象。

4、Looper(MessageQueue的管家,MessageQueue中的消息取出传递给handleMessage方法中)

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

消息处理过程
  • 1、创建Handler对象,重写handleMessage方法
  • 2、当子线程需要进行UI操作时,创建Message对象,通过Handler将消息传递出去。
  • 3、将消息添加到MessageQueue
  • 4、Looper不断尝试从MessageQueue取出待处理消息,最后发回Handler的handleMessage方法中。

由于Handler是主线程中创建的,所以此时handleMessage方法的代码也会在主线程中运行,于是就可以安心进行UI操作。

在这里插入图片描述

AsyncTask

通过AsyncTask,即使你对异步消息处理机制完全不了解,也可以十分简单从子线程切换到主线程。AsyncTask背后的实现原理也是基于异步消息处理机制的,知识Android帮我们做了很好的封装而已。

使用AsyncTask(抽象类)

如果要使用AsyncTask,就需要创建一个子类去继承它,在继承时,我们就可以为AsyncTask类指定三个泛型参数。

  • 1、Params:doInBackground函数的参数类型,在执行AsyncTask时需要传入的参数,可用于在后台任务中使用
  • 2、Progress:onProgressUpdate函数的参数类型,后台任务执行时,如果需要在界面上显示当前的进度,则使用这里指定的泛型作为进度单位
  • 3、Result:onPostExecute函数的参数类型,当任务执行完毕后,如果需要对结果进行返回,则使用这里指定的泛型作为返回值类型。
class DownloadTask extends AsyncTask<Void,Integer,Boolean>{
}
  • Void表示不需要传入参数给后台任务
  • Integer表示使用整型数据作为进度显示单位
  • Boolean表示使用Boolean数据来反馈执行结果

需要重写AsyncTask中的几个方法才能完成对任务的定制,经常需要去重写的方法有4个:

  • 1、onPreExecute():这个方法在任务开始执行之前调用,用于进行一些界面上的初始化操作,比如显示一个进度条对话框。
  • 2、doInBackground(Params…):这个方法中的所有代码都会在子线程中运行,我们需要在这里去处理所有的耗时任务。任务一旦完成就可以通过return语句来将任务的执行结果返回。如果AsyncTask的第三个泛型参数指定的是Void,就可以不返回任务执行结果。在这个方法中是不可以进行UI操作的,如果需要更新UI元素,比如说反馈当前任务的执行进度,可以调用publishProgress(Progress…)方法来完成。
  • 3、onProgressUpdate(Progress…):当在后台任务中调用了publishProgress(Progress…)方法后,onProgressUpdate(Progress…)方法就会很快被调用,该方法中携带的参数就是在后台任务中传递过来的,在这个方法中可以对UI进行操作,利用参数中的数值就可以对界面元素进行相应的更新
  • 4、onPostExecute(Result):当后台任务执行完毕并通过return语句进行返回时,这个方法就很快会被调用。返回的数据会作为参数传递到此方法中。可以利用返回的数据进行一些UI操作,比如说提醒任务执行的结果以及关闭进度条对话框等。
class DownloadTask extends AsyncTask<Void,Integer,Boolean> {
    @Override
    protected void onPreExecute() {
        progressDialog.show(); //显示进度对话框
    }

    @Override
    protected Boolean doInBackground(Void... Params) {
        try{
            while (true){
                int downloadPercent = doDownload(); //这是一个虚构的方法
                publishProgress(downloadPercent); //传入当前的下载进度,这样onProgressUpdate()方法就会很快被调用。
                if (downloadPercent >= 100){
                    break;
                }
            }
        }catch (Exception e){
            return false;
        }
        return true;
    }

    @Override
    protected void onProgressUpdate(Integer... values) {
        progressDialog.setMessage("Downloaded"+values[0]+"%");
    }

    @Override
    protected void onPostExecute(Boolean result) {
        progressDialog.dismiss(); //关闭进度条对话框
        if (result){
            Toast.makeText(context,"Download succeed",Toast.LENGTH_SHORT).show();
        }else{
            Toast.makeText(context,"Download failed",Toast.LENGTH_SHORT).show();
        }
    }
}

简单来说,使用AsyncTask的诀窍就是在DoInBackground()方法中执行具体的耗时任务,在onProgressUpdate()方法中进行UI操作,在onPostExecute()方法中执行一些任务的收尾工作。

如果想要调动这个任务,只需要编写以下代码即可,new DownloadTask().execute();

服务的基本用法

1、定义一个服务(继承Service类)

通过New–》Service–》Service可以定义一个服务。

在创建时Exported表示是否允许除了当前程序之外的其他程序访问这个服务。Enable表示是否启动这个服务。

既然是定义一个服务,自然应该在服务中去处理一些事情,那处理事情的逻辑应该写在哪里呢?需要重写Service中的一些方法。如下所示:

  • onCreate():在服务创建的时候调用
  • onStartCommand():在服务启动的时候调用
  • onDestroy():在服务销毁的时候调用

每个服务都需要在AndroidManifest.xml中进行注册才能生效,通过Android Studio创建Service会自动在AndroidManifest文件中进行注册。

<service
   android:name=".MyService"
    android:enabled="true"
    android:exported="true"></service>

启动和停止服务(主要借助Intent来实现)

完全是由活动决定服务何时停止。

例子:
MyService:

package com.example.servicetest;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;

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

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

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

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d("MyService","onStartCommand executed");
        return super.onStartCommand(intent, flags, startId);
    }

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

MainActivity:

package com.example.servicetest;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button startService = (Button)findViewById(R.id.start_service);
        Button stopService = (Button)findViewById(R.id.stop_service);
        startService.setOnClickListener(this);
        stopService.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.start_service:
                Intent startIntent = new Intent(this,MyService.class);
                //startService()和stopService()方法都是定义在Context类中的,所以我们在活动中可以直接调用这两个方法
                startService(startIntent); //启动服务
                break;
            case R.id.stop_service:
                Intent stopIntent = new Intent(this,MyService.class);
                stopService(stopIntent); //停止服务
                break;
            default:
                break;
        }
    }
}

服务在活动中启动的

服务如果启动成功,可以在Settings->Developer options->Running Service中找到它。

onCreate()和onStartCommand()方法的区别:onCreate()是在服务第一次创建的时候调用的,而onStartCommand()方法则在每次启动服务的时候都会调用。

活动和服务进行通信

有没有什么办法可以让活动和服务的关系更紧密呢?比如在活动中指挥服务去干什么,服务就去干什么。

  • 借助onBind()方法来实现

活动绑定服务,当一个活动和服务绑定了之后,就可以调用服务里的Binder提供的方法了。

MyService类

package com.example.servicetest;

import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;

public class MyService extends Service {

    private DownloadBinder mbinder = new DownloadBinder();
    class DownloadBinder extends Binder{
        public void startDownload(){
            Log.d("MyService","startDownload executed");
        }

        public int getProgress(){
            Log.d("MyService","getProgress executed");
            return 0;
        }

    }

    public MyService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        return mbinder;
    }

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

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d("MyService","onStartCommand executed");
        return super.onStartCommand(intent, flags, startId);
    }

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

MainActivity类

package com.example.servicetest;

import androidx.appcompat.app.AppCompatActivity;

import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    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) {

        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button bindService = (Button)findViewById(R.id.bind_service);
        Button unbindService = (Button)findViewById(R.id.unbind_service);
        bindService.setOnClickListener(this);
        unbindService.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.bind_service:

                Intent bindIntent = new Intent(this,MyService.class);
                ///将MainActivity和MyService进行绑定
                //BIND_AUTO_CREATE表示活动和服务进行绑定后自动创建服务
                //这会使得MyService中的onCreate方法得到执行,onStartCommand方法不会执行。
                bindService(bindIntent,connection,BIND_AUTO_CREATE); //绑定服务
                break;
            case R.id.unbind_service:
                unbindService(connection); //解绑服务
                break;
            default:
                break;
        }
    }
}

服务的生命周期

  • 一旦在任何位置调用了Context的startService方法,相应的服务就会启动起来。并回调onStartCommand方法,若之前服务没创建,就会在调用onStartCommand之前调用onCreate方法。
  • 虽然每调用一次startService方法就会调用一次onStartCommand,但是实际上每个服务只会存在一个实例。所以不管调用多少次startService,只需要调用一次stopService,服务就会停止下来。
  • 调用Context的bindService来获取服务的持久连接,这时就会回调服务中的onBind方法。如果服务之前没创建国,onCreate会在onBind之前执行,之后调用方会获取到onBind方法中返回的IBinder对象的实例。这样就可以自由和服务进行通信

调用stopService,服务中的onDestroy方法就会执行,当调用bindService方法,又去调用unBindService方法,onDestroy方法也会执行。

如果当服务绑定和执行了,必须停止服务和解绑都调用,onDestroy方法才会执行。

服务的更多技巧

使用前台服务(前台服务会一直有一个正在运行的图标在系统状态栏显示)

服务的优先级比较低,当系统出现内存不足的时候,就会回收掉正在后台运行的服务,如果你想服务一直保持运行,可以考虑前台服务。

public class MyService extends Service {

    @Override
    public void onCreate() {
        super.onCreate();
        Log.d("MyService","onCreate executed");
        Intent intent = new Intent(this,MainActivity.class);
        PendingIntent pi = PendingIntent.getActivity(this,0,intent,0);
        Notification notification = new NotificationCompat.Builder(this)
                .setContentTitle("This is content title")
                .setContentText("This is content text")
                .setWhen(System.currentTimeMillis())
                .setSmallIcon(R.mipmap.ic_launcher)
                .setLargeIcon(BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher))
                .setContentIntent(pi)
                .build();
        startForeground(1,notification);
    }
}

代码和之前在第8章中学习的创建停止的方法类似,只不过这次在创建出Notification对象后并没有使用NotificationManager来将通知显示出来,而是调用了startForeground方法,调用该方法就会让MyService变成一个前台服务,并在系统状态栏显示出来。

使用IntentService

服务中的代码都是默认运行在主线程中,如果直接在服务里去处理一些耗时的逻辑,就很容易出现ANR(Application Not Responding)的情况。应该使用多线程去处理这些耗时的任务,这是就需要使用IntentService。

根据IntentService的特性,服务在运行结束之后会自动停止。

MyIntentService:

package com.example.servicetest;

import android.app.IntentService;
import android.content.Intent;
import android.util.Log;

import androidx.annotation.Nullable;

public class MyIntentService extends IntentService {

    public MyIntentService(){
        super("MyIntentService"); //调用父类的有参构造函数
    }

    //这个方法已经在子线程中运行,不用担心出现ANR的问题。
    @Override
    protected void onHandleIntent(@Nullable Intent intent) {
        //打印当前线程的id
        Log.d("MyIntentService","Thread id is"+Thread.currentThread().getId());
    }

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

MainActivity:

package com.example.servicetest;

import androidx.appcompat.app.AppCompatActivity;

import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {



    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button startIntentService = (Button)findViewById(R.id.start_intenr_service);
        startIntentService.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.start_intenr_service:
                //打印主线程的id
                Log.d("MainActivity","Thread id is"+Thread.currentThread().getId());
                Intent intentService = new Intent(this,MyIntentService.class);
                startService(intentService);
                break;
            default:
                break;
        }
    }
}

在AndroidManifest.xml中注册服务

<service android:name=".MyIntentService" />
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值