Android初级工程师面试题答案——Android题型

面试题答案(按照以上往下的顺序依次排序):

Android类型题目:

1.Android有几种布局?

参考网站:https://blog.csdn.net/shenggaofei/article/details/52450668

1.LinearLayout(线性布局) 

LinearLayout容器中的组件一个挨一个排列,通过控制android:orientation属性,可控制各组件是横向排列还是纵向排列。

LinearLayout的常用XML属性及相关方法:

XML属性 相关方法 说明
android:gravity setGravity(int) 设置布局管理器内组件的对齐方式
android:orientation setOrientation(int) 设置布局管理器内组件的排列方式,可以设置为horizontal、vertical两个值之一


其中,gravity属性支持top, left, right, center_vertical, fill_vertical, center_horizontal, fill_horizontal, center, fill, clip_vertical, clip_horizontal。也可以同时指定多种对齐方式的组合。
 

2.RelativeLayout(相对布局)

RelativeLayout的XML属性及相关方法说明:

XML属性 相关方法 说明
android:gravity setGravity(int) 设置布局管理器内组件的对齐方式
android:ignoreGravity setIgnoreGravity(int) 设置哪个组件不受gravity属性的影响

为了控制该布局容器的各子组件的布局分布,RelativeLayout提供了一个内部类:RelativeLayout.LayoutParams。

RelativeLayout.LayoutParams里只能设为boolean的XML属性(根据父容器定位):

XML属性 说明
android:layout_centerHorizontal 设置该组件是否位于父布局容器的水平居中位置
android:layout_centerVertical 设置该组件是否位于父布局容器的垂直居中位置
android:layout_centerInParent 设置该组件是否位于父布局容器的居中位置
android:layout_alignParentLeft 设置该组件是否对齐于父布局容器的左边界
android:layout_alignParentRight 设置该组件是否对齐于父布局容器的右边界
android:layout_alignParentTop 设置该组件是否对齐于父布局容器的上边界
android:layout_alignParentBottom 设置该组件是否对齐于父布局容器的下边界

RelativeLayout.LayoutParams里属性值为其他UI组件ID的XML属性(根据兄弟组件定位):

XML属性 说明
android:layout_toLeftOf 控制该组件位于对应ID组件的左侧
android:layout_toRightOf 控制该组件位于对应ID组件的右侧
android:layout_above 控制该组件位于对应ID组件的上方
android:layout_below 控制该组件位于对应ID组件的下方
android:layout_alignLeft 控制该组件对齐ID组件的左边界
android:layout_alignRight 控制该组件对齐ID组件的右边界
android:layout_alignTop 控制该组件对齐ID组件的上边界
android:layout_alignBottom 控制该组件对齐ID组件的下边界

3.FrameLayout(帧布局)

FrameLayout直接继承自ViewGroup组件。帧布局为每个加入其中的组件创建一个空白的区域(称为一帧),每个子组件占据一帧,这些帧会根据gravity属性执行自动对齐。

FrameLayout的常用XM了属性及方法:

XML属性 相关方法 说明
android:foreground setForeground(Drawable) 设置该帧布局容器的前景图像
android:foregroundGravity setForeGroundGraity(int) 定义绘制前景图像的gravity属性

4.TableLayout(表格布局)

TableLayout继承自Linearout,本质上仍然是线性布局管理器。表格布局采用行、列的形式来管理UI组件,并不需要明确地声明包含多少行、多少列,而是通过添加TableRow、其他组件来控制表格的行数和列数。

每向TableLayout中添加一个TableRow就代表一行;

每向TableRow中添加一个一个子组件就表示一列;

如果直接向TableLayout添加组件,那么该组件将直接占用一行;

在表格布局中,可以为单元格设置如下三种行为方式:

 

TableLayout的常用XML属性及方法 :

  • Shrinkable:该列的所有单元格的宽度可以被收缩,以保证该表格能适应父容器的宽度;
  • Strentchable:该列所有单元格的宽度可以被拉伸,以保证组件能完全填满表格空余空间;
  • Collapsed:如果该列被设置为Collapsed,那么该列的所有单元格会被隐藏;
XML属性 相关方法 说明
android:collapseColumns setColumns(int, boolean) 设置需要被隐藏的列的序号,多个序号间用逗号分隔
android:shrinkColumns setShrinkAllColumns(boolean) 设置需要被收缩的列的序号
android:stretchColumns setStretchAllColumns(boolean) 设置允许被拉伸的列的序号

5.GridLayout(网格布局)

GridLayout是Android4.0增加的网格布局控件,与之前的TableLayout有些相似,它把整个容器划分为rows × columns个网格,每个网格可以放置一个组件。性能及功能都要比tablelayout好,比如GridLayout布局中的单元格可以跨越多行,而tablelayout则不行,此外,其渲染速度也比tablelayout要快。

GridLayout提供了setRowCount(int)和setColumnCount(int)方法来控制该网格的行和列的数量。

GridLayout常用的XML属性和方法说明:

XML属性 相关方法 说明
android:rowCount setRowCount(int) 最大行数
android:columnCount setColumnCount(int) 最大列数
android:alignmentMode setAlignmentMode(int) 设置该布局管理器采用的对齐模式:alignBounds:对齐子视图边界 alignMargins :对齐子视距内容,默认值
android:columnOrderPreserved setColumnOrderPreserved(boolean) 使列边界显示的顺序和列索引的顺序相同,默认是true
android:rowOrderPreserved setRowOrderPreserved(boolean) 使行边界显示的顺序和行索引的顺序相同,默认是true
android:useDefaultMargins setUseDefaultMargins(boolean)

设置该布局管理器是否使用默认的页边距

没有指定视图的布局参数时使用默认的边距,默认值是false

为了控制GridLayout布局容器中各子组件的布局分布,GridLayout提供了一个内部类:GridLayout.LayoutParams,来控制Gridlayout布局容器中子组件的布局分布。

GridLayout.LayoutParams常用的XML属性和方法说明(item属性):

