很全面的Android面试题 | 掘金技术征文

阅读 2428
收藏 203
2017-09-15
原文链接:www.cnblogs.com

这些有些来源于网站、有些来源于自己的思考。可能有些问题网上有、可能有些问题对应的答案也有,有可能有些问题讲的不对,能指正的希望大家不吝指教。

 

Activity

什么是Activity

四大组件之一,一个和用户交的互界面就是一个activity,是所有 View 的容器

 

Activity 生命周期

生命周期描述的是一个类 从创建(new出来)到死亡(垃圾回收)的过程中会执行的方法.

在这个过程中会针对不同的生命阶段会调用不同的方法

Activity从创建到销毁有多种状态,从一种状态到另一种状态时会激发相应的回调方法,这些回调方法包括:

  • oncreate:Activity对象创建完毕,但此时不可见
  • onstart:Activity在屏幕可见,但是此时没有焦点
  • onResume:Activity在屏幕可见,并且获得焦点
  • onPause:Activity此时在屏幕依然可见,但是已经没有焦点
  • onStop:Activity已经不可见了,但此时Activity的对象还在内存中
  • onDestroy:Activity对象被销毁

其实这些方法都是两两对应的,onCreate创建与onDestroy销毁; onStart可见与onStop不可见;onResume可编辑(即焦点)与onPause;

还有一个onRestart方法了,在Activity被onStop后,但是没有被onDestroy,在再次启动此Activity时就调用onRestart(而不再调用onCreate)方法; 如果被onDestroy了,则是调用onCreate方法。

   

两个Activity之间跳转时必然会执行的是哪几个方法。

一般情况比如说有两个activity,分别叫A,B ,当在A里面激活B组件的时候, A 会调用 onPause()方法,然后B 调用onCreate() ,onStart(), OnResume() , 这个时候B覆盖了窗体, A会调用onStop()方法.  如果B呢 是个透明的,或者是对话框的样式, 就不会调用onStop()方法。

因此,我们在两个activities中传递数据,或者共享资源时(如数据库连接),需要在前一个activity的onPause()方法而不是onStop()方法中进行

 

横竖屏切换时候Activity的生命周期。

这个生命周期跟清单文件里的配置有关系

1、不设置Activity的android:configChanges时,切屏会重新调用各个生命周期,默认首先销毁当前activity,然后重新加载

2、设置Activity的android:configChanges="orientation|keyboardHidden|screenSize"时,切屏不会重新调用各个生命周期,只会执行onConfigurationChanged方法, 游戏开发中, 屏幕的朝向都是写死的. 

 

如何将一个Activity设置成窗口的样式。

可以自定义一个activity的样式

android:theme="@android:style/Theme.Dialog"

 

你后台的Activity被系统 回收怎么办?如果后台的Activity由于某原因被系统回收了,如何在被系统回收之前保存当前状态?


 

 

 

 

 

 

除了在栈顶的activity,其他的activity都有可能在内存不足的时候被系统回收,一个activity越处于栈底,被回收的可能性越大.如果有多个后台进程,在选择杀死的目标时,采用最近最少使用算法(LRU)。

Activity中提供了一个 onSaveInstanceState()回调方法,这个方法会保证一定在活动被回收之前调用, 可以通过这个方法来解决活动被回收时临时数据得不到保存的问题。 onSaveInstanceState()方法会携带一个 Bundle类型的参数,Bundle提供了一系列的方法用于保存数据,比如可以使用 putString()方法保存字符串,使用 putInt()方法保存整型数据。每个保存方法需要传入两个参数,第一个参数是键,用于后面从 Bundle中取值,第二个参数是真正要保存的内容。在 MainActivity中添加如下代码就可以将临时数据进行保存:

?
1 2 3 4 5 protected void onSaveInstanceState(Bundle outState) {     super.onSaveInstanceState(outState);     String tempData = "Something you just typed" ;     outState.putString("data_key", tempData); }
onCreate()方法其实也有一个Bundle类型的参数。这个参数在一般情况下都是null,但是当活动被系统回收之前有通过 onSaveInstanceState()方法来保存数据的话,这个参就会带有之前所保存的全部数据 ?
1 2 3 4 5 6 protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        if (savedInstanceState != null) {      String tempData = savedInstanceState.getString("data_key" );      }

也可以通过onRestoreInstanceState来存储和恢复数据,区别是不需要判断空了,onRestoreInstanceState调用一定是有值的

 Activity的 onSaveInstanceState() 和 onRestoreInstanceState()并不是生命周期方法,它们不同于 onCreate()、onPause()等生命周期方法,它们并不一定会被触发。当应用遇到意外情况(如:内存不足、用户直接按Home键)由系统销毁一个Activity,onSaveInstanceState() 会被调用。但是当用户主动去销毁一个Activity时,例如在应用中按返回键,onSaveInstanceState()就不会被调用。除非该activity是被用户主动销毁的,通常onSaveInstanceState()只适合用于保存一些临时性的状态,而onPause()适合用于数据的持久化保存。

onSaveInstanceState()被执行的场景有哪些:

系统不知道你按下HOME后要运行多少其他的程序,自然也不知道activity A是否会被销毁,因此系统都会调用onSaveInstanceState(),让用户有机会保存某些非永久性的数据。以下几种情况的分析都遵循该原则

  1. 当用户按下HOME键时
  2. 长按HOME键,选择运行其他的程序时
  3. 锁屏时
  4. 从activity A中启动一个新的activity时
  5. 屏幕方向切换时

 

如何退出Activity?

退出activity 直接调用 finish () 方法 

