Android开发面试经典题目

本文整理了Android开发面试中的核心知识点,涵盖JAVA基础、集合、IO流、多线程、Android四大组件等方面。深入讲解了面向对象特性、集合框架、多线程安全问题、ContentProvider、线程同步以及Android系统启动流程。此外,还介绍了Android应用的启动过程,从解析清单文件、启动Activity到生命周期的监控。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

下面是整理的Android知识点,可以平时巩固知识点,仅供复习使用。

☆  JAVA技能

  • 有良好的JAVA基础,熟练掌握面向对象思想:

理解面向对象:

面向对象是一种思想,是基于面向过程而言的,就是说面向对象是将功能等通过对象来实现,将功能封装进对象之中,让对象去实现具体的细节;这种思想是将数据作为第一位,而方法或者说是算法作为其次,这是对数据一种优化,操作起来更加的方便,简化了过程。面向对象有三大特征:封装性、继承性、多态性,其中封装性指的是隐藏了对象的属性和实现细节,仅对外提供公共的访问方式,这样就隔离了具体的变化,便于使用,提高了复用性和安全性。对于继承性,就是两种事物间存在着一定的所属关系,那么继承的类就可以从被继承的类中获得一些属性和方法;这就 提高了代码的复用性。继承是作为多态的前提的。多态是说父类或接口的引用指向了子类对A象,这就提高了程序的扩展性,也就是说只要实现或继承了同一个接口或类,那么就可以使用父类中相应的方法,提高程序扩展性,但是多态有一点不好之处在于:父类引用不能访问子类中的成员。

举例来说:就是:比如说你要去饭店吃饭,你只需要饭店,找到饭店的服务员,跟她说你要吃什么,然后叫会给你做出来让你吃,你并不需要知道这个饭是怎么错的,你只需要面向这个服务员,告诉他你要吃什么,然后他也只需要面向你吃完收到钱就好,不需要知道你怎么对这个饭进行吃。

1、特点:

1:将复杂的事情简单化。

       2:面向对象将以前的过程中的执行者,变成了指挥者。

       3:面向对象这种思想是符合现在人们思考习惯的一种思想。

2、面向对象的三大特征:封装,继承、多态

1.封装:只隐藏对象的属性和实现细节,仅对外提供公共访问方式

好处:将变化隔离、便于使用、提高复用性、提高安全性

原则:将不需要对外提供的内容隐藏起来;把属性隐藏,提供公共方法对其访问

2.继承:提高代码复用性;继承是多态的前提

注:

①子类中所有的构造函数都会默认访问父类中的空参数的构造函数,默认第一行有super();若无空参数构造函数,子类中需指定;另外,子类构造函数中可自己用this指定自身的其他构造函数。

3.多态

是父类或接口定义的引用变量可以指向子类或具体实现类的实例对象

好处:提高了程序的扩展性

弊端:当父类引用指向子类对象时,虽提高了扩展性,但只能访问父类中具备的方法,不可访问子类中的方法;即访问的局限性。

前提:实现或继承关系;覆写父类方法。

 

  • 熟练使用集合、IO流及多线程

一、集合:

1、特点:存储对象;长度可变;存储对象的类型可不同;

2、集合框架

2Collection

1List有序的;元素可重复,有索引

(add(index, element)、add(index, Collection)、remove(index)、set(index,element)、get(index)、subList(from, to)、listIterator())

ArrayList:底层是数组结构,查询快,增删慢,不同步。

LinkedList:底层是链表结构,增删快,查询慢,不同步

addFist();addLast()  getFirst();getLast()

removeFirst();removeLast() 获取并删除元素,无元素将抛异常:NoSuchElementException

替代的方法(JDK1.6):

offerFirst();offerLast();

peekFirst();peekLast();无元素返回null

pollFirst();pollLast();删除并返回此元素,无元素返回null     

Vector:底层是数组结构,线程同步,被ArrayList取代了

注:了对于判断是否存在,以及删除等操作,以依赖的方法是元素的hashCodeequals方法

ArrayList判断是否存在和删除操作依赖的是equals方法

2Set:无序的,无索引,元素不可重复

HashSet:底层是哈希表,线程不同步,无序、高效

保证元素唯一性:通过元素的hashCode和equals方法。若hashCode值相同,则会判断equals的结果是否为true;hashCode不同,不会调用equals方法

LinkedHashSet:有序,是HashSet的子类

TreeSet:底层是二叉树,可对元素进行排序,默认是自然顺序

       保证唯一性:Comparable接口的compareTo方法的返回值

===TreeSet两种排序方式:两种方式都存在时,以比较器为主

第一种:自然排序(默认排序):

       添加的对象需要实现Comparable接口,覆盖compareTo方法

第二种:比较器

       添加的元素自身不具备比较性或不是想要的比较方式。将比较器作为参数传递进去。

       定义一个类,实现Comparator接口,覆盖compare方法。当主要条件相同时,比较次要条件。

 

3Map集合:

1HashTable:底层数据结构是哈希表,不可存入null键和null值。同步的

       Properties继承自HashTable,可保存在流中或从流中加载,是集合和IO流的结合产物

2HashMap:底层数据结构是哈希表;允许使用null键和null值,不同步,效率高

       TreeMap

              底层数据结构时二叉树,不同步,可排序

              Set很像,Set底层就是使用了Map集合

方法:

V put(K key, V value) ;  void putAll(Map m)

void clear();  V remove(Object key)

boolean containsKey(Object key);  containsValue(Object key);  isEmpty()

V get(Object key); int size(); Collection<V> values()