XML属性 说明
android:layout_row 指定该单元格在第几行显示
android:layout_column 指定该单元格在第几列显示
android:layout_rowSpan 指定该单元格占据的行数
android:layout_columnSpan 指定该单元格占据的列数
android:layout_gravity

指定该单元格在容器中的位置

(可以设置填充或剪裁)

android:layout_rowWeight (API21加入,21以下需导包) 行权重
android:layout_columnWeight (API21加入,21以下需导包) 列权重

 android:layout_gravity属性(填充,裁剪):

android:layout_gravity 作用
center 不改变元素的大小,仅居中
center_horizontal 不改变大小,水平居中
center_vertical 不改变大小,垂直居中
top 不改变大小,置于顶部
left 不改变大小,置于左边
bottom 不改变大小,置于底部
right 不改变大小,置于右边
start 不改变大小,根据系统语言,置于开始位置
end 不改变大小,置于结尾
fill 拉伸元素控件,填满其应该所占的格子
fill_vertical 仅垂直方向上拉伸填充
fill_horizontal 仅水平方向上拉伸填充
clip_vertical 垂直方向上裁剪元素,仅当元素大小超过格子的空间时
clip_horizontal 水平方向上裁剪元素,仅当元素大小超过格子的空间时

6.AbsoluteLayout(绝对布局)(该布局已经过时,不应该使用或少使用)

即Android不提供任何布局控制,而是由开发人员自己通过X坐标、Y坐标来控制组件的位置。每个组件都可指定如下两个XML属性:

  • layour_x;
  • layout_y;

绝对布局已经过时,不应使用或少使用。

界面布局类型的选择和性能优化
首先得明确,界面布局类型的嵌套越多越深越复杂,会使布局实例化变慢,使Activity的展开时间延长。建议尽量减少布局嵌套,尽量减少创建View对象的数量。

1 . 减少布局层次,可考虑用RelativeLayout来代替LinearLayout。通过Relative的相对其他元素的位置来布局,可减少块状嵌套;

2 . 另一种减少布局层次的技巧是使用  标签来合并布局;

3 . 重用布局。Android支持在XML中使用  标签,  通过指定android:layout属性来指定要包含的另一个XML布局。

如:


2.layout_gravity和gravity的区别?

参考网站:https://zhidao.baidu.com/question/2117066944908238827.html

他们的区别在于:

android:layout_gravity:设置控件本身相对于父控件的显示位置。

android:gravity:设置的是控件自身上面的内容位置。

3.描述一下Android的四大组件。

参考网站:https://www.jianshu.com/p/6c63d85154cc   https://blog.csdn.net/xchaha/article/details/80398620

1.Activity(活动):

Activity是一个应用组件,用户可与其提供的屏幕进行交互,执行拨打电话、拍摄、发送电子邮件或查看地图等操作。

2.BrodcastReceiver(广播):

BrodcastReceiver是一种广泛运用在应用程序之间进行传输信息的机制,BrodcastReceiver是对发送出来的广播进行过滤接收并响应的一类组件,自身并不实现图形用户界面,但是他收到某个通知后,BordcastReceiver可以启动Activity作为响应,或者通过notifyicationManager提醒用户,或者启动Service等。

3.ContentProvider(内容提供者):

ContentProvider使一个应用程序的指定数据集提供给其他应用程序,允许应用程序将自己内部的数据共享给其他应用程序。作用:提供一套接口,给其他应用程序,其他应用程序就可以进行接口的调用,从而进行数据的增删查改。

4.Service(服务):

Service是一个可以在后台执行长时间运行操作而不提供用户界面的应用组件,服务可由其他应用组件启动,而且即使用户切换到其他应用,服务仍然在后台继续运行,组件可以绑定服务进行交互,甚至是执行线程间通信。

4.请描述一下Activty生命周期。    5.OnRestart()和OnResume()的区别。

参考网站:https://blog.csdn.net/Peter__Li/article/details/89285103

Activity生命周期:onCreate >> onStart >> onResume >> onPause >> onStop || onRestart >> onDestroy

  • 完整生存期:活动在onCreate()和onDestroy()方法之间所经历的
  • 可见生存期:活动在onStart()和onStop()方法之间所经历的
  • 交互活动期:活动在onResume()和onPause()方法之间所经历的

    如图:

  Android初级工程师面试题答案——Android题型_第1张图片

 OnRestart()和OnResume()的区别:

onRestart是可见生存期,在调用该方法之后活动由不可见到可见状态。

onResume是活动交互期,在调用该方法之后活动由不可交互到可交互状态。

6.Activity和Fragment的生命周期?   7.怎么理解Activity和Fragment(碎片)的关系?

参考网站:https://www.jianshu.com/p/0b1262be921b   https://www.jianshu.com/p/6fb2936d2d3c

Activity生命周期: 

onCreate(创建) >> onStart(开始)>> onResume(继续) >> onPause(暂停) >> onStop (停止)|| onRestart(重新开始) >> onDestroy(销毁)

onCreate:创建时调用

onStart:可见时调用

onResume:获取焦点时调用

onPause:失去焦点时调用

onStop:不可见时调用

onRestart:重启时调用

onDestroy:销毁时调用

 


 

Fragment生命周期:

onAttach(关联) >> onCreate(创建) >> onCreateView(创建视图) >> onActivityCreated(已创建) >>  onStart (开始)

>> onResume(继续) >> onPause(暂停) >> onStop(停止) >> onDestroyView(销毁视图) >> onDestroy(销毁)

>> onDetach(分离)

onAtteah:与宿主Activity创建关联时调用

onCreate:创建时调用

onCreateView:加载Fragment布局时调用

onActivityCreated:宿主Activity创建完毕时调用

onStart:可见时调用

onResume:获取焦点时调用

onPause:失去焦点时调用

onStop:不可见时调用

onDestroyView:移除Fragment布局时调用

onDestory:销毁时调用

onDetach:与宿主Activity解除关联时调用

 


 

 Activity与Fragment的关系:

Fragment与Activity生命周期协调一致,宿主Activity会直接影响Fragment的生命周期,宿主Activity的每次声明周期回调都会引发每个Fragment类似的回调。

 

8.在manifest中可以声明哪几种Activity的启动模式,请分别列举并简单描述各自的特点。

参考网站:https://blog.csdn.net/yz_cfm/article/details/85601516   https://www.cnblogs.com/rancvl/p/5529070.html  

                  https://www.cnblogs.com/EX32/p/4623764.html

4种启动模式: standard(标准模式)  singleTop(栈顶复用模式) singleTask(栈内复用模式) singleInstance(单实例模式)

standard:

标准模式,系统默认模式。每次启动一个活动不管这个实例是否存在都会重新创建一个新的实例。这是一种典型的多实例实现,一个任务栈中可以有多个实例,每个实例也可以属于不同的任务栈。在这种模式下,谁启动了这个活动,那么这个活动就运行在启动它的那个活动所在的栈中。

Android初级工程师面试题答案——Android题型_第2张图片

     不同的Activity在同一个栈中,

     每次创建新实例的时候,新实例便会堆放到栈顶。

    Android初级工程师面试题答案——Android题型_第3张图片

      当点返回键的时候,便会从栈顶按顺序将实例移除栈。直至栈为空则退回到桌面。

Log打印(一直点跳转本身activity会一直创建新的实例):

04-23 16:24:03.044 1881-1881/? D/MainActivity1: com.example.lipeter.activitylifecycletest.MainActivity@4246d7f8
04-23 16:24:03.044 1881-1881/? D/MainActivity1: 47
04-23 16:24:12.524 1881-1881/com.example.lipeter.activitylifecycletest D/MainActivity1: com.example.lipeter.activitylifecycletest.MainActivity@424e6708
04-23 16:24:12.524 1881-1881/com.example.lipeter.activitylifecycletest D/MainActivity1: 47
04-23 16:24:14.424 1881-1881/com.example.lipeter.activitylifecycletest D/MainActivity1: com.example.lipeter.activitylifecycletest.MainActivity@424ffb48
04-23 16:24:14.424 1881-1881/com.example.lipeter.activitylifecycletest D/MainActivity1: 47
04-23 16:24:15.014 1881-1881/com.example.lipeter.activitylifecycletest D/MainActivity1: com.example.lipeter.activitylifecycletest.MainActivity@42517a38
04-23 16:24:15.014 1881-1881/com.example.lipeter.activitylifecycletest D/MainActivity1: 47
04-23 16:24:15.364 1881-1881/com.example.lipeter.activitylifecycletest D/MainActivity1: com.example.lipeter.activitylifecycletest.MainActivity@4252f460
04-23 16:24:15.364 1881-1881/com.example.lipeter.activitylifecycletest D/MainActivity1: 47
04-23 16:24:15.604 1881-1881/com.example.lipeter.activitylifecycletest D/MainActivity1: com.example.lipeter.activitylifecycletest.MainActivity@42546fb0
04-23 16:24:15.604 1881-1881/com.example.lipeter.activitylifecycletest D/MainActivity1: 47

singleTop:

栈顶复用模式。在这种模式下,如果新的活动已经位于任务栈的栈顶,那么此活动不会被重新创建,同时他的onNewIntent方法会被回调,通过此方法的参数我们可以取出当前请求的信息。如果新活动的实例已存在但不是位于栈顶,那么新活动仍然会重新创建。

Android初级工程师面试题答案——Android题型_第4张图片

     不同的Activity在同一个栈中。   

    ① 如果此时Aty1的实例在栈顶,此时再创建一个新的Aty1并不会成功。

    Android初级工程师面试题答案——Android题型_第5张图片

    ② 如果Aty_1不在栈顶,此时再创建一个Aty1的实例,会产生一个新的Aty1实例到栈顶。

    Android初级工程师面试题答案——Android题型_第6张图片

    当点返回键的时候,便会从栈顶按顺序将实例移除栈。直至栈为空则退回到桌面。

 Log打印1(一直点跳转本身activity不会一直创建新的实例,无论点多少下都只会存在一个实例):

04-23 16:46:38.874 3567-3567/com.example.lipeter.activitylifecycletest D/MainActivity1: com.example.lipeter.activitylifecycletest.MainActivity@42468b38
04-23 16:46:38.874 3567-3567/com.example.lipeter.activitylifecycletest D/MainActivity1: 56

 Log打印2(跳转SecondActivity后再跳转MianActivity会创建新的实例,这里我连续点了几次循环,再一直按返回键直到退出):

04-23 16:49:12.374 3676-3676/com.example.lipeter.activitylifecycletest D/MainActivity1: com.example.lipeter.activitylifecycletest.MainActivity@424694f8
04-23 16:49:12.374 3676-3676/com.example.lipeter.activitylifecycletest D/MainActivity1: 57
04-23 16:49:16.284 3676-3676/com.example.lipeter.activitylifecycletest D/SecondActivity1: com.example.lipeter.activitylifecycletest.SecondActivity@424e1f00
04-23 16:49:16.284 3676-3676/com.example.lipeter.activitylifecycletest D/SecondActivity1: 57
04-23 16:49:18.084 3676-3676/com.example.lipeter.activitylifecycletest D/MainActivity1: com.example.lipeter.activitylifecycletest.MainActivity@424fc768
04-23 16:49:18.084 3676-3676/com.example.lipeter.activitylifecycletest D/MainActivity1: 57
04-23 16:49:20.764 3676-3676/com.example.lipeter.activitylifecycletest D/SecondActivity1: com.example.lipeter.activitylifecycletest.SecondActivity@42514640
04-23 16:49:20.764 3676-3676/com.example.lipeter.activitylifecycletest D/SecondActivity1: 57
04-23 16:49:21.644 3676-3676/com.example.lipeter.activitylifecycletest D/MainActivity1: com.example.lipeter.activitylifecycletest.MainActivity@4252d280
04-23 16:49:21.644 3676-3676/com.example.lipeter.activitylifecycletest D/MainActivity1: 57
04-23 16:49:22.704 3676-3676/com.example.lipeter.activitylifecycletest D/SecondActivity1: com.example.lipeter.activitylifecycletest.SecondActivity@425451b8
04-23 16:49:22.714 3676-3676/com.example.lipeter.activitylifecycletest D/SecondActivity1: 57
04-23 16:49:23.764 3676-3676/com.example.lipeter.activitylifecycletest D/MainActivity1: com.example.lipeter.activitylifecycletest.MainActivity@4255de98
04-23 16:49:23.764 3676-3676/com.example.lipeter.activitylifecycletest D/MainActivity1: 57
04-23 16:49:25.824 3676-3676/com.example.lipeter.activitylifecycletest D/MainActivity1: onDestroy: 
04-23 16:49:25.964 3676-3676/com.example.lipeter.activitylifecycletest D/MainActivity1: onRestart: 
04-23 16:49:27.574 3676-3676/com.example.lipeter.activitylifecycletest D/MainActivity1: onDestroy: 
04-23 16:49:27.904 3676-3676/com.example.lipeter.activitylifecycletest D/MainActivity1: onRestart: 
04-23 16:49:29.354 3676-3676/com.example.lipeter.activitylifecycletest D/MainActivity1: onDestroy: 
04-23 16:49:30.564 3676-3676/com.example.lipeter.activitylifecycletest D/MainActivity1: onRestart: 
04-23 16:49:32.154 3676-3676/com.example.lipeter.activitylifecycletest D/MainActivity1: onDestroy: 

