服务吧,在程序即便关闭的时候还是可以回后台运行,不搞情怀了。后台功能属于四大组件之一。
服务是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
捋一遍异步消息处理流程
- 主线程创建Handler对象,重写handlerMessage()方法
- 子线程需要处理UI的地方创建Message对象,发送给Handler
- 消息会被添加到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"