Android中的四大组件回顾

1 Activity

1.1 Activity的启动模式

standard :每次启动都会创建activity,系统的默认模式(可以不指定)

single top : 如果在任务的栈顶正好存在该Activity的实例, 就重用该实例,否者就会创建新的实例并放入栈顶(即使栈中已经存在该Activity实例,只要不在栈顶,都会创建实例)。如果存在A-B-C三个Activity,启动D,则D会被创建形成A-B-C-D;如果是A-B-C-D,D是栈顶的情况下,再次启动D,则D不会被创建

single task :如果在栈中已经有该Activity的实例,就重用该实例(会调用实例的onNewIntent())。重用时,会让该实例回到栈顶,因此在它上面的实例将会被移除栈。如果栈中不存在该实例,将会创建新的实例放入栈中。

案例1: 使用singleTask启动模式可以用来退出整个应用:将主Activity设为singleTask模式,然后在要退出的Activity中转到主Activity,从而将主Activity只上的Activity都清除,然后重写主Activity的OnNewIntent方法,在方法中添加finish(),将最后一个Activity结束掉。

案例2:如果一个应用的Activity有多种方式调用启动的情况,多个调用希望只有一个Activity的实例存在,这就需要Activity的 onNewIntent(Intent intent)方法。只要在Activity中加入自己的onNewIntent(intent)的实现加上Manifest中对Activity设置 lanuchMode=“singleTask”就可以。

onNewIntent()非常好用,Activity第一次启动的时候执行 onCreate()---->onStart()---->onResume()等后续生命周期函数,也就时说第一次启动Activity 并不会执行到onNewIntent(). 而后面如果再有想启动Activity的时候,那就是执行onNewIntent()---->onResart()------>onStart()----->onResume(). 如果android系统由于内存不足把已存在Activity释放掉了,那么再次调用的时候会重新启动Activity即执行onCreate()---->onStart()---->onResume()等。

当调用到onNewIntent(intent)的时候,需要在onNewIntent() 中使用setIntent(intent)赋值给Activity的Intent.否则,后续的getIntent()都是得到老的Intent。

single instance: 全局单实例(应用场景:地图,Activity初始化需要大量资源),singleInstance的Activity会出现在一个新的任务栈中,而且该任务栈只存在该Activity
Activity的Manifest.xml中设置,例如:

android:launchMode="singleTask"

Intent Flag启动模式:参考:

frameworks/base/core/java/android/content/Intent.java

Intent.FLAG_ACTIVITY_NEW_TASK :这种启动模式启动的Activity都会被放入一个新的任务栈中,用于从Service启动Activity,因为Service和Activity并不在同一任务栈

If set, this activity will become the start of a new task on this history stack. A task (from the activity that started it to the next task activity) defines an atomic group of activities that the user can move to. Intent.

FLAG_ACTIVITY_SINGLE_TOP : 使用singleTop模式启动Activity

If set, the activity will not be launched if it is already running at the top of the history stack

Intent.FLAG_ACTIVITY_CLEAR_TOP:使用singleTask模式来启动一个Activity

If set, and the activity being launched is already running in the current task, then instead of launching a new instance of that activity, all of the other activities on top of it will be closed and this Intent will be delivered to the (now on top) old activity as a new Intent

Intent.FLAG_ACTIVITY_NO_HISTORY: 使用这种模式启动其他Activity后,该Activity就消失,将不会保留在Activity栈中。

If set, the new activity is not kept in the history stack. As soon as the user navigates away from it, the activity is finished.

Intent.FLAG_ACTIVITY_REORDER_TO_FRONT:

说明:
如果已经启动了四个Activity:A,B,C和D,在D Activity里,想再启动一个Actvity B,但不变成A,B,C,D,B,而是希望是A,C,D,B,则可以像下面写代码:

Intent intent = new Intent(this, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
startActivity(intent);

清空任务栈可以通过设置AndroidMainifest文件中标签的属性来实现clearTaskOnLaunch :每次返回该Activity时,都将该Activity之上的所有Activity清楚掉。
finishOnTaskLaunch:作用于自己身上,当离开该Activity所处的Task,那么用户再返回时,该Activity就会被finish掉。
alwaysRetainTaskState :该Activity将不接受任何清除命令,一直保持在当前Task状态。

案例3
比如:打开QQ客户端,进入到好友聊天界面,然后按HOME返回桌面
这时候有两种情况:
1、如果你在最近任务中切换会QQ客户端,那么还是显示好友聊天界面
2、如果点击QQ客户端图标打开,依然显示好友聊天界面

实现以上功能需要在Androidmanifest.xml文件中,给根Activity加上

android:alwaysRetainTaskState="true"

另外,如果有android:launchMode=“singleTask” ,则把这句删除

当我们按HOME键返回桌面,任务栈的状态被保留着,当我们点击应用图标打开再次应用时,系统会判断是否已经存在以之前的最初打开的Activity为根Activity的栈,如果有,那么就直接使用该栈,并显示栈顶的Activity.1.1.2 Activity生命周期

1.2 Activity生命周期

在这里插入图片描述
OnCreate : 创建Activity时调用 销毁以前只执行一次
OnStart: 打开Activity时调用
OnResume: 唤醒Activity时调用
OnPaused: 暂停Activity时调用当系统调用activity中的onPause(),从技术上讲,意味着activity仍然处于部分可见的状态.但更多时候意味着用户正在离开这个activity,并马上会进入Stopped state. 通常应该在onPause()回调方法里面做以下事情:

停止动画或者是其他正在运行的操作,那些都会导致CPU的浪费. 
提交在用户离开时期待保存的内容(例如邮件草稿). 释放系统资源,例如broadcast receivers, 
sensors (比如GPS), 或者是其他任何会影响到电量的资源。

例如, 如果程序使用Camera,onPause()会是一个比较好的地方去做那些释放资源的操作。

OnStopped: 停止Activity时调用
OnDestroy: 销毁Activity时调用

有三个状态是静态的,这三个状态下activity可以存在一段比较长的时间。(其它几个状态会很快就切换掉,停留的时间比较短暂)

Resumed:该状态下,activity处在前台,用户可以与它进行交互
Paused:该状态下,activity的部分被另外一个activity所遮盖:另外的activity来到前台,但是半透明的,不会覆盖整个屏幕。 被暂停的activity不再接受用户的输入且不再执行任何代码。
Stopped:该状态下, activity完全被隐藏,对用户不可见。可以认为是在后台。当stopped, activity实例与它的所有状态信息(如成员变量等)都会被保留,但activity不能执行任何代码。

一旦onCreate 操作完成,系统会迅速调用onStart() 与onResume()方法。我们的activity不会在Created或者Started状态停留。技术上来说, activity在onStart()被调用后开始被用户可见,但是 onResume()会迅速被执行使得activity停留在Resumed状态,直到一些因素发生变化才会改变这个状态。例如接收到一个来电,用户切换 到另外一个activity,或者是设备屏幕关闭。

异常情况下Activity生命周期异常分析:

当系统配置发生改变后,Activity会被销毁,其onPause,onStop,onDestroy方法均会被调用,同时由于 Activity是在异常情况下终止运行的,此时系统会调用onSaveInstanceState来保存当前Activity的状态。同时在恢复时 Activity运行时,又会调用onRestoreInstanceState方法来恢复Activity的状态。同时我们也要知道,当Activity在异常终止然后又重新启动创建时,系统默认会为我们在onSaveInstanceState方法中用 Bundle保存当前Activity一些状态,如文本框输入的数据,ListView滚动的位置,然后又会在onCreate方法之前调用 onRestoreInstanceState恢复这些状态。

Fragment和Activity生命周期的关系

在创建的过程中,是Activity带领Fragment执行生命周期中的方法,所以生命周期方法的执行顺序是这样的:

1.Activity–onCreate(); 
2.Fragment–onAttach(); 
3.Fragment–onCreate(); 
4.Fragment–onCreateView(); 
5.Fragment–onActivityCreated();

接着是这样的:

6.Activity–onStart(); 
7.Fragment–onStart(); 
8.Activity–onResume(); 
9.Fragment–onResume(); 

我们知道,无论对于Activity还是对于Fragment,onResume这个生命周期都是他们执行时间最长的,当我们的Activity或者Fragment打开之后,它就一直处于这个生命周期中。
当销毁的时候,春江水暖鸭先知,当然是Fragment先感知到,于是销毁的时候就是Fragment带领Activity:

10.Fragment–onPause(); 
11.Activity–onPause(); 
12.Fragment–onStop(); 
13.Activity–onStop(); 
14.Fragment–onDestroyView(); 
15.Fragment–onDestroy(); 
16.Fragment–onDetach(); 
17.Activity–onDestroy(); 

上面这个顺序有一个前提,就是我们所有的日志打印代码都是紧挨着super方法写。因为如果我们如果把Fragment写在了布局文件中,同时又在Activity的onCreate()方法中的setContentView之后打印日志,那么我们看到的生命周期的执行顺序就会有所不同,不过只是细微的差别。

总之一句话,在创建的过程中,是Activity带领着Fragment,在销毁的过程中,是Fragment带领着Activity。

1.3 Activity的交互

当第一个Activity跳转到第二个Activity时,首先执行的是第一个Activity的OnPaused方法,然后执行第二个Activity的OnCreate、OnStart、OnResumed方法,等到第二个Activity完全加载完毕(可显示状态)以后才会执行第一个Activity的Onstopped方法,当然跳转过程不会使第一个Activity被销毁。

当从第二个Activity返回到第一个Activity时,首先执行的是第二个Activity的OnPaused方法,然后调用第一个Activity的OnRestart方法、OnStart方法、OnResumed方法,最后第一个Activity已经加载完毕(可显示状态),调用第二个Activity的OnStopped、OnDestroy方法来销毁它。

1.4 Activity横竖屏切换

Activity横竖屏切换时并不是直接实现了横屏效果,而是先销毁了原来的Activity,然后调用OnCreate、OnStart、OnResume方法来呈现一个新的Activity。

因为Activity在横竖屏切换时会重新创建Activity这样会使原本的Activity的进度发生改变(例如播放器),所以有些情况下我们需要通过OnSaveInstanceState(Bundle outState)这个方法来保存一些数据

具体使用方法
在OnSaveInstanceState(Bundle outState)方法

@override
public void onSaveInstaceState(Bundle outState){
    //一般用于保存当前状态
    super.OnSaveInstaceState(outState);
    outState.putString("key","value");
}

//我们看到OnCreate方法中有一个参数就是Bundle SaveInstaceState
//所以我们在OnCreate中来获取保存数据。
if(saveInstanceState != null){
    String str = saveInstaceState.getString("key");
}

OnSaveInstanceState方法是在OnPause方法执行以后才开始执行的

AndroidMenifest.xml中定义activity的属性

. android:configChanges="orientation|keyboardHidden"的使用

当在activity加上属性:

android:configChanges="keyboardHidden|orientation"

就不会重启 activity.而只是调用onConfigurationChanged(Configuration newConfig).这样就可以在这个方法里调整显示方式. 在xml文件里面可以进行配置configChanges也可以在代码中动态配置

注意:

1、不设置Activity的android:configChanges时,切屏会重新调用各个生命周期,切横屏时会执行一次,切竖屏时会执行两次

2、设置Activity的android:configChanges="orientation"时,切屏还是会重新调用各个生命周期,切横、竖屏时只会执行一次

3、设置Activity的android:configChanges="orientation|keyboardHidden"时,切屏不会重新调用各个生命周期,只会执行onConfigurationChanged方法

1.5 Activity之Recreate

我们可以通过Activity的recreate方法来重建Activity,recreate多走了两个方法onSaveInstanceState(Bundle outState)和onRestoreInstanceState(),即通过Bundle来绑定之前的状态。如果你在Activity的onCreate方法中添加了Fragment,在Activity执行recreate方法时也会多次创建重复的Fragment这样也会造成内存泄漏,其结局办法为,在Recreate之前我们可以先remove掉之前已经包含的Fragment。

   @Override
    public void recreate() {
        try {//避免重启太快 恢复
            FragmentTransaction fragmentTransaction =  
                    getSupportFragmentManager().beginTransaction();
            for (Fragment fragment : fragmentAdapter.getFragmentsList()) {
                fragmentTransaction.remove(fragment);
            }
            fragmentTransaction.commitAllowingStateLoss();
        } catch (Exception e) {
        }
        super.recreate();
    }

1.6 Activity的相关命令

adb shell dumpsys activity---------------查看ActvityManagerService 所有信息
adb shell dumpsys activity activities----------查看Activity组件信息
adb shell dumpsys activity services-----------查看Service组件信息
adb shell dumpsys activity providers----------产看ContentProvider组件信息
adb shell dumpsys activity broadcasts--------查看BraodcastReceiver信息
adb shell dumpsys activity intents--------------查看Intent信息
adb shell dumpsys activity processes---------查看进程信息

adb shell dumpsys activity activities
查看当前resume的是哪个activity:
adb shell dumpsys activity | grep ResumedActivity

adb shell am start -n packagename/packagename.activity
如:adb shell am start -n com.mediatek.mtklogger/com.mediatek.mtklogger.MainActivity

2 BroadcastReceiver

BroadcastReceiver注册的有两种方式,一种是可以在AndroidManifest.xml中注册,另一种可以在运行时的代码中使用Context.registerReceiver()进行注册。用户还可以通过Context.sendBroadcast()将他们自己的intent broadcasts广播给其他的应用程序。

BroadcastReceiver 自身并不实现图形用户界面,但是当它收到某个通知后, BroadcastReceiver 可以启动Activity 作为响应,或者通过 NotificationMananger 提醒用户,或者启动 Service 等等。

2.1 自定义广播接收者

public class MyBroadcastReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        //接收到广播
        Log.d("BroadcastReceiver","BroadcastReceiver收到了消息");
    }
}