singleTask:

栈内复用模式。这是一种单实例模式,在这种模式下,每当启动该活动时,首先会在返回栈中检查是否存在该活动的实例,如果发现已经存在就直接使用该实例,并把在这个活动之上的活动统统出栈,和singleTop模式一样,系统也会回调其onNewIntent方法。

Android初级工程师面试题答案——Android题型_第7张图片

    不同的Activity在同一个栈中。

    ① 如果此时Aty1的实例在栈顶,此时再创建一个新的Aty1并不会成功。

  Android初级工程师面试题答案——Android题型_第8张图片

    ② 如果Aty_1不在栈顶,此时再创建一个Aty1的实例,页面会返回到任务栈中存在的Aty1的实例,并将Aty1上面所有的实例移出栈。

    Android初级工程师面试题答案——Android题型_第9张图片

    当点返回键的时候,便会从栈顶按顺序将实例移除栈。直至栈为空则退回到桌面

Log打印1(和singleTop一样,一直点跳转本身activity不会一直创建新的实例,无论点多少下都只会存在一个实例):

04-23 21:40:48.217 11638-11638/? D/MainActivity1: com.example.lipeter.activitylifecycletest.MainActivity@4246ab50
04-23 21:40:48.217 11638-11638/? D/MainActivity1: 71

Log打印2(MainActivity跳转SecondActivity再跳转到ThirdActivity最后再跳转到了MainActivity并没有创建新的实例,SecondActivity和ThirdActivity这时已经统统出栈了,从我点了几次循环的内存地址可看出):

04-23 21:55:45.207 12499-12499/? D/MainActivity1: com.example.lipeter.activitylifecycletest.MainActivity@42466a98
04-23 21:55:45.207 12499-12499/? D/MainActivity1: 78
04-23 21:55:47.677 12499-12499/com.example.lipeter.activitylifecycletest D/SecondActivity1: com.example.lipeter.activitylifecycletest.SecondActivity@424dfd70
04-23 21:55:47.677 12499-12499/com.example.lipeter.activitylifecycletest D/SecondActivity1: 78
04-23 21:55:48.577 12499-12499/com.example.lipeter.activitylifecycletest D/ThirdActivity1: com.example.lipeter.activitylifecycletest.ThirdActivity@424fa9a8
04-23 21:55:48.577 12499-12499/com.example.lipeter.activitylifecycletest D/ThirdActivity1: 78
04-23 21:55:48.657 12499-12499/com.example.lipeter.activitylifecycletest D/MainActivity1: onRestart: 
04-23 21:55:49.997 12499-12499/com.example.lipeter.activitylifecycletest D/SecondActivity1: com.example.lipeter.activitylifecycletest.SecondActivity@4251d5e8
04-23 21:55:49.997 12499-12499/com.example.lipeter.activitylifecycletest D/SecondActivity1: 78
04-23 21:55:51.257 12499-12499/com.example.lipeter.activitylifecycletest D/ThirdActivity1: com.example.lipeter.activitylifecycletest.ThirdActivity@42536260
04-23 21:55:51.257 12499-12499/com.example.lipeter.activitylifecycletest D/ThirdActivity1: 78
04-23 21:55:51.327 12499-12499/com.example.lipeter.activitylifecycletest D/MainActivity1: onRestart: 
04-23 21:55:53.897 12499-12499/com.example.lipeter.activitylifecycletest D/SecondActivity1: com.example.lipeter.activitylifecycletest.SecondActivity@42551450
04-23 21:55:53.897 12499-12499/com.example.lipeter.activitylifecycletest D/SecondActivity1: 78
04-23 21:55:54.777 12499-12499/com.example.lipeter.activitylifecycletest D/ThirdActivity1: com.example.lipeter.activitylifecycletest.ThirdActivity@4256a0a0
04-23 21:55:54.777 12499-12499/com.example.lipeter.activitylifecycletest D/ThirdActivity1: 78
04-23 21:55:54.857 12499-12499/com.example.lipeter.activitylifecycletest D/MainActivity1: onRestart: 

singleInstance:

单实例模式。这是一种加强的singleTask模式,他除了有singleTask模式的所有特征外,还加强了一点,具有这种模式的活动只能单独地位于一个任务栈中,它会启动一个新的返回栈来管理这个活动