用户点击back键 就是退出一个activity ,退出activity 会执行 onDestroy()方法 。

1、抛异常强制退出:

该方法通过抛异常,使程序Force Close。不推荐使用

验证可以,但是,需要解决的问题是,如何使程序结束掉,而不弹出Force Close的窗口。

安全结束进程  android.os.Process.killProcess(android.os.Process.myPid());

2、记录打开的Activity:

每打开一个Activity,就用集合记录下来。在需要退出时,关闭每一个Activity即可。

可以写在Application里,直接get Application.list.add,在需要退出时遍历集合里的Activity,finish掉

也可以定义一个baseactivity里面进行操作,记得一般不用的话最后都需要把list=null

3、发送特定广播:

//在baseactivity里注册广播

registerReceiver(receiver, filter)

//想退出的时候就在onRecriver方法里finish()。

4、可以通过 intent的flag 来实现.. intent.setFlag(FLAG_ACTIVITY_CLEAR_TOP)激活一个新的activity,然后在新的activity的oncreate方法里面就可以finish掉.

讲一讲你对activity的理解 

把上面的几点用自己的心得写出来

 

两个Activity之间怎么传递数据?

 

  • 基本数据类型可以通过Intent 传递数据  

    //把数据封装至intent对象中

    //把数据封装至bundle对象中     Bundle bundle = new Bundle();     bundle.putString("malename", "李志");     //把bundle对象封装至intent对象中     intent.putExtras(bundle);

    startActivity(intent); 

  • 传递对象
如果要传递对象,需要把对象类序列化,然后intent.putExtra("mp3Info", mp3Info) 在另一个activity,或服务、广播中取出: Mp3Info mp3Info =(Mp3Info)intent.getSerializableExtra("mp3Info"); 实现Parcelable接口的可以用intent直接传 ,和Serializable区别就是Parcelable是直接在内存中读写,要比序列化要快, Serializabl会使用反射,序列化和反序列化过程需要大量 I/O 操作,但是要注意Parcelable传递对象的大小。

 

 

怎么让在启动一个Activity是就启动一个service?

在activity的onCreate()方法里面 startService();

 

如何返回数据

基本流程:

  • 使用 startActivityForResult(Intent intent, int requestCode) 方法打开 Activity;
  • 新 Activity 中调用 setResult(int resultCode, Intent data) 设置返回数据之后,关闭 Activity 就会调用onActivityResult 方法;
  • 在原来的activity里重写 onActivityResult(int requestCode, int resultCode, Intent data) 方法;
  • 注意:新的 Activity 的启动模式不能设置成 singleTask(如果已创建,会使用以前创建的)与 singleInstance(单

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

 

请描述一下Intent 和 Intent Filter。

Android 中通过 Intent 对象来表示一条消息,一个 Intent 对象不仅包含有这个消息的目的地,还可以包含消息的内容,这好比一封 Email,其中不仅应该包含收件地址,还可以包含具体的内容。对于一个 Intent 对象,消息“目的地”是必须的,而内容则是可选项。

通过Intent 可以实现各种系统组件的调用与激活.

Intent filter:是传递的信息,这些信息不是必须的,有:

Action: 动作 view

Data: 数据uri uri

Category : 而外的附加信息

 

隐式跳转
  • 隐式意图跳转至指定Activity

     

    ?
    1 2 3 4 5 6 7   <intent-filter >       <action android:name="com.itheima.second" />       <data android:scheme="asd" android:mimeType="aa/bb"/>       <category android:name="android.intent.category.DEFAULT" />   </intent-filter>   Intent intent = new Intent("com.example.activitytest.ACTION_START" );
category 是默认的,不需要写,每个 Intent中只能指定一个 action,但却能指定多个 category intent.addCategory("com.example.activitytest.MY_CATEGORY"); 可以调用 Intent中的 addCategory()方法来添加一个 category intent-filter节点及其子节点都可以同时定义多个,隐式启动时只需与任意一个匹配即可 // 电话
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)
   

PendingIntent和Intent 区别

它们都可以去指明某一个“意图”,都可以用于启动活动、启动服务以及发送广播等。不同的是,Intent更加倾向于去立即执行某个动作,而 PendingIntent更加倾向于在某个合适的时机去执行某个动作。所以,也可以把 PendingIntent简单地理解为延迟执行的 Intent。

PendingIntent的用法同样很简单,它主要提供了几个静态方法用于获取 PendingIntent的实例,可以根据需求来选择是使用 getActivity()方法、getBroadcast()方法、还是 getService()方法。

每8小时开启广播

?
1 2 3 4 5 6 AlarmManager manager = (AlarmManager) getSystemService(ALARM_SERVICE); int anHour = 8 * 60 * 60 * 1000; // 这是8小时的毫秒数 long triggerAtTime = SystemClock.elapsedRealtime() + anHour; Intent i = new Intent(this, AutoUpdateReceiver. class); PendingIntent pi = PendingIntent.getBroadcast(this, 0, i, 0); manager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerAtTime, pi);

  

Intent传递数据和Bundle传递数据的区别

Intent传递数据和Bundle传递数据是一回事,

Intent传递时内部还是调用了Bundle。

以下为源码:

?
1 2 3 4 5 6 7 public Intent putExtra(String name, boolean value) {    if (mExtras == null ) {       mExtras = new Bundle();    }    mExtras.putBoolean(name, value);    return this; }

  

启动一个Activity有哪几种方法

startActivity

startActivityforresult

launcher的activity

widget

 


同一个程序,但不同的Activity是否可以放在不同的Task任务栈中?

