Activity : 界面

基础信息

Activity :

应用程序中,一个Activity通常就是一个单独的屏幕,它上面可以显示一些控件也可以监听并处理用户的事件做出响应。

Activity之间通过Intent进行通信。在Intent 的描述结构中,有两个最重要的部分:动作和动作对应的数据。

典型的动作类型有:M AIN(activity的门户)、VIEW、PICK、EDIT 等。而动作对应的数据则以URI 的形式进行表示。例如:要查看一个人的联系方式,你需要创建一个动作类型为VIEW 的intent,以及一个表示这个人的URI。

与之有关系的一个类叫IntentFilter。相对于intent 是一个有效的做某事的请求,一个intentfilter 则用于描述一个activity(或者IntentReceiver)能够操作哪些intent。一个activity 如果要显示一个人的联系方式时,需要声明一个IntentFilter,这个IntentFilter 要知道怎么去处理VIEW 动作和表示一个人的URI。IntentFilter 需要在AndroidManifest.xml 中定义。通过解析各种intent,从一个屏幕导航到另一个屏幕是很简单的。当向前导航时,activity 将会调用startActivity(Intent myIntent)方法。然后,系统会在所有安装的应用程序中定义的IntentFilter 中查找,找到最匹配myIntent 的Intent 对应的activity。新的activity 接收到myIntent 的通知后,开始运行。当startActivity 方法被调用将触发解析myIntent 的动作,这个机制提供了两个关键好处:

A、Activities 能够重复利用从其它组件中以Intent 的形式产生的一个请求;

B、Activities 可以在任何时候被一个具有相同IntentFilter 的新的Activity 取代。

AndroidManifest文件中含有如下过滤器的Activity组件为默认启动类当程序启动时系统自动调用它

<intent-filter>
       <action android:name="android.intent.action.MAIN" />
       <category android:name="android.intent.category.LAUNCHER" />
</intent-filter>

启动模式

  • 代码启动
Intent intent = new Intent();
intent = intent.setClass(this, act.getClass);
if (bundle != null) {
    intent.putExtras(bundle);
}
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
startActivity(intent);
  • 控制Activity运行在一个单独的进程(process)中……
<activity android:name=".MyActivity" android:label="@string/app_nam"  
    android:process=":remote">  
</activity>
  • 四种启动模式(launcheMode)
<activity android:name=".app.InterstitialMessageActivity"  
          android:label="@string/interstitial_label"  
          android:theme="@style/Theme.Dialog"  
          android:launchMode="singleTask"  
</activity>
  • standard:默认启动模式
  • singleTop:如果Activity
  • singleTask:一个任务栈只有一个
  • singleInstance:另开一个任务栈

// 也可以通过一下常量的方式修改
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);

BroadcastReceive : 广播接收者

基础信息

你的应用可以使用它对外部事件进行过滤只对感兴趣的外部事件(如当电话呼入时,或者数据网络可用时)进行接收并做出响应。广播接收器没有用户界面。然而,它们可以启动一个activity或serice 来响应它们收到的信息,或者用NotificationManager 来通知用户。通知可以用很多种方式来吸引用户的注意力──闪动背灯、震动、播放声音等。一般来说是在状态栏上放一个持久的图标,用户可以打开它并获取消息。

广播类型:

普通广播,通过Context.sendBroadcast(Intent myIntent)发送的

有序广播,通过Context.sendOrderedBroadcast(intent, receiverPermission)发送的,该方法第2个参数决定该广播的级别,级别数值是在 -1000 到 1000 之间 , 值越大 , 发送的优先级越高;广播接收者接收广播时的级别级别(可通过intentfilter中的priority进行设置设为2147483647时优先级最高),同级别接收的先后是随机的, 再到级别低的收到广播,高级别的或同级别先接收到广播的可以通过abortBroadcast()方法截断广播使其他的接收者无法收到该广播,还有其他构造函数