Set<K> keySet();  Set<Map.Entry<K,V>> entrySet()

 

2.3Map集合两种取出方式:

第一种:Set<K> keySet()

       取出Map集合中的所有键放于Set集合中,然后再通过键取出对应的值

Set<String> keySet = map.keySet();

Iterator<String> it = keySet.iterator();

while(it.hasNext()){

       String key = it.next();

       String value = map.get(key);

//…..

}

第二种:Set<Map.Entry<K,V>> entrySet()

       取出Map集合中键值对的映射放于Set集合中,然后通过Map集合中的内部接口,然后通过其中的方法取出

Set<Map.Entry<String,String>> entrySet = map.entrySet();

Iterator<Map.Entry<String,String>> it = entrySet.iterator();

While(it.hasNext()){

       Map.Entry<String,String> entry = it.next();

       String key = entry.getKey();

       String value = entry.getValue();

       //……

}

 

2.4CollectionMap的区别:

Collection:单列集合,一次存一个元素

Map:双列集合,一次存一对集合,两个元素(对象)存在着映射关系

 

2.5、集合工具类:

Collections:操作集合(一般是list集合)的工具类。方法全为静态的

sort(List list);list集合进行排序; sort(List list, Comparator c) 按指定比较器排序

fill(List list, T obj);将集合元素替换为指定对象;

swap(List list, int I, int j)交换集合指定位置的元素

shuffle(List list); 随机对集合元素排序

reverseOrder() :返回比较器,强行逆转实现Comparable接口的对象自然顺序

reverseOrder(Comparator c):返回比较器,强行逆转指定比较器的顺序

 

2.6CollectionCollections的区别:

Collectionsjava.util下的工具类,实现对集合的查找、排序、替换、线程安全化等操作。

Collection:是java.util下的接口,是各种单列集合的父接口,实现此接口的有ListSet集合,存储对象并对其进行操作。

 

3Arrays

       用于操作数组对象的工具类,全为静态方法

asList():将数组转为list集合

       好处:可通过list集合的方法操作数组中的元素:

isEmpty()contains()indexOf()set()

       弊端:数组长度固定,不可使用集合的增删操作。

如果数组中存储的是基本数据类型,asList会将数组整体作为一个元素存入集合

集合转为数组:Collection.toArray()

       好处:限定了对集合中的元素进行增删操作,只需获取元素

 

二、IO

1、结构:

 

字节流:InputStreamOutputStream

字符流:ReaderWriter

Reader:读取字符流的抽象类

       BufferedReader:将字符存入缓冲区,再读取

              LineNumberReader:带行号的字符缓冲输入流

       InputStreamReader:转换流,字节流和字符流的桥梁,多在编码的地方使用

              FileReader:读取字符文件的便捷类。

 

Writer:写入字符流的抽象类

       BufferedWriter:将字符存入缓冲区,再写入

       OutputStreamWriter:转换流,字节流和字符流的桥梁,多在编码的地方使用

              FileWriter:写入字符文件的便捷类。

 

InputStream:字节输入流的所有类的超类

       ByteArrayInputStream:含缓冲数组,读取内存中字节数组的数据,未涉及流

       FileInputStream:从文件中获取输入字节。媒体文件

              BufferedInputStream:带有缓冲区的字节输入流

              DataInputStream:数据输入流,读取基本数据类型的数据

       ObjectInputStream:用于读取对象的输入流

       PipedInputStream:管道流,线程间通信,与PipedOutputStream配合使用

       SequenceInputStream:合并流,将多个输入流逻辑串联。

OutputStream:此抽象类是表示输出字节流的所有类的超类

       ByteArrayOutputStream:含缓冲数组,将数据写入内存中的字节数组,未涉及流

       FileOutStream:文件输出流,将数据写入文件

              BufferedOutputStream:带有缓冲区的字节输出流

              PrintStream:打印流,作为输出打印

              DataOutputStream:数据输出流,写入基本数据类型的数据

       ObjectOutputStream:用于写入对象的输出流

       PipedOutputStream:管道流,线程间通信,与PipedInputStream配合使用

2、流操作规律:

       明确源和目的:

              数据源:读取,InputStreamReader

              目的:写入:OutStreamWriter

       数据是否是纯文本:

              是:字符流,ReaderWriter

              否:字节流,InputStreamOutStream

       明确数据设备:

              源设备:内存、硬盘、键盘

              目的设备:内存、硬盘、控制台

       是否提高效率:用BufferedXXX

3、转换流:将字节转换为字符,可通过相应的编码表获得

       转换流都涉及到字节流和编码表

 

三、多线程

--à进程和线程:

1)进程是静态的,其实就是指开启的一个程序;而线程是动态的,是真正执行的单元,执行的过程。其实我们平时看到的进程,是线程在执行着,因为线程是作为进程的一个单元存在的。

2)同样作为基本的执行单元,线程是划分得比进程更小的执行单位。

3)每个进程都有一段专用的内存区域。与此相反,线程却共享内存单元(包括代码和数据),通过共享的内存单元来实现数据交换、实时通信与必要的同步操作。

1、创建线程的方式:

创建方式一:继承Thread

    1:定义一个类继承Thread

    2:覆盖Thread中的run方法(将线程运行的代码放入run方法中)。

    3:直接创建Thread的子类对象

    4:调用start方法(内部调用了线程的任务(run方法));作用:启动线程,调用run方法

 

方式二:实现Runnable

    1:定义类实现Runnable接口

    2:覆盖Runnable接口中的run方法,将线程的任务代码封装到run中

    3:通过Thread类创建线程对象