2.2 注册广播

动态注册

注册广播接收者时无论动态还是静态必须设置其action的值以便能够找到它

        //动态注册
        myBroadcastReceiver = new MyBroadcastReceiver();
        IntentFilter filter = new IntentFilter();
        filter.addAction("android.intent.action.MY_BROADCAST");
        registerReceiver(myBroadcastReceiver,filter);

注意:动态注册需要在activity或者service销毁时注销注册

@Override
protected void onDestroy() {
     super.onDestroy();
     unregisterReceiver(myBroadcastReceiver);
}

静态注册

静态注册会在程序启动时自动加载,无需再java代码中再注册。

 <receiver
       android:name=".MyBroadcastReceiver" >
       <intent-filter>
            <!--action用于标记广播,发送消息时用于找到广播接收者-->
            <action android:name="android.intent.action.MY_BROADCAST" />
       </intent-filter>

 </receiver>

receive标签

android:enabled=["true" | "false"]

//此broadcastReceiver能否接收其他App的发出的广播
//默认值是由receiver中有无intent-filter决定的:如果有intent-filter,默认值为true,否则为false
android:exported=["true" | "false"]

//当前broadcast Receiver 是否可以从当前应用外部获取Receiver messag
android:name=".MyBroadcastReceiver"

//具有相应权限的广播发送者发送的广播才能被此BroadcastReceiver所接收;
android:permission="string"

//BroadcastReceiver运行所处的进程
//默认为app的进程,可以指定独立的进程
//注:Android四大基本组件都可以通过此属性指定自己的独立进程
android:process="string"

2.3 发送广播

发送普通广播(无序广播)

普通广播是无序的,无序广播无优先级,且无序广播是异步发送的,几乎所有广播接收者会在同一时间接收到广播,无法被截断。

Intent intent = new Intent();
//对应BroadcastReceiver中intentFilter的action
intent.setAction("android.intent.action.MY_BROADCAST");
//发送广播
sendBroadcast(intent);

发送有序广播

有序广播根据优先级,优先级高的广播接收者接收到消息后才会继续向下传递,可以截断。通过设置priority的值来设置优先级(-1000到1000)值越大优先级越大。有序广播的接收需要权限,所以我们需要自己定义一个权限。

对于有序消息,动态注册的BroadcastReceiver总是先于静态注 册的BroadcastReceiver被触发。对于同样是动态注册的BroadcastReceiver,优先级别高的将先被触发,而静态注册的 BroadcastReceiver总是按照静态注册的顺序执行。

其次,按照接收者的优先级顺序接收广播 , 优先级别在 intent-filter 中的 priority 中声明 ,-1000 到1000 之间 , 值越大 , 优先级越高 . 可以终止广播意图的继续传播 . 接收者可以篡改内容 . 同级别接收是先后是随机的,如果先接收到的把广播截断了,同级别的例外的接收者是无法收到该广播的。(abortBroadcast() )

定义权限

<!--定义权限-->
<permission android:protectionLevel="normal"
     android:name="permission.MY_BROADCAST_PERMISSION"
     tools:ignore="ManifestOrder,WrongManifestParent" />
<!--使用权限-->
<uses-permission android:name="permission.MY_BROADCAST_PERMISSION" />

注册广播

<receiver
    android:name=".FirstBroadcastReceiver">
    <intent-filter  android:priority="2">
        <!--action用于标记广播,发送消息时用于找到广播接收者-->
        <action android:name="android.intent.action.MY_BROADCAST" />
    </intent-filter>
</receiver>
<receiver
    android:name=".SecondBroadcastReceiver"  android:priority="1">
    <intent-filter>
        <!--action用于标记广播,发送消息时用于找到广播接收者-->
        <action android:name="android.intent.action.MY_BROADCAST" />
    </intent-filter>
</receiver>

发送广播

Intent intent = new Intent();
//对应BroadcastReceiver中intentFilter的action
intent.setAction("android.intent.action.MY_BROADCAST");
//发送有序广播,第二个参数为权限名称
sendOrderedBroadcast(intent,"permission.MY_BROADCAST_PERMISSION");

发送粘性广播

需要权限

sendStickyBroadcast(intent); 当处理完之后的Intent ,依然存在,直到你把它去掉。去掉是用这个方法removeStickyBroadcast(intent);

//粘性广播,及先发送了广播,再注册广播接收者,广播接收者仍然可以接收到之前的广播
Intent intent = new Intent();
//对应BroadcastReceiver中intentFilter的action
intent.setAction("android.intent.action.MY_BROADCAST");
//发送无序广播
sendStickyBroadcast(intent);

注意:sendStickyOrderedBroadcast();该方法可以发送具有粘性的有序广播

sendStickyOrderedBroadcast (),在这个方法发来的广播中,代码注册方式中,收到广播先后次序为:注明优先级的、代码注册的、没有优先级的;如果都没有优先级,代码注册收到为最先。

abortBroadcast ():

这个方法可以截获由 sendOrderedBroadcast () 发送来的 广播,让其它广播接收者无法收到这个广播。

clearAbortBroadcast ()

这个方法是针对上面的 abortBroadcast() 方法的,用于取消截获广播。这样它的下一级广播接收者就能够收到该广播了。

getAbortBroadcast ()