异步广播,通过Context.sendStickyBroadcast(Intent myIntent)发送的,还有sendStickyOrderedBroadcast(intent, resultReceiver, scheduler, initialCode, initialData, initialExtras)方法,该方法具有有序广播的特性也有异步广播的特性;发送异步广播要: 权限,接收并处理完Intent后,广播依然存在,直到你调用removeStickyBroadcast(intent)主动把它去掉

注意:发送广播时的intent参数与Contex.startActivity()启动起来的Intent不同,前者可以被多个订阅它的广播接收器调用,后者只能被一个(Activity或service)调用

监听广播Intent步骤:

1> 写一个继承BroadCastReceiver的类,重写onReceive()方法,广播接收器仅在它执行这个方法时处于活跃状态。当onReceive()返回后,它即为失活状态,注意:为了保证用户交互过程的流畅,一些费时的操作要放到线程里,如类名SMSBroadcastReceiver

2> 注册该广播接收者,注册有两种方法程序动态注册和AndroidManifest文件中进行静态注册(可理解为系统中注册)如下:

  • 静态注册,注册的广播,下面的priority表示接收广播的级别”2147483647”为最高优先级
<receiver android:name=".SMSBroadcastReceiver" >
  <intent-filter android:priority = "2147483647" >
    <action android:name="android.provider.Telephony.SMS_RECEIVED" />
  </intent-filter>
</receiver >
  • 动态注册,一般在Activity可交互时onResume()内注册BroadcastReceiver
IntentFilter intentFilter=new IntentFilter("android.provider.Telephony.SMS_RECEIVED");
registerReceiver(mBatteryInfoReceiver ,intentFilter);

//取消注册
unregisterReceiver(receiver);

注意:

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

  1. 动态注册广播接收器还有一个特点,就是当用来注册的Activity关掉后,广播也就失效了。静态注册无需担忧广播接收器是否被关闭,只要设备是开启状态,广播接收器也是打开着的。也就是说哪怕app本身未启动,该app订阅的广播在触发时也会对它起作用

系统常见广播Intent,如开机启动、电池电量变化、时间改变等广播

基本使用

  • 自定义类继承 BroadcastReceiver
  • 在配置清单注册:设置监听的广播
<receiver android:name="包名.类名">
    <intent-filter>
        //监听的目标动作
        <action android:name="android.intent.action.NEW_OUTGOING_CALL"
    </intent-filter>
</receiver>
  • 在自定义 BroadcastReceiver 中的 onReceiver 处理信息
  • 大多数都是需要权限的
  • BroadcastReceiver类有方法
getResultData(""),获得动作数据
setResultData(""),改变动作数据
  • 开机启动
//onReceive方法配置

Intent activity=new Intent(context,MainActivity.class);

//在Activity以外调用startActivity方法开启一个Activity,需要声明一个标记标记,
//以后看到int的形参中,flag、type、state一般都是静态常量
activity.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
//打开页面
context.startActivity(activity);
  • 代码注册接收的广播
IntentFilter filter = new IntentFilter();
// 去添加调频的频道
filter.addAction(Intent.ACTION_SCREEN_OFF);
filter.addAction(Intent.ACTION_SCREEN_ON);
receiver = new ScreenReceiver();
registerReceiver(receiver, filter); 

// 取消注册
unregisterReceiver(receiver);

自己发广播

  • 代码发送
Intent intent=new Intent();
intent.setAction("ll.wang.miss");
sendOrderedBroadcast(intent,null,null,null,100,"中央给每个……",null);
//intent:要发送的广播频段
//permition:接收者要声明的权限
//resultReceiver:监听(不需在清单文件配置。)对象创建在下面
//handler:在哪个线程new,就会回调到哪个线程
//initialCode:广播发送时携带的初始化数据:int
//initialData:广播发送时携带的初始化数据:String
//initialbundle:可以理解为键值对用于如果前两个数据不够,在这里添加,额外的空间
  • 判断接收到的广播类型
intent.getAction();

无序广播和有序广播

  1. 无序广播:
sendBroadcast(intent);

自定义无序广播,设置一个动作(设置一个频率),可以传递数据,应用之间传递数据就可以使用无序广播。
  1. 有序广播