4、并将Runnable接口的子类对象作为Thread类的构造函数参数进行传递

作为参数传递的原因是让线程对象明确要运行的run方法所属的对象。

区别:

       继承方式:线程代码放在Thread子类的run方法中

       实现方式:线程存放在接口的子类run方法中;避免了单继承的局限性,建议使用。

2、线程状态:

新建:start()

临时状态:具备cpu的执行资格,但是无执行权

运行状态:具备CPU的执行权,可执行

冻结状态:通过sleep或者wait使线程不具备执行资格,需要notify唤醒,并处于临时状态。

消亡状态:run方法结束或者中断了线程,使得线程死亡。

3、多线程安全问题:

多个线程共享同一数据,当某一线程执行多条语句时,其他线程也执行进来,导致数据在某一语句上被多次修改,执行到下一语句时,导致错误数据的产生。

因素:多个线程操作共享数据;多条语句操作同一数据

解决:

       原理:某一时间只让某一线程执行完操作共享数据的所有语句。

       办法:使用锁机制:synchronizedlock对象

4、线程的同步:

当两个或两个以上的线程需要共享资源,他们需要某种方法来确定资源在某一刻仅被一个线程占用,达到此目的的过程叫做同步(synchronization)。

同步代码块:synchronized(对象){},将需要同步的代码放在大括号中,括号中的对象即为锁。

同步函数:放于函数上,修饰符之后,返回类型之前。

5waitsleep的区别:(执行权和锁区分)

wait:可指定等待的时间,不指定须由notifynotifyAll唤醒。

       线程会释放执行权,且释放锁。

sleep:必须制定睡眠的时间,时间到了自动处于临时(阻塞)状态。

       即使睡眠了,仍持有锁,不会释放执行权。

 

Android 的进程与线程:

1、进程的生命周期:

1)、进程的创建及回收:

       进程是被系统创建的,当内存不足的时候,又会被系统回收

2)、进程的级别:

Foreground Process       前台进程

Visible Process              可视进程

Service Process             服务进程:可以提高级别的

Background Process       后台进程

Empty Process              空进程(无组件启动,做进程缓存使用,恢复速度快)

 

 

☆  Android技能

  • 熟练掌握Android四大组件,常用的布局文件,自定义控件等

Android中4大组件是:ContentProvider、Activity、BroadcastReceiver和Service

清单文件:

1、所有的应用程序必须要有清单文件

在manifest节点下需要声明当前应用程序的包名

2、包名:声明包的名字,必须唯一

       如果两个应用程序的包名和签名都相同,后安装的会覆盖先安装的

3、声明的程序的组件(4大组件)

       其中比较特殊的是广播接收者,可以不在清单文件中配置,可以通过代码进行注册

4、声明程序需要的权限:保护用户的隐私

5、可以控制服务在单独的进程中的,四大组件都可以配置这个属性process

在组件节点配置process:

       如:android:process="xxx.ooo.xxx"

比如说:处理图片的时候,会很耗内存,就需要在单独的新的进程中,可以减少内存溢出的几率

 

 

一、ContentProvider 内容提供者

1、特点

①、可以将应用中的数据对外进行共享;

②、数据访问方式统一,不必针对不同数据类型采取不同的访问策略;

③、内容提供者将数据封装,只暴露出我们希望提供给其他程序的数据(这点有点类似Javabeans);

④、内容提供者中数据更改可被监听;

 

2、创建内容提供者

  • 定义类继承ContentProvider,根据需要重写其内容方法(6个方法):
  1. onCreate()                                    创建内容提供者时,会调用这个方法,完成一些初始化操作;
  2. crud相应的4个方法              用于对外提供CRUD操作;
  3. getType()                                      返回当前Url所代表数据的MIME类型:

返回的是单条记录:以vnd.android.cursor.item/ 开头,如:vnd.android.cursor.item/person

返回的是多条记录:以vnd.android.cursor.dir/ 开头,如:vnd.android.cursor.dir/person

  • 在清单文件的<application>节点下进行配置,<provider>标签中需要指定name、authorities、exported属性
    1. name:             为全类名;
    2. authorities:   是访问Provider时的路径,要唯一;
    3. exported:      用于指示该服务是否能够被其他应用程序组件调用或跟它交互
  • URI代表要操作的数据,由scheme、authorites、path三部分组成:
  1. content://com.itheima.sqlite.provider/person
  2. scheme:         固定为content,代表访问内容提供者;
  3. authorites:    <provider>节点中的authorites属性;
  4. path:               程序定义的路径,可根据业务逻辑定义;
  • 操作 URI的UriMather与ContentUris工具类:

       当程序调用CRUD方法时会传入Uri

  1. UriMatcher:表示URI匹配器,可用于添加Uri匹配模式,与匹配Uri(见下代码);
  1. ContentUris:用于操作Uri路径后面的ID部分,2个重要的方法:
  1. withAppendedId(uri, id)  为路径加上ID部分;
  2. parseId(uri) 用于从路径中获取ID部分;

 

示例代码(内容提供者类):

public class HeimaProvider extends ContentProvider {

       private static final int PERSON = 1;                   // 匹配码

       private static final int STUDENT = 2;                // 匹配码

       private static final int PERSON_ID = 3;             // 匹配码

       private MyHelper helper;

       /** Uri匹配器 */

       private UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

       @Override

