安卓学习之后台服务

安卓多线程

先从线程开始学起

线程的基本用法
  • 用Thread

跟java的用法一样,比如说,定义一个线程只需要新建一个类继承自Thread,然后重写父类的run()方法,并在里面编写耗时逻辑即可。

class MyThread extends Thread{
        @Override
        public void run() {
            //处理具体的逻辑
        }
    }

启动就更简单了,只需要new出线程的实例,然后调用它的start()方法

new MyThread().start();
  • 用Runnable接口
class MyThread implements Thread{
        @Override
        public void run() {
            //处理具体的逻辑
        }
    }

使用这种写法,那么启动线程的方法就会改变

MyThread mythread=new MyThread();
new Thread(mythread).start();/

可以看到,Thread构造函数接收了Runnable参数,而我们new出的MyThread正是一个实现了Runnable接口的对象,所以可以将他传入Thread的构造函数中,然后调用start()方法。

  • 也可以使用匿名类的方式
new Thread(new Runnable() {
            @Override
            public void run() {
                
            }
        }).start();
在子线程中更新UI

在安卓中,如果想更新程序的组件,则必须在主线程进行,否则将出现异常,下面我用一个实例来演示一遍。

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/change_text"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="改变文字"/>
    <TextView
        android:id="@+id/text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:text="你好世界"
        android:textSize="20sp"/>

</RelativeLayout>
public class MainActivity extends AppCompatActivity {

    private TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textView=(TextView)findViewById(R.id.text);
        Button button=(Button)findViewById(R.id.change_text);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        textView.setText("你也好");
                    }
                }).start();
            }
        });
    }
}

好了,我们运行…
在这里插入图片描述
接着去看错误日志,可以看出是在子线程进行ui所导致的错误。

android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

但是当我们必须在子线程里去执行一些耗时任务,然后根据任务的执行结果来更新相应的ui控件,怎么办呢?
安卓很好的为我们提供了一套异步消息处理机制,完美解决在子线程中进行ui操作的问题。

使用方法
修改MainActivity的代码

public class MainActivity extends AppCompatActivity {

    private  static final int UPDATE_TEXT=1;//定义一个常量

    private TextView textView;

    private Handler handler=new Handler(){//新增一个handler对象
        @Override
        public void handleMessage(Message msg) {//重写handleMessage方法,对message进行处理
            if (msg.what==UPDATE_TEXT){//如果message的What字段等于UPDATE_TEXT,就执行
                    //在这里进行ui操作
                    textView.setText("你也好");
            }
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textView=(TextView)findViewById(R.id.text);
        Button button=(Button)findViewById(R.id.change_text);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        Message message=new Message();//创建message对象
                        message.what=UPDATE_TEXT;//将message的what字段的值改成UPDATE_TEXT
                        handler.sendMessage(message);//调用sendMessage()方法将这条message发送出去,然后Handler就会收到消息并执行,注意此时handlerMessafe()方法就是在主线程中运行的,所以我们可以放心的在这里进行ui操作
                    }
                }).start();
            }
        });
    }
}

在这里插入图片描述

Handler消息处理机制

主要由四个部分组成:Message,Handler,MessageQueue和Looper.

  1. Message

Message是在线程之间传递的信息,他可以在内部携带少量的信息,用于在不同线程之间交换数据,可以使用what字段,也可以使用arg1和arg2字段携带一些整型数据,使用obj字段携带object字段。

  1. Handler

Handler就是处理者的意思,主要用于发送和处理信息的。发送消息一般是使用Handler的sendMessage()方法,而发送的消息会传递到Handler的handlerMessage()方法中。

  1. MessageQueue

MessageQueue是消息队列的意思,它主要用于存放所有通过Handler发送的消息。等待被处理,每个线程中只会有一个MessageQueue对象。

  1. Looper

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

在这里插入图片描述
而runOnUiThread()方法其实就是一个异步消息处理机制的接口封装。

  • 用runOnUiThread直接更新ui
runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                       
                    }
                });
使用AsyncTask

为了我i们在子线程中对UI进行操作,安卓还提供了另外一个工具,借助AsyncTask,即使你对异步处理消息机制完全不了解,也可以十分简单的从子线程切换到主线程。