sendOrderedBroadcast(intent,null,new DachenReceiver(),null,Activity.RESULT_OK,"发钱了,每人1000",null);

接收者按照优先级进行接受,有序广播可以被终止,也可以被修改。

如果优先级一样,谁现在Manifest配置清单中注册,谁在前。
如果是两个应用,优先级相同,谁先安装谁先收到,按最后一次安装时间算。
priority    从-1000~1000不等,谁大谁优先级就高。
默认为0;
不需要连续

<receiver android:name="ll.wang.miss.receiver">
    <intent-filter android:priority="1000">
        <action android:name="ll.wang.miss"/>
    </intent-filter>
</receiver>
  1. 有序广播接收者的获取
String data=getResultData();
int code=getResultCode();
//有序的接受,先收到的改了,后面的到手就是被改的!
//前面的终止广播,后面的就得不到了
abortBroadcast();//终止广播。不需要写目标,直接终止接收到的。

ContentProvider : 内容提供者

基础信息

android平台提供了Content Provider使一个应用程序的指定数据集提供给其他应用程序。这些数据可以存储在文件系统中、在一个SQLite数据库、或以任何其他合理的方式,

其他应用可以通过ContentResolver类(见ContentProviderAccessApp例子)从该内容提供者中获取或存入数据.(相当于在应用外包了一层壳),

只有需要在多个应用程序间共享数据是才需要内容提供者。例如,通讯录数据被多个应用程序使用,且必须存储在一个内容提供者中

它的好处:统一数据访问方式。

android系统自带的内容提供者(顶级的表示数据库名,非顶级的都是表名)这些内容提供者在SDK文档的android.provider Java包中都有介绍。见:http://developer.android.com/reference/android/provider/package-summary.html

├────Browser

├────CallLog

├────Contacts

│ ├────Groups

│ ├────People

│ ├────Phones

│ └────Photos

├────Images

│ └────Thumbnails

├────MediaStore

│ ├────Albums

│ ├────Artists

│ ├────Audio

│ ├────Genres

│ └────Playlists

├────Settings

└────Video

CallLog:地址和接收到的电话信息

Contact.People.Phones:存储电话号码

Setting.System:系统设置和偏好设置

使用Content Provider对外共享数据的步骤

1>继承ContentProvider类并根据需求重写以下方法:

public boolean onCreate();//处理初始化操作

/**
* 插入数据到内容提供者(允许其他应用向你的应用中插入数据时重写)
* @param uri
* @param initialValues 插入的数据
* @return
*/
public Uri insert(Uri uri, ContentValues initialValues);

/**
* 从内容提供者中删除数据(允许其他应用删除你应用的数据时重写)
* @param uri
* @param selection 条件语句
* @param selectionArgs 参数
* @return
*/
public int delete(Uri uri, String selection, String[] selectionArgs);

/**
* 更新内容提供者已存在的数据(允许其他应用更新你应用的数据时重写)
* @param uri
* @param values 更新的数据
* @param selection 条件语句
* @param selectionArgs 参数
* @return
*/
public int update(Uri uri, ContentValues values, String selection,
             String[] selectionArgs);

/**
* 返回数据给调用者(允许其他应用从你的应用中获取数据时重写)
* @param uri
* @param projection 列名
* @param selection 条件语句
* @param selectionArgs 参数
* @param sortOrder 排序
* @return
*/
public Cursor query(Uri uri, String[] projection, String selection,
             String[] selectionArgs, String sortOrder) ;         

/**
* 用于返回当前Uri所代表数据的MIME类型
* 如果操作的数据为集合类型(多条数据),那么返回的类型字符串应该为vnd.android.cursor.dir/开头
* 例如要得到所有person记录的Uri为content://com.bravestarr.provider.personprovider/person,
     *   那么返回的MIME类型字符串应该为"vnd.android.cursor.dir/person"
* 如果操作的数据为单一数据,那么返回的类型字符串应该为vnd.android.cursor.item/开头
* 例如要得到id为10的person记录的Uri为content://com.bravestarr.provider.personprovider/person/10,
     *   那么返回的MIME类型字符串应该为"vnd.android.cursor.item/person"
* @param uri
*/
public String getType(Uri uri)