       public boolean onCreate() {

              System.out.println("onCreate...");

              helper = new MyHelper(getContext());

              // == 添加 uri 匹配模式, 设置匹配码(参数3) Uri如果匹配就会返回相应的匹配码 ==

              uriMatcher.addURI("com.itheima.sqlite.provider", "person", PERSON);                                       

              uriMatcher.addURI("com.itheima.sqlite.provider", "#", PERSON_ID);                          // #表示匹配数字,*表示匹配文本

              uriMatcher.addURI("com.itheima.sqlite.provider", "student", STUDENT);

              return true;

       }

       @Override

       public Uri insert(Uri uri, ContentValues values) {

              SQLiteDatabase db = helper.getWritableDatabase();

              switch (uriMatcher.match(uri)) {                                      // 匹配uri

              case PERSON:

                     long id = db.insert("person", "id", values);

                     db.close();

                     return ContentUris.withAppendedId(uri, id);                     // 在原uri上拼上id,生成新的uri并返回;  

              case STUDENT:

                     long insert = db.insert("student", "id", values);

                     System.out.println("数据文件中,没有student表,也不会报错");

                     db.close();

                     return ContentUris.withAppendedId(uri, insert);         // 为路径上,加上ID

              default:

                     throw new IllegalArgumentException(String.format("Uri:%s 不是合法的uri地址", uri));

              }

       }

       @Override

       public int delete(Uri uri, String selection, String[] selectionArgs) {

              SQLiteDatabase db = helper.getWritableDatabase();

              switch (uriMatcher.match(uri)) {                                             // 匹配uri

              case PERSON_ID:

                     long parseId = ContentUris.parseId(uri);                           // 获取传过来的ID值

                     selection = "id=?";                                                          // 设置查询条件

                     selectionArgs = new String[] { parseId + "" };                  // 查询条件值

              case PERSON:

                     int delete = db.delete("person", selection, selectionArgs);

                     db.close();

                     return delete;

              default:

                     throw new IllegalArgumentException(String.format("Uri:%s 不是合法的uri地址", uri));

              }

       }

       @Override

       public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {

              SQLiteDatabase db = helper.getWritableDatabase();

              switch (uriMatcher.match(uri)) {

              case PERSON_ID:

                     long parseId = ContentUris.parseId(uri);                           // 获取传过来的ID值

                     selection = "id=?";                                                          // 设置查询条件

                     selectionArgs = new String[] { parseId + "" };                  // 查询条件值

              case PERSON:

                     int update = db.update("person", values, selection, selectionArgs);

                     db.close();

                     return update;

              default:

                     throw new IllegalArgumentException(String.format("Uri:%s 不是合法的uri地址", uri));

              }

       }

       @Override

       public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {

              SQLiteDatabase db = helper.getWritableDatabase();

              switch (uriMatcher.match(uri)) {

              case PERSON_ID:

                     // == 根据ID查询 ==

                     long parseId = ContentUris.parseId(uri);                           // 获取传过来的ID值

                     selection = "id=?";                                                          // 设置查询条件

                     selectionArgs = new String[] { parseId + "" };                  // 查询条件值

              case PERSON:

                     Cursor cursor = db.query("person", projection, selection, selectionArgs, null, null, sortOrder);

                     // == 注意:此处的 db与cursor不能关闭 ==

                     return cursor;

              default:

                     throw new IllegalArgumentException(String.format("Uri:%s 不是合法的uri地址", uri));

              }

       }

       // 返回传入URI的类型,可用于测试URI是否正确

       @Override

       public String getType(Uri uri) {

              switch (uriMatcher.match(uri)) {

              case PERSON_ID:

                     return "vnd.android.cursor.item/person";             // 表示单条person记录

              case PERSON:

                     return "vnd.android.cursor.dir/person";        // 表单多个person记录

              default:

                     return null;

              }

       }

}

清单中的配置:

 <provider

     android:exported="true"

     android:name="com.itheima.sqlite.provider.HeimaProvider"

     android:authorities="com.itheima.sqlite.provider" />

authorities 可以配置成如下形式(系统联系人的):

       android:authorities="contacts;com.android.contacts"

“;” 表示的是可使用 contacts, 与 com.android.contacts

 

3、内容解析者ContentResolver

  • 通过Context获得ContentResolver内容访问者对象(内容提供者的解析器对象);
  • 调用ContentResolver对象的方法即可访问内容提供者

测试类代码:

public class HeimaProviderTest extends AndroidTestCase {

       /** 测试添加数据 */

       public void testInsert() {

              ContentResolver resolver = this.getContext().getContentResolver();

              Uri uri = Uri.parse("content://com.itheima.sqlite.provider/person");

              ContentValues values = new ContentValues();

              values.put("name", "小翼");

              values.put("balance", 13000);

              Uri insert = resolver.insert(uri, values);               // 获取返回的uri,如:content://com.itheima.sqlite.provider/7

              System.out.println(insert);

       }

       /** 测试删除 */

       public void testRemove() {

              ContentResolver resolver = this.getContext().getContentResolver();

              Uri uri = Uri.parse("content://com.itheima.sqlite.provider/person");

              int count = resolver.delete(uri, "id=?", new String[] { 3 + "" });

              System.out.println("删除了" + count + "行");

       }

       /** 测试更新 */

       public void testUpdate() {

              ContentResolver resolver = this.getContext().getContentResolver();

              Uri uri = Uri.parse("content://com.itheima.sqlite.provider/person");

              ContentValues values = new ContentValues();

              values.put("name", "小赵 update");

              values.put("balance", 56789);

              int update = resolver.update(uri, values, "id=?", new String[] { 6 + "" });

              System.out.println("更新了" + update + "行");

       }