这个方法作用是:判断是否调用了 abortBroadcast (),如果先调用 abortBroadcast (),接着再调用getAbortBroadcast (),将返回 true; 如果在调用 abortBroadcast() 、 clearAbortBroadcast ()

getAbortBroadcast (),将返回 false;

2.4 生命周期

每次广播到来时 , 会重新创建 BroadcastReceiver 对象 , 并且调用 onReceive() 方法 , 执行完以后 , 该对象即被销毁 . 当 onReceive() 方法在 10 秒内没有执行完毕, Android 会认为该程序无响应 . 所以在BroadcastReceiver 里不能做一些比较耗时的操作 , 否侧会弹出 ANR(Application No Response) 的对话框 。

关于耗时操作的处理

如果需要完成一项比较耗时的工作 , 应该通过发送 Intent 给 Service, 由 Service 来完成 . 这里不能使用子线程来解决 , 因为 BroadcastReceiver 的生命周期很短 , 子线程可能还没有结束BroadcastReceiver 就先结束了 .BroadcastReceiver 一旦结束 , 此时 BroadcastReceiver 的所在进程很容易在系统需要内存时被优先杀死 , 因为它属于空进程 ( 没有任何活动组件的进程 ). 如果它的宿主进程被杀死 , 那么正在工作的子线程也会被杀死 . 所以采用子线程来解决是不可靠的 .

一般在onResume中注册广播,在onPause中注销广播

2.5 Broadcast的相关命令

adb shell am broadcast -a the.action.anything com.waves.maxxservice/.MaxxServiceBootReceiver

通过上面的命令向com.waves.maxxservice/.MaxxServiceBootReceiver 发送一个action 为the.action.anything的广播, MaxxServiceBootReceiver 就会receive到

因此j接收广播的一方最好加上判断 ,比如接收BOOT_COMPLETED

 if ("android.intent.action.BOOT_COMPLETED".equals(in_intent.getAction())) {
     ...//再做执行
 }
<receiver
    android:name=".MaxxServiceBootReceiver"
    android:enabled="true"
    android:exported="true" >
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED" />
    </intent-filter>
</receiver>

public class MaxxServiceBootReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context in_context, Intent in_intent) {
        Log.d("MaxxServiceBoot", "in_intent.getAction():" + in_intent.getAction());
        if ("android.intent.action.BOOT_COMPLETED".equals(in_intent.getAction())) {
            MaxxLogger.Debug("MaxxService launching on boot completed");
            in_context.startService(new Intent(in_context, MaxxService.class));
        }
    }
}

3 ContentProvider

Android应用程序可以使用文件或SqlLite数据库来存储数据。Content Provider提供了一种多应用间数据共享的方式。Content Provider是个实现了一组用于提供其他应用程序存取数据的标准方法的类。

如果你的数据和已存在的Content provider 数据结构一致,可以将数据写到已存在的 Content provider 中,当然前提是获取写该 Content provider 的权限。比如把OA中的成员通讯信息加入到系统的联系人 Contentprovider 中。
每一个ContentProvider都会对外提供一个公共的URI,如果应用程序有数据需要共享,就需要使用ContentProvider为这些数据定义一个URI,然后其他应用程序就可以通过ContentProvider传入这个URI来对数据进行操作。

URI由3个部分组成:“content://”、数据的路径、标示ID(可选)。

​ content://media/internal/images 这个URI返回设备上存储的所有图片

​ content://contacts/people/5 联系人信息中ID为5的联系人记录

​ content://contacts:/people/ 这个URI返回设备上得所有联系人信息。

3.1 标准的Content Provider

Android提供了一些已经在系统中实现的标准Content Provider,比如联系人信息,图片库等等,你可以用这些Content Provider来访问设备上存储的联系人信息,图片等等

查询记录:

在Content Provider中使用的查询字符串有别于标准的SQL查询。很多诸如select, add, delete, modify等操作我们都使用一种特殊的URI来进行,这种URI由3个部分组成,

“content://”, 代表数据的路径,和一个可选的标识数据的ID。以下是一些示例URI:

content://media/internal/images  这个URI将返回设备上存储的所有图片
content://contacts/people/  这个URI将返回设备上的所有联系人信息
content://contacts/people/45 这个URI返回单个结果(联系人信息中ID为45的联系人记录)

例如:

依次读取联系人信息表中的指定数据列name和number

package com.henry.testApp;
public class ContentProviderDemo extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        displayContactRecords();
    }
    private void displayContactRecords() {
       //该数组中包含了所有要返回的字段
       String columns[] = new String[] { People.NAME, People.NUMBER };
       Uri mContacts = People.CONTENT_URI;
       Cursor cur = managedQuery(
               mContacts,
               columns,  // 要返回的数据字段
               null,          // WHERE子句
               null,         // WHERE 子句的参数
               null         // Order-by子句
       );
       if (cur.moveToFirst()) {
           String name = null;
           String phoneNo = null;
           do {
               // 获取字段的值
               name = cur.getString(cur.getColumnIndex(People.NAME));
               phoneNo = cur.getString(cur.getColumnIndex(People.NUMBER));
               Toast.makeText(this, name + ” ” + phoneNo, Toast.LENGTH_LONG).show();
          } while (cur.moveToNext());
       }
    }
}

修改记录:

可以使用ContentResolver.update()方法来修改数据,我们来写一个修改数据的方法:

private void updateRecords(int recNo, String name){
    Uri uri = ContentUri.withAppendedId(People.CONTENT_URI, recNo);
    ContentValues values = new ContentValues();
    values.put(People.NAME, name);
    getContentResolver().update(uri, values, null, null);
}

现在你可以调用上面的方法来更新指定记录:

updateRecords(10, ”James”);   //更改第10条记录的name字段值为“James”

添加记录:

增加记录,我们可以调用ContentResolver.insert()方法,该方法接受一个要增加的记录的目标URI,以及一个包含了新记录值的Map对象,调用后的返回值是新记录的URI,包含记录号

例如,对联系人信息簿中进行数据的添加

private void insertRecords(String name, String phoneNo){
    ContentValues values = new ContentValues();
    values.put(People.NAME, name);
    Uri uri = getContentResolver().insert(People.CONTENT_URI, values);
    Log.d(”ANDROID”, uri.toString());
    Uri numberUri = Uri.withAppendedPath(uri, People.Phones.CONTENT_DIRECTORY);
    values.clear();
    values.put(Contacts.Phones.TYPE, People.Phones.TYPE_MOBILE);
    values.put(People.NUMBER, phoneNo);
    getContentResolver().insert(numberUri, values);
}