这些方法中的Uri参数,得到后需要进行解析然后做对应处理,Uri表示要操作的数据,包含两部分信息:

1.需要操作的contentprovider

   2.对contentprovider中的什么数据进行操作,一个Uri格式:结构头://authorities(域名)/路径(要操作的数据,根据业务而定)

          content://com.bravestarr.provider.personprovider/person/10

说明:contentprovider的结构头已经由android规定为content://

authorities用于唯一标识这个contentprovider程序,外部调用者可以根据这个找到他

路径表示我们要操作的数据,路径的构建根据业务而定.路径格式如下:

要操作person表行号为10的记录,可以这样构建/person/10

   要操作person表的所有记录,可以这样构建/person

2>在AndroidManifest.xml中使用对ContentProvider进行配置注册(内容提供者注册它自己就像网站注册域名),ContentProvider采用authoritie(原意授权,可理解为域名)作为唯一标识,方便其他应用能找到

<application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >
        <!-- authorities属性命名建议:公司名.provider.SomeProvider-->
        <provider android:name=".PersonProvider" android:authorities="com.bravestarr.provider.personprovider"/>
         ...
</application>

使用方法

  • 自定义 ContentProvider 继承自 抽象类 ContentProvider
  • 配置清单注册
// authorities 唯一标识地址

<provider android:name=".PersonContentProvider" 
    android:authorities="com.jbridge.provider.personprovider"/>
  • 对外开放的Uri
content://配置好的authorities/查询的数据路径

authorities 可以根据配置清单找到
查询的数据路径 为 path

使用 UriMatcher 匹配 Uri

  • 在 ContentProvider 中新建常量
private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);
  • 在 static 方法中添加每一个匹配,如果最后Uri的路径每一个都不匹配,默认返回 UriMatcher.NO_MATCH(-1),如果匹配到,返回自定义常量
// ctrl+shift+x 迅速变成大写 变小写 y
private static final int PLAYER_OK = 1;
private static final int COUNTRY_OK = 2;

// 在静态代码块中 先制定规则
static {
    // 如果对方填子路径 填对了 那么匹配码就是PLAYER_OK
    // 我们建议子路径最好和表名一致    方便我们获取
    sURIMatcher.addURI(AUTHORITIES, "player", PLAYER_OK);
    sURIMatcher.addURI(AUTHORITIES, "country", COUNTRY_OK);
}
  • 使用 UriMatcher 常量 判断 Uri 匹配
int code = sURIMatcher.match(uri);
switch(code){
    case PLAYER_OK:

        break;
    case COUNTRY_OK:

        break;
    default:
    throw new IllegalArgumentException("哥们,你的Uri填错了,老子找不到,不知道你要访问哪个表。");
        break;
}

内容解析者 ContentResolver

  • 使用 内容解析者
Uri uri = Uri.parse("content://sms");
// 主机名 在短信代码的清单文件中获取
// 表名:去provider的源码中找静态代码块
Cursor cursor = getContentResolver().query(uri,
        new String[] { "address", "date", "read", "type", "body" },
        null, null, null);

内容观察者 ContentObserver

  • 自定义 ContentObserver 重写 onChange 方法
  • 注册 ContentObserver
getContentResolver().registerContentObserver(Uri.parse("content://call_log"), true, observer);
  • 取消注册 ContentObserver
getContentResolver().unregisterContentObserver(observer);
  • 自己的 ContentProvider 可以被观察
getContext().getContentResolver().notifyChange(uri,null);

Service : 服务

基础信息

一个Service 是一段长生命周期的,没有用户界面的程序,可以用来开发如监控类程序。