       /** 测试查询 */

       public void testQueryOne() {

              ContentResolver resolver = this.getContext().getContentResolver();

              Uri uri = Uri.parse("content://com.itheima.sqlite.provider/person");

              Cursor c = resolver.query(uri, new String[] { "name", "balance" }, "id=?", new String[] { 101 + "" }, null);

              if (c.moveToNext()) {

                     System.out.print(c.getString(0));

                     System.out.println(" " + c.getInt(1));

              }

              c.close();

       }

       /**测试查询全部 */

       public void testQueryAll() {

              ContentResolver resolver = this.getContext().getContentResolver();

              Uri uri = Uri.parse("content://com.itheima.sqlite.provider/person");

              Cursor c = resolver.query(uri, new String[] { "id", "name", "balance" }, null, null, "name desc");

              while (c.moveToNext()) {

                     System.out.println(c.getInt(0) + ", " + c.getString(1) + ", " + c.getInt(2));

              }

              c.close();

       }

       /** 测试查询一条 */

       public void testQueryOneWithUriId() {

              ContentResolver resolver = this.getContext().getContentResolver();

              Uri uri = Uri.parse("content://com.itheima.sqlite.provider/3");          // 查询ID为3的记录

              Cursor c = resolver.query(uri, new String[] { "id", "name", "balance" }, null, null, null);

              if (c.moveToNext()) {

                     System.out.println(c.getInt(0) + ", " + c.getString(1) + ", " + c.getInt(2));

              }

              c.close();

       }

       /** 测试获取内容提供者的返回类型 */

       public void testGetType() {

              ContentResolver resolver = this.getContext().getContentResolver();

              System.out.println(resolver.getType(Uri.parse("content://com.itheima.sqlite.provider/2")));

              System.out.println(resolver.getType(Uri.parse("content://com.itheima.sqlite.provider/person")));

       }

}

 

4、监听内容提供者的数据变化

  • 在内容提供者中可以通知其他程序数据发生变化

通过Context的getContentResolver()方法获取ContentResolver

调用其notifyChange()方法发送数据修改通知,发送到系统的公共内存(消息信箱中)

  • 在其他程序中可以通过ContentObserver监听数据变化

通过Context的getContentResolver()方法获取ContentResolver

调用其registerContentObserver()方法指定对某个Uri注册ContentObserver

自定义ContentObserver,重写onChange()方法获取数据

示例代码(发通知部分):

       public int delete(Uri uri, String selection, String[] selectionArgs) {

              SQLiteDatabase db = helper.getWritableDatabase();

                     int delete = db.delete("person", selection, selectionArgs);

                     // == 通过内容访问者对象ContentResolve 发通知给所有的Observer ==

                     getContext().getContentResolver().notifyChange(uri, null);      

                     db.close();

                     return delete;

              }

       }

监听部分:

       // 注册内容观察者事件

       private void initRegisterContentObserver() {

              Uri uri = Uri.parse("content://com.itheima.sqlite.provider");      // 监听的URI

              // == 第2个参数:true表示监听的uri的后代都可以监听到 ==

              getContentResolver().registerContentObserver(uri, true, new ContentObserver(new Handler()) {

                     public void onChange(boolean selfChange) {                    // 接到通知就执行                                                      

                            personList = personDao.queryAll();                                                                                                

                            ((BaseAdapter) personListView.getAdapter()).notifyDataSetChanged();

                     }

              });

       }

 

5、区别Provider/Resolver/Observer

1)ContentProvider:内容提供者

       把一个应用程序的私有数据(如数据库)信息暴露给别的应用程序,让别的应用程序可以访问;

       在数据库中有对应的增删改查的方法,如果要让别的应用程序访问,需要有一个路径uri:

通过content:// 路径对外暴露,uri写法:content://主机名/表名

2)ContentResolver:内容解析者

       根据内容提供者的路径,对数据进行操作(crud);

3)ContentObserver:内容观察者

       可以理解成android系统包装好的回调,数据发送变化时,会执行回调中的方法;

       ContentResolver发送通知,ContentObserver监听通知;

当A的数据发生变化的时候,A就会显示的通知一个内容观察者,不指定观察者,就会发消息给一个路径

 

二、Activity活动

描述:

1)表示用户交互的一个界面(活动),每一个activity对应一个界面

2)是所有View的容器:button,textview,imageview;我们在界面上看到的都是一个个的view

3)有个ActivityManager的管理服务类,用于维护与管理Activity的启动与销毁;

Activity启动时,会把Activity的引用放入任务栈中

4)一个应用程序可以被别的应用程序的activity开启

       此时,是将此应用程序的引用加入到了开启的那个activity的任务栈中了

5) activity是运行在自己的程序进程里面的

       在一个应用程序中,可以申请单独的进程,然此应用程序中的一个组件在新的进程中运行

6)可以在activity里面添加permission标签,调用者必须加入这个权限

       与钱打交道的界面,都不允许被其他应用程序随意打开

如果觉得那个activity比较重要,可以在清单文件中配置,防止别人随意打开,需要配置一个权限

自定义权限:

在清单文件中配置permission,创建一个新的权限

       创建后,就会在清单文件中生成这个权限了

此时,需要开启这个界面,就需要使用这个权限

Tips

       *不可使用中文文本,需要使用字符串,抽取出来

*声明之后,会在gen的目录下,多出来一个文件:Manifest的文件,系统也存在一个这样的文件

 

 

1、创建Activity

1)定义类继承自Activity类;

