Android 部分
目录
1. Android中的四大组件有哪些?各自有什么作用?
2. activity的生命周期
- 启动Activity: onCreate()—>onStart()—>onResume(),Activity进入运行状态。
- Activity退居后台: 当前Activity转到新的Activity界面或按Home键回到主屏: onPause()—>onStop(),进入停滞状态。
- Activity返回前台: onRestart()—>onStart()—>onResume(),再次回到运行状态。
- Activity退居后台,且系统内存不足, 系统会杀死这个后台状态的Activity,若再次回到这个Activity,则会走onCreate()–>onStart()—>onResume()
- 锁定屏与解锁屏幕 只会调用onPause(),而不会调用onStop方法,开屏后则调用onResume()
3. Activity销毁但Task如果没有销毁掉,当Activity重启时这个AsyncTask该如何解决?
还是屏幕旋转这个例子,在重建Activity的时候,会回调Activity.onRetainNonConfigurationInstance()
重新传递一个新的对象给AsyncTask,完成引用的更新
4. Asynctask为什么要设置为只能够一次任务
个AsyncTask对象只能被执行一次,即只能调用一次execute;sDefaultExecutor是SerialExecutor的一个实例,而且它是个静态变量。也就是说,一个进程里面所有AsyncTask对象都共享同一个SerialExecutor对象。考虑到线程安全问题,所以只能够执行一次
5. 若Activity已经销毁,此时AsynTask执行完并返回结果,会报异常么?
当一个App旋转时,整个Activity会被销毁和重建。当Activity重启时,AsyncTask中对该Activity的引用是无效的,因此onPostExecute()就不会起作用,若AsynTask正在执行,则会报 view not attached to window manager 异常。同样也是生命周期的问题,在 Activity 的onDestory()方法中调用Asyntask.cancal方法,让二者的生命周期同步
6. 内存不足时,系统会杀死后台的Activity,如果需要进行一些临时状态的保存,在哪个方法进行(activity的状态保存)
Activity的 onSaveInstanceState() 和 onRestoreInstanceState()并不是生命周期方法,不同于 onCreate()、onPause()等生命周期方法,它们并不一定会被触发。当应用遇到意外情况(如:内存不足、用户直接按Home键)由系统销毁一个Activity,onSaveInstanceState() 会被调用。但是当用户主动去销毁一个Activity时,例如在应用中按返回键,onSaveInstanceState()就不会被调用。除非该activity是被用户主动销毁的,通常onSaveInstanceState()只适合用于保存一些临时性的状态,而onPause()适合用于数据的持久化保存。
7. activity的launchMode及其使用场景
- standard: standard模式是默认的启动模式,不用为配置android:launchMode属性即可,当然也可以指定值为standard。standard启动模式,不管有没有已存在的实例,都生成新的实例。
- singleTop: 我们在上面的基础上为指定属性android:launchMode=”singleTop”,系统就会按照singleTop启动模式处理跳转行为。跳转时系统会先在栈结构中寻找是否有一个Activity实例正位于栈顶,如果有则不再生成新的,而是直接使用( 会调用实例的 onNewIntent() )。如果系统发现存在有Activity实例,但不是位于栈顶,重新生成一个实例。 这就是singleTop启动模式,如果发现有对应的Activity实例正位于栈顶,则重复利用,不再生成新的实例。适合接收通知启动的内容显示页面,例如,某个新闻客户端的新闻内容页面或者阅读类App的内容页面。
- singleTask: 如果发现有对应的Activity实例,则使此Activity实例之上的其他Activity实例统统出栈,使此Activity实例成为栈顶对象,显示到幕前。适合作为程序入口点,例如浏览器的主界面。不管从多少个应用启动浏览器,只会启动主界面一次,其余情况都会走onNewIntent,并且会清空主界面上面的其他页面。
- singleInstance: 这种启动模式比较特殊,因为它会启用一个新的栈结构,将Acitvity放置于这个新的栈结构中,并保证不再有其他Activity实例进入。 闹铃的响铃界面。 你以前设置了一个闹铃:上午6点。在上午5点58分,你启动了闹铃设置界面,并按 Home 键回桌面;在上午5点59分时,你在微信和朋友聊天;在6点时,闹铃响了,并且弹出了一个对话框形式的 Activity(名为 AlarmAlertActivity) 提示你到6点了(这个 Activity 就是以 SingleInstance 加载模式打开的),你按返回键,回到的是微信的聊天界面,这是因为 AlarmAlertActivity 所在的 Task 的栈只有他一个元素, 因此退出之后这个 Task 的栈空了。如果是以 SingleTask 打开 AlarmAlertActivity,那么当闹铃响了的时候,按返回键应该进入闹铃设置界面。
8. 如何把一个应用设置为系统应用
- Android设置是Debug版本,且root,直接将该apk用adb工具push到system/app或system/priv-app
- 如果是非root设备,需要编译后烧写镜像
- 有些权限(如WRITE_SECURE_SETTINGS)不开放给第三方应用,只能在对应设备源码总编译然后作为系统app使用
9. service的生命周期,它的启动方式有什么区别?(Activity启动Service的两种方式)
service 启动方式有两种,一种是通过startService()方式进行启动,另一种是通过bindService()方式进行启动。不同的启动方式他们的生命周期是不一样.
- startService:生命周期和调用者不同.启动后若调用者未调用stopService而直接退出,Service仍会运行。这种方式启动的service,生命周期是这样:调用startService() --> onCreate()--> onStartConmon()--> onDestroy()。这种方式启动的话,需要注意一下几个问题,第一:当我们通过startService被调用以后,多次在调用startService(),onCreate()方法也只会被调用一次,而onStartConmon()会被多次调用,当我们调用stopService()的时候,onDestroy()就会被调用,从而销毁服务。第二:当我们通过startService启动时候,通过intent传值,在onStartConmon()方法中获取值的时候,一定要先判断intent是否为null。
- bindService:生命周期与调用者绑定,调用者一旦退出,Service就会调用unBind->onDestory。这种方式绑定service,生命周期走法:bindService-->onCreate()-->onBind()-->unBind()-->onDestroy() bingservice 这种方式进行启动service好处是更加便利activity中操作service,比如加入service中有几个方法,a,b ,如果要在activity中调用,在需要在activity获取ServiceConnection对象,通过ServiceConnection来获取service中内部类的类对象,然后通过这个类对象就可以调用类中的方法,当然这个类需要继承Binder对象
10. Android两个应用能在同一个任务栈吗?
栈一般以包名命名,两个应用的签名和udid要相同
11. Fragment是什么?你曾经遇到哪些有关Fragment的问题?
- Fragment可以作为Activity界面的一部分组成出现.一个Activity中可以同时出现多个Fragment,并一个Fragment也可以在多个Activity中使用.
- Fragment重叠的问题(重启应用就好了) :首先,Android管理Fragment有两种方式,使用add、hide、show的方式和replace方式,两种方式各有优缺点。
- replace方式 :如果使用这种方式,是可以避免重叠的问题,但是每次replace会把生命周期全部执行一遍,如果在这些生命周期函数 里拉取数据的话,就会不断重复的加载刷新数据,所以我们并不推荐使用这种方式。
- add、hide、show的方式:虽然这种方式避免了可能重复加载刷新数据的问题,但是会出现重叠的问题。
- 产生重叠的原因:当系统内存不足,Fragment 的宿主 Activity 回收的时候,Fragment 的实例并没有随之被回收。Activity 被系统回收时,会主动调用 onSaveInstance() 方法来保存视图层(View Hierarchy),所以当 Activity 通过导航再次被重建时,之前被实例化过的 Fragment 依然会出现在 Activity 中,此时的 FragmentTransaction 中的相当于又再次 add 了 fragment 进去的,hide()和show()方法对之前保存的fragment已经失效了,所以就出现了重叠。
- 解决重叠的方法:
- 方法一:通过注释掉这句话,这样主 Activity 因为种种原因被回收的时候就不会保存之前的 fragment state
@Override protectedvoidonSaveInstanceState(Bundle outState) { //如果用以下这种做法则不保存状态,再次进来的话会显示默认tab //总是执行这句代码来调用父类去保存视图层的状态 //super.onSaveInstanceState(outState); }
- 方法二(推荐使用):重写onAttachFragment,重新让新的Fragment指向了原本未被销毁的fragment,它就是onAttach方法对应的Fragment对象
@Override public void onAttachFragment(Fragment fragment) { if (tab1 == null && fragment instanceof Tab1Fragment) tab1 = fragment; if (tab2 == null && fragment instanceof Tab2Fragment) tab2 = fragment; if (tab3 == null && fragment instanceof Tab3Fragment) tab3 = fragment; if (tab4 == null && fragment instanceof Tab4Fragment) tab4 = fragment; }
- 方法三: 思路同样是阻止系统恢复Fragment state,在FragmentActivity保存所有Fragment状态前把Fragment从FragmentManager中移除掉。
protected void onSaveInstanceState(Bundle outState) { FragmentTransaction transaction = fm.beginTransaction(); transaction.remove(tab1); transaction.remove(tab2); transaction.remove(tab3); transaction.remove(tab4); transaction.commitAllowingStateLoss(); super.onSaveInstanceState(outState); }
- 方法四:在onSaveInstanceState(outState);中去保存fragment,当activity被恢复时,取出这些fragment即可。(个人比较喜欢)
-
@Override protected void onSaveInstanceState(Bundle outState) { L.i("MainActivity onSaveInstanceState"); /*fragment不为空时 保存*/ if (homeFragment != null) { getSupportFragmentManager().putFragment(outState, HOME_FRAGMENT_KEY, homeFragment); } if (dashboardFragment != null) { getSupportFragmentManager().putFragment(outState, DASHBOARD_FRAGMENT_KEY, dashboardFragment); } if (noticeFragment != null) { getSupportFragmentManager().putFragment(outState, NOTICE_FRAGMENT_KEY, noticeFragment); } super.onSaveInstanceState(outState); }
以上便是解决fragment重叠问题的方法if (savedInstanceState != null) { /*获取保存的fragment 没有的话返回null*/ homeFragment = (HomeFragment) getSupportFragmentManager().getFragment(savedInstanceState, HOME_FRAGMENT_KEY); dashboardFragment = (DashboardFragment) getSupportFragmentManager().getFragment(savedInstanceState, DASHBOARD_FRAGMENT_KEY); noticeFragment = (NoticeFragment) getSupportFragmentManager().getFragment(savedInstanceState, NOTICE_FRAGMENT_KEY); addToList(homeFragment); addToList(dashboardFragment); addToList(noticeFragment); } else { initFragment(); }
12. 是否使用过本地广播,和全局广播有什么区别?
本地广播在本应用范围内传播,不用担心隐私数据泄露,不用担心别的应用伪造广播.相比全局广播,本地广播更高效.
13. broadcast的注册方式与区别
- 广播是分为有序广播和无序广播。
- 静态注册:在清单文件(Androidmanifest.xml)中注册, 常见的有监听设备启动,常驻注册不会随程序生命周期改变 ,即使退出了页面,也可以收到广播这种广播一般用于想开机自启动啊等等,由于这种注册的方式的广播是常驻型广播,所以会占用CPU的资源。
- 动态注册:在代码中注册,这种注册方式也叫非常驻型广播,随着程序的结束,也就停止接受广播了(有些广播只能通过动态方式注册,比如时间变化事件、屏幕亮灭事件、电量变更事件,因为这些事件触发频率通常很高,如果允许后台监听,会导致进程频繁创建和销毁,从而影响系统整体性能)。这种注册方式优先级较高。最后需要解绑,否会会内存泄露
14. 为什么Android引入广播机制?
引入广播机制可以方便几大组件的信息和数据交互。
b:程序间互通消息(例如在自己的应用程序内监听系统来电)
c:效率上(参考UDP的广播协议在局域网的方便性)
d:设计模式上(反转控制的一种应用,类似监听者模式)
15. IntentService有何优点?
- IntentService是Service的子类,是一个异步的,会自动停止的服务,很好解决了传统的Service中处理完耗时操作忘记停止并销毁Service的问题
- 生成一个默认的且与线程相互独立的工作线程执行所有发送到onStartCommand()方法的Intent,可以在onHandleIntent()中处理.
- 串行队列,每次只运行一个任务,不存在线程安全问题,所有任务执行完后自动停止服务,不需要自己手动调用stopSelf()来停止.
- 内部保存着一个HandlerThread,looper,与handler等成员变量,维护者自身的消息队列
- 每次后台任务执行完都会尝试关闭自身,但是当且仅当IntentServerce 消息队列中的最后一条执行完成才会真正stop自己
- 会创建独立的worker线程来处理所有的Intent请求;
- 会创建独立的worker线程来处理onHandleIntent()方法实现的代码,无需处理多线程问题;
- 所有请求处理完成后,IntentService会自动停止,无需调用stopSelf()方法停止Service;
- 为Service的onBind()提供默认实现,返回null;
- 为Service的onStartCommand提供默认实现,将请求Intent添加到队列中;
- IntentService不会阻塞UI线程,而普通Serveice会导致ANR异常
- Intentservice若未执行完成上一次的任务,将不会新开一个线程,是等待之前的任务完成后,再执行新的任务,等任务完成后再次调用stopSelf()
16. Android 实现数据存储的几种方式?
- SharedPreference: SharedPreferences是一种轻量级的数据存储机制,他将一些简单的数据类型的数据,包括boolean类型,int类型,float类型,long类型以及String类型的数据,以键值对的形式存储在应用程序的私有Preferences目录(/data/data/<包名>/shared_prefs/)中,这种Preferences机制广泛应用于存储应用程序中的配置信息。
- 内部存储
- 外部存储
- SQLite数据库: 当应用程序需要处理的数据量比较大时,为了更加合理地存储、管理、查询数据,我们往往使用关系数据库来存储数据。Android系统的很多用户数据,如联系人信息,通话记录,短信息等,都是存储在SQLite数据库当中的,所以利用操作SQLite数据库的API可以同样方便的访问和修改这些数据。
- 网络存储:
- 文件存储: 通过Java.io.FileInputStream和java.io.FileOutputStream这两个类来实现对文件的读写,java.io.File类则用来构造一个具体指向某个文件或者文件夹的对象。
- ContentProvider: 主要用于在不同的应用程序之间实现数据共享的功能,不同于sharepreference和文件存储中的两种全局可读写操作模式,内容提供其可以选择只对哪一部分数据进行共享,从而保证我们程序中的隐私数据不会有泄漏的风险
17. 介绍一下ContentProvider 是如何实现数据共享的
当一个应用程序需要把自己的数据暴露给其他程序使用时,该应用程序就可通过提供ContentProvider来实现;其他应用程序就可通过ContentResolver来操作ContentProvider暴露的数据。 一旦某个应用程序通过ContentProvider暴露了自己的数据操作接口,那么不管该应用程序是否启动,其他应用程序都可以通过该接口来操作该应用程序的内部数据,包括增加数据、删除数据、修改数据、查询数据等。
ContentProvider以某种Uri的形式对外提供数据,允许其他应用访问或修改数据;其他应用程序使用ContentResolver根据Uri去访问操作指定数据。 步骤:
1. 定义自己的ContentProvider类,该类需要继承Android提供的ContentProvider基类。
2. 在AndroidManifest.xml文件中注册个ContentProvider,注册ContenProvider时需要为它绑定一个URL。 例: android:authorities=”com.myit.providers.MyProvider” /> 说明:authorities就相当于为该ContentProvider指定URL。 注册后,其他应用程序就可以通过该Uri来访问MyProvider所暴露的数据了。
3. 接下来,使用ContentResolver操作数据,Context提供了如下方法来获取ContentResolver对象。 一般来说,ContentProvider是单例模式,当多个应用程序通过ContentResolver来操作 ContentProvider提供的数据时,ContentResolver调用的数据操作将会委托给同一个ContentProvider处理。 使用ContentResolver操作数据只需两步:
- 调用Activity的ContentResolver获取ContentResolver对象。
- 根据需要调用ContentResolver的insert()、delete()、update()和query()方法操作数据即可
18. ContentProvider和sql的区别
ContentProvider的主要还是用于数据共享,其可以对Sqlite,SharePreferences,File等进行数据操作用来共享数据。而sql的可以理解为数据库的一门语言,可以使用它完成CRUD等一系列的操作
19. [Android]如何导入已有的外部数据库
我们都知道android系统下数据库应该存放在 /data/data/com.*.*(package name)/ 目录下,所以我们需要做的是把已有的数据库传入那个目录下。操作方法是用FileInputStream读取原数据库,再用FileOutputStream把读取到的东西写入到那个目录。
操作方法:
- 把原数据库包括在项目源码的 res/raw 目录下,然后建立一个DBManager类
- 然后在程序的首个Activity中示例化一个DBManager对象,然后对其执行openDatabase方法就可以完成导入了,可以把一些要对数据库进行的操作写在DBManager类里,然后通过DBManager类的对象调用;也可以在完成导入之后通过一个SQliteDatabase类的对象打开数据库,并执行操作。
20. 如何将打开res aw目录中的数据库文件?
在Android中不能直接打开res aw目录中的数据库文件,而需要在程序第一次启动时将该文件复制到手机内存或SD卡的某个目录中,然后再打开该数据库文件。复制的基本方法是使用getResources().openRawResource方法获得res aw目录中资源的 InputStream对象,然后将该InputStream对象中的数据写入其他的目录中相应文件中。在Android SDK中可以使用SQLiteDatabase.openOrCreateDatabase方法来打开任意目录中的SQLite数据库文件。
21. 一条最长的短信息约占多少byte?
中文70(包括标点),英文160,160个字节。
22. Context与ApplicationContext的区别
Application的Context是一个全局静态变量,SDK的说明是只有当你引用这个context的生命周期超过了当前activity的生命周期,而和整个应用的生命周期挂钩时,才去使用这个application的context。
在android中context可以作很多操作,但是最主要的功能是加载和访问资源。在android中有两种context,一种是 application context,一种是activity context,通常我们在各种类和方法间传递的是activity context。
23. 什么是aar?aar是jar有什么区别?
一、arr:
- Android库项目的二进制归档文件,包含所有资源,class以及res资源文件全部包含。
- 将aar解压(后缀改为.zip,再解压文件)打开后,可以看到每个aar解压后的内容可能不完全一样,但是都会包含AndroidManifest.xml,classes.jar,res,R.txt。
打aar包方法:
- 先把想要打包成sdk的项目做成Android libraries B,不要建成Android project ;
- 然后建立一个新的Android project A 去调用写好的libraries B;
- 运行后,studio就自动把我们的librarys B自动打包成aar包了,这就是我们想要的sdk了(路径:module 下,build/outputs/aar/)
- 如果运行后,没在目录下看到 aar,可以按照下图方式执行,执行成功后,就可以在左侧的:build/outputs/aar/ 下看到对应的 aar 文件了:
作者:viky_lyn
链接:https://www.jianshu.com/p/0a2572a63ed5
來源:简书
Android Studio使用aar方式:
- 拷贝到:libs目录
- build.gradle 配置文件中更改为
repositories {
flatDir {
dirs'libs'
}
}
dependencies {
compile(name:'genius', ext:'aar')
}
二、jar
- 只包含了class文件与清单文件 ,不包含资源文件,如图片等所有res中的文件。
- JAR(Java Archive,Java 归档文件)是与平台无关的文件格式,它允许将许多文件组合成一个压缩文件。
- Jar的优点:安全性、减少下载时间、传输平台扩展、包密封、包版本控制、可移植性。
- 打jar包时,项目里的res文件是用不了的,若想用图片文件,可以将图片文件放进assets文件里面打进jar包再进行调用,但必须注意jar里面assets文件夹里面的文件不能和调用项目里面assets文件夹里面的文件重名。
打jar包方法:
- 网上方法很多也很详细,不再赘述,给个链接:http://blog.csdn.net/u013895206/article/details/52692415
使用jar方式:
*.jar:拷贝到:libs目录,eclipse直接导入即可,AndroidStudio项目中添加:
dependencies {
compile fileTree(include: ['*.jar'], dir:'libs')
}
重新编译一次项目既可完成加载。
23. 如何将SQLite数据库(dictionary.db文件)与apk文件一起发布?
可以将dictionary.db文件复制到Eclipse Android工程中的res aw目录中。所有在res aw目录中的文件不会被压缩,这样可以直接提取该目录中的文件。可以将dictionary.db文件复制到res aw目录中
24. Service和Activity通信
- 通过Binder
- 通过broadcast
25. 如何保证Service在后台不被kill
- Service设置成START_STICKY kill 后会被重启(等待5秒左右),重传Intent,保持与重启前一样
- 通过 startForeground将进程设置为前台进程, 做前台服务,优先级和前台应用一个级别,除非在系统内存非常缺,否则此进程不会被 kill
- 双进程Service: 让2个进程互相保护**,其中一个Service被清理后,另外没被清理的进程可以立即重启进程
- QQ黑科技: 在应用退到后台后,另起一个只有 1 像素的页面停留在桌面上,让自己保持前台状态,保护自己不被后台清理工具杀死
- 在已经root的设备下,修改相应的权限文件,将App伪装成系统级的应用 Android4.0系列的一个漏洞,已经确认可行
- 用C编写守护进程(即子进程) : Android系统中当前进程(Process)fork出来的子进程,被系统认为是两个不同的进程。当父进程被杀死的时候,子进程仍然可以存活,并不受影响。鉴于目前提到的在Android->- Service层做双守护都会失败,我们可以fork出c进程,多进程守护。死循环在那检查是否还存在,具体的思路如下(Android5.0以上的版本不可行)
- 用C编写守护进程(即子进程),守护进程做的事情就是循环检查目标进程是否存在,不存在则启动它。
- 在NDK环境中将1中编写的C代码编译打包成可执行文件(BUILD_EXECUTABLE)。主进程启动时将守护进程放入私有目录下,赋予可执行权限,启动它即可。
- 联系厂商,加入白名单
26. Android中如何获得手机的唯一标示.
- 首先尝试读取IMEI、Mac地址、CPU号等物理信息(有不少工具可以修改IMEI);
- 如果均失败,可以自己生成UUID然后保存到文件(文件也可能被篡改或删除)
27. Android应用中验证码登录都有哪些实现方案
验证码应该只有两种获取方式: 从服务器端获取图片, 通过短信服务,将验证码发送给客户端这两种
28. 为什么要设计Bundle而不是直接使用Map?
- Bundle父类 BaseBundle内部是由ArrayMap实现的,ArrayMap的内部实现是两个数组,一个int数组是存储对象数据对应下标,一个对象数组保存key和value,内部使用二分法对key进行排序,所以在添加、删除、查找数据的时候,都会使用二分法查找,只适合于小数据量操作,如果在数据量比较大的情况下,那么它的性能将退化。而HashMap内部则是数组+链表结构,所以在数据量较少的时候,HashMap的Entry Array比ArrayMap占用更多的内存。因为使用Bundle的场景大多数为小数据量,我没见过在两个Activity之间传递10个以上数据的场景,所以相比之下,在这种情况下使用ArrayMap保存数据,在操作速度和内存占用上都具有优势,因此使用Bundle来传递数据,可以保证更快的速度和更少的内存占用。
- 另外一个原因,则是在Android中如果使用Intent来携带数据的话,需要数据是基本类型或者是可序列化类型,HashMap使用Serializable进行序列化,而Bundle则是使用Parcelable进行序列化。而在Android平台中,更推荐使用Parcelable实现序列化,虽然写法复杂,但是开销更小,所以为了更加快速的进行数据的序列化和反序列化,系统封装了Bundle类,方便我们进行数据的传输。
29. Android中有哪几种解析xml的类,官方推荐的是哪种?他们的原理是什么,区别在哪</