Android初级工程师面试题答案——Android题型_第10张图片

    不同的Activity 处于不同栈中

    ① 如果此时Aty1的实例在栈顶,此时再创建一个新的Aty1并不会成功。

   Android初级工程师面试题答案——Android题型_第11张图片

    ② 如果此时Aty2的实例还未被创建,则创建的Aty2的实例时会存放到一个新的任务栈中。

    Android初级工程师面试题答案——Android题型_第12张图片

    ③ 如果此时Aty1的实例已存在且我们在Aty2的页面中创建新的Aty1实例时,则不会创建新的Aty1实例,而是页面跳转到原来任务栈中存在的Aty1的实例,但存放Aty2实例的任务栈仍然存在

    Android初级工程师面试题答案——Android题型_第13张图片

 当点返回键的时候,便会依次销毁相应的任务栈,直至任务栈数为空则退回到系统桌面。

Log打印1(和singleTop、singleTask一样,一直点跳转本身activity不会一直创建新的实例,无论点多少下都只会存在一个实例):

04-23 22:45:52.902 14637-14637/? D/MainActivity1: com.example.lipeter.activitylifecycletest.MainActivity@42465880
04-23 22:45:52.902 14637-14637/? D/MainActivity1: 88

Log打印2(SecondActivity是被指定为singleInstance模式的,MianActivity跳转到SecondActivity又创建了新的返回栈):

04-23 22:48:10.432 14883-14883/com.example.lipeter.activitylifecycletest D/MainActivity1: com.example.lipeter.activitylifecycletest.MainActivity@424690a8
04-23 22:48:10.432 14883-14883/com.example.lipeter.activitylifecycletest D/MainActivity1: 90
04-23 22:48:12.542 14883-14883/com.example.lipeter.activitylifecycletest D/SecondActivity1: com.example.lipeter.activitylifecycletest.SecondActivity@424e1a80
04-23 22:48:12.552 14883-14883/com.example.lipeter.activitylifecycletest D/SecondActivity1: 91

 (指定MianActivity为singleInstance模式第二个活动也会创建新的返回栈)

04-23 22:53:09.182 15159-15159/? D/MainActivity1: com.example.lipeter.activitylifecycletest.MainActivity@4246d620
04-23 22:53:09.182 15159-15159/? D/MainActivity1: 94
04-23 22:53:13.502 15159-15159/com.example.lipeter.activitylifecycletest D/SecondActivity1: com.example.lipeter.activitylifecycletest.SecondActivity@424e6980
04-23 22:53:13.502 15159-15159/com.example.lipeter.activitylifecycletest D/SecondActivity1: 95

Log打印3(指定MianActivity为singleInstance模式,跳转到SecondActivity再跳转MianActivity时,不会再创建新的实例,而且SecondActivity的任务栈也仍然存在):

04-23 22:57:31.832 15297-15297/? D/MainActivity1: com.example.lipeter.activitylifecycletest.MainActivity@42464ba8
04-23 22:57:31.832 15297-15297/? D/MainActivity1: 96
04-23 22:57:35.552 15297-15297/com.example.lipeter.activitylifecycletest D/SecondActivity1: com.example.lipeter.activitylifecycletest.SecondActivity@424dde88
04-23 22:57:35.552 15297-15297/com.example.lipeter.activitylifecycletest D/SecondActivity1: 97
04-23 22:57:38.212 15297-15297/com.example.lipeter.activitylifecycletest D/MainActivity1: onRestart: 

9.Activity的缓存方法是怎么样的?   10.简单描述下,当Activity在后台销毁的时候,如何保存和恢复状态和数据。

参考网站:https://blog.csdn.net/a136447572/article/details/81031544      https://blog.csdn.net/fenggering/article/details/53907654

9.Activity的缓存方法是怎么样的?

Activity的缓存方法主要有onSaveInstanceState(Bundle outState)、onRestoreInstanceState(Bundle saveInstanceState)、onCeate(Bundle saveInsanceState),Android系统的回收机制会在未经用户主动操作的情况下销毁Activity,而为了避免这种机制的系统回收Activity导致数据丢失,Android为我们提供了onSaveInstanceState(Bundle outState)和onRestoreInstanceState(Bundle saveInstanceState)、onCreate(Bundle saeInstanceState)用于保存和恢复数据。

10.简单描述下,当Activity在后台销毁的时候,如何保存和恢复状态和数据。

在缓存方法中都会有个Bundle的对象,Bundle 这个在API里的定义是:A mapping from String keys to various {@link Parcelable} values. 是一个map对象。所以保存状态和恢复数据也从Bundle入手,通过键值对的形式来存取数据。

补充其他知识点

一.onSaveInstanceState(Bundle outSate)在什么时机下会被调用?

答案:当活动有可能被系统回收的情况下,而且是在onStop()之前。注意是有可能,如果是已经确定会被销毁,比如用户按下了Back键,或者调用了finish()方法销毁活动,则该方法不会被调用,或者也可以说,此方法只有活动被异常终止的情况下会被调用。

总结会调用该方法的几种情况:

1.但用户按下HOME键时

2.长按HOME键,选择运行其他的程序时。

3.从当前活动启动一个新的活动时

4.按下电源键(关闭屏幕显示)时

5.屏幕方向切换时

前4种情况下,活动的生命周期为:

onPause >> onSaveInstanceState >> onStop

(这个是参考博主测试的结果,但是根据《Android开发艺术探索》,说onPause和onSaveInstanceState的顺序是不一定的)

第5种情况下,活动的生命周期为:

onPause >> onSaceInstanceState >> onStop >> onDestroy >> onCreate >> onStart >> onRestreInstanceState >> onResume

二.onRestoreInstanceState(Bundle saveInstanceState)在什么时机下会被调用?

答案:onRestoreInstanceState(Bundle saveInstanceState)方法只有在活动确实是被系统回收,重新创建活动的情况下才会被调用。

拿上面第5种情况来说,活动生命周期如下:

onPause >> onSaceInstanceState >> onStop >> onDestroy >> onCreate >> onStart >> onRestreInstanceState >> onResume

这里onRestoreInstanceState被调用,是因为屏幕切换时原来的活动确实被系统回收了,又重新创建一个新的活动。

而按HOME键返回桌面,又马上返回程序回到原来的页面时,活动生命周期如下:

onPause >> onSaveInstanceState >> onStop >> onRestart > onStart >> onResume

因为活动没有被系统回收,因此onRestoreInstanceState没有被调用。

所以说如果onRestreInstanceState 被调用过,onSaceInstanceState 也肯定被调用过,页面必然被回受过

三.onCreate()里也有Bundle参数,可以用来恢复数据,它和onRestoreInstanceState有什么区别?

因为onSaveInstanceState不一定会被调用,所以onCreate中的Bundle参数可能为空,如果使用onCreate来恢复数据,一定要做非空值判断。

而调用onRestreInstanceState是在onStart之后被调用的。有时候我们需要onCreate中做一些初始化完成后再恢复数据,用onRestreInstanceState会比较方便。

11.在程序配置改变的时候,如切换横竖屏,会导致Activity销毁,此时如何保存和恢复大量数据(如几张图片)。

这里以一张图片为例,如果有几张图片就使用putParcelableArray()的形式传递。 

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        Bitmap bitmap = ((BitmapDrawable)mImageView.getDrawable()).getBitmap();  //获取当前imageView图片的位图bitmap,Bitmap对象已经实现了Parcelable
        outState.putParcelable("photo", bitmap);  //以键值对形式传递数据  
    }


    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
        Bitmap bitmap = savedInstanceState.getParcelable("photo");  //这边也是以键值对的形式取出来
        mImageView.setImageBitmap(bitmap);  //把bitmap对象设置到imageView里面
    }

12.Activity中有个EditText,里面有一段文本,如何保证在Activity销毁和恢复的时候,EditText的状态自动保存,需要对EditText做哪些设置

 与上面的例子相同也是以同样的形式存储与取出

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        String content = mEditText.getText().toString();
        outState.putString("editText", content);
    }


    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
        String content = savedInstanceState.getString("editText");
        mEditText.setText(content);

    }

13.Intent可以传递哪些数据?如果是对象,怎么传递?

参考书籍:《第一行代码》郭霖

Intent可以传递的数据:

1.8种基本数据类型的数据

2.实现了 Serializable 的对象及其数组 (可序列化)

3.实现了Parcelable的对象以及数组(可打包,音译)

4.String(实际上String也实现了 Serializable )/CharSequence实例类型的数据及其数组

如果是对象的话,有两种传递方式:

第一种使用Serializable :

第一步,先将要传递的对象实现Serializable接口:

public class Woman implements Serializable {
    private String name;
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

第二步,Intent使用putExtra方法,将实现序列化的对象放入方法中作为参数进行传递:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button button = findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Woman woman = new Woman();
                woman.setName("小红");
                woman.setAge(20);
                Intent intent = new Intent(getApplicationContext(), SecondActivity.class);
                intent.putExtra("woman", woman);
                startActivity(intent);
            }
        });
    }

 第三步,在接收的方法先getIntent,然后再用getSerializableExtra来接收序列化对象:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);

        TextView textView = findViewById(R.id.textView);
        Intent intent = getIntent();
        Woman woman = (Woman) intent.getSerializableExtra("woman");
        textView.setText("年龄:" + woman.getAge() + "名字:" + woman.getName());
    }

总结:

这种方式的优点是比较方便简单,缺点就是效率比较低,没有Parcelable的方法效率高 


第二种使用Parcelable:

第一步,也是先让要传递的对象实现Parcelable接口,但是这个接口实现后必须重写两个方法,describeContents返回0就行了,writeToParcel需要调用Parcel 中的writeXxx方法,将对象中的字段写出:

public class Man implements Parcelable {
    private String name;
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(name);
        dest.writeInt(age);
    }
}

第二步,在要传递的对象中提供一个名为CREATOR的常量,这里创建了Parcelable的子接口Creator的一个实现,并将泛型指定为传递的对象,并且这里也要重写子接口Creator中的两个方法,createFromParcel和newArray方法,createFromParcel中使用Parcel对象的形参来读取对象中的字段并创建要传递的对象返回(有两种写法,两种写法原理都相同,详情见如下),注意这里读取的顺序一定要和刚刚写的顺序完全相同,否则数据就会错乱或者为null,newArray只要new一个要传递对象数组即可,数组内的参数写形参size即可:

写法一,《第一行代码》中的写法:

public class Man implements Parcelable {
    private String name;
    private int age;

    public static final Creator CREATOR = new Creator() {
        @Override
        public Man createFromParcel(Parcel source) {
            Man man = new Man();
            man.name = source.readString();
            man.age = source.readInt();
            return man;
        }

        @Override
        public Man[] newArray(int size) {
            return new Man[size];
        }
    };

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(name);
        dest.writeInt(age);
    }
}

写法二,Android Studio中自动生成的写法(注意提供无参的构造方法):

public class Man implements Parcelable {
    private String name;
    private int age;


    public Man() {
    }

    protected Man(Parcel in) {
        name = in.readString();
        age = in.readInt();
    }

    public static final Creator CREATOR = new Creator() {
        @Override
        public Man createFromParcel(Parcel in) {
            return new Man(in);
        }

        @Override
        public Man[] newArray(int size) {
            return new Man[size];
        }
    };

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(name);
        dest.writeInt(age);
    }
}

第三步,将将传递的对象传入putExtra:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button button = findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Man man  = new Man();
                man.setAge(23);
                man.setName("小明");
                Intent intent = new Intent(getApplicationContext(), SecondActivity.class);
                intent.putExtra("man", man);
                startActivity(intent);
            }
        });
    }

 第四步,在接收的方法先getIntent,然后再用getParcelableExtra来接收可打包对象:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);

        TextView textView = findViewById(R.id.textView);
        Intent intent = getIntent();
        Man man = intent.getParcelableExtra("man");
        textView.setText("年龄:" + man.getAge() + "名字:" + man.getName());
    }

总结:

这种方式的优点是效率比较高,缺点就是做法和Serializable方式比起来更为复杂

14.使用服务发送一个广播或者使用服务接收一个广播你会怎么做?(有点忘记了因为这是和老板面谈的时候问的)