2)在清单文件中Application节点中声明<activity>节点;

       <activity

            android:name="com.itheima.activity.MainActivity"

            android:label="@string/app_name" >

            <!-- 程序的入口,LAUNCHER表示桌面快捷方式,进入的是此Activity -->

            <intent-filter>

                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />  <!—启动时,默认匹配 --

            </intent-filter>

        </activity>

 

2、启动Activity

通过意图(Intent)来启动一个Activity;

  1. 显示启动:

显示启动一般用于自己调用自己的情况(在当前应用找),这样的启动方式比较快速,创建Intent后指定包名和类名;

       Intent intent = new Intent(this, OtherActivity.class);

       startActivity(intent);             // 启动新的Activity

       或者:

       Intent intent = new Intent();

       intent.setClassName("com.itheima.activity", "com.itheima.activity.OtherActivity"); // 包名、全类名

       startActivity(intent);             // 启动新的Activity

2)隐式启动:

一般用于调用别人的Activity,创建Intent后指定动作和数据以及类型;

       // 电话

       Intent intent = new Intent();

       intent.setAction(Intent.ACTION_CALL);                                        // 设置动作

       intent.setData(Uri.parse("tel://123456"));                                 // 设置数据

       // 网页

       intent.setAction(Intent.ACTION_VIEW);

       intent.setData(Uri.parse("http://192.168.1.45:8080/androidWeb"));

       // 音频/视频,设置type

       intent.setAction(Intent.ACTION_VIEW);

       intent.setDataAndType(Uri.parse("file:///mnt/sdcard/daqin.mp3"), "audio/*");  // 设置数据和数据类型,将启动音频播放器(vedio)

3)为隐式启动配置意图过滤器:

    1. 显式意图是指在创建意图时指定了组件,而隐式意图则不指定组件,通过动作、类型、数据匹配对应的组件;
    2. 在清单文件中定义<activity>时需要定义<intent-filter>才能被隐式意图启动;
    3. <intent-filter>中至少配置一个<action>和一个<category>,否则无法被启动;
    4. Intent对象中设置的actioncategorydata在<intent-filter>必须全部包含Activity才能启动;
    5. <intent-filter>中的<action>、<category>、<data>都可以配置多个,Intent对象中不用全部匹配,每样匹配一个即可启动;
    6. 如果一个意图可以匹配多个Activity,Android系统会提示选择;

         <!-- 注册 Activity, lable 表示Activity的标题 -->

        <activity

            android:name="com.itheima.activity.OtherActivity"

            android:label="OtherActivity" >

            <!-- 配置隐式意图,匹配http -->

            <intent-filter>

                <action android:name="android.intent.action.VIEW" />           <!—必须,表示动作为View -->

                <data android:scheme="http" />                                             <!—http开头-->                             

                <category android:name="android.intent.category.DEFAULT" /> <!-- 必须,表示启动时,默认匹配 -->

            </intent-filter>

            <!-- 匹配tel -->

             <intent-filter>

                <action android:name="android.intent.action.CALL" />                  

                <data android:scheme="tel" />                                                                                

                <category android:name="android.intent.category.DEFAULT" /> <!-- 必须,表示启动 -->

            </intent-filter>

            <!-- 匹配 音频、视频 -->

            <intent-filter>

                <action android:name="android.intent.action.VIEW" />                   

                <data android:scheme="file" android:mimeType="audio/*" />   <!—文件协议l类型 -->                                                                         

                <data android:scheme="file" android:mimeType="video/*" />                

                <category android:name="android.intent.category.DEFAULT" /> <!-- 必须,表示启动 -->                                                       

            </intent-filter>

     </activity>

 

3、启动时传递数据

可通过意图Intent对象实现Activity之间的数据传递;

使用Intent.putExtra()方法装入一些数据, 被启动的Activity可在 onCreate方法中getIntent()获取;

可传输的数据类型: a.基本数据类型(数组),  b. String(数组),  c. Bundle(Map),  d. Serializable(Bean), e.Parcelable(放在内存一个共享空间里);

基本类型:

       Intent intent = new Intent(this, OtherActivity.class);

       intent.putExtra("name", "张飞");         // 携带数据

       intent.putExtra("age", 12);

       startActivity(intent);

一捆数据:

       Intent intent = new Intent(this, OtherActivity.class);

       Bundle b1 = new Bundle();

       b1.putString("name", "赵云");

       b1.putInt("age", 25);

       Bundle b2 = new Bundle();

       b2.putString("name", "关羽");

       b2.putInt("age", 44);

       intent.putExtra("b1", b1);

       intent.putExtra("b2", b2);

序列化对象(须实现序列化接口):

       Intent intent = new Intent(this, OtherActivity.class);

       Person p = new Person("张辽", 44);

       intent.putExtra("p", p);

接收数据:

       在OtherActivity 的onCreate()方法,通过 getIntent().get 相关的数据的方法来获取数据;

 

4、关闭时返回数据

基本流程:

  1. 使用startActivityForResult(Intent intent, int requestCode) 方法打开Activity;
  2. 重写onActivityResult(int requestCode, int resultCode, Intent data) 方法;
  3. 新Activity中调用setResult(int resultCode, Intent data) 设置返回数据之后,关闭Activity就会调用上面的onActivityResult方法;

注意:新的Activity的启动模式不能设置成 singleTask(如果已创建,会使用以前创建的)与singleInstance(单例,单独的任务栈),

         不能被摧毁(执行不到finish方法),父Activity中的 onActivityResult方法将不会执行;