这样我们就可以调用insertRecords(name, phoneNo)的方式来向联系人信息簿中添加联系人姓名和电话号码。

删除记录:

Content Provider中的getContextResolver.delete()方法可以用来删除记录,下面的记录用来删除设备上所有的联系人信息:

private void deleteRecords() {
    Uri uri = People.CONTENT_URI;
    getContentResolver().delete(uri, null, null);
}

你也可以指定WHERE条件语句来删除特定的记录:

getContentResolver().delete(uri, “NAME=” + “‘XYZ XYZ’”, null);

这将会删除name为‘XYZ XYZ’的记录。

3.2 创建Content Provider

要创建我们自己的Content Provider的话,我们需要遵循以下几步:

  1. 创建一个继承了ContentProvider父类的类

    public class MediaProvider extends ContentProvider {
    }
    
  2. 定义一个名为CONTENT_URI,并且是public static final的Uri类型的类变量,你必须为其指定一个唯一的字符串值,最好的方案是以类的全名称, 如:

public static final Uri CONTENT_URI = Uri.parse( “content://com.google.android.MyContentProvider”);

  1. 创建你的数据存储系统。大多数Content Provider使用Android文件系统或SQLite数据库来保持数据,但是你也可以以任何你想要的方式来存储。

  2. 定义你要返回给客户端的数据列名。如果你正在使用Android数据库,则数据列的使用方式就和你以往所熟悉的其他数据库一样。但是,你必须为其定义一个叫_id的列,它用来表示每条记录的唯一性。

  3. Content Provider 存储文件: 如果你要存储字节型数据,比如位图文件等,那保存该数据的数据列其实是一个表示实际保存文件的URI字符串,客户端通过它来读取对应的文件数据,处理这种数据类型的Content Provider需要实现一个名为_data的字段,_data字段列出了该文件在Android文件系统上的精确路径。这个字段不仅是供客户端使用,而且也可以供ContentResolver使用。客户端可以调用ContentResolver.openOutputStream()方法来处理该URI指向的文件资源,如果是ContentResolver本身的话,由于其持有的权限比客户端要高,所以它能直接访问该数据文件。

    通常表中文件类型的数据列名取名_data形式,重写openFile方法

    @Override
    public Parcel FileDescriptor openFile(Uri uri, String mode)
      throws FileNotFoundException {
     
      // Find the row ID and use it as a filename.
      String rowID = uri.getPathSegments().get(1);
     
      // Create a file object in the application’s external
      // files directory.
      String picsDir = Environment.DIRECTORY_PICTURES;
      File file =
         newFile(getContext().getExternalFilesDir(picsDir), rowID);
     
      // If the file doesn’t exist, create it now.
      if(!file.exists()) {
         try{
           file.createNewFile();
         } catch(IOException e) {
           Log.d(TAG, “File creation failed:+ e.getMessage());
         }
      }
     
      // Translate the mode parameter to the corresponding Parcel File
      // Descriptor open mode.
      intfileMode = 0;
      if(mode.contains(“w”))
         fileMode |= ParcelFileDescriptor.MODE_WRITE_ONLY;
      if(mode.contains(“r”))
         fileMode |= ParcelFileDescriptor.MODE_READ_ONLY;
      if(mode.contains(+))
         fileMode |= ParcelFileDescriptor.MODE_APPEND;
     
      // Return a Parcel File Descriptor that represents the file.
      return ParcelFileDescriptor.open(file, fileMode);
    }
    
  4. 声明public static String型的变量,用于指定要从游标处返回的数据列。

  5. 查询返回一个Cursor类型的对象。所有执行写操作的方法如insert(), update() 以及delete()都将被监听。我们可以通过使用ContentResover().notifyChange()方法来通知监听器关于数据更新的信息。

  6. 在AndroidMenifest.xml中使用标签来设置Content Provider。

  7. 如果你要处理的数据类型是一种比较新的类型,你就必须先定义一个新的MIME类型,以供ContentProvider.geType(url)来返回。MIME类型有两种形式:一种是为指定的单个记录的,还有一种是为多条记录的。这里给出一种常用的格式:

    vnd.android.cursor.item/vnd.yourcompanyname.contenttype (单个记录的MIME类型)
    
    

    比如, 一个请求列车信息的URI如content://com.example.transportationprovider/trains/122 可能就会返回type vnd.android.cursor.item/vnd.example.rail这样一个MIME类型。

    vnd.android.cursor.dir/vnd.yourcompanyname.contenttype (多个记录的MIME类型)
    

    比如, 一个请求所有列车信息的URI如content://com.example.transportationprovider/trains 可能就会返回vnd.android.cursor.dir/vnd.example.rail这样一个MIME 类型。

下列代码将创建一个Content Provider,它仅仅是存储用户名称并显示所有的用户名称(使用 SQLLite数据库存储这些数据):

package com.henry.testProvider
public class ProviderUsers{
    public static final String AUTHORITY = "com.henry.MyContentProvider";
    //BaseColumn类中已经包含了 _id字段
    public static final class ProviderUser implements BaseColumns {
         //每个Content Provider应该使用公有静态常量 CONTENT_URI ,使其更容易被访问。
         public static final Uri CONTENT_URI  = 
                 Uri.parse(”content://com.henry.MyContentProvider”);
         // 表数据列
         public static final String  USER_NAME  = “USER_NAME”;
     }
}

上面的类中定义了Content Provider的CONTENT_URI,以及数据列。下面我们将定义基于上面的类来定义实际的Content Provider类:

package com.henry.testProvider.android;
public class MyContentProvider extends ContentProvider {
    private SQLiteDatabase     mSqlDB;
    private DatabaseHelper    mDBHelper;
    private static final String  DATABASE_NAME     = “Users.db”;
    private static final int  DATABASE_VERSION   = 100;
    private static final String TABLE_NAME   = “User”;
    private static final String TAG = “MyContentProvider”;

    // Create the constants used to differentiate between
    // the different URI requests.
    private static final int ALLROWS = 1;
    private static final int SINGLE_ROW = 2;
 
    private static final UriMatcher uriMatcher;
 
    // Populate the UriMatcher object, where a URI ending
    // in ‘elements’ will correspond to a request for all
    // items, and ‘elements/[rowID]’ represents a single row.
    static{
        uriMatcher = newUriMatcher(UriMatcher.NO_MATCH);
        uriMatcher.addURI(“com.henry.MyContentProvider”,
                “users”, ALLROWS);
        uriMatcher.addURI(ProviderUsers.AUTHORITY,
                “users/#”, SINGLE_ROW);
    }
     
    private static class DatabaseHelper extends SQLiteOpenHelper {
        
        DatabaseHelper(Context context) {
            super(context, DATABASE_NAME, null, DATABASE_VERSION);
        }
        
        @Override
        public void onCreate(SQLiteDatabase db) {
        //创建用于存储数据的表
        db.execSQL(”Create table ” + TABLE_NAME +( _id INTEGER PRIMARY KEY AUTOINCREMENT, USER_NAME TEXT););
        }
        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
            db.execSQL(”DROP TABLE IF EXISTS ” + TABLE_NAME);
            onCreate(db);
        }
    }
    
    @Override
    public int delete(Uri uri, String s, String[] as) {
        return 0;
    }
    
    @Override
    public String getType(Uri uri) {
        // Return a string that identifies the MIME type
        // for a Content Provider URI
        switch(uriMatcher.match(uri)) {
            case ALLROWS:
                return “vnd.android.cursor.dir/vnd.henry.users”;
            case SINGLE_ROW:
                return “vnd.android.cursor.item/vnd.henry.user”;
            default:
                throw new IllegalArgumentException(“Unsupported URI:+ uri);
     }
        return null;
    }
    
    @Override
    public Uri insert(Uri uri, ContentValues contentvalues) {
        mSqlDB = mDBHelper.getWritableDatabase();
        long rowId = mSqlDB.insert(TABLE_NAME, “”, contentvalues);
        if (rowId > 0) {
            Uri rowUri = ContentUris.appendId(ProviderUsers.User.CONTENT_URI.
                    buildUpon(), rowId).build();
            getContext().getContentResolver().notifyChange(rowUri, null);
            return rowUri;
        }
        throw new SQLException(”Failed to insert row into ” + uri);
    }
    
    @Override
    public boolean onCreate() {
        mDBHelper = new DatabaseHelper(getContext());
        return (mDBHelper == null) ? false : true;
    }
    
    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
            String[] selectionArgs, String sortOrder) {
        SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
        SQLiteDatabase db = mDBHelper.getReadableDatabase();
        qb.setTables(TABLE_NAME);
        Cursor c = qb.query(db, projection, selection, null, null, null, sortOrder);
        c.setNotificationUri(getContext().getContentResolver(), uri);
        return c;
    }
    @Override
    public int update(Uri uri, ContentValues contentvalues, String s, String[] as) {
        return 0;
    }
}



一个名为MyContentProvider的Content Provider创建完成了,它用于从Sqlite数据库中添加和读取记录。

Content Provider的入口需要在AndroidManifest.xml中配置:

<provider android:name=”.MyContentProvider”
            android:authorities=”com.henry.MyContentProvider”/>

使用provider标签:包含name和aythorities的属性。authorities属性定义基础URI。代表一个数据库。

之后使用这个定义好的Content Provider:

package com.henry.testProvider;
public class ContentProviderUseDemo extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        insertRecord(”ProviderUser”);
        displayRecords();
    }
   
    private void insertRecord(String userName) {
        ContentValues values = new ContentValues();
        values.put(ProviderUsers.User.USER_NAME, userName);
        getContentResolver().insert(ProviderUsers.User.CONTENT_URI, values);
    }
    private void displayRecords() {
        String columns[] = new String[] { ProviderUsers.User._ID,
                                         ProviderUsers.User.USER_NAME };
        Uri myUri = ProviderUsers.User.CONTENT_URI;
        Cursor cur = managedQuery(myUri, columns,null, null, null );
        if (cur.moveToFirst()) {
            String id = null;
            String userName = null;
            do {
                id = cur.getString(cur.getColumnIndex(ProviderUsers.User._ID));
                userName = cur.getString(cur.getColumnIndex(
                        ProviderUsers.User.USER_NAME));
                Toast.makeText(this, id + ” ” + userName, Toast.LENGTH_LONG).show();
           } while (cur.moveToNext());
       }
    }
}