这里以这个本地服务为例,已上传至GitHub,自行clone下来就ok。

GitHub地址

15.列举一些保证Service不被后台销毁的方式。

参考网站:https://blog.csdn.net/xialong_927/article/details/80262622   https://www.cnblogs.com/shen-hua/p/5836386.html

1. 在onStartCommand方法中,返回START_STICKY

在StartCommand()几个常量:

  • START_STICKY 

系统重新创建服务并且调用onStartCommand()方法,但并不会传递最后一次传递的intent,只是传递一个空的intent。除非存在将要传递来的intent,那么就会传递这些intent。这个适合播放器一类的服务,不需要执行命令,只需要独自运行,等待任务。

  • START_NOT_STICKY 

系统不重新创建服务,除非有将要传递来的intent。这是最安全的选项,可以避免在不必要的时候运行服务。

  • START_REDELIVER_INTENT 

系统重新创建服务并且调用onStartCommand()方法,传递最后一次传递的intent。其余存在的需要传递的intent会按顺序传递进来。这适合像下载一样的服务,立即恢复,积极执行。
 

@Override  
public int onStartCommand(Intent intent, int flags, int startId) {  
    flags = START_STICKY;  
    return super.onStartCommand(intent, flags, startId);  
}

手动返回START_STICKY,亲测当service因内存不足被kill,当内存又有的时候,service又被重新创建,比较不错,但是不能保证任何情况下都被重建,比如进程被干掉了….

2. 通过前台服务来提升Service的优先级

前台服务是被认为用于已知的正在运行的服务,当系统需要释放内存时不会优先杀掉该进程。前台进程必须发一个notification在状态栏中显示,知道进程被杀死。因为前台服务一直消耗一部分资源,但不像一般服务那样会在需要的时候被杀掉,所以为了节约资源,保护电池寿命,一定要在建前台服务的时候发送notification,提示用户。当然系统提供的方法就必须有notification参数的,所以不要想着怎么把notification隐藏掉。

    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
    @Override
    public void onCreate() {
        super.onCreate();
        Log.d(TAG, "onCreate: 已执行");
        Intent intent = new Intent(this, MainActivity.class);
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0);
        Notification notification = new Notification.Builder(this)
                .setContentTitle("这是内容标题")
                .setContentText("这是内容正文")
                .setWhen(System.currentTimeMillis())
                .setSmallIcon(R.mipmap.ic_launcher)
                .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))
                .setContentIntent(pendingIntent)
                .build();
        startForeground(1, notification);   //前台服务
    }

3. 在onDestroy中发送广播,让广播来开启自己

服务+广播方式,就是当service调用到onDestroy的时候,发送一个自定义的广播,当收到广播的时候,重新启动service;

        
            
                
            
        

在service中onDestroy的时候:

    @Override
    public void onDestroy() {
        Log.d(TAG, "onDestroy: ");
        stopForeground(true);  //有前台服务的时候把这句话加上
        Intent intent = new Intent("com.peterli.localservicetest.destroy");
        sendBroadcast(intent);
        super.onDestroy();
    }

在MyReceiver中:

public class MyReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        assert intent.getAction() != null;
        if (intent.getAction().equals("com.peterli.localservicetest.destroy")) {
            Intent serviceIntent = new Intent(context, MyService.class);
            context.startService(serviceIntent);
        }
    }
}

当然,从理论上来讲这个方案是可行的,实验一下结果也是可行的。但是有些情况下,发送的广播在消息队列中排的靠后,就有可能服务还没有接收到广播就销毁了(只是猜想)。所以为了能让这个机制完美运行,可以开启两个服务,相互监听,相互启动。服务A监听B的广播来启动B,服务B监听A的广播来启动A。经过实验,这个方案是可行的。


以上是正常应该知道的方式,下面是某些大神总结出来的,参考下 :

对了原网站中就一句话说的挺好的,与大家共勉之:

引用知乎Android Developer的一句话:强烈建议不要这么做,不仅仅从用户角度考虑,作为Android开发者也有责任去维护Android的生态环境。现在很多Android开发工程师,主力机居然是iPhone而不是Android设备,感到相当悲哀

4. 双进程Service

让2个进程互相保护,其中一个Service被清理后,另外没被清理的进程可以立即重启进程

5. AlarmManager不断启动service

该方式原理是通过定时警报来不断启动service,这样就算service被杀死,也能再启动。同时也可以监听网络切换、开锁屏等广播来启动service。

参考实现方式如下:

 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //实现方法
        Intent intent =new Intent(mContext, MyService.class);
        PendingIntent sender=PendingIntent.getService(mContext, 0, intent, 0);
        AlarmManager alarm=(AlarmManager)getSystemService(ALARM_SERVICE);
        alarm.setRepeating(AlarmManager.RTC_WAKEUP,System.currentTimeMillis,5*1000,sender);
}

该方式基本可以保证在正常运行情况下,以及任务栏移除历史任务后(小米、魅族手机除外),service不被杀死。但是360等软件管家依然可以杀死。另外还有不断启动的逻辑处理麻烦。

6. QQ黑科技

在应用退到后台后,另起一个只有 1 像素的页面停留在桌面上,让自己保持前台状态,保护自己不被后台清理工具杀死

7. 在已经root的设备下,修改相应的权限文件,将App伪装成系统级的应用(Android4.0系列的一个漏洞,已经确认可行)

  • Android系统中当前进程(Process)fork出来的子进程,被系统认为是两个不同的进程。当父进程被杀死的时候,子进程仍然可以存活,并不受影响。鉴于目前提到的在Android-Service层做双守护都会失败,我们可以fork出c进程,多进程守护。死循环在那检查是否还存在,具体的思路如下(Android5.0以下可行)
  1. 用C编写守护进程(即子进程),守护进程做的事情就是循环检查目标进程是否存在,不存在则启动它。
  2. 在NDK环境中将1中编写的C代码编译打包成可执行文件(BUILD_EXECUTABLE)。
  3. 主进程启动时将守护进程放入私有目录下,赋予可执行权限,启动它即可。

