后台默默的劳动者
01 服务
??服务是android中实现程序后台运行的解决方案.适合执行那些不需要和用户交互且要求长期运行的任务.
??服务的运行不依赖于其他任何用户界面,即使程序被切换到后台,或者用户打开另外一个应用,服务仍然可以正常运行.
02 子线程更新UI会出现错误
??Android的UI也是线程不安全的.想更新UI元素,必须在主线程中进行,否则会有异常.
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new Thread(new Runnable() {
@Override
public void run() {
textView.setText("hello");
}
}).start();
}
});
03 异步消息处理案例
private Handler handler = new Handler(Looper.myLooper()){
@Override
public void handleMessage(@NonNull Message msg) {
switch (msg.what){
case UPDATE_TEXT:
textView.setText("hello");
break;
default:
break;
}
}
};
new Thread(new Runnable() {
@Override
public void run() {
Message message= new Message();
message.what = UPDATE_TEXT;
handler.sendMessage(message);
}
}).start();
04 异步消息处理机制
1. Message
??Message是线程之间传递的消息.可以携带少量信息. Message的what字段,arg1和arg2字段可以携带整型数据.obj字段携带一个obj对象.
2. Handler
??处理者,用于发送和处理消息.发送一般使用Handler的sendMessage()方法. 一系列处理后消息会到达Handler的handleMessage()方法中.
3. MessageQueue
??消息队列.用于存放所有通过Handler发送的消息. 消息会一直存在于消息队列中等待被处理.每个线程中只会有一个MessageQueue对象.
4. Looper
??每个线程中的MessageQueue的管家,调用Looper的loop()方法后,就会进入一个无限循环当中.每当发现MessageQueue中存在一条消息,就会把它取出,传递到Handler的hanleMessage()方法中.每个线程也只有一个Looper对象
流程:
- 在主线程中创建一个Handler对象,并重写handleMessage()方法.
- 子线程要进行UI操作,就创建一个Message对象,并通过主线程的Handler对象将消息发出去.
- 消息被添加到Handler中的MessageQueue队列中.
- Looper一直从MessageQueue中取出消息并发回给Handler的handleMessage()方法中.
05 使用AsyncTask
抽象类,使用时需继承.有三个泛型参数可以指定
class DownloadTask extends AsyncTask<Void, Integer, Boolean>{
}
1. Params
??执行AsyncTask时需要传入的参数,可用于在后台任务中使用
2. Progress
??后台任务执行时,如果需要在界面上显示当前的进度,则使用这里指定的泛型作为进度单位
3. Result
??当任务执行完毕后,如果需要对结果进行返回,则使用这里指定的泛型作为返回值类型.
这里可以指定第一个泛型参数为void.表示执行时不需要传入参数给后台任务.第二个泛型参数指定为Integer,表示使用整型数据作为进度显示单位.第三个参数为布尔型,表示用布尔型数据来反馈执行结果.
经常需要重写的方法如下:
1. onPreExecute() ??这个方法会在后台任务开始执行之前调用,用于进行一些界面上的初始化操作,比如显示 一个进度条对话框等。
02. doInBackground(Params…) ??这个方法中的所有代码都会在子线程中运行,我们应该在这里去处理所有的耗时任务。任务一旦完成,就可以通过return语句将任务的执行结果返回,如果AsyncTask的第三个泛型参数指定的是Void,就可以不返回任务执行结果。注意,在这个方法中是不可以进行UI操作的,如果需要更新UI元素,比如说反馈当前任务的执行进度,可以调用onpublishProgress (Progress…)方法来完成。 03. onProgressUpdate(Progress…) ??当在后台任务中调用了publishProgress(Progress…)方法后,onProgressUpdate (Progress…)方法就会很快被调用,该方法中携带的参数就是在后台任务中传递过来的。在这个方法中可以对UI进行操作,利用参数中的数值就可以对界面元素进行相应的更新。 04. onPostExecute(Result) ??当后台任务执行完毕并通过return语句进行返回时,这个方法就很快会被调用。返回的数 据会作为参数传递到此方法中,可以利用返回的数据进行一些UI操作,比如说提醒任务执 行的结果,以及关闭进度条对话框等。
下列代码不完整
class DownloadTask extends AsyncTask<Void, Integer, Boolean> {
@Override
protected void onPreExecute() {
progressDialog.show();
}
@Override
protected Boolean doInBackground(Void... voids) {
try{
while (true){
int downloadPercent = doDownload();
publishProgress(downloadPercent);
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 aBoolean) {
progressDialog.dismiss();
if (aBoolean){
Toast.makeText(context,"Download succeed",Toast.LENGTH_SHORT).show();
}else{
Toast.makeText(context,"Download failed",Toast.LENGTH_SHORT).show();
}
}
}
想要执行该任务,只需
new DownloadTask().execute();
??这里虚构了一个doDownload()方法,用于计算当前的下载进度并返回,我们假设这个方法已经存在了。在得到了当前的下载进度后,下面就该考虑如何把它显示到界面上了,由于doInBackground()方法是在子线程中运行的,在这里肯定不能进行UI操作,所以我们可以调用publishProgress()方法并传入当前的下载进度,这样onProgressUpdate()方法就会很快被调用,在这里就可以进行UI操作了。当下载完成后,doInBackground()方法会返回一个布尔型变量,这样onPostExecute()方法就会很快被调用,这个方法也是在主线程中运行的。然后,在这里我们会根据下载的结果弹出相应的Toast提示,从而完成整个DownloadTask任务。
??简单来说,使用AsyncTask的诀窍就是,在doInBackground()方法中执行具体的耗时任务,在onProgressUpdate()方法中进行UI操作,在onPostExecute()方法中执行一些任务的收尾工作。
06 服务的用法
1 定义一个服务
??new->service->service .将Exported(是否允许除了当前程序之外其他程序访问这个服务)和Enabled(是否启用服务)属性勾中.
重写onCreate()、onStartCommand()和onDestroy()
??其中onCreate()方法会在Service创建的时候调用,onStartCommand()方法会在每次Service启动的时候调用,onDestroy()方法会在Service销毁的时候调用。
2 启动和重启服务
代码案例
主活动
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button startService = (Button) findViewById(R.id.startService);
startService.setOnClickListener(this);
Button stopService = (Button) findViewById(R.id.stopService);
stopService.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.startService:
Intent startIntent = new Intent(this,MyService.class);
startService(startIntent);
break;
case R.id.stopService:
Intent stopIntent = new Intent(this,MyService.class);
stopService(stopIntent);
break;
default:
break;
}
}
自定义服务
public class MyService extends Service {
private static final String TAG = "Service";
public MyService() {
}
@Override
public void onCreate() {
super.onCreate();
Log.d(TAG, "onCreate: executed");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "onStartCommand: executed");
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy: executed");
}
@Override
public IBinder onBind(Intent intent) {
throw new UnsupportedOperationException("Not yet implemented");
}
}
结果: oncreate方法和onstartcommand方法执行
3 活动和服务进行通信
原理:回调.
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.bindService);
bindService.setOnClickListener(this);
Button unbindService = (Button) findViewById(R.id.unbindService);
unbindService.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.bindService:
Intent bindIntent = new Intent(this,MyService.class);
bindService(bindIntent,connection,BIND_AUTO_CREATE);
break;
case R.id.unbindService:
Intent unbindIntent = new Intent(this,MyService.class);
unbindService(connection);
break;
default:
break;
}
}
}
绑定服务时只会调用服务的oncreate方法,不会调用onstartcommand方法.
07 服务的生命周期
??前面我们使用到的onCreate()、onStartCommand()、onBind()和onDestroy()等 方法都是在Service的生命周期内可能回调的方法。
??一旦在项目的任何位置调用了Context的startService()方法,相应的Service就会启动,并回调onStartCommand()方法。如果这个Service之前还没有创建过,onCreate()方法会先于onStartCommand()方法执行。Service启动了之后会一直保持运行状态,直到stopService()或stopSelf()方法被调用,或者被系统回收。注意,虽然每调用一次startService()法,onStartCommand()就会执行一次,但实际上每个Service只会存在一个实例。所以不管你调用了多少次startService()方法,只需调用一次stopService()或stopSelf()方法,Service就会停止。
前台服务
在service的onCreate中添加如下代码
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
NotificationChannel channel = new NotificationChannel(
"vashon","测试通知",NotificationManager.IMPORTANCE_HIGH);
manager.createNotificationChannel(channel);
}
Intent intent = new Intent(this,MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(
this,0,intent,0);
Notification notification = new NotificationCompat.Builder(this,"vashon")
.setContentTitle("我的通知")
.setContentText("这是通知内容")
.setSmallIcon(R.drawable.ic_launcher_background)
.setLargeIcon(BitmapFactory.decodeResource(getResources(),R.drawable.ic_launcher_background))
.setColor(Color.parseColor("#ff0000"))
.setAutoCancel(true)
.setContentIntent(pendingIntent)
.build();
startForeground(1,notification);
在manifest.xml中添加权限
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
可以看到前台服务在通知栏中.移除不了
08 使用IntentService(在子线程处理任务)
??因为服务默认在主线程中运行,有时服务需要处理耗时的逻辑,容易出现ANR. IntentService解决了这个问题.
创建IntentService类
public class MyIntentService extends IntentService {
private static final String TAG = "MyIntentService";
public MyIntentService() {
super("MyIntentService");
}
@Override
protected void onHandleIntent(Intent intent) {
Log.d(TAG, "onHandleIntent: Thread id is" + Thread.currentThread().getId());
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy: executed");
}
}
主活动中加入代码
case R.id.startIntentService:
Log.d(TAG, "Thread id is " + Thread.currentThread().getId());
Intent intentService = new Intent(this,MyIntentService.class);
startService(intentService);
break;
结果如下:
D/MainActivity: Thread id is 2
D/MyIntentService: onHandleIntent: Thread id is2508
D/MyIntentService: onDestroy: executed
|