比较好的一个例子就是一个正在从播放列表中播放歌曲的媒体播放器。在一个媒体播放器的应用中,应该会有多个activity,让使用者可以选择歌曲并播放歌曲。然而,音乐重放这个功能并没有对应的activity,因为使用者当然会认为在导航到其它屏幕时音乐应该还在播放的。在这个例子中,媒体播放器这个activity 会使用Context.startService()来启动一个service,从而可以在后台保持音乐的播放。同时,系统也将保持这个service 一直执行,直到这个service 运行结束。另外,我们还可以通过使用Context.bindService()方法,连接到一个service 上(如果这个service 还没有运行将启动它)。当连接到一个service 之后,我们还可以service 提供的接口与它进行通讯。拿媒体播放器这个例子来说,我们还可以进行暂停、重播等操作。

Service使用步骤如下

1>继承service类

   2>AndroidManifast.xml配置清单文件中<application>节点里对服务进行配置

          <service name=".SMSService"/>

服务不能自己运行,需要通过Contex.startService()或Contex.bindService()启动服务

通过startService()方法启动的服务于调用者没有关系,即使调用者关闭了,服务仍然运行想停止服务要调用Context.stopService(),此时系统会调用onDestory(),使用此方法启动时,服务首次启动系统先调用服务的onCreate()–>onStart(),如果服务已经启动再次调用只会触发onStart()方法

使用bindService()启动的服务与调用者绑定,只要调用者关闭服务就终止,使用此方法启动时,服务首次启动系统先调用服务的onCreate()–>onBind(),如果服务已经启动再次调用不会再触发这2个方法,调用者退出时系统会调用服务的onUnbind()–>onDestory(),想主动解除绑定可使用Contex.unbindService(),系统依次调用onUnbind()–>onDestory();

基本使用

  • 新建一个MyService继承自Service,并重写父类的onCreate()、onStartCommand()和onDestroy()方法
  • 开始/关闭
Intent startIntent = new Intent(this, MyService.class);
startService(startIntent);

Intent stopIntent = new Intent(this, MyService.class);
stopService(stopIntent);
  • 配置清单中注册
<service android:name="com.example.servicetest.MyService" >  
</service>
  • 生命周期
onCreate:第一次启动

onStartCommand:每次启动

onDestory:关闭服务

与Activity通讯

  • Service 中 自定义内部类 继承自 Binder
  • 使用 onBind(Intent intent) 方法将该全局变量返回(new好)
  • 重写 ServiceConnection 对象
private ServiceConnection connection = new ServiceConnection() {  

    @Override  
    public void onServiceDisconnected(ComponentName name) {  
    }  

    @Override  
    public void onServiceConnected(ComponentName name, IBinder service) {  
        myBinder = (MyService.MyBinder) service;  
        myBinder.startDownload();  
    }  
};
  • 使用 bindService() 保证Activity与Service异步(同线程)
Intent bindIntent = new Intent(this, MyService.class);  
bindService(bindIntent, connection, BIND_AUTO_CREATE);
  • unbindService() 解除关联
  • BIND_AUTO_CREATE 代表不走 onStartCommand 只走 onStart

启动逻辑

  • 先 startService 再 bindService ,之后可以先 unBindService ,然后再说 stopService
  • 先bindService 。 之后怎么做都是直接关闭 Service

特点作用

  • Service本身也在主线程,只是不需要依赖界面
  • 在不同的Activity里面start的同一个Service其实只启动了一个,这可以更方便我们使用通用型数据

前台Service

它会一直有一个正在运行的图标在系统的状态栏显示,下拉状态栏后可以看到更加详细的信息,非常类似于通知的效果。

  • 实现方法:在onCreate方法中调用
public class MyService extends Service {  

    public static final String TAG = "MyService";  

    private MyBinder mBinder = new MyBinder();  

    @Override  
    public void onCreate() {  
        super.onCreate();  
        Notification notification = new Notification(R.drawable.ic_launcher,  
                "有通知到来", System.currentTimeMillis());  
        Intent notificationIntent = new Intent(this, MainActivity.class);  
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,  
                notificationIntent, 0);  
        notification.setLatestEventInfo(this, "这是通知的标题", "这是通知的内容",  
                pendingIntent);  
        startForeground(1, notification);  
        Log.d(TAG, "onCreate() executed");  
    }  

    .........  

}