8. 联系厂商,加入白名单

16.你了解handler吗?Handler的机制是什么?

Handler机制是一套Android消息处理机制,也可以说是异步消息处理机制、Android多线程编程,用来进行线程通信,主要解决异步线程刷新UI界面。

17.介绍一下Handler机制,并说明Looper,Handler,MessageQueue的关系。

Handler机制:

     Handler机制是一套Android消息处理机制,也可以说是异步消息处理机制、Android多线程编程,用来进行线程通信,主要解决异       步线程刷新UI界面;

Looper,Handler,MessageQueue的关系:

Looper为循环器,Handler为处理者,MessageQueue是消息队列,首先处理者会发送消息(Message)到消息队列中,消息队列是一种数据结构,存储特点是先进先出,处理者发送消息后,消息就会入队到消息队列中等待被处理,循环器会一直尝试从消息队列中取出待处理的消息,然后分发消息给处理者进行处理。

18.只能在UI线程里面更新界面吗?

不一定;也可以通过线程通信的方式,在子线程或工作线程上对UI界面进行更新或者进行UI操作。

19.Android的子线程更新UI的方式有几种?

  1. Handler的sendMessage()
  2. Handler的post()
  3. runOnUIThread()
  4. 继承AsyncTask类

20.异步处理有几种方式?

  1. Handler的sendMessage()
  2. Handler的post()
  3. runOnUIThread()
  4. 继承AsyncTask类

21.列举你所知道的Android多线程之间的通讯的方式。

  1. Handler的sendMessage()
  2. Handler的post()
  3. runOnUIThread()
  4. 继承AsyncTask类

22.描述一下跨进程通讯有哪几种方式?每种方式的特点是什么?

跨进程通信有四种方式,分别对应着四大组件:

  1. Activity:Activity既可以在进程内(同一个应用程序)访问,也可以跨进程访问。Activity的跨进程访问与进程内访问略有不同。虽然它们都需要Intent对象,但跨进程访问并不需要指定Context对象和Activity的 Class对象,而需要指定的是要访问的Activity所对应的Action(一个字符串)。有些Activity还需要指定一个Uri(通过 Intent构造方法的第2个参数指定)。
  2. Broadcast:广播是一种被动跨进程通讯的方式。当某个程序向系统发送广播时,其他的应用程序只能被动地接收广播数据。这就象电台进行广播一样,听众只能被动地收听,而不能主动与电台进行沟通。在应用程序中发送广播比较简单。只需要调用sendBroadcast方法即可。该方法需要一个Intent对象。通过Intent对象可以发送需要广播的数据。
  3. Service:服务是android系统中非常重要的组件。Service可以脱离应用程序运行。也就是说,应用程序只起到一个启动Service的作用。一但Service被启动,就算应用程序关闭,Service仍然会在后台运行。android系统中的Service主要有两个作用:后台运行和跨进程通讯。后台运行就不用说了,当Service启动后,就可以在Service对象中 运行相应的业务代码,而这一切用户并不会察觉。如果想让应用程序可以跨进程通讯,就要使用我们这节讲的AIDL服 务,AIDL的全称是Android Interface Definition Language,也就是说,AIDL实际上是一种Android接口定义语言。通过这种语言定义接口后,Android Studio编译后会自动生成相应的Java代码接口代码。
  4. ContentProvider:Android应用程序可以使用文件或SqlLite数据库来存储数据。Content Provider提供了一种在多个应用程序之间数据共享的方式(跨进程共享数据)。应用程序可以利用Content Provider完成下面的工作

    1. 查询数据
    2. 修改数据
    3. 添加数据
    4. 删除数据

            虽然Content Provider也可以在同一个应用程序中被访问,但这么做并没有什么意义。Content Provider存在的目的向其他应用程序共享数据和允许其他应用程序对数据进行增、删、改操作。
    Android系统本身提供了很多Content Provider,例如,音频、视频、联系人信息等等。我们可以通过这些Content Provider获得相关信息的列表。这些列表数据将以Cursor对象返回。因此,从Content Provider返回的数据是二维表的形式。

23.Android的存储形式有几种?

Android有五大存储形式:

  1. 文件存储(通过JavaIO流的读写方法写入到文件中)

  2. SharePreferences(以键值对形式存储在xml中)

  3. SQLite(嵌入式关系型数据库,使用SQL语言)

  4. ContentProvider(作为传输数据的媒介,数据源可多样性)

  5. 网络存储(通过网络获取远程服务器的数据)

24.本地存储的方式有哪些?你用过哪些?

  1. 文件存储(通过JavaIO流的读写方法写入到文件中)

  2. SharePreferences(以键值对形式存储在xml中)

  3. SQLite(嵌入式关系型数据库,使用SQL语言)

  4. ContentProvider(作为传输数据的媒介,数据源可多样性)

  5. 网络存储(通过网络获取远程服务器的数据)

25.SQLite的基础操作?

创建:

自定义一个类继承SQLiteOpenHelper类,重写onCreate和onUpgrade抽象方法,并重载父类构造器,在onCreate方法中,使用形参db即可执行SQL语句,创建表;在需要创建数据库的代码块中,新建一个继承SQLiteOpenHelper类的对象实例,将相关参数传进去,第一个参数是上下文,第二个是数据库名称,第三个传空值就行,第四个是版本号,继承SQLiteOpenHelper类的引用调用getWritableDatabase或getReadableDatabase即可创建数据库。

升级数据库:

在新建一个继承SQLiteOpenHelper类的对象实例的方法里面,将版本号+1,在onUpgrade中先删除表再执行SQL语句,最后再调用onCreate方法

增删查改:

增:

dbHelper.getWritableDatabase可以得到一个SQLiteDatabase的对象,通过该对象可以对数据库进行增删查改操作;

db.exeSQL();执行SQL语句

db.insert();

删:

db.exeSQL();

db.delete();

查:

db.rawQuery();

db.query();

改:

db.exeSQL();

db.update();

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(Android)