上面的类将先向数据库中添加一条用户数据,然后显示数据库中所有的用户数据。

4 Service

4.1 概述

Service和其他组件一样,都是运行在主线程中,因此不能用它来做耗时的请求或者动作。你可以在服务中开一一个线程,在线程中做耗时动作。事实上四大组件都是运行在UI线程中,都不能在各自的生命周期方法中执行耗时操作或者网络请求

服务一般分为两种:

1:本地服务, Local Service 用于应用程序内部。在Service可以调用Context.startService()启动,调用Context.stopService()结束。在内部可以调用Service.stopSelf() 或 Service.stopSelfResult()来自己停止。无论调用了多少次startService(),都只需调用一次stopService()来停止。

2:远程服务, Remote Service 用于android系统内部的应用程序之间。可以定义接口并把接口暴露出来,以便其他应用进行操作。客户端建立到服务对象的连接,并通过那个连接来调用服务。调用Context.bindService()方法建立连接,并启动,以调用 Context.unbindService()关闭连接。多个客户端可以绑定至同一个服务。如果服务此时还没有加载,bindService()会先加载它。

4.2 服务的生命周期

本地服务

context.startService() ->onCreate()- >onStartCommand()->Service running–调用context.stopService() ->onDestroy()

startService()以后,会执行onCreate()—>onStartCommand()这两个生命周期方法,重复调用startService的话,onStartCommand()也会重复执行。stopService()以后,就会执行onDestory()方法。

远程服务

context.bindService()->onCreate()->onBind()->Service running–调用>onUnbind() -> onDestroy()

重复调用bindService(),onCreate(),onBind()这两个方法不会再执行。

startService和bindService混合使用分析

需求:在activity中要得到service对象进而能调用对象的方法,但同时又不希望activity finish的时候service也被destory

解决:需要startService和bindService混合使用

分析startService以及bindService后,service的生命周期

在activity onCreate()中调用startService():

protected void onCreate(Bundle savedInstanceState){
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    startService(new Intent(this,TestService.class));
}

在activity onStart()中调用bindService():

protected void onStart(){
    super.onStart();
    bindService(new Intent(this,TestService.class),mServiceConnection, 
                Context.BIND_AUTO_CREATE);
}

onCreate() ->onStartCommand() -> onBind();

如果在activity onCreate()调用bindService(),onStart()方法调用startService(),则生命周期:

onCreate() -> onBind() ->onStartCommand() ;

