Android开发 - 线程和服务

服务吧,在程序即便关闭的时候还是可以回后台运行,不搞情怀了。后台功能属于四大组件之一。

服务是Android中实现程序后台运行的解决方案,很适合执行不需要与用户交互而且长时间运行的任务。不依赖于任何UI,即便用户被切换到后台的时候,或者打开另一个程序的时候,服务仍然可以运行。
但是服务不是单独的进程,依赖于创建服务时所处的应用程序进程,当这个程序呗kill的时候,服务也就停了。服务本身并不会自动开启线程,所有代码都默认运行在主线程上,因此需要给服务手动创建子线程,否则可能会阻塞主线程。

多线程

基本用法

定义一个线程只需要新建一个类继承自Thread重写run方法,编写逻辑就行了

class MyThread extends Thread{
   
	@Override
	public void run(){
   
		//逻辑
	}
}

使用的话,new一个实例以后,调用start()启动即可。
另一种方法时继承Runnable接口

class MyThread implement Runnable{
   
	public void run(){
   
		//逻辑
	}
}

这时候调用就需要这样

MtThread myThread=new MyThread();
new Thread(myThread).start();

最常用的一种就是匿名类方法

new Thread (new Runnable(){
   
	public void run(){
   
		//逻辑
	}
}).start();

子线程中更新UI

和其他UI一样,Android的UI也是线程不安全,之前的WPF也是,得用委托才能在线程里更新UI。
一个栗子
修改activity_main.xml

<?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="com.example.k.androidpractice.MainActivity">

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/ChangeText"/>
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:text="helloworld"
        android:id="@+id/ShowText"/>

</LinearLayout>

再修改一下MainActivity.java的内容,就是获取一下控件,然后设置监听,点击按钮后修改TextView按钮的文字内容。

public class MainActivity extends AppCompatActivity {
   
    Button ChangeTextButton;
    TextView ShowTextView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
   
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ChangeTextButton=findViewById(R.id.ChangeText);
        ShowTextView=findViewById(R.id.ShowText);
        ChangeTextButton.setOnClickListener(new View.OnClickListener() {
   
            @Override
            public void onClick(View v) {
   
                new Thread(new Runnable() {
   
                    @Override
                    public void run() {
   
                        ShowTextView.setText("balabala");
                    }
                }).start();
            }
        });
    }
}

运行程序试一下。
效果是,TextView的内容改变了,但是随后程序就退出了。
报错

<font color="red">
E/AndroidRuntime: FATAL EXCEPTION: Thread-2
                  Process: com.example.k.androidpractice, PID: 9389
                  android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
                      at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:7753)
                      at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1225)
                      at android.view.View.requestLayout(View.java:23093)
                      at android.view.View.requestLayout(View.java:23093)
                      at android.view.View.requestLayout(View.java:23093)
                      at android.view.View.requestLayout(View.java:23093)
                      at android.view.View.requestLayout(View.java:23093)
                      at android.view.View.requestLayout(View.java:23093)
                      at android.view.View.requestLayout(View.java:23093)
                      at android.widget.TextView.checkForRelayout(TextView.java:8908)
                      at android.widget.TextView.setText(TextView.java:5730)
                      at android.widget.TextView.setText(TextView.java:5571)
                      at android.widget.TextView.setText(TextView.java:5528)
                      at com.example.k.androidpractice.MainActivity$1$1.run(MainActivity.java:72)
                      at java.lang.Thread.run(Thread.java:764)

</font>

说明的确不允许在子线程中修改UI,但是当必须用的时候怎么办呢,比如处理耗时的事件然后更新UI,Android提供了一套异步消息处理机制。
修改一下MainActivity代码,主要是创建了一个Handler对象,重写一下handleMessage()方法,通过msg.what判断传入的什么消息,进行相应逻辑处理。
在按钮点击事件中的子线程里,创建一个Message对象,将标识作为参数传入,标识是一个int型好像,再把Message发送给Handler,Handler收到消息后就会在handleMessage()中处理,而此时这个HandleMessage()运行在主线程。

public class MainActivity extends AppCompatActivity {
   