finish():表示关闭当前Activity,会调用onDestroy方法;

Activity_A:

       public void openActivityB(View v) {

              Intent intent = new Intent(this, Activity_B.class);

              Person p = new Person("张辽", 44);

              intent.putExtra("p", p);

              startActivityForResult(intent, 100);                                         // 此方法,启动新的Activity,等待返回结果, 结果一旦返回,自动执行onActivityResult()方法

       }

       protected void onActivityResult(int requestCode, int resultCode, Intent data) {

              if(data == null) {                                                           // 没有数据,不执行

                     return;

              }

              System.out.println(requestCode + ", " + resultCode);         // code 可用来区分,哪里返回的数据

              String name = data.getStringExtra("name");

              int age = data.getIntExtra("age", -1);

       }

Activity_B:

       public void close(View v) {

              // == 关闭当前Activity时,设置返回的数据 ==

              Intent intent = new Intent();

              intent.putExtra("name", "典韦");

              intent.putExtra("age", 55);

              setResult(200, intent);   

              finish();                        // 关闭,类似于点击了后退

       }

 

5、生命周期

1)Acitivity三种状态

  1. 运行:activity在最前端运行;
  2. 停止:activity不可见,完全被覆盖;
  3. 暂停:activity可见,但前端还有其他activity<>,注意:在当前Activitiiy弹出的对话框是Activity的一部分,弹出时,不会执行onPause方法;

2)生命周期相关的方法(都是系统自动调用,都以 on 开头):

  1. onCreate:      创建时调用,或者程序在暂停、停止状态下被杀死之后重新打开时也会调用;
  2. onStart:                   onCreate之后或者从停止状态恢复时调用;                                                            
  3. onResume:   onStart之后或者从暂停状态恢复时调用,从停止状态恢复时由于调用onStart,也会调用onResume(界面获得焦点);
  4. onPause:       进入暂停、停止状态,或者销毁时会调用(界面失去焦点);
  5. onStop:          进入停止状态,或者销毁时会调用;
  6. onDestroy:    销毁时调用;
  7. onRestart:    从停止状态恢复时调用;

3)生命周期图解:

 

       应用启动时,执行onCreate onStart onResume,退出时执行:onPause onStop onDestroy;

 

6、横竖屏切换与信息的保存恢复

切换横竖屏时,会自动查找layout-port 、layout-land中的布局文件,默认情况下,

切换时,将执行摧毁onPause onStop onDestroy,再重置加载新的布局onCreate onStart onResume

切换时如果要保存数据, 可以重写: onSaveInstanceState();

恢复数据时, 重写: onRestoreInstanceState();

 

è固定横屏或竖屏:                                 android:screenOrientation="landscape"

è横竖屏切换, 不摧毁界面(程序继续执行) android:configChanges="orientation|keyboardHidden|screenSize"

保存信息状态的相关方法:

  1. onSaveInstanceState:    

在Activity被动的摧毁或停止的时候调用(如横竖屏切换,来电),用于保存运行数据,可以将数据存在在Bundle中;

  1. onRestoreInstanceState:

该方法在Activity被重新绘制的时候调用,例如改变屏幕方向,onSavedInstanceState可onSaveInstanceState保存的数据

7、启动模式

1)任务栈的概念

问:一个手机里面有多少个任务栈?

答:一般情况下,有多少个应用正在运行,就对应开启多少个任务栈;  

       一般情况下,每开启一个应用程序就会创建一个与之对应的任务栈;

       二般情况下,如launchMode为 singleInstance,就创建自己单独的任务栈;

2)任务栈的作用:

它是存放Activity的引用的,Activity不同的启动模式,对应不同的任务栈的存放;

可通过getTaskId()来获取任务栈的ID,如果前面的任务栈已经清空,新开的任务栈ID+1,是自动增长的;

3)启动模式:

在AndroidManifest.xml中的<activity>标签中可以配置android:launchMode属性,用来控制Actvity的启动模式;

在Android系统中我们创建的Acitivity是以的形式呈现的:

①、standard:默认的,每次调用startActivity()启动时都会创建一个新的Activity放在栈顶;

②、singleTop:启动Activity时,指定Activity不在任务栈栈顶就创建,如在栈顶,则不会创建,会调用onNewInstance(),复用已经存在的实例

③、singleTask:在任务栈里面只允许一个实例,如果启动的Activity不存在就创建,如果存在直接跳转到指定的Activity所在位置,

                     如:栈内有ABCD,D想创建A, 即A上的BCD相应的Activity将移除

④、singleInstance:(单例)开启一个新的任务栈来存放这个Activity的实例在整个手机操作系统里面只有一个该任务栈的实例存在,此模式开启的Activity是运行在自己单独的任务栈中的

 

4)应用程序、进程、任务栈的区别

①、应用程序:

四大组件的集合

在清单文件中都放在application节点下

对于终端用户而言,会将其理解为activity

②、进程:

操作系统分配的独立的内存空间,一般情况下,一个应用程序会对应一个进程,特殊情况下,会有多个进程

一个应用程序会对应一个或多个进程

③、任务栈:task stack(back stack)后退栈

       记录用户的操作步骤,维护用户的操作体验,

       专门针对于activity而言的,只用于activity

       一般使用standard,其他情况用别的

 

5)启动模式的演示

1、创建两个activity,布局中设置两个按钮,分别开启两个activity

第一、standard启动模式的:开启几个就会在任务栈中存在几个任务

01和02都是存在于一个任务栈中的

 

第二、在清单文件中将02的启动模式改为singletop,