要实现这样的需求:在activity中要得到service对象调用对象的方法,但同时又不希望activity finish的时候service也被destory了。

那么在Activity的onPause方法中,执行unbindService()即可,只有onUnbind方法会执行,onDestory不会执行(Service依然存在,可通过isDestory方法判断),因为还有一个startService的启动方式存在。如果要完全退出Service,那么就得执行unbindService()以及stopService(或者stopSelf)。

先startService后bindService,或者先bindService后startService,得到的结果跟上述的一样。

这种实现方法的原理就是:只要还有一种启动方法存在,Service就会继续存活。

4.3 Service 与 Thread 的区别

Thread 是程序执行的最小单元,它是分配CPU的基本单位。可以用 Thread 来执行一些异步的操作。

Service 是android的一种机制,当它运行的时候如果是Local Service,那么对应的 Service 是运行在主进程的 main 线程上的;如果是Remote Service,那么对应的 Service 则是运行在独立进程的 main 线程上。

Thread 的运行是独立于 Activity 的,也就是说当一个 Activity 被 finish 之后,如果你没有主动停止 Thread 或者 Thread 里的 run 方法没有执行完毕的话,Thread 也会一直执行。因此这里会出现一个问题:当 Activity 被 finish 之后,你不再持有该 Thread 的引用。另一方面,你没有办法在不同的 Activity 中对同一 Thread 进行控制。

举个例子:如果你的 Thread 需要不停地隔一段时间就要连接服务器做某种同步的话,该 Thread 需要在 Activity 没有start的时候也在运行。这个时候当你 start 一个 Activity 就没有办法在该 Activity 里面控制之前创建的 Thread。因此你便需要创建并启动一个 Service ,在 Service 里面创建、运行并控制该 Thread,这样便解决了该问题(因为任何 Activity 都可以控制同一 Service,而系统也只会创建一个对应 Service 的实例)。

因此你可以把 Service 想象成一种消息服务,而你可以在任何有 Context 的地方调用 Context.startService、Context.stopService、 Context.bindService,Context.unbindService,来控制它,你也可以在 Service 里注册 BroadcastReceiver,在其他地方通过发送 broadcast 来控制它,当然这些都是 Thread 做不到的。

4.4 服务的持续运行

可以调用用startForeground方法和stopForegound方法,系统同意,在这两个方法期间不会杀死服务进程。但是服务 必须提供一个图标和视图,通过传递给用户程序一个Notification对象,将其显示在顶部的状态条中。

将service放到前台状态, 低内存时被kill的几率更低,但是文档后面又写了,如果在极度极度低内存的压力下,该service理论上还是会被kill掉。但这个情况基本不用考虑。

当然如果service怎么保持还是被kill了,那你可以通过重写onStartCommand返回变量来设置它的启动方式。比如:START_STICKY、START_REDELIVER_INTENT等等

或者可以在Service的onDestroy()中重启Service.

public void onDestroy() {  
    Intent localIntent = new Intent(); 
    localIntent.setClass(this, MyService.class); //销毁时重新启动Service 
    this.startService(localIntent); 
} 

4.5 控制service运行的主要方式

控制service运行的主要方式有两种,主要是根据onStartCommand方法返回的数值。方法:

​ 1、START_STICKY

​ 2、START_NOT_STICKY or START_REDELIVER_INTENT

​ 这里主要解释这三个变量的意义:

​ 1、 START_STICKY

​ 在运行onStartCommand后service进程被kill后,那将保留在开始状态,但是不保留那些传入的intent。不久后service就会再次尝试重新创建,因为保留在开始状态,在创建 service后将保证调用onstartCommand。如果没有传递任何开始命令给service,那将获取到null的intent

​ 2、 START_NOT_STICKY

​ 在运行onStartCommand后service进程被kill后,并且没有新的intent传递给它。Service将移出开始状态,并且直到新的明显的方法(startService)调用才重新创建。因为如果没有传递任何未决定的intent那么service是不会启动,也就是期间onstartCommand不会接收到任何null的intent。

​ 3、 START_REDELIVER_INTENT

​ 在运行onStartCommand后service进程被kill后,系统将会再次启动service,并传入最后一个intent给onstartCommand。直到调用stopSelf(int)才停止传递intent。如果在被kill后还有未处理好的intent,那被kill后服务还是会自动启动。因此onstartCommand不会接收到任何null的intent。

4.6 Service的使用

定义一个本地服务继承Service,然后在这个服务里播放媒体播放器或者记录地理位置变化。通常有时候我们的Service要与Activity交互,那么可以可以定义一个内部类,返回这个Service,当然我们要考虑到如果是以绑定方式启动服务,那么内部类可以定义为继承Binder,然后返回本地服务,具体代码如下。

package com.carson;

import android.app.Service;
import android.content.Intent;
import android.media.MediaPlayer;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;

public class LocalService extends Service {

    private static final String TAG = "LocalService";

    private IBinder binder=new LocalService.LocalBinder();
   
    @Override
    public IBinder onBind(Intent intent) {
        return binder;
    }
    
    MediaPlayer mediaPlayer=null;
    @Override
    public void onCreate() {
            Log.i(TAG, "onCreate");
            //这里可以启动媒体播放器
           // if(mediaPlayer==null)
           // mediaPlayer=MediaPlayer.create(this, uri);
            super.onCreate();
    }


    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
          Log.i(TAG, "onStartCommand");
        return START_STICKY;
    }

   
   
    @Override
    public void onDestroy() {
            Log.i(TAG, "onDestroy");
            super.onDestroy();
    }
    
    //定义内容类继承Binder
    public class LocalBinder extends Binder{
        //返回本地服务
        LocalService getService(){
            return LocalService.this;
        }
    } 
}

可以返回这个服务,然后activity可以通过服务调用服务的方法了。

对于Activity

//第一种启动服务

private void startCustomService(){
         Intent intent=new Intent(this,LocalService.class);
         startService(intent);
}

//第二种启动服务

    LocalService localService=null;
    //用bindService方法启动服务
    private void BinderService(){
         Intent intent=new Intent(this,LocalService.class);
         bindService(intent, new ServiceConnection(){
            @Override
            public void onServiceConnected(ComponentName componentName, 
                    IBinder binder) {
                //调用bindService方法启动服务时候,如果服务需要与activity交互,
                //则通过onBind方法返回IBinder并返回当前本地服务
                localService=((LocalService.LocalBinder)binder).getService();
                //这里可以提示用户,或者调用服务的某些方法
            }

            @Override
            public void onServiceDisconnected(ComponentName componentName) {
                localService=null;
                //这里可以提示用户
            }
         }, Context.BIND_AUTO_CREATE);
    }