    public static final int UPDATE_TEXT=1;
    Button ChangeTextButton;
    TextView ShowTextView;
    private Handler handler=new Handler(){
   
        @Override
        public void handleMessage(Message msg) {
   
            super.handleMessage(msg);
            switch (msg.what){
   
                case UPDATE_TEXT:ShowTextView.setText("aaaaa");break;
            }
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
   
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ChangeTextButton=findViewById(R.id.ChangeText);
        ShowTextView=findViewById(R.id.ShowText);
        ChangeTextButton.setOnClickListener(new View.OnClickListener() {
   
            @Override
            public void onClick(View v) {
   
                new Thread(new Runnable() {
   
                    @Override
                    public void run() {
   
                        Message message=new Message();
                        message.what=UPDATE_TEXT;
                        //发送
                        handler.sendMessage(message);
                    }
                }).start();
            }
        });
    }
}

然后运行程序,其实这次avd的软件又闪退了,重启AS就解决了。现在点击按钮可以发现TextView的内容改变了,而且软件不会报错。

异步消息处理机制

主要由四部分组成,Message、Handler、MesageQueue和Looper。前两者刚才用到了。

  • Message:线程之间传递消息,可以在内部携带少量信息。刚才用了what字段存储数据,还可以用arg1和arg2字段携带一些整形数据,使用obj字段携带Object对象
  • Handler:用于发送和处理消息。发送使用sendMessage(),最终会转到handleMessage()方法
  • MessageQueue:消息队列用于存放所有通过Handler发送的消息,会一直存储在消息队列中,等待被处理,每个线程只有一个MessageQueue
  • Looper:是MessageQueue管家,调用Loop的loop()后,陷入无限循环,每当MessageQueue中存有一条消息就取出来发送给handleMessage()中,每个线程也只有一个Looper
    捋一遍异步消息处理流程
  1. 主线程创建Handler对象,重写handlerMessage()方法
  2. 子线程需要处理UI的地方创建Message对象,发送给Handler
  3. 消息会被添加到MessageQueue中等待处理,此时Looper会一直试图从队列中取出待处理消息,发送给handleMessage()方法

而runOnUiThread()其实就是一个异步消息处理机制的接口封装,内部流程和上面一样。

AsyncTask

另一个比较好的在子线程中更新UI的工具 。
其背后原理也是基于异步消息处理,同样Android做好了封装。
AsyncTask是一个抽象类,必须有一个类继承它,其中有三个泛型参数

  • Params:执行AsyncTask需要传入的参数,用于后台任务中
  • Progress:后台任务执行中,如果需要在界面显示进度之类的,使用这里指定的泛型作为进度单位
  • Result:任务执行完毕后,如果需要对结果返回,在这里指定泛型作为返回值类型
class DownloadTask extends AsyncTask<Void,Integer,BBoolean>{
   
	...
}

第一个参数Void标识不需要传入参数给后台任务,第二个参数指定为整型表示用整型数据作为进度显示单位,第三个表示用布尔型数据作为返回值结果。
然后还需要再重写几个方法,主要是这四个

  • onPreExecute():在后台任务开始之前调用,用于界面初始化,比如显示一个进度条对话框
  • doInBackground(Params…):这里面的所有代码都在子线程中运行,处理耗时任务,任务完成后可以通过return将任务结果 返回,如果AsyncTask第三个参数是Void则不返回结果,且在这里面不能进行UI的操作,如果需要反馈当前任务执行进度需要调用publishProgress()方法
  • onProgressUpdate(Progress…):后台调用publishProgress(Progress…)方法以后,这个方法就会被调用,参数是在后台任务中传递过来的,在这里面可以进行对UI的操作,更新
  • onPostExecute(Result):后台任务执行完毕后并通过return语句返回的时候这个方法会调用,返回的数据作为参数传递进来,可以利用返回数据进行UI操作,比如提醒任务执行的结果,关闭对话框之类的
    在后面给个小栗子吧

服务

定义

首先先定义一个服务,在项目名右键 -> New -> Service -> Service,命名为MyService,代码如下所示

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");
    }
}

其中只有一个方法onBind(),这也是唯一一个抽象方法,必须要在子类中实现。
还要重写onCreate()、onStartCommand()和onDestroy()三个方法,分别是在服务创建时调用、在每次启动服务的时候调用和在服务销毁的时候调用。
通常希望服务一旦启动就立刻执行某个动作的话,将代码写到onStartCommand()中,服务销毁的时候需要在onDestroy()中回收不使用的资源。
其实每一个服务也都需要在AndroidManifest.xml中注册才行,但是因为是直接New的,所以已经自动完成了。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.k.androidpractice">

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

    <application
        android:name="org.litepal.LitePalApplication"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
<font color="red">
        <service
            android:name=".MyService"
            android:enabled="true"
            android:exported="true"></service>
</font>
    </application>

</manifest>

启动和停止

启动停止只要是通过Intent实现的。
小栗子
修改一下activity_main.xml内容,添加两个Button,一个开始一个停止

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值