android应用_android应用开发 - CSDN
  • 安豆是一个想学Android应用开发的小白,于是它找到自己的邻居-程序员大牛-熊哥帮忙。熊哥手把手带着安豆搭建程序的开发环境,实现应用的功能,美化应用界面,让安豆终于开发出了自己的第一个安卓应用-计算器。 ...
  • 综合前面的知识点,剔除繁杂的理论,注重实践,深入浅出综合使用Android中的知识实现一个应用市场类的App
  • 本课程是Android应用开发的进阶篇,以实践为主,学习本课程要求学习者首先掌握Java基础,Android开发基础,此课程在此基础上进一步探索,此课程不适合没有Java和Android基础的人员。
  • 一、选择题 1. 下列代码的执行结果是:( B ) public class Test3{  public static void main(String args[]){  System.out.print(100%3);  System.out.print(",&... ...
    转载请注明出处:https://blog.csdn.net/mythmayor/article/details/80229541

    一、选择题

    1. 下列代码的执行结果是:( B )

    public class Test3{
      	public static void main(String args[]){
      		System.out.print(100%3);
      		System.out.print(",");
      		System.out.println(100%3.0);
      	}
    }
    

    A、1,1
      B、1,1.0
      C、1.0,1
      D、1.0,1.0

    2. 下列哪些语句关于内存回收的说明是正确的? ( B )

    A、程序员必须创建一个线程来释放内存
      B、内存回收程序负责释放无用内存
      C、内存回收程序允许程序员直接释放内存
      D、内存回收程序可以在指定的时间释放内存对象

    3. Math.round(11.5)等于多少(). Math.round(-11.5)等于多少( C ).

    A、11 ,-11
      B、11 ,-12
      C、12 ,-11
      D、12 ,-12

    4. 下面不是Android四大组件之一的:( B )

    A、Activity
      B、Intent
      C、Service
      D、ContentProvider

    5. android 中下列属于Intent的作用的是( C )

    A、实现应用程序间的数据共享
      B、是一段长的生命周期,没有用户界面的程序,可以保持应用在后台运行,而不会因为切换页面而消失
      C、可以实现界面间的切换,可以包含动作和动作数据,连接四大组件的纽带
      D、处理一个应用程序整体性的工作

    6. 下面关于BroadcastReceiver错误的是 ( B )

    A、BroadcastReceiver有两种注册方式,静态注册和动态注册。
      B、BroadcastReceiver必须在AndroidMainfest文件中声明
      C、BroadcastReceiver的使用,一定有一方发送广播,有一方监听注册广播,onReceive方法才会被调用。
      D、广播发送的Intent都是隐式启动。

    7. 在手机开发中常用的数据库是( A )

    A、SQLite3
      B、Oracle
      C、Sql Server
      D、Db23

    8. Service中如何实现更改Activity界面元素 ( B )

    A、通过把当前activity对象传递给service对象
      B、通过向Activity发送广播
      C、通过Context对象更改Activity界面元素
      D、可以在Service中,调用Activity的方法实现更改界面元素

    9. 关于ContenValues类说法正确的是( A )

    A、他和Hashtable比较类似,也是负责存储一些名值对,但是他存储的名值对当中的 名是String类型,而值都是基本类型
      B、他和Hashtable比较类似,也是负责存储一些名值对,但是他存储的名值对当中的 名是任意类型,而值都是基本类型
      C、他和Hashtable比较类似,也是负责存储一些名值对,但是他存储的名值对当中的 名,可以为空,而值都是String类型
      D、他和Hashtable比较类似,也是负责存储一些名值对,但是他存储的名值对当中 的名是String类型,而值也是String类型

    10. 下面关于Android dvm的进程和Linux的进程,应用程序的进程说法正确的是( D )

    A、DVM指dalivk的虚拟机.每一个Android应用程序都在它自己的进程中运行,不一定拥有一个独立的Dalvik虚拟机实例.而每一个DVM都是在Linux 中的一个进程,所以说可以认为是同一个概念.
      B、DVM指dalivk的虚拟机.每一个Android应用程序都在它自己的进程中运行,不一定拥有一个独立的Dalvik虚拟机实例.而每一个DVM不一定都是在Linux 中的一个进程,所以说不是一个概念.
      C、DVM指dalivk的虚拟机.每一个Android应用程序都在它自己的进程中运行,都拥有一个独立的Dalvik虚拟机实例.而每一个DVM不一定都是在Linux 中的一个进程,所以说不是一个概念.
      D、DVM指dalivk的虚拟机.每一个Android应用程序都在它自己的进程中运行,都拥有一个独立的Dalvik虚拟机实例.而每一个DVM都是在Linux 中的一个进程,所以说可以认为是同一个概念.

    11. 下列对SharedPreferences存、取文件的说法中不正确的是( C )

    A、属于移动存储解决方案
      B、sharePreferences处理的就是key-value对
      C、读取xml文件的路径是/sdcard/shared_prefs/
      D、数据的保存格式是xml

    12. 使用AIDL 完成远程 service 方法调用下列说法不正确的是?( A )

    A、aidl 对应的接口名称不能与 aidl 文件名相同
      B、aidl 的文件的内容类似 java 代码
      C、创建一个 Service,在服务的 onBind(Intent intent)方法中返回实现了 aidl 接口的对象
      D、aidl 对应的接口的方法前面不能加访问权限修饰符

    13. 关于JSON和XML说法,错误的是( B )

    A、JSON的速度要远远快于XML
      B、JSON对数据的描述性比XML好
      C、JSON相对于XML来讲,数据的体积小
      D、JSON和XML同样拥有丰富的解析手段

    14. 下列不属于补间动画相关类的是( B )

    A、TranslateAnimation
      B、FrameAnimation
      C、RotateAnimation
      D、AlphaAnimation

    15. 关于Handler的说法不正确的是( A )

    A、它实现不同进程间通信的一种机制
      B、它避免了在新线程中刷新UI的操作
      C、它采用队列的方式来存储Message
      D、它实现不同线程间通信的一种机制

    二、填空题

    1. java.io包中的____________和____________类主要用于对对象(Object)的读写。

    答案:ObjectInputStream ObjectOutputSteam

    2. android 中service的实现方法是:_。

    答案:startService ,bindService

    3. 当启动一个Activity并且新的Activity执行完后需要返回到启动它的Activity来执行的回调函数是_______________。

    答案:startActivityResult()

    4. 程序运行的结果是:______________。

    public class Example{
      	String str=new String("good");
      	char[]ch={'a','b','c'};
      	public static void main(String args[]){
      		Example ex=new Example();
      		ex.change(ex.str,ex.ch);
      		System.out.print(ex.str+" and ");
      		Sytem.out.print(ex.ch);
      	}
      	public void change(String str,char ch[]){
      		str="test ok";
      		ch[0]='g';
      	}
    }
    

    答案:good and gbc

    三、简答编程题

    1. 请简述下Android的数据存储方式。

    答:共有五种数据存储方式。
    1.SharedPreferences方式
    2.文件存储方式
    3.SQLite数据库方式
    4.内容提供器(Content provider)方式
    5. 网络存储方式

    2. 请简述下ContentProvider是如何实现数据共享的。

    答:创建一个属于你自己的Content provider或者将你的数据添加到一个已经存在的Content provider中,前提是有相同数据类型并且有写入Content provider的权限。

    3. 什么是Intent,如何使用?

    答: Android基本的设计理念是鼓励减少组件间的耦合,因此Android提供了Intent (意图) ,Intent提供了一种通用的消息系统,它允许在你的应用程序与其它的应用程序间传递Intent来执行动作和产生事件。使用Intent可以激活Android应用的三个核心组件:活动、服务和广播接收器。
    通过startActivity() orstartActivityForResult()启动一个Activity;
    通过 startService() 启动一个服务,或者通过bindService() 和后台服务交互;
    通过广播方法(比如 sendBroadcast(),sendOrderedBroadcast(),sendStickyBroadcast())发给broadcast receivers

    4. Activity、Service、BroadcastReceiver的作用。

    答:Activity:Activity是Android程序与用户交互的窗口,是Android构造块中最基本的一种,它需要为保持各界面的状态,做很多持久化的事情,妥善管理生命周期以及一些跳转逻辑
    Service:后台服务于Activity,封装有一个完整的功能逻辑实现,接受上层指令,完成相关的指令,定义好需要接受的Intent提供同步和异步的接口
    BroadCast Receiver:接受一种或者多种Intent作触发事件,接受相关消息,做一些简单处理,转换成一条Notification,统一了Android的事件广播模型

    5. 横竖屏切换时Activity的生命周期

    答:切换时的生命周期跟清单文件里的配置有关系。
    不设置Activity的android:configChanges时,切屏会重新调用各个生命周期默认首先销毁当前activity,然后重新加载
    设置Activity android:configChanges="orientation|keyboardHidden|screenSize"时,切屏不会重新调用各个生命周期,只会执行onConfigurationChanged方法。 通常在游戏开发, 屏幕的朝向都是写死的。

    6. handler机制的原理

    答:andriod提供了 Handler 和 Looper 来满足线程间的通信。Handler 先进先出原则。Looper类用来管理特定线程内对象之间的消息交换(Message Exchange)。
    1)Looper: 一个线程可以产生一个Looper对象,由它来管理此线程里的Message Queue(消息队列)。
    2)Handler: 你可以构造Handler对象来与Looper沟通,以便push新消息到Message Queue里;或者接收Looper从Message Queue取出)所送来的消息。
    3) Message Queue(消息队列):用来存放线程放入的消息。
    4)线程:UI thread 通常就是main thread,而Android启动程序时会替它建立一个Message Queue。

    展开全文
  • Android移动开发的自测试,卷里面附带标准答案, PDF版的意见排版好了
  • 因为Androd使用Java语言来编程的,所以我们做Android开发全面的掌握Java基础是必须的。在面试的过程中,我们发现很多公司发的笔试题有很多知识点都是Java的,搞安卓久了,Java基础的一些知识点也都快忘了,今天就让...

    关注finddreams博客:http://blog.csdn.net/finddreams/article/details/44403041
    因为Androd使用Java语言来编程的,所以我们做Android开发全面的掌握Java基础是必须的。在面试的过程中,我们发现很多公司发的笔试题有很多知识点都是Java的,搞安卓久了,Java基础的一些知识点也都快忘了,今天就让我们来一起复习一些Java基础,希望能在面试中用到;

    1、Overload和Override的区别。Overloaded的方法是否可以改变返回值的类型?
    方法的重写Overriding和重载Overloading是Java多态性的不同表现。重写Overriding是父类与子类之间多态性的一种表现,重载Overloading是一个类中多态性的一种表现。如果在子类中定义某方法与其父类有相同的名称和参数,我们说该方法被重写 (Overriding)。子类的对象使用这个方法时,将调用子类中的定义,对它而言,父类中的定义如同被”屏蔽”了。如果在一个类中定义了多个同名的方法,它们或有不同的参数个数或有不同的参数类型,则称为方法的重载(Overloading)。Overloaded的方法是可以改变返回值的类型。

    2、String和StringBuffer,StringBuilder的区别
    String 字符串常量
    StringBuffer 字符串变量(线程安全)
    StringBuilder 字符串变量(非线程安全)
    String的长度是不可变的,StringBuffer,StringBuilder的长度是可变的。如果你对字符串中的内容经常进行操作,特别是内容要修改时,那么使用StringBuffer,如果最后需要String,那么使用StringBuffer的toString()方法。

    3、说出ArrayList,Vector, LinkedList的存储性能和特性
    ArrayList和Vector都是使用数组方式存储数据,此数组元素数大于实际存储的数据以便增加和插入元素,它们都允许直接按序号索引元素,但是插入元素要涉及数组元素移动等内存操作,所以索引数据快而插入数据慢,Vector由于使用了synchronized方法(线程安全),通常性能上较ArrayList差,而LinkedList使用双向链表实现存储,按序号索引数据需要进行前向或后向遍历,但是插入数据时只需要记录本项的前后项即可,所以插入速度较快。

    4.字符串“abcde”通过写一个函数不让调用第三方的字符串,实现一个字符串倒序,比如字符串“abcde”变成“edcba”
    String src = “abcde”;
    String dst = new StringBuffer(src).reverse().toString();

    5、Collection 和 Collections的区别。
    Collection是集合类的上级接口,继承与他的接口主要有Set 和List.
    Collections是针对集合类的一个帮助类,他提供一系列静态方法实现对各种集合的搜索、排序、线程安全化等操作

    6、final, finally, finalize的区别。
    final 用于声明属性,方法和类,分别表示属性不可变,方法不可覆盖,类不可继承。
    finally是异常处理语句结构的一部分,表示总是执行。
    finalize是Object类的一个方法,在垃圾收集器执行的时候会调用被回收对象的此方法,可以覆盖此方法提供垃圾收集时的其他资源回收,例如关闭文件等。

    7、sleep() 和 wait() 有什么区别?
    1.这两个方法来自不同的类分别是,sleep来自Thread类,和wait来自Object类。
    2.最主要是sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。sleep不出让系统资源;wait是进入线程等待池等待,出让系统资源,其他线程可以占用CPU。一般wait不会加时间限制,因为如果wait线程的运行资源不够,再出来也没用,要等待其他线程调用notify/notifyAll唤醒等待池中的所有线程,才会进入就绪队列等待OS分配系统资源。sleep(milliseconds)可以用时间指定使它自动唤醒过来,如果时间不到只能调用interrupt()强行打断。
    3.wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用
    4. Sleep需要捕获异常,而wait不需要

    8、同步和异步有何异同,在什么情况下分别使用他们?举例说明。
    如果数据将在线程间共享。例如正在写的数据以后可能被另一个线程读到,或者正在读的数据可能已经被另一个线程写过了,那么这些数据就是共享数据,必须进行同步存取。
    当应用程序在对象上调用了一个需要花费很长时间来执行的方法,并且不希望让程序等待方法的返回时,就应该使用异步编程,在很多情况下采用异步途径往往更有效率。

    9,抽象类与接口的区别(abstract与interface的区别)
    abstract可以修饰抽象方法,而一个类只要有一个抽象方法,就必须用abstract定义该类,即抽象类。
    用interface修饰的类,里面的方法都是抽象方法,因此在定义接口的时候,可以直接不加那些修饰,系统会默认的添上去。接口里面的字段都是公有常量,即public static final修饰的字段。

    10、线程中wait,join,sleep,yield, notify,notifyall,synchronized,区别及联系
    1).sleep()方法
    在指定时间内让当前正在执行的线程暂停执行,但不会释放“锁标志”。不推荐使用。sleep()使当前线程进入阻塞状态,在指定时间内不会执行。
    2).wait()方法
    在其他线程调用对象的notify或notifyAll方法前,导致当前线程等待。线程会释放掉它所占有的“锁标志”,从而使别的线程有机会抢占该锁。
    唤醒当前对象锁的等待线程使用notify或notifyAll方法,waite() 和notify()必须在synchronized函数或synchronized block中进行调用。
    yield方法暂停当前正在执行的线程对象。yield()只是使当前线程重新回到可执行状态,所以执行
    3)yield()的线程有可能在进入到可执行状态后马上又被执行。yield()只能使同优先级或更高优先级的线程有执行的机会。
    4).join方法
    等待该线程终止。等待调用join方法的线程结束,再继续执行。如:t.join();//主要用于等待t线程运行结束,若无此句,main则会执行完毕,导致结果不可预测。

    11、接口是否可继承接口? 抽象类是否可实现(implements)接口? 抽象类是否可继承实体类(concrete class)?
    接口可以继承接口。抽象类可以实现(implements)接口,抽象类是否可继承实体类,但前提是实体类必须有明确的构造函数。

    12、abstract的method是否可同时是static,是否可同时是native,是否可同时是synchronized?

    13、是否可以继承String类?
    String类是final类故不可以继承。

    14、java switch支持的数据类型:
    java支持的数据类型有五种
    他们分别是:
    byte、char、short、int、枚举
    以上是JDK1.6以前的版本。JDK1.7时,又增加了String,所以相对于JDK1.7而言就是六种了

    15、什么是单例模式,请写出一个来:
    Singleton模式主要作用是保证在Java应用程序中,一个类Class只有一个实例存在。
    一般Singleton模式通常有几种种形式:
    第一种形式: 定义一个类,它的构造函数为private的,它有一个static的private的该类变量,在类初始化时实例话,通过一个public的getInstance方法获取对它的引用,继而调用其中的方法。

    public class Singleton { 
    private Singleton(){} 
    
          //注意这是private 只供内部调用
          private static Singleton instance = new Singleton();
          //这里提供了一个供外部访问本class的静态方法,可以直接访问  
          public static Singleton getInstance() {
            return instance;   
          }
        }
    
    第二种形式:
    
    
    public class Singleton {
    
      private static Singleton instance = null;
    
      public static synchronized Singleton getInstance() {
      //这个方法比上面有所改进,不用每次都进行生成对象,只是第一次     
      //使用时生成实例,提高了效率!
      if (instance==null)
        instance=new Singleton();
    return instance;   } 
    }
    

    其他形式:
    定义一个类,它的构造函数为private的,所有方法为static的。
    一般认为第一种形式要更加安全些

    16、Java常用的设计模式?说明工厂模式。
    Java中的23种设计模式:
    Factory(工厂模式), Builder(建造模式), Factory Method(工厂方法模式),
    Prototype(原始模型模式),Singleton(单例模式), Facade(门面模式),
    Adapter(适配器模式), Bridge(桥梁模式), Composite(合成模式),
    Decorator(装饰模式), Flyweight(享元模式), Proxy(代理模式),
    Command(命令模式), Interpreter(解释器模式), Visitor(访问者模式),
    Iterator(迭代子模式), Mediator(调停者模式), Memento(备忘录模式),
    Observer(观察者模式), State(状态模式), Strategy(策略模式),
    Template Method(模板方法模式), Chain Of Responsibleity(责任链模式)
    工厂模式:工厂模式是一种经常被使用到的模式,根据工厂模式实现的类可以根据提供的数据生成一组类中某一个类的实例,
    通常这一组类有一个公共的抽象父类并且实现了相同的方法,但是这些方法针对不同的数据进行了不同的操作。
    首先需要定义一个基类,该类的子类通过不同的方法实现了基类中的方法。然后需要定义一个工厂类,工厂类可以根据条件
    生成不同的子类实例。当得到子类的实例后,开发人员可以调用基类中的方法而不必考虑到底返回的是哪一个子类的实例。

    展开全文
  • 注:因为实际开发与参考答案会有所不同,再者怕误导大家,所以这些面试题答案还是自己去理解!面试官会针对简历中提到的知识点由浅入深提问,所以不要背答案,多理解。 1.组件化中路由、埋点的实现 ...

    注:因为实际开发与参考答案会有所不同,再者怕误导大家,所以这些面试题答案还是自己去理解!面试官会针对简历中提到的知识点由浅入深提问,所以不要背答案,多理解。

    1.组件化中路由、埋点的实现

    参考回答:

    因为在组件化中,各个业务模块之间是各自独立的, 并不会存在相互依赖的关系, 所以一个业务模块是访问不了其他业务模块的代码的, 如果想从 A 业务模块的 A 页面跳转到 B 业务模块的 B 页面, 光靠模块自身是不能实现的,这就需要一种跨组件通信方案—— 路由(Router)

    路由主要有以下两种场景:

    • 第一种是组件之间的页面跳转 (Activity 到 Activity, Fragment 到 Fragment, Activity 到 Fragment, Fragment 到 Activity) 以及跳转时的数据传递 (基础数据类型和可序列化的自定义类类型)
    • 第二种是组件之间的自定义类和自定义方法的调用(组件向外提供服务)

    其原理在于将分布在不同组件module中的某些类按照一定规则生成映射表(数据结构通常是Map,Key为一个字符串,Value为类或对象),然后在需要用到的时候从映射表中根据字符串从映射表中取出类或对象,本质上是类的查找

    埋点则是在应用中特定的流程收集一些信息,用来跟踪应用使用的状况

    • 代码埋点:在某个事件发生时调用SDK里面相应的接口发送埋点数据,百度统计、友盟、TalkingData、Sensors Analytics等第三方数据统计服务商大都采用这种方案
    • 全埋点:全埋点指的是将Web页面/App内产生的所有的、满足某个条件的行为,全部上报到后台服务器
    • 可视化埋点:通过可视化工具(例如Mixpanel)配置采集节点,在Android端自动解析配置并上报埋点数据,从而实现所谓的自动埋点
    • 无埋点:它并不是真正的不需要埋点,而是Android端自动采集全部事件并上报埋点数据,在后端数据计算时过滤出有用数据

    2.Hook以及插桩技术

    参考回答:

    Hook是一种用于改变API执行结果的技术,能够将系统的API函数执行重定向(应用的触发事件和后台逻辑处理是根据事件流程一步步地向下执行。而Hook的意思,就是在事件传送到终点前截获并监控事件的传输,像个钩子钩上事件一样,并且能够在钩上事件时,处理一些自己特定的事件,例如逆向破解App)
    在这里插入图片描述
    Android 中的 Hook 机制,大致有两个方式:

    • 要 root 权限,直接 Hook 系统,可以干掉所有的 App。
    • 无 root 权限,但是只能 Hook 自身app,对系统其它 App 无能为力。

    插桩是以静态的方式修改第三方的代码,也就是从编译阶段,对源代码(中间代码)进行编译,而后重新打包,是静态的篡改; 而Hook则不需要再编译阶段修改第三方的源码或中间代码,是在运行时通过反射的方式修改调用,是一种动态的篡改

    3.Android的签名机制?

    参考回答:

    Android的签名机制包含有消息摘要、数字签名和数字证书

    • 消息摘要:在消息数据上,执行一个单向的 Hash 函数,生成一个固定长度的Hash值
    • 数字签名:一种以电子形式存储消息签名的方法,一个完整的数字签名方案应该由两部分组成:签名算法和验证算法
    • 数字证书:一个经证书授权(Certificate Authentication)中心数字签名的包含公钥拥有者信息以及公钥的文件

    4.Android5.0~10.0之间大的变化

    参考回答:

    4.1.Android5.0新特性

    • MaterialDesign设计风格
    • 支持64位ART虚拟机(5.0推出的ART虚拟机,在5.0之前都是Dalvik。他们的区别是:Dalvik,每次运行,字节码都需要通过即时编译器转换成机器码(JIT)。 ART,第一次安装应用的时候,字节码就会预先编译成机器码(AOT))
    • 通知详情可以用户自己设计

    4.2.Android6.0新特性

    • 动态权限管理
    • 支持快速充电的切换
    • 支持文件夹拖拽应用
    • 相机新增专业模式

    4.3.Android7.0新特性

    • 多窗口支持
    • V2签名
    • 增强的Java8语言模式
    • 夜间模式

    4.4.Android8.0(O)新特性

    • 优化通知:通知渠道 (Notification Channel) 通知标志 休眠 通知超时 通知设置 通知清除
    • 画中画模式:清单中Activity设置android:supportsPictureInPicture
    • 后台限制
    • 自动填充框架
    • 系统优化
    • 等等优化很多

    4.5.Android9.0(P)新特性

    • 室内WIFI定位
    • “刘海”屏幕支持
    • 安全增强
    • 等等优化很多

    4.6.Android10.0(Q)新特性

    • 夜间模式:包括手机上的所有应用都可以为其设置暗黑模式。
    • 桌面模式:提供类似于PC的体验,但是远远不能代替PC。
    • 屏幕录制:通过长按“电源”菜单中的"屏幕快照"来开启。

    5.说下Measurepec这个类

    参考回答:

    • 作用:通过宽测量值widthMeasureSpec和高测量值heightMeasureSpec决定View的大小
    • 组成:一个32位int值,高2位代表SpecMode(测量模式),低30位代表SpecSize( 某种测量模式下的规格大小)。

    三种模式:

    • UNSPECIFIED:父容器不对View有任何限制,要多大有多大。常用于系统内部。
    • EXACTLY(精确模式):父视图为子视图指定一个确切的尺寸SpecSize。对应LyaoutParams中的match_parent或具体数值。
    • AT_MOST(最大模式):父容器为子视图指定一个最大尺寸SpecSize,View的大小不能大于这个值。对应LayoutParams中的wrap_content。
      决定因素:值由子View的布局参数LayoutParams和父容器的MeasureSpec值共同决定。具体规则见下图:
      在这里插入图片描述

    6.请例举Android中常用布局类型,并简述其用法以及排版效率

    参考回答:

    Android中常用布局分为传统布局和新型布局

    • 传统布局(编写XML代码、代码生成):

      • 框架布局(FrameLayout):
      • 线性布局(LinearLayout):
      • 绝对布局(AbsoluteLayout):
      • 相对布局(RelativeLayout):
      • 表格布局(TableLayout):
    • 新型布局(可视化拖拽控件、编写XML代码、代码生成):

      • 约束布局(ConstrainLayout)
        在这里插入图片描述
    • 对于嵌套多层View而言,其排版效率:LinearLayout = FrameLayout >> RelativeLayout

    7.区别Animation和Animator的用法,概述其原理

    参考回答:

    • 动画的种类:前者只有透明度,旋转,平移,伸缩4种属性,而对于后者,只要是该控件的属性,且有setter该属性的方法就都可以对该属性执行一种动态变化的效果。
    • 可操作的对象:前者只能对UI组件执行动画,但属性动画几乎可以对任何对象执行动画(不管它是否显示在屏幕上)。
    • 动画播放顺序:在Animator中,AnimatorSet正是通过playTogether()、playSequentially()、animSet.play().with()、before()、after()这些方法来控制多个动画协同工作,从而做到对动画播放顺序的精确控制
      在这里插入图片描述

    8.使用过什么图片加载库?Glide的源码设计哪里很微妙?

    参考回答:

    图片加载库:Fresco、Glide、Picasso等
    Glide的设计微妙在于:

    • Glide的生命周期绑定:可以控制图片的加载状态与当前页面的生命周期同步,使整个加载过程随着页面的状态而启动/恢复,停止,销毁
    • Glide的缓存设计:通过(三级缓存,Lru算法,Bitmap复用)对Resource进行缓存设计
    • Glide的完整加载过程:采用Engine引擎类暴露了一系列方法供Request操作

    9.如何绕过9.0限制?

    参考回答:
    在这里插入图片描述

    10.对于应用更新这块是如何做的? (灰度,强制更新、分区域更新)

    参考回答:
    10.1.内部更新:

    • 通过接口获取线上版本号,versionCode

    • 比较线上的versionCode 和本地的versionCode,弹出更新窗口

    • 下载APK文件(文件下载)

    • 安装APK
      10.2.灰度更新:

    • 找单一渠道投放特别版本。

    • 做升级平台的改造,允许针对部分用户推送升级通知甚至版本强制升级。

    • 开放单独的下载入口。

    • 是两个版本的代码都打到app包里,然后在app端植入测试框架,用来控制显示哪个版本。测试框架负责与服务器端api通信,由服务器端控制app上A/B版本的分布,可以实现指定的一组用户看到A版本,其它用户看到B版本。服务端会有相应的报表来显示A/B版本的数量和效果对比。最后可以由服务端的后台来控制,全部用户在线切换到A或者B版本~

    • 无论哪种方法都需要做好版本管理工作,分配特别的版本号以示区别。 当然,既然是做灰度,数据监控(常规数据、新特性数据、主要业务数据)还是要做到位,该打的数据桩要打。 还有,灰度版最好有收回的能力,一般就是强制升级下一个正式版。
      10.3.强制更新:

    一般的处理就是进入应用就弹窗通知用户有版本更新,弹窗可以没有取消按钮并不能取消。这样用户就只能选择更新或者关闭应用了,当然也可以添加取消按钮,但是如果用户选择取消则直接退出应用。

    10.4.增量更新:

    二进制差分工具bsdiff是相应的补丁合成工具,根据两个不同版本的二进制文件,生成补丁文件.patch文件。通过bspatch使旧的apk文件与不定文件合成新的apk。 注意通过apk文件的md5值进行区分版本。

    11.会用Kotlin、Fultter吗? 谈谈你的理解

    参考回答:

    • Kotlin是一种具有类型推断的跨平台,静态类型的通用编程语言。 Kotlin旨在与Java完全互操作,其标准库的JVM版本依赖于Java类库,但类型推断允许其语法更简洁。
    • Flutter是由Google创建的开源移动应用程序开发框架。它用于开发Android和iOS的应用程序,以及为Google Fuchsia创建应用程序的主要方法
    • 关于kotlin的重要性,相信大家在日常开发可以体会到,应用到实际开发中,需要避免语法糖(例如单列模式、空值判断、高阶函数等)
    • 至于Flutter,目前Google官方文档还不完善,市面上采用此语言编写的项目较少,如需要具体深入,请参考官方文档

    想学习更多相关Android相关技术点和面试内容请关注主页查询

    展开全文
  • 安卓 Android之开发简单小应用(一) 一、简述  记 --没学过Android之开发简单小应用。(课程设计作业)  例子打包:链接:https://pan.baidu.com/s/1LEQ1oWkUX8OmtfCFVydxWQ 密码:9o0d 二、环境搭建 软件打包:...

    安卓 Android之开发简单小应用(一)

    一、简述

        记 --没学过Android之开发简单小应用。(课程设计作业)

        例子打包:链接:https://pan.baidu.com/s/1LEQ1oWkUX8OmtfCFVydxWQ 密码:9o0d

    二、环境搭建

    软件打包:链接:https://pan.baidu.com/s/1VVsZqPrwOtvMuzeeJE1y_A 密码:qspp

    下载解压即可

    1、安装JDK  (默认即可)

    2、设置环境变量

          2.1右键计算机--》属性--》高级系统设置--》高级--》环境变量

           2、2 新建变量名【ClassPath】,其值可设为 【.;%JAVA_HOME%/lib/rt.jar;%JAVA_HOME%/lib/tools.jar】。前面有个.

    (如果使用1.5以上版本的JDK,不用设置CLASSPATH环境变量,也可以正常编译和运行Java程序。)

           2、3找到Path变量,点击【编辑】,在变量值最前面加上【%JAVA_HOME%/bin;】后面有个分号“;”

    注意:记得点击确定

    补充:使用命令脚本设置环境变量,将一下内容保存为xxx.bat或xxx.cmd,然后双击运行(可能需要管理员权限)。

       (记得JAVA_PATH环境变量的值是JDK的实际安装路径,需要根据实际情况进行更改)

    ::先删除之前的JAVA_HOME(防止歧义)
    wmic ENVIRONMENT where "name='JAVA_HOME'" delete
    ::先删除之前的ClASSPATH 
    wmic ENVIRONMENT where "name='CLASSPATH'" delete
    ::创建名称为JAVA_HOME的系统环境变量, VariableValue :是JDK的安装路径
    wmic ENVIRONMENT create name="JAVA_HOME",username="<system>",VariableValue="C:\Program Files (x86)\Java\jdk1.7.0_45"
    ::备份Path环境变量(防止改错了)
    echo %Path%>Path.txt
    ::在系统环境变量Path中增加关于java的环境变量
    wmic ENVIRONMENT where "name='Path' and username='<system>'" set VariableValue=".;%%JAVA_HOME%%\bin;%%JAVA_HOME%%\jre\bin" 
    ::创建CLASSPATH系统环境变量 (听说这个可以不加)
    wmic ENVIRONMENT create name="CLASSPATH",username="<system>",VariableValue=".;%%JAVA_HOME%%\lib\dt.jar;%%JAVA_HOME%%\lib\tools.jar;" 
    ::暂停一下
    pause

    3、打开ADT (不用安装,解压后打开即用)     ADT:Android Developer Tools 安卓开发者工具

    三、新建一个工程

    然后一直点击"Next",直到点击"finish"完成。

    没有创建过虚拟设备的(模拟手机设备),需要新建。(有创建过的可以不用再创建,当然也可以进行修改虚拟设备的型号)

    四、运行项目

    右击项目--》Run As--》Andriod Application

    启动虚拟设备时信息输出

    成功启动之后,自动运行刚才运行的应用

    在项目文件下的bin 目录生成对应的.apk文件,可以直接安装到安卓手机

    五、总结

    1、启动虚拟设备之后,修改了代码,需要再次Run As程序,需要在虚拟设备中退出该应用,因为每Run As一次就会安装一次,先退出上一个版本的应用,再安装,否则可能安装失败。(不用退出虚拟设备,每一开启虚拟设备太耗时间)

    2、调试信息输出,比如说System.out.println("程序已经执行到这里了");默认是看不到的输出信息的,需要设置,(可能需要重启ADT方可生效)

    3、设置中文输入

    4、新建activity(新建一个页面)

    5、实现页面跳转并在第二个页面关闭时想第一个页面传递数据

        5.1在第一个页面添加一个按钮(直接拖拽到相应的布局文件即可)(就是打开)

    注:直接修改文本会有一个叹号,可以使用string.xml,这样就不会有叹号

     

       

     5.2写代码从第一个界面跳转到第二个界面

        5.3第一个界面对应的代码为MainActivity.java

      5.4跳转代码

    5.5当第二个页面关闭返回时的处理代码

    5.6编写第二个页面的代码(TwoActivity.java)

    5.7效果:

    6、设置app的名字

    7、导入已存在的项目

    8、设置字体

    展开全文
  • 我们知道,Android应用程序是通过消息来驱动的,即在应用程序的主线程(UI线程)中有一个消息循环,负责处理消息队列中的消息。我们也知道,Android应用程序是支持多线程的,即可以创建子线程来执行一些计算型的任务...

            我们知道,Android应用程序是通过消息来驱动的,即在应用程序的主线程(UI线程)中有一个消息循环,负责处理消息队列中的消息。我们也知道,Android应用程序是支持多线程的,即可以创建子线程来执行一些计算型的任务,那么,这些子线程能不能像应用程序的主线程一样具有消息循环呢?这些子线程又能不能往应用程序的主线程中发送消息呢?本文将分析Android应用程序线程消息处理模型,为读者解答这两个问题。

    《Android系统源代码情景分析》一书正在进击的程序员网(http://0xcc0xcd.com)中连载,点击进入!

            在开发Android应用程序中,有时候我们需要在应用程序中创建一些常驻的子线程来不定期地执行一些不需要与应用程序界面交互的计算型的任务。如果这些子线程具有消息循环,那么它们就能够常驻在应用程序中不定期的执行一些计算型任务了:当我们需要用这些子线程来执行任务时,就往这个子线程的消息队列中发送一个消息,然后就可以在子线程的消息循环中执行我们的计算型任务了。我们在前面一篇文章Android系统默认Home应用程序(Launcher)的启动过程源代码分析中,介绍Launcher的启动过程时,在Step 15(LauncherModel.startLoader)中,Launcher就是通过往一个子线程的消息队列中发送一个消息(sWorker.post(mLoaderTask)),然后子线程就会在它的消息循环中处理这个消息的时候执行从PackageManagerService中获取系统中已安装应用程序的信息列表的任务,即调用Step 16中的LoaderTask.run函数。

            在开发Android应用程序中,有时候我们又需要在应用程序中创建一些子线程来执行一些需要与应用程序界面进交互的计算型任务。典型的应用场景是当我们要从网上下载文件时,为了不使主线程被阻塞,我们通常创建一个子线程来负责下载任务,同时,在下载的过程,将下载进度以百分比的形式在应用程序的界面上显示出来,这样就既不会阻塞主线程的运行,又能获得良好的用户体验。但是,我们知道,Android应用程序的子线程是不可以操作主线程的UI的,那么,这个负责下载任务的子线程应该如何在应用程序界面上显示下载的进度呢?如果我们能够在子线程中往主线程的消息队列中发送消息,那么问题就迎刃而解了,因为发往主线程消息队列的消息最终是由主线程来处理的,在处理这个消息的时候,我们就可以在应用程序界面上显示下载进度了。

            上面提到的这两种情况,Android系统都为我们提供了完善的解决方案,前者可以通过使用HandlerThread类来实现,而后者可以使用AsyncTask类来实现,本文就详细这两个类是如何实现的。不过,为了更好地理解HandlerThread类和AsyncTask类的实现,我们先来看看应用程序的主线程的消息循环模型是如何实现的。

            1. 应用程序主线程消息循环模型

            在前面一篇文章Android应用程序进程启动过程的源代码分析一文中,我们已经分析应用程序进程(主线程)的启动过程了,这里主要是针对它的消息循环模型作一个总结。当运行在Android应用程序框架层中的ActivityManagerService决定要为当前启动的应用程序创建一个主线程的时候,它会在ActivityManagerService中的startProcessLocked成员函数调用Process类的静态成员函数start为当前应用程序创建一个主线程:

    public final class ActivityManagerService extends ActivityManagerNative    
            implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {    
        
        ......    
        
        private final void startProcessLocked(ProcessRecord app,    
                    String hostingType, String hostingNameStr) {    
        
            ......    
        
            try {    
                int uid = app.info.uid;    
                int[] gids = null;    
                try {    
                    gids = mContext.getPackageManager().getPackageGids(    
                        app.info.packageName);    
                } catch (PackageManager.NameNotFoundException e) {    
                    ......    
                }    
                    
                ......    
        
                int debugFlags = 0;    
                    
                ......    
                    
                int pid = Process.start("android.app.ActivityThread",    
                    mSimpleProcessManagement ? app.processName : null, uid, uid,    
                    gids, debugFlags, null);    
                    
                ......    
        
            } catch (RuntimeException e) {    
                    
                ......    
        
            }    
        }    
        
        ......    
        
    }    
            这里我们主要关注Process.start函数的第一个参数“android.app.ActivityThread”,它表示要在当前新建的线程中加载android.app.ActivityThread类,并且调用这个类的静态成员函数main作为应用程序的入口点。ActivityThread类定义在frameworks/base/core/java/android/app/ActivityThread.java文件中:

    public final class ActivityThread {  
        ......  
      
        public static final void main(String[] args) {  
            ......
      
            Looper.prepareMainLooper();  
             
            ......  
      
            ActivityThread thread = new ActivityThread();  
            thread.attach(false);  
      
            ...... 
            Looper.loop();  
      
            ...... 
      
            thread.detach();  
            ......  
        }  
      
        ......  
    }  
            在这个main函数里面,除了创建一个ActivityThread实例外,就是在进行消息循环了。

            在进行消息循环之前,首先会通过Looper类的静态成员函数prepareMainLooper为当前线程准备一个消息循环对象。Looper类定义在frameworks/base/core/java/android/os/Looper.java文件中:

    public class Looper {
    	......
    
    	// sThreadLocal.get() will return null unless you've called prepare().
    	private static final ThreadLocal sThreadLocal = new ThreadLocal();
    
    	......
    
    	private static Looper mMainLooper = null;
    
    	......
    
    	public static final void prepare() {
    		if (sThreadLocal.get() != null) {
    			throw new RuntimeException("Only one Looper may be created per thread");
    		}
    		sThreadLocal.set(new Looper());
    	}
    
    	......
    
    	public static final void prepareMainLooper() {
    		prepare();
    		setMainLooper(myLooper());
    		......
    	}
    
    	private synchronized static void setMainLooper(Looper looper) {
    		mMainLooper = looper;
    	}
    
    	public synchronized static final Looper getMainLooper() {
    		return mMainLooper;
    	}
    
    	......
    
    	public static final Looper myLooper() {
    		return (Looper)sThreadLocal.get();
    	}
    
    	......
    }

            Looper类的静态成员函数prepareMainLooper是专门应用程序的主线程调用的,应用程序的其它子线程都不应该调用这个函数来在本线程中创建消息循环对象,而应该调用prepare函数来在本线程中创建消息循环对象,下一节我们介绍一个线程类HandlerThread 时将会看到。

            为什么要为应用程序的主线程专门准备一个创建消息循环对象的函数呢?这是为了让其它地方能够方便地通过Looper类的getMainLooper函数来获得应用程序主线程中的消息循环对象。获得应用程序主线程中的消息循环对象又有什么用呢?一般就是为了能够向应用程序主线程发送消息了。

            在prepareMainLooper函数中,首先会调用prepare函数在本线程中创建一个消息循环对象,然后将这个消息循环对象放在线程局部变量sThreadLocal中:

    sThreadLocal.set(new Looper());
            接着再将这个消息循环对象通过调用setMainLooper函数来保存在Looper类的静态成员变量mMainLooper中:

    mMainLooper = looper;
           这样,其它地方才可以调用getMainLooper函数来获得应用程序主线程中的消息循环对象。

           消息循环对象创建好之后,回到ActivityThread类的main函数中,接下来,就是要进入消息循环了:

     Looper.loop(); 
            Looper类具体是如何通过loop函数进入消息循环以及处理消息队列中的消息,可以参考前面一篇文章Android应用程序消息处理机制(Looper、Handler)分析,这里就不再分析了,我们只要知道ActivityThread类中的main函数执行了这一步之后,就为应用程序的主线程准备好消息循环就可以了。

            2. 应用程序子线程消息循环模型

            在Java框架中,如果我们想在当前应用程序中创建一个子线程,一般就是通过自己实现一个类,这个类继承于Thread类,然后重载Thread类的run函数,把我们想要在这个子线程执行的任务都放在这个run函数里面实现。最后实例这个自定义的类,并且调用它的start函数,这样一个子线程就创建好了,并且会调用这个自定义类的run函数。但是当这个run函数执行完成后,子线程也就结束了,它没有消息循环的概念。

            前面说过,有时候我们需要在应用程序中创建一些常驻的子线程来不定期地执行一些计算型任务,这时候就可以考虑使用Android系统提供的HandlerThread类了,它具有创建具有消息循环功能的子线程的作用。

            HandlerThread类实现在frameworks/base/core/java/android/os/HandlerThread.java文件中,这里我们通过使用情景来有重点的分析它的实现。

            在前面一篇文章Android系统默认Home应用程序(Launcher)的启动过程源代码分析中,我们分析了Launcher的启动过程,其中在Step 15(LauncherModel.startLoader)和Step 16(LoaderTask.run)中,Launcher会通过创建一个HandlerThread类来实现在一个子线程加载系统中已经安装的应用程序的任务:

    public class LauncherModel extends BroadcastReceiver {
    	......
    
    	private LoaderTask mLoaderTask;
    
    	private static final HandlerThread sWorkerThread = new HandlerThread("launcher-loader");
    	static {
    		sWorkerThread.start();
    	}
    	private static final Handler sWorker = new Handler(sWorkerThread.getLooper());
    
    	......
    
    	public void startLoader(Context context, boolean isLaunching) {  
    		......  
    
    		synchronized (mLock) {  
    			......  
    
    			// Don't bother to start the thread if we know it's not going to do anything  
    			if (mCallbacks != null && mCallbacks.get() != null) {  
    				......
    
    				mLoaderTask = new LoaderTask(context, isLaunching);  
    				sWorker.post(mLoaderTask);  
    			}  
    		}  
    	}  
    
    	......
    
    	private class LoaderTask implements Runnable {  
    		......  
    
    		public void run() {  
    			......  
    
    			keep_running: {  
    				......  
    
    				// second step  
    				if (loadWorkspaceFirst) {  
    					......  
    					loadAndBindAllApps();  
    				} else {  
    					......  
    				}  
    
    				......  
    			}  
    
    			......  
    		}  
    
    		......  
    	} 
    
    	......
    }
            在这个LauncherModel类中,首先创建了一个HandlerThread对象:

    private static final HandlerThread sWorkerThread = new HandlerThread("launcher-loader");
            接着调用它的start成员函数来启动一个子线程:

    static {
        sWorkerThread.start();
    }
            接着还通过这个HandlerThread对象的getLooper函数来获得这个子线程中的消息循环对象,并且使用这个消息循环创建对象来创建一个Handler:

    private static final Handler sWorker = new Handler(sWorkerThread.getLooper());
            有了这个Handler对象sWorker之后,我们就可以往这个子线程中发送消息,然后在处理这个消息的时候执行加载系统中已经安装的应用程序的任务了,在startLoader函数中:

    mLoaderTask = new LoaderTask(context, isLaunching);  
    sWorker.post(mLoaderTask);  
            这里的mLoaderTask是一个LoaderTask对象,它实现了Runnable接口,因此,可以把这个LoaderTask对象作为参数传给sWorker.post函数。在sWorker.post函数里面,会把这个LoaderTask对象封装成一个消息,并且放入这个子线程的消息队列中去。当这个子线程的消息循环处理这个消息的时候,就会调用这个LoaderTask对象的run函数,因此,我们就可以在LoaderTask对象的run函数中通过调用loadAndBindAllApps来执行加载系统中已经安装的应用程序的任务了。

            了解了HanderThread类的使用方法之后,我们就可以重点地来分析它的实现了:

    public class HandlerThread extends Thread {
    	......
    	private Looper mLooper;
    
    	public HandlerThread(String name) {
    		super(name);
    		......
    	}
    
    	......
    
    	public void run() {
    		......
    		Looper.prepare();
    		synchronized (this) {
    			mLooper = Looper.myLooper();
    			......
    		}
    		......
    		Looper.loop();
    		......
    	}
    
    	public Looper getLooper() {
    		......
    		return mLooper;
    	}
    
    	......
    }
            首先我们看到的是,HandlerThread类继承了Thread类,因此,通过它可以在应用程序中创建一个子线程,其次我们看到在它的run函数中,会进入一个消息循环中,因此,这个子线程可以常驻在应用程序中,直到它接收收到一个退出消息为止。

            在run函数中,首先是调用Looper类的静态成员函数prepare来准备一个消息循环对象:

    Looper.prepare();
            然后通过Looper类的myLooper成员函数将这个子线程中的消息循环对象保存在HandlerThread类中的成员变量mLooper中:

    mLooper = Looper.myLooper();
            这样,其它地方就可以方便地通过它的getLooper函数来获得这个消息循环对象了,有了这个消息循环对象后,就可以往这个子线程的消息队列中发送消息,通知这个子线程执行特定的任务了。

            最在这个run函数通过Looper类的loop函数进入消息循环中:

    Looper.loop();
            这样,一个具有消息循环的应用程序子线程就准备就绪了。

            HandlerThread类的实现虽然非常简单,当然这得益于Java提供的Thread类和Android自己本身提供的Looper类,但是它的想法却非常周到,为应用程序开发人员提供了很大的方便。
            3. 需要与UI交互的应用程序子线程消息模型

            前面说过,我们开发应用程序的时候,经常中需要创建一个子线程来在后台执行一个特定的计算任务,而在这个任务计算的过程中,需要不断地将计算进度或者计算结果展现在应用程序的界面中。典型的例子是从网上下载文件,为了不阻塞应用程序的主线程,我们开辟一个子线程来执行下载任务,子线程在下载的同时不断地将下载进度在应用程序界面上显示出来,这样做出来程序就非常友好。由于子线程不能直接操作应用程序的UI,因此,这时候,我们就可以通过往应用程序的主线程中发送消息来通知应用程序主线程更新界面上的下载进度。因为类似的这种情景在实际开发中经常碰到,Android系统为开发人员提供了一个异步任务类(AsyncTask)来实现上面所说的功能,即它会在一个子线程中执行计算任务,同时通过主线程的消息循环来获得更新应用程序界面的机会。

            为了更好地分析AsyncTask的实现,我们先举一个例子来说明它的用法。在前面一篇文章Android系统中的广播(Broadcast)机制简要介绍和学习计划中,我们开发了一个应用程序Broadcast,其中使用了AsyncTask来在一个线程在后台在执行计数任务,计数过程通过广播(Broadcast)来将中间结果在应用程序界面上显示出来。在这个例子中,使用广播来在应用程序主线程和子线程中传递数据不是最优的方法,当时只是为了分析Android系统的广播机制而有意为之的。在本节内容中,我们稍微这个例子作一个简单的修改,就可以通过消息的方式来将计数过程的中间结果在应用程序界面上显示出来。

            为了区别Android系统中的广播(Broadcast)机制简要介绍和学习计划一文中使用的应用程序Broadcast,我们将本节中使用的应用程序命名为Counter。首先在Android源代码工程中创建一个Android应用程序工程,名字就为Counter,放在packages/experimental目录下。关于如何获得Android源代码工程,请参考在Ubuntu上下载、编译和安装Android最新源代码一文;关于如何在Android源代码工程中创建应用程序工程,请参考在Ubuntu上为Android系统内置Java应用程序测试Application Frameworks层的硬件服务一文。这个应用程序工程定义了一个名为shy.luo.counter的package,这个例子的源代码主要就是实现在这个目录下的Counter.java文件中:

    package shy.luo.counter;
    
    import android.app.Activity;
    import android.content.ComponentName;
    import android.content.Context;
    import android.content.Intent;
    import android.content.IntentFilter;
    import android.os.Bundle;
    import android.os.AsyncTask;
    import android.util.Log;
    import android.view.View;
    import android.view.View.OnClickListener;
    import android.widget.Button;
    import android.widget.TextView;
    
    public class Counter extends Activity implements OnClickListener {
    	private final static String LOG_TAG = "shy.luo.counter.Counter";
    
    	private Button startButton = null;
    	private Button stopButton = null;
    	private TextView counterText = null;
    
    	private AsyncTask<Integer, Integer, Integer> task = null;
    	private boolean stop = false;
    
    	@Override
    	public void onCreate(Bundle savedInstanceState) {
    		super.onCreate(savedInstanceState);
    		setContentView(R.layout.main);
    
    		startButton = (Button)findViewById(R.id.button_start);
    		stopButton = (Button)findViewById(R.id.button_stop);
    		counterText = (TextView)findViewById(R.id.textview_counter);
    
    		startButton.setOnClickListener(this);
    		stopButton.setOnClickListener(this);
    
    		startButton.setEnabled(true);
    		stopButton.setEnabled(false);
    
    
    		Log.i(LOG_TAG, "Main Activity Created.");
    	}
    
    
    	@Override
    	public void onClick(View v) {
    		if(v.equals(startButton)) {
    			if(task == null) {
    				task = new CounterTask();
    				task.execute(0);
    
    				startButton.setEnabled(false);
    				stopButton.setEnabled(true);
    			}
    		} else if(v.equals(stopButton)) {
    			if(task != null) {
    				stop = true;
    				task = null;
    
    				startButton.setEnabled(true);
    				stopButton.setEnabled(false);
    			}
    		}
    	}
    
    	class CounterTask extends AsyncTask<Integer, Integer, Integer> {
    		@Override
    		protected Integer doInBackground(Integer... vals) {
    			Integer initCounter = vals[0];
    
    			stop = false;
    			while(!stop) {
    				publishProgress(initCounter);
    
    				try {
    					Thread.sleep(1000);
    				} catch (InterruptedException e) {
    					e.printStackTrace();
    				}
    
    				initCounter++;
    			}
    
    			return initCounter;
    		}
    
    		@Override
    		protected void onProgressUpdate(Integer... values) {
    			super.onProgressUpdate(values);
    
    			String text = values[0].toString();
    			counterText.setText(text);
    		}
    
    		@Override
    		protected void onPostExecute(Integer val) {
    			String text = val.toString();
    			counterText.setText(text);
    		}
    	};
    }
            这个计数器程序很简单,它在界面上有两个按钮Start和Stop。点击Start按钮时,便会创建一个CounterTask实例task,然后调用它的execute函数就可以在应用程序中启动一个子线程,并且通过调用这个CounterTask类的doInBackground函数来执行计数任务。在计数的过程中,会通过调用publishProgress函数来将中间结果传递到onProgressUpdate函数中去,在onProgressUpdate函数中,就可以把中间结果显示在应用程序界面了。点击Stop按钮时,便会通过设置变量stop为true,这样,CounterTask类的doInBackground函数便会退出循环,然后将结果返回到onPostExecute函数中去,在onPostExecute函数,会把最终计数结果显示在用程序界面中。

           在这个例子中,我们需要注意的是:

           A. CounterTask类继承于AsyncTask类,因此它也是一个异步任务类;

           B. CounterTask类的doInBackground函数是在后台的子线程中运行的,这时候它不可以操作应用程序的界面;

           C. CounterTask类的onProgressUpdate和onPostExecute两个函数是应用程序的主线程中执行,它们可以操作应用程序的界面。

           关于C这一点的实现原理,我们在后面会分析到,这里我们先完整地介绍这个例子,以便读者可以参考做一下实验。

           接下来我们再看看应用程序的配置文件AndroidManifest.xml:

    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="shy.luo.counter"
          android:versionCode="1"
          android:versionName="1.0">
        <application android:icon="@drawable/icon" android:label="@string/app_name">
            <activity android:name=".Counter"
                      android:label="@string/app_name">
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>
        </application>
    </manifest>
           这个配置文件很简单,我们就不介绍了。

           再来看应用程序的界面文件,它定义在res/layout/main.xml文件中:

    <?xml version="1.0" encoding="utf-8"?>  
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
        android:orientation="vertical"  
        android:layout_width="fill_parent"  
        android:layout_height="fill_parent"   
        android:gravity="center">  
        <LinearLayout  
            android:layout_width="fill_parent"  
            android:layout_height="wrap_content"  
            android:layout_marginBottom="10px"  
            android:orientation="horizontal"   
            android:gravity="center">  
            <TextView    
            android:layout_width="wrap_content"   
                android:layout_height="wrap_content"   
                android:layout_marginRight="4px"  
                android:gravity="center"  
                android:text="@string/counter">  
            </TextView>  
            <TextView    
                android:id="@+id/textview_counter"  
            android:layout_width="wrap_content"   
                android:layout_height="wrap_content"   
                android:gravity="center"  
                android:text="0">  
            </TextView>  
        </LinearLayout>  
        <LinearLayout  
            android:layout_width="fill_parent"  
            android:layout_height="wrap_content"  
            android:orientation="horizontal"   
            android:gravity="center">  
            <Button   
                android:id="@+id/button_start"  
                android:layout_width="wrap_content"  
                android:layout_height="wrap_content"  
                android:gravity="center"  
                android:text="@string/start">  
            </Button>  
            <Button   
                android:id="@+id/button_stop"  
                android:layout_width="wrap_content"  
                android:layout_height="wrap_content"  
                android:gravity="center"  
                android:text="@string/stop" >  
            </Button>  
         </LinearLayout>    
    </LinearLayout>  
           这个界面配置文件也很简单,等一下我们在模拟器把这个应用程序启动起来后,就可以看到它的截图了。

           应用程序用到的字符串资源文件位于res/values/strings.xml文件中:

    <?xml version="1.0" encoding="utf-8"?>  
    <resources>  
        <string name="app_name">Counter</string>  
        <string name="counter">Counter: </string>  
        <string name="start">Start Counter</string>  
        <string name="stop">Stop Counter</string>  
    </resources> 
           最后,我们还要在工程目录下放置一个编译脚本文件Android.mk:

    LOCAL_PATH:= $(call my-dir)        
    include $(CLEAR_VARS)        
            
    LOCAL_MODULE_TAGS := optional        
            
    LOCAL_SRC_FILES := $(call all-subdir-java-files)        
            
    LOCAL_PACKAGE_NAME := Counter        
            
    include $(BUILD_PACKAGE)  
           接下来就要编译了。有关如何单独编译Android源代码工程的模块,以及如何打包system.img,请参考如何单独编译Android源代码中的模块一文。
           执行以下命令进行编译和打包:

    USER-NAME@MACHINE-NAME:~/Android$ mmm packages/experimental/Counter          
    USER-NAME@MACHINE-NAME:~/Android$ make snod
           这样,打包好的Android系统镜像文件system.img就包含我们前面创建的Counter应用程序了。
           再接下来,就是运行模拟器来运行我们的例子了。关于如何在Android源代码工程中运行模拟器,请参考在Ubuntu上下载、编译和安装Android最新源代码一文。
           执行以下命令启动模拟器:
    USER-NAME@MACHINE-NAME:~/Android$ emulator 
           最后我们就可以在Launcher中找到Counter应用程序图标,把它启动起来,点击Start按钮,就会看到应用程序界面上的计数器跑起来了:


            这样,使用AsyncTask的例子就介绍完了,下面,我们就要根据上面对AsyncTask的使用情况来重点分析它的实现了。

            AsyncTask类定义在frameworks/base/core/java/android/os/AsyncTask.java文件中:

    public abstract class AsyncTask<Params, Progress, Result> {
    	......
    
    	private static final BlockingQueue<Runnable> sWorkQueue =
    			new LinkedBlockingQueue<Runnable>(10);
    
    	private static final ThreadFactory sThreadFactory = new ThreadFactory() {
    		private final AtomicInteger mCount = new AtomicInteger(1);
    
    		public Thread newThread(Runnable r) {
    			return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
    		}
    	};
    
    	......
    
    	private static final ThreadPoolExecutor sExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE,
    		MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sWorkQueue, sThreadFactory);
    
    	private static final int MESSAGE_POST_RESULT = 0x1;
    	private static final int MESSAGE_POST_PROGRESS = 0x2;
    	private static final int MESSAGE_POST_CANCEL = 0x3;
    
    	private static final InternalHandler sHandler = new InternalHandler();
    
    	private final WorkerRunnable<Params, Result> mWorker;
    	private final FutureTask<Result> mFuture;
    
    	......
    
    	public AsyncTask() {
    		mWorker = new WorkerRunnable<Params, Result>() {
    			public Result call() throws Exception {
    				......
    				return doInBackground(mParams);
    			}
    		};
    
    		mFuture = new FutureTask<Result>(mWorker) {
    			@Override
    			protected void done() {
    				Message message;
    				Result result = null;
    
    				try {
    					result = get();
    				} catch (InterruptedException e) {
    					android.util.Log.w(LOG_TAG, e);
    				} catch (ExecutionException e) {
    					throw new RuntimeException("An error occured while executing doInBackground()",
    						e.getCause());
    				} catch (CancellationException e) {
    					message = sHandler.obtainMessage(MESSAGE_POST_CANCEL,
    						new AsyncTaskResult<Result>(AsyncTask.this, (Result[]) null));
    					message.sendToTarget();
    					return;
    				} catch (Throwable t) {
    					throw new RuntimeException("An error occured while executing "
    						+ "doInBackground()", t);
    				}
    
    				message = sHandler.obtainMessage(MESSAGE_POST_RESULT,
    					new AsyncTaskResult<Result>(AsyncTask.this, result));
    				message.sendToTarget();
    			}
    		};
    	}
    
    	......
    
    	public final Result get() throws InterruptedException, ExecutionException {
    		return mFuture.get();
    	}
    
    	......
    
    	public final AsyncTask<Params, Progress, Result> execute(Params... params) {
    		......
    
    		mWorker.mParams = params;
    		sExecutor.execute(mFuture);
    
    		return this;
    	}
    
    	......
    
    	protected final void publishProgress(Progress... values) {
    		sHandler.obtainMessage(MESSAGE_POST_PROGRESS,
    			new AsyncTaskResult<Progress>(this, values)).sendToTarget();
    	}
    
            private void finish(Result result) {
                    ......
                    onPostExecute(result);
                    ......
            }
    
    	......
    
    	private static class InternalHandler extends Handler {
    		@SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
    		@Override
    		public void handleMessage(Message msg) {
    			AsyncTaskResult result = (AsyncTaskResult) msg.obj;
    			switch (msg.what) {
    		        case MESSAGE_POST_RESULT:
    			     // There is only one result
    			     result.mTask.finish(result.mData[0]);
    			     break;
    		        case MESSAGE_POST_PROGRESS:
    			     result.mTask.onProgressUpdate(result.mData);
    			     break;
    		        case MESSAGE_POST_CANCEL:
    			     result.mTask.onCancelled();
    			     break;
    			}
    		}
    	}
    
    	private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {
    		Params[] mParams;
    	}
    
    	private static class AsyncTaskResult<Data> {
    		final AsyncTask mTask;
    		final Data[] mData;
    
    		AsyncTaskResult(AsyncTask task, Data... data) {
    			mTask = task;
    			mData = data;
    		}
    	}
    }

            从AsyncTask的实现可以看出,当我们第一次创建一个AsyncTask对象时,首先会执行下面静态初始化代码创建一个线程池sExecutor:

    private static final BlockingQueue<Runnable> sWorkQueue =
    	new LinkedBlockingQueue<Runnable>(10);
    
    private static final ThreadFactory sThreadFactory = new ThreadFactory() {
    	private final AtomicInteger mCount = new AtomicInteger(1);
    
    	public Thread newThread(Runnable r) {
    		return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
    	}
    };
    
    ......
    
    private static final ThreadPoolExecutor sExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE,
    	MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sWorkQueue, sThreadFactory);
            这里的ThreadPoolExecutor是Java提供的多线程机制之一,这里用的构造函数原型为:

    ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, 
        BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory)
            各个参数的意义如下:

            corePoolSize -- 线程池的核心线程数量

            maximumPoolSize -- 线程池的最大线程数量

            keepAliveTime -- 若线程池的线程数数量大于核心线程数量,那么空闲时间超过keepAliveTime的线程将被回收

            unit -- 参数keepAliveTime使用的时间单位

            workerQueue -- 工作任务队列

            threadFactory -- 用来创建线程池中的线程
            简单来说,ThreadPoolExecutor的运行机制是这样的:每一个工作任务用一个Runnable对象来表示,当我们要把一个工作任务交给这个线程池来执行的时候,就通过调用ThreadPoolExecutor的execute函数来把这个工作任务加入到线程池中去。此时,如果线程池中的线程数量小于corePoolSize,那么就会调用threadFactory接口来创建一个新的线程并且加入到线程池中去,再执行这个工作任务;如果线程池中的线程数量等于corePoolSize,但是工作任务队列workerQueue未满,则把这个工作任务加入到工作任务队列中去等待执行;如果线程池中的线程数量大于corePoolSize,但是小于maximumPoolSize,并且工作任务队列workerQueue已经满了,那么就会调用threadFactory接口来创建一个新的线程并且加入到线程池中去,再执行这个工作任务;如果线程池中的线程量已经等于maximumPoolSize了,并且工作任务队列workerQueue也已经满了,这个工作任务就被拒绝执行了。

            创建好了线程池后,再创建一个消息处理器:

    private static final InternalHandler sHandler = new InternalHandler();
            注意,这行代码是在应用程序的主线程中执行的,因此,这个消息处理器sHandler内部引用的消息循环对象looper是应用程序主线程的消息循环对象,消息处理器的实现机制具体可以参考前面一篇文章Android应用程序消息处理机制(Looper、Handler)分析

            AsyncTask类的静态初始化代码执行完成之后,才开始创建AsyncTask对象,即执行AsyncTask类的构造函数:

    public AsyncTask() {
    	mWorker = new WorkerRunnable<Params, Result>() {
    		public Result call() throws Exception {
    			......
    			return doInBackground(mParams);
    		}
    	};
    
    	mFuture = new FutureTask<Result>(mWorker) {
    		@Override
    		protected void done() {
    			Message message;
    			Result result = null;
    
    			try {
    				result = get();
    			} catch (InterruptedException e) {
    				android.util.Log.w(LOG_TAG, e);
    			} catch (ExecutionException e) {
    				throw new RuntimeException("An error occured while executing doInBackground()",
    					e.getCause());
    			} catch (CancellationException e) {
    				message = sHandler.obtainMessage(MESSAGE_POST_CANCEL,
    					new AsyncTaskResult<Result>(AsyncTask.this, (Result[]) null));
    				message.sendToTarget();
    				return;
    			} catch (Throwable t) {
    				throw new RuntimeException("An error occured while executing "
    					+ "doInBackground()", t);
    			}
    
    			message = sHandler.obtainMessage(MESSAGE_POST_RESULT,
    				new AsyncTaskResult<Result>(AsyncTask.this, result));
    			message.sendToTarget();
    		}
    	};
    }
            在AsyncTask类的构造函数里面,主要是创建了两个对象,分别是一个WorkerRunnable对象mWorker和一个FutureTask对象mFuture。

            WorkerRunnable类实现了Callable接口,此外,它的内部成员变量mParams用于保存从AsyncTask对象的execute函数传进来的参数列表:

    private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {
    	Params[] mParams;
    }
            FutureTask类也实现了Runnable接口,所以它可以作为一个工作任务通过调用AsyncTask类的execute函数添加到sExecuto线程池中去:

    public final AsyncTask<Params, Progress, Result> execute(Params... params) {
    	......
    
    	mWorker.mParams = params;
    	sExecutor.execute(mFuture);
    
    	return this;
    }

           这里的FutureTask对象mFuture是用来封装前面的WorkerRunnable对象mWorker。当mFuture加入到线程池中执行时,它调用的是mWorker对象的call函数:

    mWorker = new WorkerRunnable<Params, Result>() {
    	public Result call() throws Exception {
    	       ......
    	       return doInBackground(mParams);
            }
    };
            在call函数里面,会调用AsyncTask类的doInBackground函数来执行真正的任务,这个函数是要由AsyncTask的子类来实现的,注意,这个函数是在应用程序的子线程中执行的,它不可以操作应用程序的界面。

            我们可以通过mFuture对象来操作当前执行的任务,例如查询当前任务的状态,它是正在执行中,还是完成了,还是被取消了,如果是完成了,还可以通过它获得任务的执行结果,如果还没有完成,可以取消任务的执行。

            当工作任务mWorker执行完成的时候,mFuture对象中的done函数就会被被调用,根据任务的完成状况,执行相应的操作,例如,如果是因为异常而完成时,就会抛异常,如果是正常完成,就会把任务执行结果封装成一个AsyncTaskResult对象:

    private static class AsyncTaskResult<Data> {
    	final AsyncTask mTask;
    	final Data[] mData;
    
    	AsyncTaskResult(AsyncTask task, Data... data) {
    		mTask = task;
    		mData = data;
    	}
    }
            其中,成员变量mData保存的是任务执行结果,而成员变量mTask指向前面我们创建的AsyncTask对象。
            最后把这个AsyncTaskResult对象封装成一个消息,并且通过消息处理器sHandler加入到应用程序主线程的消息队列中:

    message = sHandler.obtainMessage(MESSAGE_POST_RESULT,
    	new AsyncTaskResult<Result>(AsyncTask.this, result));
    message.sendToTarget();
            这个消息最终就会在InternalHandler类的handleMessage函数中处理了:

    private static class InternalHandler extends Handler {
    	@SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
    	@Override
    	public void handleMessage(Message msg) {
    		AsyncTaskResult result = (AsyncTaskResult) msg.obj;
    		switch (msg.what) {
    		case MESSAGE_POST_RESULT:
    			// There is only one result
    			result.mTask.finish(result.mData[0]);
    			break;
    		......
    		}
    	}
    }
            在这个函数里面,最终会调用前面创建的这个AsyncTask对象的finish函数来进一步处理:

    private void finish(Result result) {
           ......
           onPostExecute(result);
           ......
    }
            这个函数调用AsyncTask类的onPostExecute函数来进一步处理,AsyncTask类的onPostExecute函数一般是要由其子类来重载的,注意,这个函数是在应用程序的主线程中执行的,因此,它可以操作应用程序的界面。
            在任务执行的过程当中,即执行doInBackground函数时候,可能通过调用publishProgress函数来将中间结果封装成一个消息发送到应用程序主线程中的消息队列中去:

    protected final void publishProgress(Progress... values) {
    	sHandler.obtainMessage(MESSAGE_POST_PROGRESS,
    		new AsyncTaskResult<Progress>(this, values)).sendToTarget();
    }
            这个消息最终也是由InternalHandler类的handleMessage函数来处理的:

    private static class InternalHandler extends Handler {
    	@SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
    	@Override
    	public void handleMessage(Message msg) {
    		AsyncTaskResult result = (AsyncTaskResult) msg.obj;
    		switch (msg.what) {
    		......
    		case MESSAGE_POST_PROGRESS:
    			     result.mTask.onProgressUpdate(result.mData);
    			     break;
    		......
    		}
    	}
    }
            这里它调用前面创建的AsyncTask对象的onPorgressUpdate函数来进一步处理,这个函数一般是由AsyncTask的子类来实现的,注意,这个函数是在应用程序的主线程中执行的,因此,它和前面的onPostExecute函数一样,可以操作应用程序的界面。

           这样,AsyncTask类的主要实现就介绍完了,结合前面开发的应用程序Counter来分析,会更好地理解它的实现原理。

           至此,Android应用程序线程消息循环模型就分析完成了,理解它有利于我们在开发Android应用程序时,能够充分利用多线程的并发性来提高应用程序的性能以及获得良好的用户体验。

    老罗的新浪微博:http://weibo.com/shengyangluo,欢迎关注!

    展开全文
  • 在微信开放平台创建移动应用时上图大家都遇到过,这里的应用签名是什么呢? 这里介绍说可以通过签名生成工具在已安装当前应用的手机中获取,(待会获取测试一下)我们先搞明白...2.创建一个Android应用 3.使用keystore文件签
  • Android应用开发实战》是一本实践与理论紧密结合的Android应用开发参考书。实践部分以一个完整的大型案例(功能完善的微博客户端)贯穿始终,以迭代的方式详细演示和讲解了该案例的开发全过程,旨在帮助读者迅速...
  • 早期的Android应用开发中,Activity/Fragment承担了过多的职责,它们不仅负责了应用界面的显示,而且负责了业务逻辑的处理。这样一来,Activity/Fragment很容易就变得臃肿、复杂,造成应用难以测试、维护和扩展。...
  • Android应用案例开发大全》是以Android手机综合应用程序开发为主题 通过11个典型范例全面且深度地讲解了单机应用 网络应用 商业案例 2D和3D游戏等多个开发领域 全书共分12章 主要以范例集的方式来讲述Android的...
  • Android应用的默认Activity 对一个Android应用来说,一般都会将某个Activity配置为默认启动的Activity。默认Activity作为应用的入口,会在桌面中显示一个图标和名字。这里稍作说明一下,Android原生系统采用二级...
  • 上文介绍了Android应用程序的启动过程,即应用程序默认Activity的启动过程,一般来说,这种默认Activity是在新的进程和任务中启动的;本文将继续分析在应用程序内部启动非默认Activity的过程的源代码,这种非默认...
  • 查看Android应用包名、Activity的几个方法
  • 本文主要讲解Android应用程序签名相关的理论知识,包括:什么是签名、为什么要给应用程序签名、如何给应用程序签名等。1、什么是签名? 如果这个问题不是放在Android开发中来问,如果是放在一个普通的版本,我想...
  • C#开发Android应用实战 使用Mono for Android和.NET C# PDF扫描版,希望对你有用处。 一般下载的都7.07MB的,那个只是一个样章,这个是书的全部。
  • Android应用程序与传统的PC应用程序一样,都是消息驱动的。也就是说,在Android应用程序主线程中,所有函数都是在一个消息循环中执行的。Android应用程序其它线程,也可以像主线程一样,拥有消息循环。Android应用...
  • Android应用双开实现

    2018-10-26 18:15:52
    Android应用双开是某些特殊人群的需要,目前已知的双开方案个人总结为3种: 1.反编译apk,然后修改uid等相关信息让系统弄认为apk有不同,然后重新安装。这个方法是简单粗暴的,不过目前主流的app估计都会预防这种...
1 2 3 4 5 ... 20
收藏数 761,453
精华内容 304,581
关键字:

android应用