14、服务
服务(Service)是 Android 中实现程序后台运行的解决方案
它非常适合用于去执行那些不需要和用户交互而且还要求长期运行的任务。
服务并不会自动开启线程,所有的代码都是默认运行在主线程当中的。也就是说,我们需要在服务的内部手动创建子线程,并在这里执行具体的任务,否则就有可能出现主线程被阻塞住的情况。
多线程编程:
Android 确实是不允许在子线程中进行 UI 操作的。但是有些时候,我们必须在子线程里去执行一些耗时任务,然后根据任务的执行结果来更新相应的 UI 控件,这该如何是好呢?
对于这种情况,Android 提供了一套异步消息处理机制,完美地解决了在子线程中进行UI 操作的问题
//在线程中调用 Handler 的 sendMessage()方法将这条Message 发送出去,Handler 就会收到这条 Message,并在 handleMessage()方法中对它进行处理。注意此时 handleMessage()方法中的代码就是在主线程当中运行的了,所以我们可以放心地在这里进行 UI 操作。
private Handler handler = new Handler() { public void handleMessage(Message msg) { switch (msg.what) { case 1: // 在这里可以进行UI操作 changeText.setText("Nice to meet you"); break; default: break; } } }; changeTextBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Toast.makeText(MainActivity.this, "Test", Toast.LENGTH_SHORT).show(); //注意线程最后需要启动 new Thread(new Runnable() { @Override public void run() { //changeText.setText("Nice to meet you"); Message message = new Message(); message.what = 1; handler.sendMessage(message); // 将Message对象发送出去 } }).start(); } });
Android 中的异步消息处理主要由四个部分组成,
Message、Handler、MessageQueue 和Looper
1. Message
Message 是在线程之间传递的消息,它可以在内部携带少量的信息,用于在不同线程之间交换数据。
上一小节中我们使用到了 Message 的 what 字段,除此之外还可以使用 arg1 和 arg2 字段来携带一些整型数据,使用 obj 字段携带一个 Object 对象。
2. Handler
Handler 顾名思义也就是处理者的意思,它主要是用于发送和处理消息的。
发送消息一般是使用 Handler 的 sendMessage()方法,而发出的消息经过一系列地辗转处理后,最终会传递到 Handler 的 handleMessage()方法中。
3. MessageQueue
MessageQueue 是消息队列的意思,它主要用于存放所有通过 Handler 发送的消息。
这部分消息会一直存在于消息队列中,等待被处理。每个线程中只会有一个 MessageQueue对象。
4. Looper
Looper 是每个线程中的 MessageQueue 的管家,调用 Looper 的 loop()方法后,就会进入到一个无限循环当中,然后每当发现 MessageQueue 中存在一条消息,就会将它取
出,并传递到 Handler 的 handleMessage()方法中。每个线程中也只会有一个 Looper 对象。
异步消息处理流程:
首先需要在主线程当中创建一个 Handler 对象,并重写handleMessage()方法。然后当子线程中需要进行 UI 操作时,就创建一个 Message 对象,并通过 Handler 将这条消息发送出去。【之后这条消息会被添加到 MessageQueue 的队列中等待被处理,而 Looper 则会一直尝试从 MessageQueue 中取出待处理消息,最后分发回 Handler的 handleMessage()方法中】。由于 Handler 是在主线程中创建的,所以此时 handleMessage()方法中的代码也会在主线程中运行,于是我们在这里就可以安心地进行 UI 操作了。
使用 AsyncTask;
由于 AsyncTask 是一个抽象类,所以如果我们想使用它,就必须要继承它。在继承时我们可以为 AsyncTask 类指定三个泛型参数,这三个参数的用途如下。
1. Params
在执行 AsyncTask 时需要传入的参数,可用于在后台任务中使用。
2. Progress
后台任务执行时,如果需要在界面上显示当前的进度,则使用这里指定的泛型作为进度
单位。
3. Result
当任务执行完毕后,如果需要对结果进行返回,则使用这里指定的泛型作为返回值类型。
因此,一个最简单的自定义 AsyncTask 就可以写成如下方式:
class DownloadTask extends AsyncTask<Void, Integer, Boolean> { …… }
1. onPreExecute()
这个方法会在后台任务开始执行之前调用,用于进行一些界面上的初始化操作,比如显示一个进度条对话框等。
2. doInBackground(Params...)
这个方法中的所有代码都会在子线程中运行,我们应该在这里去处理所有的耗时任务。任务一旦完成就可以通过 return 语句来将任务的执行结果返回,如果 AsyncTask 的
第三个泛型参数指定的是 Void,就可以不返回任务执行结果。注意,在这个方法中是不可以进行 UI 操作的,如果需要更新 UI 元素,比如说反馈当前任务的执行进度,可以调用 publishProgress(Progress...)方法来完成。
3. onProgressUpdate(Progress...)
当在后台任务中调用了 publishProgress(Progress...)方法后,这个方法就会很快被调用,方法中携带的参数就是在后台任务中传递过来的。在这个方法中可以对 UI 进行操作,利用参数中的数值就可以对界面元素进行相应地更新。
4. onPostExecute(Result)
当后台任务执行完毕并通过 return 语句进行返回时,这个方法就很快会被调用。返回的数据会作为参数传递到此方法中,可以利用返回的数据来进行一些 UI 操作,比如说提醒任务执行的结果,以及关闭掉进度条对话框等。
//启动任务: new DownloadTask().execute() //下载任务: class DownloadTask extends AsyncTask<Void, Integer, Boolean> { protected void onPreExecute() { progressDialog.show(); // 显示进度对话框 } @Override protected Boolean doInBackground(Void... params) { 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 result) { progressDialog.dismiss(); // 关闭进度对话框 // 在这里提示下载结果 if (result) { Toast.makeText(context, "Download succeeded",Toast.LENGTH_SHORT) .show(); } else { Toast.makeText(context, " Download failed", Toast.LENGTH_SHORT).show(); } } }
服务:
每一个服务都需要在 AndroidManifest.xml文件中进行注册才能生效
<application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > …… <service android:name=".MyService" ></service> </application>
public class MyService extends Service { @Override public IBinder onBind(Intent intent) { return null; } //onCreate()方法会在服务创建的时候调用 //onCreate()方法是在服务第一次创建的时候调用的,而 onStartCommand()方法则在 每次启动服务的时候都会调用 @Override public void onCreate() { super.onCreate(); } //onStartCommand()方法会在每次服务启动的时候调用 @Override public int onStartCommand(Intent intent, int flags, int startId) { return super.onStartCommand(intent, flags, startId); } //onDestroy()方法会在服务销毁的时候调用 @Override public void onDestroy() { super.onDestroy(); } }
启动服务:
Intent startIntent = new Intent(this, MyService.class);
startService(startIntent); // 启动服务
停止服务:
Intent startIntent = new Intent(this, MyService.class);
stopService(startIntent); // 启动服务
活动和服务进行通信
当一个活动和服务绑定了之后,就可以调用该服务里的 Binder 提供的方法了
首先创建了一个 ServiceConnection 的匿名类,在里面重写了onServiceConnected()方法和 onServiceDisconnected()方法,这两个方法分别会在活动与服务成功绑定以及解除绑定的时候调用。在 onServiceConnected()方法中,我们又通过向下转型得到了 DownloadBinder 的实例,有了这个实例,活动和服务之间的关系就变得非常紧密了
Activity:
private MyService.DownloadBinder downloadBinder; private ServiceConnection connection = new ServiceConnection() { @Override public void onServiceDisconnected(ComponentName name) { } @Override public void onServiceConnected(ComponentName name, IBinder service) { downloadBinder = (MyService.DownloadBinder) service; downloadBinder.startDownload(); downloadBinder.getProgress(); } }; //绑定服务和取消绑定服务按钮事件 case R.id.bind_service: //绑定服务 /*bindService()方法接收三个参数, *第一个参数就是刚刚构建出的 Intent 对象,第二个参数是前面创建出的 ServiceConnection 的实例,第三个参数则是一个标志位,这里传入 BIND_AUTO_CREATE *表示在活动和 服务进行绑定后自动创建服务。这会使得 MyService 中的 onCreate()方法得到执行,但 onStartCommand()方法不会执行。 */ Intent bindIntent = new Intent(this, MyService.class); bindService(bindIntent, connection, BIND_AUTO_CREATE); // 绑定服务 break; case R.id.unbind_service: unbindService(connection); // 解绑服务 break;
MyService中:
public class MyService extends Service { public MyService() { } private DownloadBinder mBinder = new DownloadBinder(); //服务内部类 class DownloadBinder extends Binder { public void startDownload() { Log.d("MyService", "startDownload executed"); Toast.makeText(MyService.this, "startDownload executed", Toast.LENGTH_SHORT).show(); } public int getProgress() { Log.d("MyService", "getProgress executed"); Toast.makeText(MyService.this, "getProgress executed", Toast.LENGTH_SHORT).show(); return 0; } } @Override public IBinder onBind(Intent intent) { //返回内部类对象 return mBinder; } @Override public void onCreate() { super.onCreate(); Toast.makeText(this, "MyService onCreate!", Toast.LENGTH_SHORT).show(); } @Override public int onStartCommand(Intent intent, int flags, int startId) { Toast.makeText(this, "MyService start !", Toast.LENGTH_SHORT).show(); return super.onStartCommand(intent, flags, startId); } @Override public void onDestroy() { super.onDestroy(); Toast.makeText(this, "MyService onDestory !", Toast.LENGTH_SHORT).show(); } }
服务生命周期:
一旦在项目的任何位置调用了 Context 的 startService()方法,相应的服务就会启动起来,并回调 onStartCommand()方法。【如果这个服务之前还没有创建过,onCreate()方法会先于onStartCommand()方法执行】。服务启动了之后会一直保持运行状态,直到 stopService()或stopSelf()方法被调用。注意虽然每调用一次 startService()方法,onStartCommand()就会执行一次,【但实际上每个服务都只会存在一个实例。所以不管你调用了多少次 startService()方法,只需调用一次 stopService()或 stopSelf()方法,服务就会停止下来了】。
还可以调用 Context 的 bindService()来获取一个服务的持久连接,这时就会回调服务中的 onBind()方法。类似地,如果这个服务之前还没有创建过,onCreate()方法会先于onBind()方法执行。
只要调用方和服务之间的连接没有断开,服务就会一直保持运行状态
当调用了 startService()方法后,又去调用 stopService()方法,这时服务中的 onDestroy()方法就会执行,表示服务已经销毁了。
类似地,当调用了 bindService()方法后,又去调用unbindService()方法,onDestroy()方法也会执行
一个服务既调用了 startService()方法,又调用了 bindService()方法,这种情况下要同时调用 stopService()和 unbindService()方法,onDestroy()方法才会执行
前台服务:
如果你希望服务可以一直保持运行状态,而不会由于系统内存不足的原因导致被回收,就可以考虑使用前台服务。
前台服务和普通服务最大的区别就在于,它会一直有一个正在运行的图标在系统的状态栏显示,下拉状态栏后可以看到更加详细的信息,非常类似于通知的效果。
在服务的onCreate()方法中:
Notification.Builder builder = new Notification.Builder(this); builder.setTicker("这是一个通知222") .setContentTitle("这是TITLE2222") .setContentText("这是2222通知的内容这是通知的内容这是通知的内容这是 通知的内容这是通知的内容这是通知的内容") .setWhen(System.currentTimeMillis()) .setAutoCancel(false) .setSmallIcon(R.drawable.pineapple_pic); Notification notification = builder.getNotification(); Intent intent = new Intent(this, SecondActivity.class); PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0); builder.setContentIntent(pendingIntent); startForeground(1,notification);
IntentService
服务中的代码都是默认运行在主线程当中的,如果直接在服务里去处理一些耗时的逻辑,就很容易出现 ANR(Application NotResponding)的情况。
耗时服务写法1:
public int onStartCommand(Intent intent, int flags, int startId) { //开启线程 new Thread(new Runnable() { @Override public void run() { // 处理具体的逻辑 // 执行完毕后自动停止 stopSelf(); } }).start(); return super.onStartCommand(intent, flags, startId); }
异步的、会自动停止的服务IntentService:
public class MyIntentService extends IntentService { public MyIntentService() { super("MyIntentService"); // 调用父类的有参构造函数 } @Override protected void onHandleIntent(Intent intent) { // 打印当前线程的id Log.d("MyIntentService", "Thread id is " + Thread.currentThread(). getId()); //在这个方法中可以去处理一些具体的逻辑 } @Override public void onDestroy() { super.onDestroy(); Log.d("MyIntentService", "onDestroy executed"); } }
后台执行定制任务:
Android 中的定时任务一般有两种实现方式,
一种是使用 Java API 里提供的 Timer 类,一种是使用 Android 的 Alarm 机制。
但 Timer有一个明显的短板,它并不太适用于那些需要长期在后台运行的定时任务。Android 手机就会在长时间不操作的情况下自动让 CPU 进入到睡眠状态,这就有可能导致 Timer 中的定时任务无法正常运行。
而 Alarm 机制则不存在这种情况,它具有唤醒 CPU 的功能,即可以保证每次需要执行定时任务的时候 CPU 都能正常工作。
//设定一个任务在 10 秒钟后执行,就可以写成:
AlarmManager manager = (AlarmManager) getSystemService(Context.ALARM_SERVICE); long triggerAtTime = SystemClock.elapsedRealtime() + 10 * 1000; manager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerAtTime, pendingIntent);
例子:
构造一个后台每隔1个小时执行一次的定时任务
//MainActivity中启动服务: Intent intent = new Intent(this, LongRunningService.class); startService(intent); //新建服务LongRunningService: public class LongRunningService extends Service { @Override public IBinder onBind(Intent intent) { return null; } @Override public int onStartCommand(Intent intent, int flags, int startId) { new Thread(new Runnable() { @Override public void run() { Log.d("LongRunningService", "executed at " + new Date(). toString()); } }).start(); AlarmManager manager = (AlarmManager) getSystemService(ALARM_SERVICE); int anHour = 60 * 60 * 1000; // 这是一小时的毫秒数 long triggerAtTime = SystemClock.elapsedRealtime() + anHour; //广播接收器AlarmReceiver Intent i = new Intent(this, AlarmReceiver.class); PendingIntent pi = PendingIntent.getBroadcast(this, 0, i, 0); manager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerAtTime, pi); return super.onStartCommand(intent, flags, startId); } } //构造广播接收器: public class AlarmReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { Intent i = new Intent(context, LongRunningService.class); context.startService(i); } }
配置文件中注册:
<service android:name=".LongRunningService" ></service> <receiver android:name=".AlarmReceiver" ></receiver>
基于位置:
授权:
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
public class LocationActivity extends BaseActivity { private TextView positionTextView; private LocationManager locationManager; private String provider; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.location_layout); //无法显示位置,6.0之后处理方法见后面 setLocation(); } public void setLocation(){ positionTextView = (TextView) findViewById(R.id.position_text_view); locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE); // 获取所有可用的位置提供器 List<String> providerList = locationManager.getProviders(true); if (providerList.contains(LocationManager.GPS_PROVIDER)) { provider = LocationManager.GPS_PROVIDER; } else if (providerList.contains(LocationManager.NETWORK_PROVIDER)) { provider = LocationManager.NETWORK_PROVIDER; } else { // 当没有可用的位置提供器时,弹出Toast提示用户 Toast.makeText(this, "No location provider to use",Toast.LENGTH_SHORT).show(); return; } Location location = locationManager.getLastKnownLocation(provider); if (location != null) { // 显示当前设备的位置信息 showLocation(location); } locationManager.requestLocationUpdates(provider, 5000, 1,locationListener); } protected void onDestroy() { super.onDestroy(); if (locationManager != null) { // 关闭程序时将监听器移除 locationManager.removeUpdates(locationListener); } } LocationListener locationListener = new LocationListener() { @Override public void onStatusChanged(String provider, int status, Bundle extras) { } @Override public void onProviderEnabled(String provider) { } @Override public void onProviderDisabled(String provider) { } @Override public void onLocationChanged(Location location) { // 更新当前设备的位置信息 showLocation(location); } }; private void showLocation(Location location) { String currentPosition = "latitude is " + location.getLatitude() + "\n" + "longitude is " + location.getLongitude(); positionTextView.setText(currentPosition); } }
6.0 需要在应用运行过程中请求位置权限,并且还要打开位置。
Android 6.0 扫描不到 Ble 设备需开启位置权限
https://blog.csdn.net/kjunchen/article/details/52769915
6.0 需要在应用运行过程中请求位置权限,并且还要打开位置
添加授权:
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
//判断权限: if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {//如果 API level 是大于等于 23(Android 6.0) 时 //判断是否具有权限 if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) { //判断是否需要向用户解释为什么需要申请该权限 if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.ACCESS_COARSE_LOCATION)) { Toast.makeText(this, "自Android 6.0开始需要打开位置权限才可以搜索到Ble设备", Toast.LENGTH_SHORT).show(); } //请求权限 ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_COARSE_LOCATION}, 1); } }else{ setLocation(); } //回调 public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { if (requestCode == 1) { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { //用户允许改权限,0表示允许,-1表示拒绝 PERMISSION_GRANTED = 0, PERMISSION_DENIED = -1 //permission was granted, yay! Do the contacts-related task you need to do. //这里进行授权被允许的处理 setLocation(); } else { //permission denied, boo! Disable the functionality that depends on this permission. //这里进行权限被拒绝的处理 Toast.makeText(this, "permission denied", Toast.LENGTH_SHORT).show(); } } else { super.onRequestPermissionsResult(requestCode, permissions, grantResults); } }
经纬值转换成看得懂的位置信息
private void showLocation(final Location location) { new Thread(new Runnable() { @Override public void run() { try { // 组装反向地理编码的接口地址 StringBuilder url = new StringBuilder(); url.append("http://maps.googleapis.com/maps/api/geocode/json?latlng="); url.append(location.getLatitude()).append(",") url.append(location.getLongitude()); url.append("&sensor=false"); HttpClient httpClient = new DefaultHttpClient(); HttpGet httpGet = new HttpGet(url.toString()); // 在请求消息头中指定语言,保证服务器会返回中文数据 httpGet.addHeader("Accept-Language", "zh-CN"); HttpResponse httpResponse = httpClient.execute(httpGet); if (httpResponse.getStatusLine().getStatusCode() == 200) { HttpEntity entity = httpResponse.getEntity(); String response = EntityUtils.toString(entity,"utf-8"); JSONObject jsonObject = new JSONObject(response); JSONArray resultArray = jsonObject.getJSONArray("results"); if (resultArray.length() > 0) { JSONObject subObject = resultArray.getJSONObject(0); // 取出格式化后的位置信息 String address = subObject.getString("formatted_address"); Message message = new Message(); message.what = SHOW_LOCATION; message.obj = address; handler.sendMessage(message); } } } catch (Exception e) { e.printStackTrace(); } } }).start(); } private Handler handler = new Handler() { public void handleMessage(Message msg) { switch (msg.what) { case SHOW_LOCATION: String currentPosition = (String) msg.obj; positionTextView.setText(currentPosition); break; default: break; } } };
...
相关推荐
《第一行代码Android学习练习代码10》是一个针对Android初学者的实践项目,旨在通过具体的编程练习加深对《第一行代码》这本书中Android开发知识的理解。这个压缩包包含两个核心练习:DYHDM_09_01...
《第一行代码-Android 源代码》是郭霖撰写的一本面向初学者的Android编程书籍,这本书通过实例引导读者逐步了解和掌握Android应用开发。源代码压缩包提供了书中所有示例程序的完整代码,方便读者实践和学习。以下是...
1.在Spring Boot中,你可以使用@Scheduled注解来创建定时任务。将@Scheduled注解与方法一起使用,指定任务执行的时间表达式。 2.使用Spring的TaskScheduler: Spring提供了TaskScheduler接口和相关实现,用于任务...
《第一行代码》是一本非常适合初学者入门的书籍,它涵盖了Android开发的基础知识。以下将详细解析这十个经典代码,帮助你深入理解Android开发的核心概念。 1. **Hello, World!**:每个程序员生涯的起点,Android也...
- 实时更新天气信息,可以采用定时任务,如AlarmManager配合BroadcastReceiver,或者使用WorkManager。另外,前台服务也可以保持后台运行,但需注意电量和性能的影响。现在更推荐使用JobScheduler,它可以在满足...
然后,多线程处理是实现定时任务的核心。Android中的AsyncTask类允许在后台线程执行耗时操作,同时保持与主线程的通信。在本例中,定时关机的逻辑可能在AsyncTask的`doInBackground`方法中执行,确保不会阻塞用户...
这可能包括音乐播放服务、定时任务或者后台数据同步。源代码可能包含服务的启动、绑定和停止,以及如何使用IntentService处理一次性任务。 第11章可能涉及的是广播接收器(BroadcastReceiver)和内容观察者...
14. **通知和AlarmManager**:展示如何创建和管理通知,以及使用AlarmManager设置定时任务。 15. **第三方库集成**:如Dagger依赖注入框架,GreenDAO数据库库,Retrofit网络库等,源代码会展示如何集成和使用这些库...
6. **服务(Service)**:学习如何创建和使用后台服务,进行长时间运行的任务,如音乐播放、定时任务等。 7. **BroadcastReceiver**:广播接收器是响应系统广播事件的组件,案例会教你如何注册和使用它。 8. **...
在365手机秘书中,可能有定时任务服务,或者接收系统闹钟、日历事件的广播,以便及时提醒用户。 4. **数据库与ContentProvider**:数据存储是应用程序的重要组成部分。Android提供了SQLite数据库,用于存储结构化...
5. **服务(Service)**:如果源代码中有Service,那么你可以了解后台运行任务的实现,比如音乐播放、定时任务等。 6. **BroadcastReceiver**:广播接收器用于监听系统或自定义广播事件,如网络状态变化、接收到...
AndroidDemoCNG可能展示如何创建和控制Service,如音乐播放服务或定时任务服务。 6. **BroadcastReceiver**:广播接收器用于监听系统或自定义广播事件。开发者可以学习如何注册和使用BroadcastReceiver来响应特定...
通过sendMessageDelayed()方法,我们可以设定消息在多长时间后被处理,这对于实现定时任务非常有用。另外,还可以使用sendEmptyMessage()发送一个无数据的消息,仅触发handleMessage()方法的执行,适用于仅需触发...
7. **服务(Service)**:包括了后台运行的服务,如音乐播放服务,定时任务服务等。 8. **广播接收器(BroadcastReceiver)**:如何注册和使用广播接收器响应系统或自定义广播事件。 9. **内容提供者(Content ...
同时,通过定时任务或者广播接收器,可以实现天气信息的定期刷新。 此外,你还会学到如何与远程服务器进行交互、错误处理策略、数据缓存策略(如使用`SharedPreferences`或`LruCache`)以及UI线程与后台线程的分离...
《疯狂Android讲义》是Android开发领域的一本经典教材,其第三版的光盘代码包含了大量的实例和练习,旨在帮助读者深入理解Android应用开发的核心概念和技术。这些代码分为两个部分,分别对应书中的前10章和11到19章...
创建第一个Android项目是每个开发者必经的步骤。通过Android Studio,你可以选择“Empty Activity”模板来快速创建一个新的应用项目。编写简单的"Hello, World!"程序,了解基本的布局XML文件(如activity_main.xml)...
6. **服务**:Android服务可以长时间运行在后台,执行如音乐播放、定时任务等。源码可能展示了如何创建、启动和绑定服务。 7. **权限管理**:Android 6.0及以上版本引入了运行时权限,源码可能包含如何请求和处理...
7. **Service**:后台运行的服务,即使应用被关闭也能继续执行任务,如音乐播放、定时任务等。 8. **BroadcastReceiver**:广播接收器用于监听系统或自定义广播事件,实现异步消息处理。 9. **Intent Filter**:...
7. **服务与后台运行**:Service组件允许应用在后台长时间运行,例如音乐播放、定时任务等。源码会展示如何正确管理和使用服务。 8. **权限管理**:Android 6.0及以上版本引入了运行时权限,源代码会解释如何在应用...