首先我们来看AsyncTask的基本用法,用于AsyncTask是一个抽象类,所以如果我们想使用他,必须要创建一个子类去继承他,在继承时我们可以为AsyncTask类指定3个泛型整数,

  • params。在执行AsyncTask时,需要传入的参数,可用于在后台任务中使用。
  • progress。后台任务执行时,如果需要对任务在界面显示当前的进度,则使用这里的指定的泛型作为进度单位
  • result。当任务执行完毕时,如果需要对结果进行返回。则使用这里指定的泛型作为返回值类型
class DownloadTask extends AsyncTask<Void, Integer,Boolean>{
  //这里我们把第一个泛型参数设置成void,表示在执行AsyncTask的时候不需要传入参数给后台服务
  //第二个人泛型参数指定为Integer,表示使用整型数据来作为进度显示单位
  //第三个泛型指定为Boolean,则表示使用布尔型数据来反馈执行结果
    }

现在的AsyncTask还是一个空任务,需要重写几个方法才能完成对任务的定制。经常重写的有以下四个

onPreExecute() 
//这个方法会在后台任务开始执行之前调用,用于进行一些界面上的初始化操作,比如显示进度条对话框等
   
onPostExecute(Boolean aBoolean)
/**这个方法所有代码都会在子线程中运行,我们应该在这里去处理所有的耗时任务。任务一旦完成就可以通过return语句来将任务的执行结果返回。
如果AsyncTask的第三个泛型参数为void,就可以不返回任务执行结果。
注意:在这个方法中是不可以进行ui操作的,如果需要更新ui元素,比如说反馈任务的执行进度,可以调用publishProgress(Progress..)来完成**/

 onProgressUpdate(Integer... values) 
 /**
 当在后台任务中调用了publishProgress(Progress...)方法后,onProgressUpdata(Progress..)方法就会很快被调用,
 该方法中携带的参数就是在后台任务中传递过来的。
 在这个方法中可以UI进行操作,利用参数中的数值就可以对界面元素进行相应的更新
**/

doInBackground(Void... voids) 
/**
当后台任务执行完毕时并通过return语句进行返回时,
这个方法就很快被调用。返回的数据会作为参数传递到此方法中,
可以利用返回的数据进行一些ui操作,比如说提醒任务的结果,以及关闭对话框等
**/

比如这个

    class DownloadTask extends AsyncTask<Void, Integer,Boolean>{
    
        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            progressDialog.show;//显示进度对话框
        }
        @Override
        protected Boolean doInBackground(Void... voids) {
            while (true){
                int downloadpercent = doDownload;//这是一个虚构的方法
                publishProgress(downloadpercent);
                if (downloadpercent>=100){
                    break;
                }
            }
            return true;
        }
        @Override
        protected void onProgressUpdate(Integer... values) {
            //在这里更新下载进度
            progressDialog.setMessage("Dowmload"+values[0]+"%");
        }

        @Override
        protected void onPostExecute(Boolean aBoolean) {
            //关闭进度提示框
            progressDialog.dismiss();
            //提示下载结果
            if (aBoolean){
                Toast.makeText(MainActivity.this, "下载完成", Toast.LENGTH_SHORT).show();
            }else {
                Toast.makeText(MainActivity.this, "没", Toast.LENGTH_SHORT).show();
            }
        }

服务的基本用法

定义一个服务

新建项目,然后右键,New-Service-Service

Exported属性表示允许除了当前程序之外的其他程序访问这个服务。Enabled属性表示是否启用这个服务。

然后重写一些方法。

 @Override
    public void onCreate() {//在服务创建时调用
        super.onCreate();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
    //每次服务启动时调用
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {//销毁时调用
        super.onDestroy();
    }

注意:
每一个服务都要在AndroidManifext.xml文件中注册才可以

 <service
            android:name=".MyService"
            android:enabled="true"
            android:exported="true"></service>
启动和停止服务
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/start_service"
        android:text="启动Service"/>
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/stop_service"
        android:text="停止Service"/>
    

</LinearLayout>

MainActivity中的代码


public class MainActivity extends AppCompatActivity implements View.OnClickListener {


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

    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.start_service:
                Intent intent = new Intent(this,MyService.class);
                startService(intent);//启动服务
                break;
            case R.id.stop_service:
                Intent intent1=new Intent(this,MyService.class);
                stopService(intent1);//停止服务
                break;
            default:
                break;
        }
    }
}

Service中代码


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", "创建服务 ");
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {//每次服务启动时调用
        Log.d("MyService", "启动服务");
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {//销毁时调用
        super.onDestroy();
        Log.d("MyService", "停止服务 ");
    }
}