远程Service

即将Service推送到另外一个进程运行,即使阻塞,也不会阻塞本APP的进程

  • 修改配置清单注册信息
<service  
    android:name="com.example.servicetest.MyService"  
    android:process=":remote" >  
</service>
  • 关联远程Service

使用AIDL通讯

  • 首先需要新建一个AIDL文件,在这个文件中定义好Activity需要与Service进行通信的方法。
package com.example.servicetest;  
interface MyAIDLService {  
    int plus(int a, int b);  
    String toUpperCase(String str);  
}
  • 然后修改MyService中的代码,在里面实现我们刚刚定义好的MyAIDLService接口,如下所示:Stub其实就是Binder的子类
public class MyService extends Service {  

    ......  

    @Override
    public IBinder onBind(Intent intent) {  
        return mBinder;  
    }  

    MyAIDLService.Stub mBinder = new Stub() {  

        @Override  
        public String toUpperCase(String str) throws RemoteException {  
            if (str != null) {  
                return str.toUpperCase();  
            }  
            return null;  
        }  

        @Override  
        public int plus(int a, int b) throws RemoteException {  
            return a + b;  
        }  
    };  

}
  • onServiceConnected() 方法中转换
MyAIDLService myAIDLService = MyAIDLService.Stub.asInterface(service);  
try {  
    int result = myAIDLService.plus(3, 5);  
    String upperStr = myAIDLService.toUpperCase("hello world");  
    Log.d("TAG", "result is " + result);  
    Log.d("TAG", "upperStr is " + upperStr);  
} catch (RemoteException e) {  
    e.printStackTrace();  
}

跨APP调用

  • 配置清单添加action参数,来完成隐式 Intent
<?xml version="1.0" encoding="utf-8"?>  
<manifest xmlns:android="http://schemas.android.com/apk/res/android"  
    package="com.example.servicetest"  
    android:versionCode="1"  
    android:versionName="1.0" >  

    ......  

    <service  
        android:name="com.example.servicetest.MyService"  
        android:process=":remote" >  
        <intent-filter>  
            <action android:name="com.example.servicetest.MyAIDLService"/>  
        </intent-filter>  
    </service>  

</manifest>
  • 首先需要将MyAIDLService.aidl文件从ServiceTest项目中拷贝过来,注意要将原有的包路径一起拷贝过来
  • 在新的Activity启动该Service
public class MainActivity extends Activity {  

    private MyAIDLService myAIDLService;  

    private ServiceConnection connection = new ServiceConnection() {  

        @Override  
        public void onServiceDisconnected(ComponentName name) {  
        }  

        @Override  
        public void onServiceConnected(ComponentName name, IBinder service) {  
            myAIDLService = MyAIDLService.Stub.asInterface(service);  
            try {  
                int result = myAIDLService.plus(50, 50);  
                String upperStr = myAIDLService.toUpperCase("comes from ClientTest");  
                Log.d("TAG", "result is " + result);  
                Log.d("TAG", "upperStr is " + upperStr);  
            } catch (RemoteException e) {  
                e.printStackTrace();  
            }  
        }  
    };  

    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  
        Button bindService = (Button) findViewById(R.id.bind_service);  
        bindService.setOnClickListener(new OnClickListener() {  
            @Override  
            public void onClick(View v) {  
                Intent intent = new Intent("com.example.servicetest.MyAIDLService");  
                bindService(intent, connection, BIND_AUTO_CREATE);  
            }  
        });  
    }  

}

附:

不过还有一点需要说明的是,由于这是在不同的进程之间传递数据,Android对这类数据的格式支持是非常有限的,基本上只能传递Java的基本数据类型、字符串、List或Map等。那么如果我想传递一个自定义的类该怎么办呢?这就必须要让这个类去实现Parcelable接口,并且要给这个类也定义一个同名的AIDL文件。这部分内容并不复杂,而且和Service关系不大,所以就不再详细进行讲解了,感兴趣的朋友可以自己去查阅一下相关的资料。