在绑定服务的时候,需要一个服务连接对象,ServiceConnection,服务一旦连接,就会调用onServiceConnected方法,我们可以在这个方法里面返回我们的本地服务对象,具体看代码;而在服务断开时候会调用onServiceDisconnected方法,我们可以清理一些服务资源。

再比如:

 private ITctAlexaVoiceService mITctAlexaVoiceService;
 private ServiceConnection mConnection = new ServiceConnection() {

        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            mITctAlexaVoiceService = ITctAlexaVoiceService.Stub.asInterface(iBinder);
            Log.d(TAG, "mITctAlexaVoiceService = "+mITctAlexaVoiceService);
            refreshUI();
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            mITctAlexaVoiceService = null;
            mIsSignIn = false;
            Log.d(TAG, "onServiceDisconnected:mITctAlexaVoiceService = null");
        }
    };

   private void bindAlexaService(){
        try{
            Intent intent = new Intent(ITctAlexaVoiceService.class.getName());
            intent.setPackage("com.amazon.alexa.avs.companion");
            boolean success = mContext.bindService(intent, mConnection, 
                     Context.BIND_AUTO_CREATE);
            Log.d(TAG, "bindAlexaService state ="+success);
        }catch(Exception e){
            e.printStackTrace();
        }
    }

    private void startAlexa(){
        Log.i(TAG, "startAlexa()");
        if (mITctAlexaVoiceService == null) {
            Log.i(TAG, "bindservice alexavoiceservice begin");
            Intent intent = new Intent(ITctAlexaVoiceService.class.getName());
            intent.setPackage("com.amazon.alexa.avs.companion");
            mContext.bindServiceAsUser(intent, mConnection, 
                    Context.BIND_AUTO_CREATE, UserHandle.CURRENT);

            Log.i(TAG, "bindservice alexavoiceservice end");
        } else {
            try {
                Log.i(TAG, "ITctAlexaVoiceService.startSession");
                mITctAlexaVoiceService.startSession();
            } catch (Exception e) {
                Log.d("AlexaVoiceService","bindTctAlexaVoiceService e =" + e);
            }
        }
    }

4.7 IntentService

IntentService是继承于Service并处理异步请求的一个类,在IntentService内有一个工作线程来处理耗时操作,启动IntentService的方式和启动传统Service一样,同时,当任务执行完后,IntentService会自动停止,而不需要我们去手动控制。另外,可以启动IntentService多次,而每一个耗时操作会以工作队列的方式在IntentService的onHandleIntent回调方法中执行,并且,每次只会执行一个工作线程,执行完第一个再执行第二个,以此类推。

所有请求都在一个单线程中,不会阻塞应用程序的主线程(UI Thread),同一时间只处理一个请求。

public class IntentSer extends IntentService {

    private String path_url = 
            "http://t1.ituba.cc/uploads/allimg/c101201/12911X4Fb460-2E024.jpg";

    public IntentSer() {
        super("IntentSer");
        // TODO 自动生成的构造函数存根
    }
    @Override
    protected void onHandleIntent(Intent intent) {
        // TODO 自动生成的方法存根
        
        File file = new File(this.getFilesDir(),"service.png");
        try {
            FileOutputStream outputStream = new FileOutputStream(file);
            
            InputStream inputStream = new URL(path_url).openStream();
            byte[] buffer = new byte[1024];
            int len = -1;
            while((len = inputStream.read(buffer)) != -1){
                outputStream.write(buffer, 0, len);
            }
            outputStream.close();
            inputStream.close();
        } catch (FileNotFoundException e) {
            // TODO 自动生成的 catch 块
            e.printStackTrace();
        } catch (MalformedURLException e) {
            // TODO 自动生成的 catch 块
            e.printStackTrace();
        } catch (IOException e) {
            // TODO 自动生成的 catch 块
            e.printStackTrace();
        }
    }
}

1.A Service is not a separate process. The Service object itself does not imply it is running in its own process; unless otherwise specified, it runs in the same process as the application it is part of.Service不是一个单独的进程 ,它和应用程序在同一个进程中

2.A Service is not a thread. It is not a means itself to do work off of the main thread (to avoid Application Not Responding errors).Service不是一个线程,所以我们应该避免在Service里面进行耗时的操作

如果有耗时操作在Service里,就必须开启一个单独的线程来处理

IntentService相对于Service来说,有几个非常有用的优点,首先我们看看官方文档的说明:

IntentService is a base class for Services that handle asynchronous requests (expressed as Intents) on demand. Clients send requests throughstartService(Intent) calls; the service is started as needed, handles each Intent in turn using a worker thread, and stops itself when it runs out of work.

This “work queue processor” pattern is commonly used to offload tasks from an application’s main thread. The IntentService class exists to simplify this pattern and take care of the mechanics. To use it, extend IntentService and implement onHandleIntent(Intent). IntentService will receive the Intents, launch a worker thread, and stop the service as appropriate.

All requests are handled on a single worker thread – they may take as long as necessary (and will not block the application’s main loop), but only one request will be processed at a time.

使用队列的方式将请求的Intent加入队列,然后开启一个worker thread(线程)来处理队列中的Intent,对于异步的startService请求,IntentService会处理完成一个之后再处理第二个,每一个请求都会在一个单独的worker thread中处理,不会阻塞应用程序的主线程,这里就给我们提供了一个思路,如果有耗时的操作与其在Service里面开启新线程还不如使用IntentService来处理耗时操作。

优点:

用IntentService有什么好处呢?首先,我们省去了在Service中手动开线程的麻烦,第二,当操作完成时,我们不用手动停止Service

使用场景: 在Android开发中,我们或许会碰到这么一种业务需求,一项任务分成几个子任务,子任务按顺序先后执行,子任务全部执行完后,这项任务才算成功。那么,利用几个子线程顺序执行是可以达到这个目的的,但是每个线程必须去手动控制,而且得在一个子线程执行完后,再开启另一个子线程。或者,全部放到一个线程中让其顺序执行。这样都可以做到,但是,如果这是一个后台任务,就得放到Service里面,由于Service和Activity是同级的,所以,要执行耗时任务,就得在Service里面开子线程来执行。那么,有没有一种简单的方法来处理这个过程呢,答案就是IntentService。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

安德路

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值