此时02处于栈顶,就只会创建一个02的任务,再开启02,也不会创建新的

 

第三、将02的启动模式改为singletask

       如果02上面有其他任务栈,就会将其他的清除掉,利用这个已经创建的02

       当开启02的时候,即先将01清除,然后利用下面的02

 

第四、将02的启动模式改为singleinstance

       可以通过打印任务栈的id(调用getTaskId()方法)得知,两个activity不在同一个任务栈中

若先开启三个01,在开启02,任务栈如图:

 

再开启01,任务栈的示意图如下:

 

此时按返回键,会先一层一层清空01,最后再清空02

空进程:任务栈清空,意味着程序退出了,但进程留着,这个就是空进程,容易被系统回收;

8、内存管理

       Android系统在运行多个进程时,如果系统资源不足,会强制结束一些进程,优先选择哪个进程来结束是有优先级的。

会按照以下顺序杀死:

①、空:  进程中没有任何组件;

②、后台:进程中只有停止状态的Activity;

③、服务:进程中有正在运行的服务;

④、可见:进程中有一个暂停状态的Activity;

⑤、前台:进程中正在运行一个Activity;

Activity在退出的时候进程不会销毁, 会保留一个空进程方便以后启动. 但在内存不足时进程会被销毁;

Activity中不要在Activity做耗时的操作, 因为Activity切换到后台之后(Activity停止了), 内存不足时, 也容易被销毁;

 

三、BroadcastReceiver 广播接收者

系统的一些事件,比如来电,来短信,等等,会发广播;可监听这些广播,并进行一些处理;

Android3.2以后,为了安全起见,对于刚安装的应用,需要通过点击进入应用(界面,用户确认之后),接收者才能起作用;

以后即使没有启动其界面,也能接收到广播;

1、定义广播接收者

1)定义类继承BroadcastReceiver,重写onReceive方法

2)清单文件中声明<receiver>,需要在其中配置<intent-filter>指定接收广播的类型;

3)当接收到匹配广播之后就会执行onReceive方法;

4)有序广播中,如果要控制多个接收者之间的顺序,可在<intent-filter>配置priority属性,系统默认为0,值越大,优先级越高;

5)BroadcastReceiver除了在清单文件中声明,也可以在代码中声明,使用registerReceiver方法注册Receiver;

 <!-- 配置广播接收者,监听播出电话 -->

     <receiver android:name="com.itheima.ipdialer.CallReceiver" >

          <intent-filter>

               <action android:name="android.intent.action.NEW_OUTGOING_CALL" />

           </intent-filter>

 </receiver>

 

2、广播的分类

1)普通广播:

普通广播不可中断,不能互相传递数据;

2)有序广播:

广播可中断,通过调用abortBroadcast()方法;

接收者之间可以传递数据;

 

3、广播接收者的注册方式

4大组件中,只有广播接收者是一个非常特殊的组件,其他3大组件都需要在清单文件中注册;

广播接收者,有2中注册方式:清单文件与代码方式,区别:

1)清单文件注册广播接收者,只要应用程序被部署到手机上,就立刻生效,不管进程是否处于运行状态;

2)代码方式,如果代码运行了,广播接收者才生效,如果代码运行结束,广播接收者,就失效;

这属于动态注册广播,临时用一下,用的时候,register,不用时unregister;

代码方式示例:

       // 广播接收者

       private class InnerReceiver extends BroadcastReceiver {

              @Override

              public void onReceive(Context context, Intent intent) {

                     String phone = getResultData();

                     String address = AddressDao.queryAddress(getApplicationContext(), phone);

                     showAddress(address);

              }

       }

       // 注册示例代码

       public void onCreate() {

              // == 服务启动时,注册广播接收者 ==

              innerReceiver = new InnerReceiver();

              // 指定意图过滤器

              IntentFilter filter = new IntentFilter(Intent.ACTION_NEW_OUTGOING_CALL);

              this.registerReceiver(innerReceiver, filter);

       }

       // 销毁  

       public void onDestroy() {

              // == 服务停止时,移除广播接收者 ==

              this.unregisterReceiver(innerReceiver);

              innerReceiver = null;

              super.onDestroy();

       }

 

4、发送广播

1)发送普通广播

①、使用sendBroadcast()方法可发送普通广播;

②、通过Intent确定广播类型,可携带数据,所有接收者都可以接收到数据,数据不能被修改,不会中断;

接收者无序(试验测试,是按照安装顺序来接收的);

③、广播时,可设置接收者权限,仅当接收者含有权限才能接收;

④、接收者的<receiver>也可设置发送方权限,只接受含有相应权限应用的广播;

发送者:

       Intent intent = new Intent("com.itheima.broadcast.TEST");       // 指定动作;接收者,需要配置 intent filter才能接受到此广播

       intent.setFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);  // 包含未启动的过的应用(也可以收到广播),默认为不包含

       intent.putExtra("data", "这是来着广播发送者发来的贺电");           // 广播发送者intent中的数据,接收者,修改不了

       sendBroadcast(intent, null);                                                    // 发送无序广播,异步获取数据,不可中断,接收者之间不可传数据

 

接收者:

       public class AReceiver extends BroadcastReceiver {

              public void onReceive(Context context, Intent intent) {

                     System.out.println("AReceiver: " + intent.getStringExtra("data"));

              }

       }

      <receiver android:name="com.itheima.a.AReceiver">

            <intent-filter android:priority="2" >

                <action android:name="com.itheima.broadcast.TEST" />   <!—接收指定动作的广播 -->

            </in

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值