运行成功
在这里插入图片描述

活动和服务进行通信

比如说我们希望在service里提供一个下载功能,然后活动中可以决定何时开始下载,以及随时查看下载进度。实现这个功能的思路是创建一个专门的Binder对象来对下载功能进行管理。修改MyService的代码

public class MyService extends Service {
    private DownloadBinder mBiner=new DownloadBinder();
    class DownloadBinder extends Binder{
        public void startDownload(){
            Log.d("MyService", "开始下载 ");
        }
        public int getProgress(){
            Log.d("MyService", "获取下载进度 ");
            return 0;
        }
    }
    

    @Override
    public IBinder onBind(Intent intent) {
        return mBiner;//返回mBiner实例
    }

		......
}

修改activity_mian中的代码添加绑定服务和取消服务的按钮

  <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/bind_service"
        android:text="绑定服务"/>
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/unbind_service"
        android:text="取消绑定服务"/>

MainActivity的方法


public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private MyService.DownloadBinder downloadBinder;
    private ServiceConnection connection=new ServiceConnection() {//创建ServiceConnection的实例。
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {//绑定成功时调用
            downloadBinder= (MyService.DownloadBinder) service;//向下转型。得到DownloadBinder的实例
            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 bind=findViewById(R.id.bind_service);
        Button unbind=findViewById(R.id.unbind_service);
        bind.setOnClickListener(this);
        unbind.setOnClickListener(this);

    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            .....
            case R.id.bind_service:
                Intent intent2 = new Intent(this,MyService.class);
                bindService(intent2,connection,BIND_AUTO_CREATE);
                //绑定服务 第一个参数是intent对象,第二个是ServiceConnection实例,第三个是则是标志位,这里的表示在活动和服务进行绑定后自动创建服务。
                break;
            case R.id.unbind_service:
                Intent intent3=new Intent(this,MyService.class);
                unbindService(connection);//解绑
                break;

            default:
                break;
        }
    }
}
使用前台服务
<!--android 9.0上使用前台服务,需要添加权限-->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

修改MyService的代码


public class MyService extends Service {
    ......
    @Override
    public void onCreate() {//在服务创建时调用
        super.onCreate();
        Log.d("MyService", "创建服务 ");
        /**
         * 下面的是通知栏
         * 不懂的可以去这里查看
         * https://blog.csdn.net/qq_36120465/article/details/105266387
         */
            String id = "my_channel_01";
            String name="我是渠道名字";
            NotificationManager notificationManager= (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
        Notification notification=null;
        if (Build.VERSION.SDK_INT>=Build.VERSION_CODES.O){
            NotificationChannel mchannel=new NotificationChannel(id,name,NotificationManager.IMPORTANCE_HIGH);
            notificationManager.createNotificationChannel(mchannel);
        }
        NotificationCompat.Builder builder=new NotificationCompat.Builder(this,id);
        Intent intent=new Intent(this, MainActivity.class);
        PendingIntent pi= PendingIntent.getActivity(this,0,intent,0);

        builder.setSmallIcon(R.mipmap.ic_launcher)
                .setContentTitle("通知标题")
                .setContentText("通知内容")
                .setAutoCancel(true)//点击通知是否可以移除
                .setContentIntent(pi);//跳转

        startForeground(1, builder.build());
        super.onCreate();

    }
		.......	
}
使用IntentService

因为Service中几个方法的回调都是在主线程中,如果使用Service执行特别耗时的操作,建议单独新建线程去操作,避免阻塞主线程(UI线程)。IntentService在内部帮我们新建的线程,执行完毕任务后会自动关闭,无需手动结束它。


public class MyIntentService extends IntentService {


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

    @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", "thread is stop");


    }
}

在MainActivity中


public class MainActivity extends AppCompatActivity implements View.OnClickListener {

........

    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.start_service:
                Log.d("MainActivity", "thread id is"+Thread.currentThread().getId());

                Intent intent = new Intent(this,MyIntentService.class);
                startService(intent);//启动服务
                break;

            default:
                break;
        }
    }
}

log日志

D/MainActivity: thread id is2
D/MyIntentService: thread id is307
D/MyIntentService: thread is stop

可以看到,不仅MyIntentService和MainActivity的线程id不同,而且onDestroy()方法也执行了,说明MyIntentService在运行完后确实自动停止了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值