启动模式里有个Singleinstance,可以运行在另外的单独的任务栈里面。用这个模式启动的activity,在内存中只有一份,这样就不会重复的开启。

也可以在激活一个新的activity时候, 给intent设置flag,Intent的flag添加FLAG_ACTIVITY_NEW_TASK,这个被激活的activity就会在新的task栈里面

 

Android中Task任务栈的分配。

1)任务栈的概念

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

答:一般情况下,有多少个应用正在运行,就对应开启多少个任务栈;每开启一个应用程序就会创建一个与之对应的任务栈;

栈:后进先出,最先进栈,就会最后出栈。Activity的启动模式就是修改任务栈的排列情况

2)任务栈的作用:

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

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

 首先来看下Task的定义,Google是这样定义Task的:Task实际上是一个Activity栈,通常用户感受的一个Application就是一个Task。从这个定义来看,Task跟Service或者其他Components是没有任何联系的,它只是针对Activity而言的。

 

 Activity的启动模式

standard 标准启动模式(自己启动自己会按三次才能退出)
singleTop 单一顶部模式
    • 如果任务栈的栈顶存在这个要开启的activity,不会重新的创建activity,而是复用已经存在的activity。如果栈顶没有或者不在栈顶,会重新创建
    • 会调用 onNewInstance(),复用已经存在的实例
    • 应用场景:singleTop适合接收通知启动的内容显示页面。例如,某个新闻客户端的新闻内容页面,如果收到10个新闻推送,每次都打开一个新闻内容页面是很耗内存的。
singeTask 单一任务栈,在当前任务栈里面只能有一个实例存在
  • 当开启activity的时候,就去检查在任务栈里面是否有实例已经存在,如果有实例存在就复用这个已经存在的activity,并且把这个activity上面的所有的别的activity都清空,复用这个已经存在的activity。保证整个任务栈里面只有一个实例存在
  • 会调用 onNewInstance(),复用已经存在的实例
  • 应用场景:
singleTask适合作为程序入口点,例如应用中的主页(Home页)。假设用户在主页跳转到其他页面,运行多次操作后想返回到主页,假设不使用SingleTask模式,在点击返回的过程中会多次看到主页,这明显就是设计不合理了。

singleInstance:activity会运行在自己的任务栈里面,并且这个任务栈里面只有一个实例存在
    • 如果你要保证一个activity在整个手机操作系统里面只有一个实例存在,使用singleInstance
    • 应用场景: 电话拨打界面
singleInstance适合需要与程序分离开的页面。例如我们有个需求,需要打开别的应用,这个时候如果不设置singleInstance的话,这个新打开的应用在我们程序的任务栈里,用户想要按任务键切换的话没法切换。

 

 

 

Android下的进程

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

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

会按照以下顺序杀死(进程级别):

①、空: 进程中没有任何组件(无组件启动,做进程缓存使用,恢复速度快),任务栈清空,意味着程序退出了,但进程留着,这个就是空进程,容易被系统回收;

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

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

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

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

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

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

 

 

 Service

什么是Service以及描述下它的生命周期。Service有哪些启动方法,有什么区别,怎样停用Service?

在Service的生命周期中,被回调的方法比Activity少一些,只有onCreate, onStartCommand, onDestroy, onBind和onUnbind。

通常有两种方式启动一个Service,他们对Service生命周期的影响是不一样的。

1 通过startService

    Service会经历 onCreate 到onStartCommand,然后处于运行状态,stopService的时候调用onDestroy方法。

   如果是调用者自己直接退出而没有调用stopService的话,Service会一直在后台运行。

2 通过bindService   

    Service会运行onCreate,然后是调用onBind, 这个时候调用者和Service绑定在一起。调用者退出了,Srevice就会调用onUnbind->onDestroyed方法。 所谓绑定在一起就共存亡了。调用者也可以通过调用unbindService方法来停止服务,这时候Srevice就会调用onUnbind->onDestroyed方法。

 

1.一旦在项目的任何位置调用了Context的startService()方法,相应的服务就会启动起来,并回调onStartCommand()方法。如果这个服务之前还没有创建过,onCreate()方法会先于onStartCommand()方法执行。
2.服务启动了之后会一直保持运行状态,直到 stopService()或stopSelf()方法被调用。注意虽然每调用一次startService()方法,onStartCommand()就会执行一次,但实际上每个服务都只会存在一个实例。所以不管调用了多少次startService()方法,只需调用一次stopService()或stopSelf()方法,服务就会停止下来了。

3.当调用了startService()方法后,又去调用 stopService()方法,这时服务中的 onDestroy()方法就会执行,表示服务已经销毁了。类似地,当调用了 bindService()方法后,又去调用unbindService()方法,onDestroy()方法也会执行,这两种情况都很好理解。但是需要注意,我们是完全有可能对一个服务既调用了startService()方法,又调用了bindService()方法的,这种情况下该如何才能让服务销毁掉呢?根据Android系统的机制,一个服务只要被启动或者被绑定了之后,就会一直处于运行状态,必须要让以上两种条件同时不满足,服务才能被销毁。所以,这种情况下要同时调用stopService()和unbindService()方法,onDestroy()方法才会执行 这样就把服务的生命周期完整地走了一遍。

start –> bind -> unbind -> stop 经常使用服务长期后台运行,又可以调用服务中的方法

 

service如何杀不死?

1.onStartCommand方法,返回START_STICKY(粘性)当service因内存不足被kill,当内存又有的时候,service又被重新创建

2.设置优先级,在服务里的ondestory里发送广播 在广播里再次开启这个服务,双进程守护

 

service是否在main thread中执行, service里面是否能执行耗时的操作?

默认情况,如果没有显示的指定service所运行的进程, Service和Activity是运行在当前app所在进程的main thread(UI主线程)里面

service里面不能执行耗时的操作(网络请求,拷贝数据库,大文件 ),需要在子线程中执行 new Thread(){}.start();

 

service里面可以弹土司吗

service里面弹toast需要添加到主线程里执行
?
1 2 3 4 5 6 7 8 9 10 @Override    public void onCreate(){        handler = new Handler(Looper.getMainLooper());                                System.out.println( "service started");       handler.post(new Runnable() {               @Override               public void run() {                  Toast.makeText(getApplicationContext(), "Test" ,Toast.LENGTH_SHORT).show();               });    }

 

Activity怎么和service绑定,怎么在activity中启动自己对应的service?

startService() 一旦被创建 调用着无关,没法使用service里面的方法

bindService () 把service 与调用者绑定 ,如果调用者被销毁, service会销毁,可以使用service 里面的方法

 

 Service与Activity怎么实现通信

方法一:

  1. 添加一个继承Binder的内部类,并添加相应的逻辑方法
  2. 重写Service的onBind方法,返回我们刚刚定义的那个内部类实例
  3. Activity中创建一个ServiceConnection的匿名内部类,并且重写里面的onServiceConnected方法和onServiceDisconnected方法,这两个方法分别会在活动与服务成功绑定以及解除绑定的时候调用,在onServiceConnected方法中,我们可以得到一个刚才那个service的binder对象,通过对这个binder对象进行向下转型,得到我们那个自定义的Binder实例,有了这个实例,做可以调用这个实例里面的具体方法进行需要的操作了

方法二
通过BroadCast(广播)的形式
当我们的进度发生变化的时候我们发送一条广播,然后在Activity的注册广播接收器,接收到广播之后更新视图

 

什么是IntentService?有何优点?

普通的service ,默认运行在ui main 主线程,Sdk给我们提供的方便的,带有异步处理的service类

OnHandleIntent() 处理耗时的操作,不需要开启子线程,这个方法已经在子线程中运行了

Intentservice若未执行完成上一次的任务,将不会新开一个线程,是等待之前的任务完成后,再执行新的任务,等任务完成后再次调用stopService()

 

startForeground(id , notification)

拥有service的进程具有较高的优先级。当内存不足时,拥有service的进程具有较高的优先级。

1. 如果service正在调用onCreate,  onStartCommand或者onDestory方法,那么用于当前service的进程相当于前台进程以避免被killed。

2. 如果当前service已经被启动(start),拥有它的进程则比那些用户可见的进程优先级低一些,但是比那些不可见的进程更重要,这就意味着service一般不会被killed.

3. 如果客户端已经连接到service (bindService),那么拥有Service的进程则拥有最高的优先级,可以认为service是可见的。

4. 如果service可以使用startForeground(int, Notification)方法来将service设置为前台状态,那么系统就认为是对用户可见的,并不会在内存不足时killed。

 

什么时候使用Service?

如果有其他的应用组件作为Service,Activity等运行在相同的进程中,那么将会增加该进程的重要性。

1.Service的特点可以让他在后台一直运行,可以在service里面创建线程去完成耗时的操作.

2.Broadcast receiver捕获到一个事件之后,可以起一个service来完成一个耗时的操作.

 

 

广播

请描述一下Broadcast Receiver。

Android中:系统在运行过程中,会产生会多事件,那么某些事件产生时,比如:电量改变、收发短信、拨打电话、屏幕解锁、开机,系统会发送广播,只要应用程序接收到这条广播,就知道系统发生了相应的事件,从而执行相应的代码。使用广播接收者,就可以收听广播

广播分两种:有序广播、无序广播

无序广播:无序广播不可中断,不能互相传递数据;

有序广播:一个接一个的传递,广播可中断,通过调用 abortBroadcast()方法;接收者之间可以传递数据(intent);

无序广播(标准广播)

  • 所有与广播中的action匹配的广播接收者都可以收到这条广播,并且是没有先后顺序,视为同时收到

有序广播

  • 所有与广播中的action匹配的广播接收者都可以收到这条广播,但是是有先后顺序的,按照广播接收者的优先级排序
    • 优先级的定义:-1000~1000
    • <intent-filter android:priority="100" >
      <action android:name="com.example.broadcasttest.MY_BROADCAST"/>
      </intent-filter> 
    • 最终接收者:所有广播接收者都接收到广播之后,它才接收,并且一定会接收
    • abortBroadCast:阻止其他接收者接收这条广播,类似拦截,只有有序广播可以被拦截

如何注册广播

广播的方式一般有两种,在代码中注册和在 AndroidManifest.xml中注册,其中前者也被称为动态注册,后者也被称为静态注册。 动态注册:需要使用广播接收者时,执行注册的代码,不需要时,执行解除注册的代码
  • 安卓中有一些广播接收者,必须使用代码注册,清单文件注册是无效的
  1. 屏幕锁屏和解锁
  2. 电量改变
?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public class MainActivity extends Activity {     private IntentFilter intentFilter;     private NetworkChangeReceiver networkChangeReceiver;     @Override     protected void onCreate(Bundle savedInstanceState) {            super.onCreate(savedInstanceState);            setContentView(R.layout.activity_main);            intentFilter = new IntentFilter();            intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE" );            networkChangeReceiver = new NetworkChangeReceiver();            registerReceiver(networkChangeReceiver, intentFilter);     }     @Override     protected void onDestroy() {            super.onDestroy();            unregisterReceiver(networkChangeReceiver);     }     class NetworkChangeReceiver extends BroadcastReceiver {             @Override             public void onReceive(Context context, Intent intent) {                  Toast.makeText(context, "network changes" ,Toast.LENGTH_SHORT).show();             }     } }
静态注册: 可以使用清单文件注册
  • 广播一旦发出,系统就会去所有清单文件中寻找,哪个广播接收者的action和广播的action是匹配的,如果找到了,就把该广播接收者的进程启动起来

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

 

避免使用隐式 Intent 广播(静态、动态注册)敏感信息,信息可能被其他注册了对应BroadcastReceiver 的 App 接收 
如果广播仅限于应用内,则可以使用 LocalBroadcastManager#sendBroadcast()实现,避免敏感信息外泄和 Intent 拦截的风险。  ?
1 2 3 Intent intent = new Intent("my-sensitive-event"); intent.putExtra("event", "this is a test event"); LocalBroadcastManager.getInstance(this).sendBroadcast(intent);

  

 

广播的生命周期

a. 广播接收者的生命周期非常短暂的,在接收到广播的时候创建,onReceive()方法结束之后销毁;

b. 广播接收者中不要做一些耗时的工作,否则会弹出 Application No Response错误对话框;

c. 最好也不要在广播接收者中创建子线程做耗时的工作,因为广播接收者被销毁后进程就成为了空进程,很容易被系统杀掉;

d. 耗时的较长的工作最好放在服务中完成;


 

 

内容提供者

请介绍下ContentProvider是如何实现数据共享的。

是四大组件之一,内容提供者的作用:把私有数据暴露给其他应用,通常,是把私有数据库的数据暴露给其他应用

应用的数据库是不允许其他应用访问的,内容提供者的作用就是让别的应用访问到你的数据库。

在清单文件中定义内容提供者的标签,注意必须要有authorities属性,这是内容提供者的主机名,功能类似地址

?
1 2 3 4 <provider android:name="com.itheima.contentprovider.PersonProvider"     android:authorities="com.itheima.person"     android:exported="true"  ></provider>

把自己的数据通过uri的形式共享出去,android 系统下不同程序,数据默认是不能共享访问 

需要去实现一个类去继承ContentProvider  

 

为什么要用ContentProvider?它和sql的实现上有什么差别?

屏蔽数据存储的细节,对用户透明,用户只需要关心操作数据的uri就可以了

不同app之间共享,操作数据

Sql也有增删改查的方法.

但是contentprovider 还可以去增删改查本地文件. xml文件的读取,更改,网络数据读取更改

 

 

请介绍下Android的数据存储方式

1.文件储存,在内部文件和SD卡

getCacheDir(),在data/data/包名/ cache

getFilesDir(),在data/data/包名/ files

SD卡:首先通过 File file = new File(Environment.getExternalStorageDirectory(), "info.txt"),然后通过io存储

2.SharedPreference 

3.SQLite数据库:
当应用程序需要处理的数据量比较大时,为了更加合理地存储、管理、查询数据,往往使用关系数据库来存储数据。Android系统的很多用户数据,如联系人信息,通话记录,短信息等,都是存储在SQLite数据库当中的,所以利用操作SQLite数据库的API可以同样方便的访问和修改这些数据。

4.ContentProvider:
主要用于在不同的应用程序之间实现数据共享的功能,不同于sharepreference和文件存储中的两种全局可读写操作模式,内容提供其可以选择只对哪一部分数据进行共享,从而保证我们程序中的隐私数据不会有泄漏的风险

 

 

Fragment

简单说说Fragment ?

用途:在一个Activity里切换界面,切换界面时只切换Fragment里面的内容

生命周期方法跟Activity一致,可以理解把其为就是一个Activity,与Activity同存亡,Activity的XX方法调用,Fragment的XX方法就调用

 

他们是怎么进行传递数据的?

活动传递给Fragment:为了方便碎片和活动之间进行通信, FragmentManager提供了一个类似于findViewById()的方法,专门用于从布局文件中获取碎片的实例,前提是自己在布局文件中定义fragment这个标签,代码如下所示:

RightFragment rightFragment = (RightFragment) getFragmentManager()
.findFragmentById(R.id.right_fragment);
 

调用 FragmentManager的 findFragmentById()方法,可以在活动中得到相应碎片的实例,然后就能轻松地调用碎片里的方法了。还有findViewByTag,在replace 的时候设置tag

或者在fragment里声明接口,然后activity获得fragment对象调用接口里的方法

 

fragment数据传递给活动,直接getActivity就可以调用活动里的方法了

activity给fragment传递数据一般不通过fragment的构造方法来传递,会通过setArguments来传递,因为当横竖屏会调用fragment的空参构造函数,数据丢失。

 

fragment和 fragment数据传递

首先在一个fragment可以得到与它相关联的活动,然后再通过这个活动去获取另外一个 fragment的实例,这样也就实现了不同fragment之间的通信功能

 

FragmentManager , add 和 replace 有什么区别?

  • 使用 FragmentTransaction 的时候,它提供了这样两个方法,一个 add , 一个 replace ,add 和 replace 影响的只是界面,而控制回退的,是事务。
  • add 是把一个fragment添加到一个容器 container 里。replace 是先remove掉相同id的所有fragment,然后在add当前的这个fragment。
  • 在大部分情况下,这两个的表现基本相同。因为,一般,咱们会使用一个FrameLayout来当容器,而每个Fragment被add 或者 replace 到这个FrameLayout的时候,都是显示在最上层的。所以你看到的界面都是一样的。但是, 使用add的情况下,这个FrameLayout其实有2层,多层肯定要比一层的来得浪费,所以还是推荐使用replace。 当然有时候还是需要使用add的。比如要实现轮播图的效果,每个轮播图都是一个独立的Fragment,而他的容器FrameLayout需要add多个Fragment,这样他就可以根据提供的逻辑进行轮播了。 而至于返回键的时候,这个跟事务有关,跟使用add还是replace没有任何关系。
  • replace()方法会将被替换掉的那个Fragment彻底地移除掉,因此最好的解决方案就是使用hide()和show()方法来隐藏和显示Fragment,这就不会让Fragment的生命周期重走一遍了。

 

Fragment和view的区别

Fragment可以 有效的对 view进行管理(增删和替换)而且结构更加清晰,有模块化的实现思想。

用view 很多逻辑代码可能都需要写在Activity里,如果view很多, 耦合度会很高。用Fragment则可以各自管理,起了解耦的作用。

一般软件简单的话直接view,复杂的用Fragment。

viewpager是一个滑动切换的控件,Fragment 是一个轻量级的Activity,这个Fragment可以放到这个Viewpager里面去运行。

例如QQ或微信那样,可以来回切换不同的选项卡,即切换了不同的Fragment。 通常 Viewpager 会放 fargment或者view

 

Fragment和Activity的区别

因为现在的手机屏幕都比较大了,Activity会把内容拉的很长,用Fragment的话可以左侧是列表,右侧是内容。 在一个Activity里切换界面,切换界面时只切换Fragment里面的内容。Fragment通常用来作为一个activity界面的一部分。

 

view

请介绍下Android中常用的五种布局。

FrameLayout(帧布局),LinearLayout (线性布局),AbsoluteLayout(绝对布局),RelativeLayout(相对布局),TableLayout(表格布局)

  • FrameLayout:从屏幕的左上角开始布局,叠加显示, 实际应用 播放器的暂停按钮. 
  • LinearLayout:线性布局,他首先是一个一个从上往下罗列在屏幕上。每一个LinearLayout里面又可分为垂直布局、水平布局。当垂直布局时,每一行就只有一个元素,多个元素依次垂直往下;水平布局时,只有一行,每一个元素依次向右排列。
  • AbsoluteLayout:用X,Y坐标来指定元素的位置android:layout_x="20px" ,android:layout_y="12px" ,指定平板机型的游戏开发、机顶盒开发中经常用到绝对布局
  • RelativeLayout:在相对的布局中主要就进行避免覆盖的问题,就是组件1可能会覆盖在组件2上(屏幕适配),在相对的布局中主要就进行避免覆盖的问题,就是组件1可能会覆盖在组件2上
  • TableLayout:有几行,就有几个<TableRow/>,有几列,那么在<TableRow>中就有几个<TestView>,TableRow的子节点的宽和高是包裹内容,不需要指定宽高
  

TextView 、ImageView ,Button,ImageButton他们之间的联系和区别

ImageView 控件负责显示图片,其图片来源既可以是资源文件的id,也可以是Drawable对象或 Bitmap 对象,还可以是 内容提供者(Content Provider)的Uri.

ImageButton 控件 继承自 ImageView。与Button相同之处:都用于响应按钮的点击事件

不同之处:ImageButton只能显示图片;Button用于显示文字

 

屏幕适配

  • 开发时选取主流屏幕 1280*720,用相对布局和线性布局
  • 用dp sp不用px,dp单位动态匹配
  • 开发后期在不同的分辨率上测试,没有太大问题可以上线
  • 权重适配:weight 只有线性布局有
  • 代码适配:getWindowManager().getDefaultDisplay().getWidth();获得屏幕的宽高
  • 如果屏幕放不下了,可以使用 ScrollView(可以上下拖动)
  • 布局适配:layout-800x180 针对某一种屏幕 工作量大
  • 尺寸适配:dp=px/设备密度=getResource().getDisplayMetrice().dsnsity;
  • 根据不同分辨率的屏幕建立不同的valuse,比如valuse-1280x720,values里的dimens里算出dp,最后引用系统会自动匹配。
(约等于)320*240(0.5) 480*320(1) 480*800(1.5) 1280*720(2)就不用布局适配了
  • 在进行开发的时候,我们需要把合适大小的图片放在合适的文件夹里面。大分辨率图片(单维度超过 1000)大分辨率图片建议统一放在 xxhdpi 目录下管理,否则将导致占用内存成倍数增加。
说明: 为了支持多种屏幕尺寸和密度,Android 为多种屏幕提供不同的资源目录进行适配。为不同屏幕密度提供不同的位图可绘制对象,可用于密度特定资源的配置限定符(在下面详述) 包括 ldpi(低)、 mdpi(中)、 hdpi(高)、 xhdpi(高)、 xxhdpi (超超高)和 xxxhdpi(超超超高)。例如,高密度屏幕的位图应使用 drawable-hdpi。根据当前的设备屏幕尺寸和密度,将会寻找最匹配的资源,如果将高分辨率图片放入低密度目录,将会造成低端机加载过大图片资源,又可能造成 OOM,同时也是资源浪费,没有必要在低端机使用大。

 

RelativeLayout和FrameLayout的区别

FrameLayout主要是在多层之间的布局,RelativeLayout则是在同层之间不同位置之间的布局,效果上没有什么大的区别,都可以实现,只是看哪种实现更容易。比如我们收到信息了,我们可以在信息图标上增加FrameLayout 用于显示气泡。

 

Padding和Margin有什么区别?

Padding 是控件内容的距离margin是控件和控件间的距离

 

Listview如何显示不同条目?

第一种:在getview方法里根据position 进行判断,比如,position等于0是,显示标题,否则显示数据,当然相应的setOnItemClickListener也要去判断

第二种:根据listview里的getItemViewType,getViewTypeCount方法显示不同条目

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 /** 根据位置 判断当前条目是什么类型 */ @Override public int getItemViewType(int position) {  //20         if (position == datas.size()) { // 当前是最后一个条目         return MORE_ITEM;     }     return getInnerItemViewType(position); // 如果不是最后一个条目 返回默认类型 } private int getInnerItemViewType(int position) {     return DEFAULT_ITEM; } /** 当前ListView 有几种不同的条目类型 */ @Override public int getViewTypeCount() {     return super.getViewTypeCount() + 1; // 2 有两种不同的类型 }

  

ScrowView 使用的注意

在不同的屏幕上显示内容不同的情况,其实这个问题我们往往是用滚动视图来解决的,也就是 ScrowView,

需要注意的是 ScrowView 中使用 layout_weight 是无效的,既然使用 ScrowView 了,就把它里面的控件的大小都设成固定的吧。

 

Android UI中的View如何刷新

在主线程中 拿到view调用Invalide()方法,在子线程里面可以通过postInvalide()方法;

invalidate();//主线程,刷新当前视图 导致 执行onDraw执行

postInvalidate();//子线程

 

什么是ANR(异步加载) 如何避免它?

android在主线程是不能加载网络数据或图片、数据库查询、复杂业务逻辑处理以及费时任务操作,因为Android的UI操作并不是线程安全的,并且所有涉及UI的操作必须在UI线程中完成。Android应用在5s内无响应的话会导致ANR(Application Not Response),这就要求开发者必须遵循两条法则:1、不能阻塞UI线程,2、确保只在UI线程中访问Android UI工具包。

Activity 5秒 broadcast10秒,服务20秒,耗时的操作在主thread里面完成

解决办法:Thread + Handler + Message ,Thread + Handler + post,AsyncTask,intentservice

runOnUiThread(Runnable)在子线程中直接使用该方法,可以更新UI

 

实现侧边栏、和指示器效果、页面滑动有几种方式

侧边栏:自定义、slidingmenu、DrawerLayout 、SlidingDrawer

指示器效果:自定义、viewpager里面的PagerTabStrip、ActionBar Tab标签、viewpagerindicate、FragmentTabHost、TabActivity、radiobutton

页面滑动:自定义、viewpager、手势识别器,其实就是onTouchEvent提供的简单工具类,onTouchEvent将触摸事件委托给了手势识别器

 

 

其他

Gradle中buildToolsVersion和TargetSdkVersion的区别是什么

compileSdkVersion, minSdkVersion 和 targetSdkVersion 的作用:他们分别控制可以使用哪些 API ,要求的 API 级别是什么,以及应用的兼容模式。TargetSdkVersion 设为23那么是按6.0设置的(运行时权限),小于23是按6.0以前的方式(安装时默认获得权限,且用户无法在安装App之后取消权限)

 

进程间怎么通信

binder是安卓中的一个类,它实现了IBinder接口,是安卓中跨进程通信的方式。当绑定服务的时候会返回一个binder对象,然后通过他进行多进程间的通信。

其实进程间通信就是为了实现数据共享。一个程序不同组件在不同进程也叫多进程,和俩个应用没有本质区别。使用process属性可以实现多进程,但是会带来很多麻烦,主要原因是共享数据会失败,弊端有:静态和单利失效,同步失效,sharedprefer可靠性减低等问题

Intent,Binder(AIDL),sharedpre、Messenger

  • 使用intent的附加信息extras来传递,通过bundle,传递的是bundle支持的类型,比如基本数据类型、实现pracellable或serializeable的对象
  • 使用文件共享,序列化或是sharedpre,不过不适用于读写并发的操作
  • 通过message进行传递,在远程服务里创建message对像,在onbind里返回(message.getbinder)。在客户端绑定服务,拿着message对象发消息(可以用bundle)。在远程服务的handlermessage方法就会收到。他是一个个处理的,如果大量并发请求用aidl,mrssage底层就是aidl
  • AIDL
  • socket可以实现俩个终端通信,也可以在一个设备的俩个进程通信。需要在服务里创建服务端
  • ContentProvider(进程间数据共享)和message一样,底层也是binder,除了oncreate方法其他方法(crud)都是运行在bindler线程里。所以在oncerate里不能做耗时操作。在其他应用访问通过uri(主机名),例如gercontentResolver.query(uri,null,null....)
  • 有以上6种

使用多进程显而易见的好处就是分担主进程的内存压力。我们的应用越做越大,内存越来越多,将一些独立的组件放到不同的进程,它就不占用主进程的内存空间了。当然还有其他好处,有心人会发现Android后台进程里有很多应用是多个进程的,因为它们要常驻后台,特别是即时通讯或者社交应用,不过现在多进程已经被用烂了。典型用法是在启动一个不可见的轻量级私有进程,在后台收发消息,或者做一些耗时的事情,或者开机启动这个进程,然后做监听等。还有就是防止主进程被杀守护进程,守护进程和主进程之间相互监视,有一方被杀就重新启动它。

 

假设手机本地需要缓存数据,如何保证和服务器的数据统一?

  • 比如有个网络更新的功能,activity可以每隔半小时开启service去访问服务器,获取最新的数据。

  • 在缓存文件里面加入时间戳,根据实际情况在一定的时间差内再次访问网络数据、判断URL

在缓存的第一行写一个上当前时间,读的时候判断是不是过期,根据需求看需要多久跟新

 

分页怎么做的?

分页根据服务器接口参数决定每次加载多少,getviewtype,getitemview

分批处理 解决的是时间等待的问题,不能解决内存占用的问题。

要想解决内存占用问题,可以采用分页方式

 

什么Context

Android工程环境中像Activity、Service、BroadcastReceiver等系统组件,而这些组件并不是像一个普通的Java对象new一下就能创建实例的了,而是要有它们各自的上下文环境,这就是Context。可以这样讲,Context是维持Android程序中各组件能够正常工作的一个核心功能类。

Context功能很多,可以弹出Toast、启动Activity、启动Service、发送广播、操作数据库等等等等都需要用到Context。

Context一共有Application、Activity和Service三种类型:Context数量 = Activity数量 + Service数量 + 1 。1代表着Application的数量,因为一个应用程序中可以有多个Activity和多个Service,但是只能有一个Application。 Application通常作为工具类来使用的,Application中在onCreate()方法里去初始化各种全局的变量数据是一种比较推荐的做法,但是如果你想把初始化的时间点提前到极致,也可以去重写attachBaseContext()方法。不能写在构造函数里。

 

Android程序与Java程序的区别?

Android程序用android sdk开发,java程序用javasdk开发.

Android SDK引用了大部分的Java SDK,少数部分被Android SDK抛弃,比如说界面部分,java.awt swing package除了java.awt.font被引用外,其他都被抛弃,在Android平台开发中不能使用。 Android sdk 添加工具jar httpclient , pull opengl

 

Android中的动画有哪几类,它们的特点和区别是什么?

三种:补间动画、帧动画、属性动画。 补间动画是放置到res/anim/下面 帧动画是放置到res/drawable/下面,子节点为animation-list,在这里定义要显示的图片和每张图片的显示时长 补间动画
  • 如果动画中的图像变换比较有规律时,例如图像的移动(TranslateAnimation)、旋转(RotateAnimation)、缩放(ScaleAnimation)、透明度渐变(AlphaAnimation),这些图像变化过程中的图像都可以根据一定的算法自动生成,我们只需要指定动画的第一帧和最后一帧图像即可,这种自动生成中间图像的动画就是补间动画。
  • 补间动画,只是一个动画效果,组件其实还在原来的位置上,xy没有改变
帧动画:传统的动画方法,通过顺序的播放排列好的图片来实现,类似电影,一张张图片不断的切换,形成动画效果,要自己指定每一帧

属性动画:动画的对象除了传统的View对象,还可以是Object对象,动画结束后,Object对象的属性值被实实在在的改变了

 

Android工程的目录结构 

src:项目的java代码

assets:资源文件夹,存放视频或者音乐等较大的资源文件

bin:存放应用打包编译后的文件

res:资源文件夹,在这个文件夹中的所有资源,都会有资源id,读取时通过资源id就可以读取

     资源id不能出现中文

 

coverview原理:

这个参数用于将之前加载好的布局进行缓存,以便之后可以进行重用,在 getView()方法中进行了判断,如果 convertView为空,则使用

LayoutInflater去加载布局,如果不为空则直接对 convertView进行重用

 

android的启动流程

当程序启动Linux内核后,会加载各种驱动和数据结构,当有了驱动以后,开始启动Android系统同时会加载用户级别的第一个进程init(system\core\init.c),加载init.rc文件,会启动一个Zygote进程,此进程是Android系统的一个母进程,用来启动Android的其他服务进程,然后接着在里面启动各种硬件服务和activity。Android系统启动完成,打开了Luncher应用的Home界面。

 

Logcat

1. Log.v()

这个方法用于打印那些最为琐碎的,意义最小的日志信息。对应级别 verbose,是Android日志里面级别最低的一种。

2. Log.d()

这个方法用于打印一些调试信息, 这些信息对你调试程序和分析问题应该是有帮助的。对应级别 debug,比 verbose高一级。

3. Log.i()

这个方法用于打印一些比较重要的数据,这些数据应该是你非常想看到的,可以帮你分析用户行为的那种。对应级别 info,比 debug高一级。

4. Log.w()

这个方法用于打印一些警告信息,提示程序在这个地方可能会有潜在的风险,最好去修复一下这些出现警告的地方。对应级别 warn,比 info高一级。

5. Log.e()

这个方法用于打印程序中的错误信息,比如程序进入到了 catch语句当中。当有错误信息打印出来的时候,一般都代表你的程序出现严重问题了,必须尽快修复。对应级别 error,比 warn高一级.

 

推送

所有需要客户端被动接收信息的功能模块,都可以用推送实现,比如A想给B发消息,A调服务器接口,服务器只是存数据,它调推送的接口,推送去去找B。推送用的是xmpp协议, 是一种基于TCP/IP的协议, 这种协议更适合消息发送

- socket 套接字, 发送和接收网络请求
- 长连接 keep-alive, 服务器基于长连接找到设备,发送消息
- 心跳包 , 客户端会定时(30秒一次)向服务器发送一段极短的数据,作为心跳包, 服务器定时收到心跳,证明客户端或者,才会发消息.否则将消息保存起来,等客户端活了之后(重新连接),重新发送.

 

 由于篇幅原因,附俩篇优化和自定义控件常见的面试题,希望对大家有所帮助:

Android优化指南

Android自定义控件概念总结

 

 

       

 

评论