Android 八股文(一)

基础篇

Activity

什么是Activity?

四大组件之一,一般的,一个用户交互界面对应一个 activity

setContentView() ,// 要显示的布局

button.setOnclickLinstener{}, activity 是 Context 的子类,同时实现了 window.callback 和 keyevent.callback, 可以处理与窗体用户交互的事件.

我开发常用的的有 FragmentActivitiyListActivity ,PreferenceActivity ,TabAcitivty 等…

如果界面有共同的特点或者功能的时候,还会自己定义一个 BaseActivity.

Activity 生命周期

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

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

如何保存 Activity 的状态或者 ( Activiy 重启怎么保存数据?)

Activity 的状态通常情况下系统会自动保存的,只有当我们需要保存额外的数据时才需要使用到这样的功能。

一般来说, 调用onPause()和onStop()方法后的activity 实例仍然存在于内存中, activity 的所有信息和状态数据不会消失, 当 activity 重新回到前台之后, 所有的改变都会得到保留。但是当系统内存不足时, 调用onPause()和onStop()方法后的activity 可能会被系统摧毁, 此时内存中就不会存有该activity 的实例对象了。

如果之后这个activity 重新回到前台, 之前所作的改变就会消失。为了避免此种情况的发生, 我们可以覆写 onSaveInstanceState()方法。

onSaveInstanceState()方法接受一个 Bundle 类型的参数, 开发者可以将状态数据存储到这个 Bundle 对象中, 这样即使 activity 被系统摧毁, 当用户重新启动这个 activity 而调用它的onCreate()方法时, 上述的Bundle 对象会作为实参传递给 onCreate()方法, 开发者可以从 Bundle 对象中取出保存的数据, 然后利用这些数据将 activity 恢复到被摧毁之前的状态。

需要注意的是, onSaveInstanceState()方法并不是一定会被调用的, 因为有些场景是不需要保存状态数据的. 比如用户按下 BACK 键退出 activity 时, 用户显然想要关闭这个 activity, 此时是没有必要保存数据以供下次恢复的, 也就是onSaveInstanceState()方法不会被调用. 如果调用 onSaveInstanceState()方法, 调用将发生在 onPause()或 onStop()方法之前。

1
2
3
4
5
@Override
protected void onSaveInstanceState(Bundle outState) {
// TODO Auto-generated method stub
super.onSaveInstanceState(outState);
}

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

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

  1. 通常情况用户退出一个 Activity 只需按返回键,我们写代码想退出 activity 直接调用 finish()方法就行。

  2. 记录打开的 Activity:

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

1
2
3
4
5
6
7
8
//伪代码
List<Activity> lists ;// 在 application 全局的变量里面
lists = new ArrayList<Activity>();
lists.add(this);
for(Activity activity: lists) {
activity.finish();
}
lists.remove(this);
  1. 发送特定广播:
    在需要结束应用时,发送一个特定的广播,每个 Activity 收到广播后,关闭即可。
1
2
3
4
5
6
7
8
9
10
11
//给某个 activity 注册接受接受广播的意图
//伪代码
List<Activity> lists ;// 在 application 全局的变量里面
lists = new ArrayList<Activity>();
lists.add(this);
for(Activity activity: lists) {
activity.finish();
}
lists.remove(this);
registerReceiver(receiver, filter)
//如果过接受到的是 关闭 activity 的广播 就调用 finish()方法 把当前的activity finish()掉
  1. 递归退出
    在打开新的 Activity 时使用 startActivityForResult,然后自己加标志,在onActivityResult 中处理,递归关闭。

  2. 通过 intent 的 flag 来实现
    intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)激活一个新的 activity。

此时如果该任务栈中已经有该 Activity,那么系统会把这个 Activity 上面的所有Activity 干掉。其实相当于给 Activity 配置的启动模式为 SingleTop。

Activity 的 四 种 启 动 模 式 , singletop 和singletask 区别是什么?一般书签的使用模式是singletop,那为什么不使用 singletask?

singleTop 跟 standard 模式比较类似。唯一的区别就是,当跳转的对象是位于栈顶的 activity(应该可以理解为用户眼前所 看到的 activity)时,程序将不会生成一个新的activity 实例,而是直接跳到现存于栈顶的那个 activity 实例。

拿上面的例子来说,当 Act1 为 singleTop 模式时,执行跳转后栈里面依旧只有一个实例,如果现在按返回键程序将直接退出。

singleTask 模式和 singleInstance 模式都是只创建一个实例的。在这种模式下,无论跳转的对象是不是位于栈顶的 activity,程序都不会生成一个新的实例(当然前提是栈里面已经有这个实例)。这种模式相当有用,在以后的多activity 开发中,常会因为跳转的关系导致同个页面生成多个实例,这个在用户体验上始终有点不好,而如果你将对应的 activity 声明为 singleTask 模式,这种问题将不复存在。在主页的 Activity 很常用

Android 中的 Context, Activity,Appliction 有什么区别?

  • 相同:
    Activity 和 Application 都是 Context 的子类。

Context 从字面上理解就是上下文的意思,在实际应用中它也确实是起到了管理上下文环境中各个参数和变量的总用,方便我们可以简单的访问到各种资源。

  • 不同:
    维护的生命周期不同。 Context 维护的是当前的 Activity 的生命周期,Application 维护的是整个项目的生命周期。

使用 context 的时候,小心内存泄露,防止内存泄露,注意一下几个方面:

  1. 不要让生命周期长的对象引用 activity context,即保证引用 activity 的对象
    要与 activity 本身生命周期是一样的。
  2. 对于生命周期长的对象,可以使用 application,context。
  3. 避免非静态的内部类,尽量使用静态类,避免生命周期问题,注意内部类对外部对象引用导致的生命周期变化。

两个Activity 之间传递数据,除了 intent,广播接收者,content provider 还有啥?

  1. 利用 static 静态数据,public static 成员变量

  2. 利用外部存储的传输,
    例如 File 文件存储
    SharedPreferences 首选项
    Sqlite 数据库

Context 是 什 么 ? , 一 个 应 用有多少个Context?

  1. 它描述的是一个应用程序环境的信息,即上下文。
  2. 该类是一个抽象(abstract class)类,Android 提供了该抽象类的具体实现类(ContextIml)。
  3. 通过它我们可以获取应用程序的资源和类,也包括一些应用级别操作,例如:启动一个 Activity,发送广播,接受 Intent,信息,等。
  4. 一个应用 Context 数量=Activity 个数+service 个数 +1 个

activity 之间, 还有 activity 和 service 之间如何传值

  • Activity 之间的传值:
  1. startActivity,通过 Intent 对象的各种 putExtra 方法来进行传递。

在第二个 Activity 对象中,可以通过 getIntent() 方法来得到跳转到这个 Activity 的 Intent 对象,然后通过 Intent 对象的各种 getXXExtra 方法来得到我们的传过来的值。

  1. SharedPreferences

使用 SharedPreferences 存储数据,可以在不同的 Activity 中读取这些数据

1
2
3
4
5
6
7
8
9
// 存储数据
val sharedPref = getSharedPreferences("my_prefs", Context.MODE_PRIVATE)
with(sharedPref.edit()) {
putString("key", "value")
apply()
}

// 读取数据
val value = sharedPref.getString("key", null)
  • Activity 和 service 之间传值:
  1. 在 activity 中通过 startService(intent)即可,同样 intent.putStringExtra(),然后再service中的onStart函数中获取该值,

this.getIntent(),intent.getString()
在这里我们需要在 Mainfeist 文件中注册这个 service

1
2
<service Android:enabled="true" android:name=".Service">
</service>
  1. service 可以从 public int onStartCommand(Intent intent, int flags, intstartId)中取出从 activity 中传过来的值。intent.getExtra()获得 bundle 对象,可从中取值。

  2. activity 也可以用 bindService(intent, conn,BIND_AUTO_CREATE);传值,把要传的值绑定在 intent 里,在 service 的 public IBinder onBind(Intentintent) 方法里取得 intent。

  3. 同时也可以在 reseiver 里面注册一个广播,在 activity 里sendbroadcast(intent)传值。可以传递图片,用 Intent 把图片的地址或者把图片对象用 Intent 传过去,用bitmap 对象

Service

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

默认情况,如果没有显示的指 servic 所运行的进程, Service 和 activity 是运行在当前 app 所在进程的 main thread(UI 主线程)里面。service 里面不能执行耗时的操作(网络请求,拷贝数据库,大文件 )特殊情况 ,可以在清单文件配置 service 执行所在的进程 ,让 service 在另外的进程中执行

1
2
3
4
<service
android:name="com.baidu.location.f"
android:enabled="true"
android:process=":remote"/>

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

Activity 通 过 bindService(Intent service, ServiceConnection conn, int flags)跟 Service 进行绑定,当绑定成功的时候 Service 会将代理对象通过回调的形式传给 conn,这样我们就拿到了 Service 提供的服务代理对象。在 Activity 中可以通过 startService 和 bindService 方法启动 Service。一般情况下如果想获取 Service 的服务对象那么肯定需要通过 bindService()方法,比如音乐播放器,第三方支付等。如果仅仅只是为了开启一个后台任务那么可以使用 startService()方法。

请描述一下 Service 的生命周期

Service 有绑定模式和非绑定模式,以及这两种模式的混合使用方式。不同的使用方法生命周期方法也不同。

非绑定模式: 当第一次调用 startService 的时候执行的方法依次为onCreate()、onStartCommand(),当 Service 关闭的时候调用 onDestory 方法。

绑定模式:第一次 bindService()的时候,执行的方法为 onCreate()、onBind() 解除绑定的时候会执行 onUnbind()、onDestory()。

上面的两种生命周期是在相对单纯的模式下的情形。我们在开发的过程中还必须注意 Service 实例只会有一个,也就是说如果当前要启动的 Service 已经存在了那么就不会再次创建该 Service 当然也不会调用 onCreate()方法。

一个 Service 可以被多个客户进行绑定,只有所有的绑定对象都执行了onBind()方法后该 Service 才会销毁,不过如果有一个客户执行了 onStart() 方法,那么这个时候如果所有的 bind 客户都执行了 unBind()该 Service 也不会销毁。

Service 的生命周期图如下所示,帮助大家记忆。

png

什么是IntentService?有何优点?

  1. IntentService 简介
    IntentService 是Service 的子类,比普通的Service 增加了额外的功能。先看Service 本身存在两个问题:
    Service不会专门启动一条单独的进程,Service与它所在应用位于同一个进程中;
    Service 也不是专门一条新线程,因此不应该在Service 中直接处理耗时的任务;

  2. IntentService 特征
    会创建独立的worker 线程来处理所有的Intent 请求;
    会创建独立的worker线程来处理onHandleIntent()方法实现的代码,无需处理多线程问题;

所有请求处理完成后,IntentService会自动停止,无需调用stopSelf()方法停止Service;

为Service 的onBind()提供默认实现,返回null;

为Service的onStartCommand提供默认实现,将请求Intent添加到队列中;

使用IntentService
本人写了一个IntentService 的使用例子供参考。该例子中一个
MainActivity 一个MyIntentService,这两个类都是四大组件当然需要在清单文件中注册。这里只给出核心代码:

MainActivity.java:

1
2
3
4
5
public void click(View view){
Intent intent = new Intent(this, MyIntentService.class);
intent.putExtra("start", "MyIntentService");
startService(intent);
}

MyIntentService.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class MyIntentService extends IntentService {
private String ex = "";
private Handler mHandler = new Handler() {
public void handleMessage(android.os.Message msg) {
Toast.makeText(MyIntentService.this, "-e " + ex, Toast.LENGTH_LONG).show();
}
};
}
public MyIntentService(){
super("MyIntentService");
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
ex = intent.getStringExtra("start");
return super.onStartCommand(intent, flags, startId);
}

@Override
protected void onHandleIntent(Intent intent) {
try {
Thread.sleep(1000);
}
catch (InterruptedException e) {
e.printStackTrace();
}
mHandler.sendEmptyMessage(0);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}

Activity、Intent、Service是什么关系

他们都是Android 开发中使用频率最高的类。其中Activity 和Service 都是Android四大组件之一。他俩都是Context类的子类ContextWrapper的子类,因此他俩可以算是兄弟关系吧。不过兄弟俩各有各自的本领,Activity 负责用户界面的显示和交互,Service负责后台任务的处理。Activity和Service之间可以通过Intent传递数据,因此可以把Intent看作是通信使者。

Service和Activity在同一个线程吗

对于同一app来说默认情况下是在同一个线程中的,main Thread(UI Thread)。

7.Service里面可以弹Toast吗

可以的。toast有个条件就是得有一个Context上下文,而Service本身就是Context的子类,因此在Service里面弹toast是完全可以的。比如我们在Service 中完成下载任务后可以弹一个toast通知用户。

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

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

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

  1. 通过startServiceService 会经历onCreate 到onStart,然后处于运行状态,stopService 的时候调用onDestroy 方法。如果是调用者自己直接退出而没有调用stopService 的话,Service 会一直在后台运行。

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

需要注意的是如果这几个方法交织在一起的话,会出现什么情况呢?

一个原则是Service 的onCreate 的方法只会被调用一次,就是你无论多少次的startService 又bindService,Service 只被创建一次。如果先是bind了,那么start的时候就直接运行Service的onStart方法,如果先是start,那么bind的时候就直接运行onBind方法。

如果service 运行期间调用了bindService,这时候再调用stopService 的话,service 是不会调用onDestroy 方法的,service 就stop 不掉了,只能调用UnbindService, service 就会被销毁如果一个service 通过startService 被start 之后,多次调用startService 的话,service会多次调用onStart方法。多次调用stopService的话,service 只会调用一次onDestroyed方法。如果一个service 通过bindService 被start 之后,多次调用bindService 的话,service 只会调用一次onBind 方法。多次调用unbindService 的话会抛出异常。

在service的生命周期方法onstartConmand()可不可以执行网络操作?如何在service中执行网络操作?

ServiceonStartCommand() 方法中确实可以执行网络操作,但由于网络操作可能会阻塞主线程,这通常是不推荐的。正确的方法是在后台线程中进行网络请求。你可以使用以下几种方法来执行网络操作:

  1. 使用 AsyncTask(适用于简单的异步任务):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    class MyService : Service() {
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
    AsyncTask.execute {
    // 在后台线程中执行网络操作
    performNetworkRequest()
    }
    return START_STICKY
    }

    private fun performNetworkRequest() {
    // 实现网络请求
    }
    }
  2. 使用 HandlerThread(适用于需要更复杂线程管理的情况):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    class MyService : Service() {
    private val handlerThread = HandlerThread("NetworkThread")
    private lateinit var handler: Handler

    override fun onCreate() {
    super.onCreate()
    handlerThread.start()
    handler = Handler(handlerThread.looper)
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
    handler.post {
    // 在后台线程中执行网络操作
    performNetworkRequest()
    }
    return START_STICKY
    }

    private fun performNetworkRequest() {
    // 实现网络请求
    }

    override fun onDestroy() {
    super.onDestroy()
    handlerThread.quitSafely()
    }
    }
  3. 使用 ExecutorService(适用于更高效的线程池管理):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    class MyService : Service() {
    private val executor = Executors.newSingleThreadExecutor()

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
    executor.execute {
    // 在后台线程中执行网络操作
    performNetworkRequest()
    }
    return START_STICKY
    }

    private fun performNetworkRequest() {
    // 实现网络请求
    }

    override fun onDestroy() {
    super.onDestroy()
    executor.shutdown()
    }
    }
  4. 使用 WorkManager(适用于需要保证任务执行的场景):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    class MyService : Service() {
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
    val workRequest = OneTimeWorkRequest.Builder(NetworkWorker::class.java).build()
    WorkManager.getInstance(this).enqueue(workRequest)
    return START_STICKY
    }

    override fun onBind(intent: Intent?): IBinder? {
    return null
    }
    }

    class NetworkWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
    override fun doWork(): Result {
    // 执行网络操作
    performNetworkRequest()
    return Result.success()
    }

    private fun performNetworkRequest() {
    // 实现网络请求
    }
    }

选择合适的方法取决于你的需求,比如是否需要任务保证执行(WorkManager),还是只是简单的后台任务(AsyncTaskHandlerThreadExecutorService)。

Broadcast Receiver

请描述一下 BroadcastReceiver

BroadCastReceiver 是 Android 四大组件之一,主要用于接收系统或者app 发送的广播事件。

广播分两种:有序广播和无序广播。

内部通信实现机制:通过 Android 系统的 Binder 机制实现通信。

无序广播:完全异步,逻辑上可以被任何广播接收者接收到。优点是效率较高。缺点是一个接收者不能将处理结果传递给下一个接收者,并无法终止广播 intent 的传播。

有序广播:按照被接收者的优先级顺序,在被接收者中依次传播。比如有三个广播接收者 A,B,C,优先级是 A > B > C。那这个消息先传给 A,再传给 B,最后传给 C。每个接收者有权终止广播,比如 B 终止广播,C 就无法接收到。此外A 接收到广播后可以对结果对象进行操作,当广播传给 B 时,B 可以从结果对象中取得 A 存入的数据。

在通过 Context.sendOrderedBroadcast(intent, receiverPermission,resultReceiver, scheduler, initialCode, initialData, initialExtras)时我们可以指定 resultReceiver 广播接收者,这个接收者我们可以认为是最终接收者,通常情况下如果比他优先级更高的接收者如果没有终止广播,那么他的 onReceive 会被执行两次,第一次是正常的按照优先级顺序执行,第二次是作为最终接收者接收。如果比他优先级高的接收者终止了广播,那么他依然能接收到广播。在我们的项目中经常使用广播接收者接收系统通知,比如开机启动、sd 挂载、低电量、外播电话、锁屏等。

如果我们做的是播放器,那么监听到用户锁屏后我们应该将我们的播放之暂停等。

在 manifest 和代码中如何注册和使用 BroadcastReceiver

在清单文件中注册广播接收者称为静态注册,在代码中注册称为动态注册。

静态注册的广播接收者只要 app 在系统中运行则一直可以接收到广播消息,动态注册的广播接收者当注册的Activity 或者Service 销毁了那么就接收不到广播了。
静态注册:在清单文件中进行如下配置

1
2
3
4
5
6
<receiver android:name=".BroadcastReceiver1" >
<intent-filter>
<action android:name="android.intent.action.CALL" >
</action>
</intent-filter>
</receiver>

动态注册:在代码中进行如下注册

1
2
3
4
receiver = new BroadcastReceiver(); 
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(CALL_ACTION);
context.registerReceiver(receiver, intentFilter);

BroadCastReceiver 的生命周期

  1. 广播接收者的生命周期非常短暂的, 在接收到广播的时候创建,
    onReceive()方法结束之后销毁;
  2. 广播接收者中不要做一些耗时的工作, 否则会弹出 Application No
    Response 错误对话框;
  3. 最好也不要在广播接收者中创建子线程做耗时的工作,因为广播接收者被
    销毁后进程就成为了空进程,很容易被系统杀掉;
  4. 耗时的较长的工作最好放在服务中完成;

Android 引入广播机制的用意

  1. 从 MVC 的角度考虑(应用程序内) 其实回答这个问题的时候还可以这样问,android 为什么要有那 4 大组件,现在的移动开发模型基本上也是照搬的web 那一套 MVC 架构,只不过是改了点嫁妆而已。android 的四大组件本质上就是为了实现移动或者说嵌入式设备上的 MVC 架构,它们之间有时候是一种相互依存的关系,有时候又是一种补充关系,引入广播机制可以方便几大组件的信息和数据交互。
  2. 程序间互通消息(例如在自己的应用程序内监听系统来电)
  3. 效率上(参考 UDP 的广播协议在局域网的方便性)
  4. 设计模式上(反转控制的一种应用,类似监听者模式)

ContentProvider

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

在 Android 中如果想将自己应用的数据(一般多为数据库中的数据)提供给第三发应用,那么我们只能通过 ContentProvider 来实现了。

ContentProvider 是应用程序之间共享数据的接口。使用的时候首先自定义一个类继承 ContentProvider,然后覆写 query、insert、update、delete 等方法。因为其是四大组件之一因此必须在 AndroidManifest 文件中进行注册。

把自己的数据通过 uri 的形式共享出去,android 系统下 不同程序 数据默认是不能共享访问,需要去实现一个类去继承 ContentProvider

第三方可以通过 ContentResolver 来访问该 Provider

1
2
3
4
<provider 
android:exported="true"
android:name="com.itheima.contenProvider.provider.PersonContentPro
vider"android:authorities="com.itheima.person" />
1
2
3
4
5
6
7
8
9
publicclass PersonContentProvider extends ContentProvider{
public boolean onCreate(){

}
query(Uri, String[], String, String[], String)
insert(Uri, ContentValues)
update(Uri, ContentValues, String, String[])
delete(Uri, String, String[])
}

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

ContentProvider 屏蔽了数据存储的细节,内部实现对用户完全透明,用户只需要关心操作数据的 uri 就可以了,ContentProvider 可以实现不同 app 之间共享。

Sql 也有增删改查的方法, 但是 sql 只能查询本应用下的数据库。而ContentProvider 还可以去增删改查本地文件. xml 文件的读取等。

说说 ContentProvider、ContentResolver、ContentObserver之间的关系

  1. ContentProvider 内容提供者,用于对外提供数据
  2. ContentResolver.notifyChange(uri)发出消息
  3. ContentResolver 内容解析者,用于获取内容提供者提供的数据
  4. ContentObserver 内容监听器,可以监听数据的改变状态
  5. ContentResolver.registerContentObserver()监听消息。

使用 contentProvider 获取本地所有的音频文件

Android 中,系统为多媒体类型的文件(比如图片、音频、视频等)建立了数据库(sqlite 数据库),将文件的名称、大小等信息存储起来,从而完成多媒体数据的维护工作;所以我们需要实现建立一个实体类。
可以根据 ContentResover 获取到一个 Cursor,然后根据这个游标,遍历所有的歌曲的信息,设置给实体类,得到你想要的音频文件。因为是从本地读取数据,所以需要添加权限

1
2
<uses-permission 
android:name="android.permission.READ_EXTERNAL_STORAGE"/>

Intent

Intent 传递数据时,可以传递哪些类型数据?

Intent 可以传递的数据类型非常的丰富,java 的基本数据类型和 String 以及他们的数组形式都可以, 除此之外还可以传递实现了 Serializable 和 Parcelable 接口的对象。

Serializable 和 Parcelable 的区别

在使用内存的时候, Parcelable 类比 Serializable 性能高, 所以推荐使用Parcelable 类。

  1. Serializable 在序列化的时候会产生大量的临时变量,从而引起频繁的
    GC。
  2. Parcelable 不能使用在要将数据存储在磁盘上的情况。尽管 Serializable
    效率低点,但在这种情况下,还是建议你用 Serializable 。

实现:

  1. Serializable 的实现,只需要继承 Serializable 即可。这只是给对象打
    了一个标记,系统会自动将其序列化。
  2. Parcelabel 的实现,需要在类中添加一个静态成员变量 CREATOR,这
    个变量需要继承 Parcelable.Creator 接口
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 MyParcelable implements Parcelable {
private int mData;
public int describeContents() {
return 0;
}

public void writeToParcel(Parcel out, int flags) {
out.writeInt(mData);
}

public static final Parcelable.Creator<MyParcelable> CREATOR = new Parcelable.Creator<MyParcelable>() {
public MyParcelable createFromParcel(Parcel in) {
return new MyParcelable(in);
}

public MyParcelable[] newArray(int size) {
return new MyParcelable[size];
}
};

private MyParcelable(Parcel in) {
mData = in.readInt();
}
}

请描述一下 Intent 和 IntentFilter

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

IntentFilter: 可以理解为邮局或者是一个信笺的分拣系统…

这个分拣系统通过 3 个参数来识别

Action: 动作 view
Data: 数据 uri
Category: 而外的附加信息

Action 匹 配
Action 是一个用户定义的字符串,用于描述一个 Android 应用程序组件,一 个 IntentFilter 可 以 包 含 多 个 Action 。 在 AndroidManifest.xml 的Activity 定义时可以在其 节点指定一个 Action 列表用于标示Activity 所能接受的“动作”,例如:

1
2
3
4
5
<intent-filter >
<action android:name="android.intent.action.MAIN" />
<action android:name="cn.itheima.action" />
……
</intent-filter>

如果我们在启动一个 Activity 时使用这样的 Intent 对象:

1
2
Intent intent =new Intent(); 
intent.setAction("cn.itheima.action");

那么所有的 Action 列表中包含了“cn.itheima”的 Activity 都将会匹配成功。

Android 预定义了一系列的 Action 分别表示特定的系统动作。这些Action 通过常量的方式定义在 android.content. Intent 中,以“ACTION_” 开头。我们可以在 Android 提供的文档中找到它们的详细说明。

URI 数据匹配

一个 Intent 可以通过 URI 携带外部数据给目标组件。在 节点中,通过 节点匹配外部数据。
mimeType 属性指定携带外部数据的数据类型,scheme 指定协议,host、port、path 指定数据的位置、端口、和路径。如下:

1
2
<dataandroid:mimeType="mimeType"android:scheme="scheme" 
android:host="host" android:port="port" android:path="path"/>

如果在 Intent Filter 中指定了这些属性,那么只有所有的属性都匹配成功时 URI 数据匹配才会成功。

Category 类别匹配
节点中可以为组件定义一个 Category 类别列表, 当Intent 中包含这个列表的所有项目时 Category 类别匹配才会成功

Fragment

Fragment 跟 Activity 之间是如何传值的

当 Fragment 跟 Activity 绑定之后, 在 Fragment 中可以直接通过getActivity()方法获取到其绑定的 Activity 对象,这样就可以调用 Activity 的方法了。在 Activity 中可以通过如下方法获取到 Fragment 实例

1
2
3
FragmentManager fragmentManager = getFragmentManager(); 
Fragment fragment = fragmentManager.findFragmentByTag(tag);
Fragment fragment = fragmentManager.findFragmentById(id);

获取到 Fragment 之后就可以调用 Fragment 的方法。也就实现了通信功能。

描述一下 Fragment 的生命周期

Fragment 的 replace 和 add 方法的区别

Fragment 本身并没有 replace 和 add 方法, 这里的理解应该为使用FragmentManager 的replace 和add 两种方法切换Fragment 时有什么不同。
我们经常使用的一个架构就是通过 RadioGroup 切换 Fragment, 每个Fragment 就是一个功能模块.

实现这个功能可以通过 replace 和 add 两种方法。
Fragment 的容器一个 FrameLayout,add 的时候是把所有的 Fragment 一层一层的叠加到了 FrameLayout 上了,而 replace 的话首先将该容器中的其他Fragment 去除掉然后将当前 Fragment 添加到容器中。
一个 Fragment 容器中只能添加一个 Fragment 种类,如果多次添加则会报异常,导致程序终止,而 replace 则无所谓,随便切换。
因为通过 add 的方法添加的 Fragment,每个 Fragment 只能添加一次,因此如果要想达到切换效果需要通过Fragment 的的hide 和show 方法结合者使用。
将要显示的show 出来,将其他 hide 起来。这个过程 Fragment 的生命周期没有变化。
通 过 replace 切 换 Fragment , 每 次 都 会 执 行 上 一 个 Fragment 的onDestroyView,新 Fragment 的 onCreateView、onStart、onResume 方法。

基于以上不同的特点我们在使用的使用一定要结合着生命周期操作我们的视图和数据。

Fragment 如何实现类似Activity 栈的压栈和出栈效果的?

Fragment 的事物管理器内部维持了一个双向链表结构,该结构可以记录我们每次 add 的 Fragment 和 replace 的 Fragment,然后当我们点击 back 按钮的时候会自动帮我们实现退栈操作。

除此之外因为我们要使用 FragmentManger 用的是 FragmentActivity,因此 FragmentActivity 的 onBackPress 方法必定重新覆写了。打开看一下,发现确实如此。

1
2
3
4
5
6
7
8
9
10
/**
* Take care of popping the fragment back stack or finishing the activi
* as appropriate.
*/
public void onBackPressed() {
if (!mFragments.popBackStackImmediate()) {
finish();
}
}
//mFragments 的原型是 FragmentManagerImpl,看看这个方法都干嘛了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Override
public boolean popBackStackImmediate() {
checkStateLoss();
executePendingTransactions();
return popBackStackState(mActivity.mHandler, null, -1, 0);
}
//看看 popBackStackState 方法都干了啥,其实通过名称也能大概了解 只给几个片段吧,

while (index >= 0) {
//从后退栈中取出当前记录对象
BackStackRecord bss = mBackStack.get(index);
if (name != null && name.equals(bss.getName())) {
break;
}
if (id >= 0 && id == bss.mIndex) {
break;
}
index--;
}

Fragment 的好处

  1. Fragment 可以使你能够将 activity 分离成多个可重用的组件,每个都有它自己的生命周期和 UI。
  2. Fragment 可以轻松得创建动态灵活的 UI 设计,可以适应于不同的屏幕尺寸。从手机到平板电脑。
  3. Fragment 是一个独立的模块,紧紧地与 activity 绑定在一起。可以运行中动态地移除、加入、交换等。
  4. Fragment 提供一个新的方式让你在不同的安卓设备上统一你的 UI。
  5. Fragment 解决 Activity 间的切换不流畅,轻量切换。
  6. Fragment 替代 TabActivity 做导航,性能更好。
  7. Fragment 在 4.2.版本中新增嵌套 fragment 使用方法,能够生成更好的界面效果。

如何切换 fragement,不重新实例化

翻看了 Android 官方 Doc,和一些组件的源代码,发现 replace()这个方法只是在上一个 Fragment 不再需要时采用的简便方法.

正确的切换方式是 add(),切换时 hide(),add()另一个 Fragment;再次切换时,只需 hide()当前,show()另一个。

这样就能做到多个 Fragment 切换不重新实例化

1
2
3
4
5
6
7
8
9
10
11
public void switchContent(Fragment from, Fragment to) {
if (mContent != to) {
mContent = to;
FragmentTransaction transaction = mFragmentMan.beginTransaction().setCustomAnimations(android.R.anim.fad e_in, R.anim.slide_out);
if (!to.isAdded()) { // 先判断是否被 add 过
transaction.hide(from).add(R.id.content_frame, to).commit(); // 隐藏当前的 fragment,add 下一个到 Activity 中
} else {
transaction.hide(from).show(to).commit(); // 隐藏当前的fragment,显示下一个
}
}
}

进阶篇

Android 性能优化

如何对 Android 应用进行性能分析

一款 App 流畅与否安装在自己的真机里,玩几天就能有个大概的感性认识。不过通过专业的分析工具可以使我们更好的分析我们的应用。而在实际开发中,我们解决完当前应用所有 bug 后,就会开始考虑到新能的优化。

如果不考虑使用其他第三方性能分析工具的话,我们可以直接使用 ddms 中的工具,其实 ddms 工具已经非常的强大了。ddms 中有 traceview、heap 、 allocation tracker 等工具都可以帮助我们分析应用的方法执行时间效率和内存使用情况。

什么情况下会导致内存泄露

Android 的虚拟机是基于寄存器的Dalvik,它的最大堆大小一般是16M,有的机器为24M。因此我们所能利用的内存空间是有限的。如果我们的内存占用超过了一定的水平就会出现OutOfMemory 的错误。

内存溢出的几点原因:

  1. 资源释放问题

程序代码的问题,长期保持某些资源,如Context、Cursor、IO流的引用,资源得不到释放造成内存泄露。

  1. 对象内存过大问题

保存了多个耗用内存过大的对象(如Bitmap、XML 文件),造成内存超出限制。

  1. static关键字的使用问题

static是Java中的一个关键字,当用它来修饰成员变量时,那么该变量就属于该类,而不是该类的实例。所以用static修饰的变量,它的生命周期是很长的,如果用它来引用一些资源耗费过多的实例(Context的情况最多),这时就要谨慎对待了。

1
2
3
public class ClassName {
private static Context mContext;//省略
}

以上的代码是很危险的,如果将Activity赋值到mContext的话。那么即使该Activity已经onDestroy,但是由于仍有对象保存它的引用,因此该Activity 依然不会被释放。

我们举Android 文档中的一个例子。

1
2
3
4
5
6
7
8
9
10
11
12
private static Drawable sBackground;

@Override
protected void onCreate(Bundle state) {
super.onCreate(state);
TextView label =newTextView(this);//getApplicationContext label.setText("Leaks arebad");
if (sBackground == null) {
sBackground = getDrawable(R.drawable.large_bitmap);
}
label.setBackgroundDrawable(sBackground);
setContentView(label);
}

sBackground是一个静态的变量,但是我们发现,我们并没有显式的保存Contex 的引用,但是,当Drawable与View连接之后,Drawable就将View设置为一个回调,由于View中是包含Context的引用的,所以,实际上我们依然保存了Context的引用。这个引用链如下:
Drawable->TextView->Context
所以,最终该Context 也没有得到释放,发生了内存泄露。

针对static 的解决方案

  • 应该尽量避免static成员变量引用资源耗费过多的实例,比如Context。

  • Context尽量使用ApplicationContext,因为Application的Context 的生命周期比较长,引用它不会出现内存泄露的问题。

  • 使用WeakReference代替强引用。比如可以使用WeakReferencemContextRef;

  1. 线程导致内存溢出

线程产生内存泄露的主要原因在于线程生命周期的不可控。我们来考虑下面一段代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class MyActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
new MyThread().start();
}
private class MyThread extends Thread{
@Override
public void run() {
super.run();
//dosomthingwhile(true)
}
}
}

段代码很平常也很简单,是我们经常使用的形式。我们思考一个问题:假设MyThread的run函数是一个很费时的操作,当我们开启该线程后,将设备的横屏变为了竖屏,一般情况下当屏幕转换时会重新创建Activity,按照我们的想法,老的Activity 应该会被销毁才对,然而事实上并非如此。

由于我们的线程是Activity的内部类,所以MyThread中保存了Activity 的一个引用,当MyThread的run函数没有结束时,MyThread是不会被销毁的,因此它所引用的老的Activity也不会被销毁,因此就出现了内存泄露的问题。

有些人喜欢用Android 提供的AsyncTask,但事实上AsyncTask 的问题更加严重,Thread 只有在run 函数不结束时才出现这种内存泄露问题,然而AsyncTask 内部的实现机制是运用了ThreadPoolExcutor,该类产生的Thread 对象的生命周期是不确定的,是应用程序无法控制的,因此如果AsyncTask 作为Activity 的内部类,就更容易出现内存泄露的问题。

针对这种线程导致的内存泄露问题的解决方案:

  • 将线程的内部类,改为静态内部类(因为非静态内部类拥有外部类对象的强引用,而静态类则不拥有)。

  • 在线程内部采用弱引用保存Context引用。

如何避免OOM 异常

OOM 内存溢出,想要避免OOM 异常首先我们要知道什么情况下会导致OOM 异常。

  1. 图片过大导致OOM
    Android 中用bitmap 时很容易内存溢出,比如报如下错误:
    Java.lang.OutOfMemoryError : bitmap size exceeds VM budget。

解决方法:
方法1:等比例缩小图片
方法2:对图片采用软引用,及时地进行recyle()操作
方法3:使用加载图片框架处理图片,如专业处理加载图片的ImageLoader 图片加载框架。还有我们学的XUtils 的BitMapUtils 来做处理。

  1. 界面切换导致OOM

一般情况下,开发中都会禁止横屏的。因为如果是来回切换话,activity 的生命周期会重新销毁然后创建。

有时候我们会发现这样的问题,横竖屏切换N 次后OOM 了。
这种问题没有固定的解决方法,但是我们可以从以下几个方面下手分析。

1、看看页面布局当中有没有大的图片,比如背景图之类的。
去除xml 中相关设置,改在程序中设置背景图(放在onCreate()方法中):

1
2
3
Drawable drawable = getResources().getDrawable(R.drawable.id); 
ImageView imageView = new ImageView(this);
imageView.setBackgroundDrawable(drawable);

在Activity destory 时注意,drawable.setCallback(null); 防止Activity 得不到及时的释放。

2、跟上面方法相似,直接把xml配置文件加载成view再放到一个容器里,然后直接调用this.setContentView(View view);方法,避免xml的重复加载。

3、在页面切换时尽可能少地重复使用一些代码
比如:重复调用数据库,反复使用某些对象等等……

  1. 查询数据库没有关闭游标

程序中经常会进行查询数据库的操作,但是经常会有使用完毕Cursor后没有关闭的情况。如果我们的查询结果集比较小,对内存的消耗不容易被发现,只有在常时间大量操作的情况下才会出现内存问题,这样就会给以后的测试和问题排查带来困难和风险。

  1. 构造Adapter 时,没有使用缓存的convertView

在使用ListView 的时候通常会使用Adapter,那么我们应该尽可能的使用ConvertView。
为什么要使用convertView?
当convertView 为空时,用setTag()方法为每个View 绑定一个存放控件的ViewHolder对象。当convertView不为空,重复利用已经创建的view的时候,使用getTag()方法获取绑定的ViewHolder对象,这样就避免了findViewById 对控件的层层查询,而是快速定位到控件。

  1. Bitmap 对象不再使用时调用recycle()释放内存
    有时我们会手工的操作Bitmap对象,如果一个Bitmap对象比较占内存,当它不再被使用的时候,可以调用Bitmap.recycle()方法回收此对象的像素所占用的内存,但这不是必须的,视情况而定。

  2. 其他
    Android应用程序中最典型的需要注意释放资源的情况是在Activity的生命周期中,在onPause()、onStop()、onDestroy()方法中需要适当的释放资源的情况。使用广播没有注销也会产生OOM。

ANR 是什么?怎样避免和解决ANR

在Android 上,如果你的应用程序有一段时间响应不够灵敏,系统会向用户显示一个对话框,这个对话框称作应用程序无响应(ANR:Application Not Responding)对话框。用户可以选择让程序继续运行,但是,他们在使用你的应用程序时,并不希望每次都要处理这个对话框。因此,在程序里对响应性能的设计很重要,这样,系统不会显示ANR给用户。

ANR 一般有三种类型:

  1. KeyDispatchTimeout(5 seconds) –主要类型按键或触摸事件在特定时间内无响应

  2. BroadcastTimeout(10 seconds) BroadcastReceiver 在特定时间内无法处理完成

  3. ServiceTimeout(20 seconds) –小概率类型,Service 在特定的时间内无法处理完成

超时的原因一般有两种:

  1. 当前的事件没有机会得到处理(UI 线程正在处理前一个事件没有及时完成或者looper 被某种原因阻塞住)

  2. 当前的事件正在处理,但没有及时完成
    UI线程尽量只做跟UI相关的工作,耗时的工作(数据库操作,I/O,连接网络或者其他可能阻碍UI线程的操作)放入单独的线程处理,尽量用Handler来处理UI thread和thread之间的交互。

UI 线程主要包括如下:

Activity:onCreate(), onResume(), onDestroy(), onKeyDown(), onClick()
AsyncTask: onPreExecute(), onProgressUpdate(), onPostExecute(), onCancel()
Mainthread handler: handleMessage(), post(runnable r)

查找ANR的方式:

  1. 导出/data/data/anr/traces.txt,找出函数和调用过程,分析代码
  2. 通过性能LOG 人肉查找

Android 线程间通信有哪几种方式

  • 共享内存(变量);
  • 文件,数据库;
  • Handler;
  • Java里的wait(),notify(),notifyAll()

Devik 进程,linux 进程,线程的区别

Dalvik虚拟机运行在Linux操作系统之上。Linux操作系统并没有纯粹的线程概念,只要两个进程共享一个地址空间,那么就可以认为它们是同一个进程的两个线程。Linux系统提供了两个fork和clone 调用,其中,前者是用来创建进程的,而后者是用来创建线程的。

一般来说,虚拟机的进程和线程都是和目标机器本地操作系统的进程和线程一一对应的,这样的好处是可以使本地操作系统来调度进程和线程。

每个Android 应用程序进程都有一个Dalvik 虚拟机实例。这样做得好处是Android 应用程序进程之间不会互相影响,也就是说,一个Android 应用程序进程的意外终止,不会影响到其他的应用程序进程的正常运行。

每个Android应用程序进程都是由一种称为Zygote的进程fork出来的。

Zygote 进程是由init 进程启动起来的,也就是在系统启动的时候启动的。

Zygnote进程在启动的时候,会创建一个虚拟机实例,并且在这个虚拟机实例将所有的Java核心库都加载起来。每当Zygote进程需要创建一个Android应用程序进程的时候,它就通过复制自身来实现,也就是通过fork 系统调用来实现。这些被fork出来的Android应用程序进程,一方面是复制了Zygote进程中的虚拟机实例,另外一方面是与Zygote进程共享了同一套Java核心库。这样不仅Android程序进程的创建很快,而且所有的应用程序都共享同一套Java核心库而节省了内存空间。

描述一下android 的系统架构?

  1. android系统架构分从下往上为linux内核层、运行库、应用程序框架层、和应用程序层。

  2. linux kernel:负责硬件的驱动程序、网络、电源、系统安全以及内存管理等功能。

  3. libraries和androidruntime:libraries:即c/c++函数库部分,大多数都是开放源代码的函数库,例如webkit,该函数库负责android网页浏览器的运行,例如标准的c函数库libc、openssl、sqlite等,当然也包括支持游戏开发2dsgl和3dopengles,在多媒体方面有mediaframework 框架来支持各种影音和图形文件的播放与显示,例如mpeg4、h.264、mp3、aac、amr、jpg和png等众多的多媒体文件格式。android 的runtime负责解释和执行生成的dalvik格式的字节码。

  4. applicationframework(应用软件架构),java应用程序开发人员主要是使用该层封装好的api进行快速开发。

  5. applications:该层是java的应用程序层,android内置的googlemaps、e-mail、即时通信工具、浏览器、mp3播放器等处于该层,java开发人员开发的程序也处于该层,而且和内置的应用程序具有平等的位置,可以调用内置的应用程序,也可以替换内置的应用程序。

android 应用对内存是如何限制的?我们应该如何合理使用内存?

如何限制的?

Android 应用的开发语言为Java,每个应用最大可使用的堆内存受到Android 系统的限制

  • Android 每一个应用的堆内存大小有限
  • 通常的情况为16M-48M
  • 通过ActivityManager 的getMemoryClass()来查询可用堆内存限制
  • 3.0(HoneyComb)以上的版本可以通过largeHeap=“true”来申请更多的堆内存
  • NexueHeap 512
  • 如果试图申请的内存大于当前余下的堆内存就会引发OutOfMemoryError()
  • 应用程序由于各方面的限制,需要注意减少内存占用,避免出现内存泄漏。

如何合理使用内存

1、注意资源回收,像数据库,输入输出流,定位操作这样的对象,要在使用完及时关闭流。
2、少使用静态变量,因为系统将静态变量的优先级设定的很高,会最后回收。所以可能因为静态变量导致该回收的没有回收。而回收了不该回收的内存。
3、注意大图片的缩放,如果载入的图片很大,要先经过自己程序的处理,降低分辨率等。最好设置多种分辨率格式的图片,以减少内存消耗。
4、动态注册监听,把一些只有显示的时候才使用到的监听放进程序内部,而不是放在manifesat 中去。
5、减少使用动画,或者适当减少动画的帧数。
6、注意自己的程序逻辑,在该关闭自己程序的控件的时候,主动关闭,不要交给系统去决定。(这个要自己把握好,也不是说都自己搞定,只有那些自己确定需要关闭的对象,自己将其关闭。)

请解释下Android 程序运行时权限与文件系统权限的区别?

apk程序是运行在虚拟机上的,对应的是Android独特的权限机制,只有体现到文件系统上时才使用linux的权限设置。

  1. linux 文件系统上的权限
1
-rwxr-x--xsystem system 4156 2010-04-3016:13test.apk

代表的是相应的用户/用户组及其他人对此文件的访问权限,与此文件运行
起来具有的权限完全不相关。比如上面的例子只能说明system用户拥有对此文件的读写执行权限;system组的用户对此文件拥有读、执行权限;其他人对此文件只具有执行权限。而test.apk运行起来后可以干哪些事情,跟这个就不相关了。千万不要看apk文件系统上属于system/system用户及用户组,或者root/root 用户及用户组,就认为apk 具有system 或root 权限

  1. Android 的权限规则
  • Android 中的apk 必须签名
  • 基于UserID 的进程级别的安全机制
  • 默认apk 生成的数据对外是不可见的
  • AndroidManifest.xml 中的显式权限声明

Framework 工作方式及原理,Activity 是如何生成一个view 的,机制是什么?

所有的框架都是基于反射和配置文件(manifest)的。

普通的情况:

Activity 创建一个view 是通过ondraw 画出来的, 画这个view 之前呢,还会调用onmeasure 方法来计算显示的大小.

特殊情况:

Surfaceview 是直接操作硬件的,因为或者视频播放对帧数有要求,onDraw 效率太低,不够使,Surfaceview 直接把数据写到显存。

多线程间通信和多进程之间通信有什么不同,分别怎么实现?

  1. 进程间的通信方式
  • 管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。

  • 有名管道(namedpipe) :有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。

  • 信号量(semophore) :信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。

  • 消息队列( messagequeue) :消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。

  • 信号(sinal ) :信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。

  • 共享内存(shared memory) :共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号两,配合使用,来实现进程间的同步和通信

  • 套接字(socket ) :套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。

  1. 线程间的通信方式

锁机制:包括互斥锁、条件变量、读写锁
*互斥锁提供了以排他方式防止数据结构被并发修改的方法。
*读写锁允许多个线程同时读共享数据,而对写操作是互斥的。
*条件变量可以以原子的方式阻塞进程,直到某个特定条件为真为止。对条件的测试是在互斥锁的保护下进行的。条件变量始终与互斥锁一起使用。

信号量机制(Semaphore):包括无名线程信号量和命名线程信号量

信号机制(Signal):类似进程间的信号处理

线程间的通信目的主要是用于线程同步,所以线程没有像进程通信中的用于数据交换的通信机制。

AIDL

什么是AIDL 以及如何使用

  1. aidl是Android interface definition Language 的英文缩写,意思Android 接口定义语言。
  2. 使用aidl 可以帮助我们发布以及调用远程服务,实现跨进程通信。
  3. 将服务的aidl 放到对应的src 目录,工程的gen 目录会生成相应的接口类

我们通过bindService(Intent,ServiceConnect,int)方法绑定远程服务,在bindService 中有一个ServiceConnec 接口,我们需要覆写该类的onServiceConnected(ComponentName,IBinder)方法,这个方法的第二个参数IBinder 对象其实就是已经在aidl 中定义的接口,因此我们可以将IBinder 对象强制转换为aidl 中的接口类。

我们通过IBinder获取到的对象(也就是aidl文件生成的接口)其实是系统产生的代理对象,该代理对象既可以跟我们的进程通信,又可以跟远程进程通信,作为一个中间的角色实现了进程间通信。

AIDL 的全称是什么?如何工作?能处理哪些类型的数据

AIDL全称Android Interface Definition Language(AndRoid接口描述语言)是一种接口描述语言; 编译器可以通过aidl文件生成一段代码,通过预先定义的接口达到两个进程内部通信进程跨界对象访问的目的。需要完成2件事情:

  1. 引入AIDL 的相关类.;
  2. 调用aidl 产生的class.理论上, 参数可以传递基本数据类型和String, 还有就是Bundle 的派生类, 不过在Eclipse 中,目前的ADT 不支持Bundle 做为参数。

Android 中的事件处理

Handler 机制

Android中主线程也叫UI 线程,那么从名字上我们也知道主线程主要是用来创建、更新UI的,而其他耗时操作,比如网络访问,或者文件处理,多媒体处理等都需要在子线程中操作,之所以在子线程中操作是为了保证UI的流畅程度,手机显示的刷新频率是60Hz,也就是一秒钟刷新60次,每16.67毫秒刷新一次,为了不丢帧,那么主线程处理代码最好不要超过16毫秒。当子线程处理完数据后,为了防止UI处理逻辑的混乱,Android只允许主线程修改UI,那么这时候就需要Handler来充当子线程和主线程之间的桥梁了。

我们通常将Handler 声明在Activity 中,然后覆写Handler 中的handleMessage 方法, 当子线程调用handler.sendMessage() 方法后handleMessage 方法就会在主线程中执行。

这里面除了Handler、Message外还有隐藏的Looper和MessageQueue对象。

在主线程中Android 默认已经调用了Looper.preper()方法,调用该方法的目的是在Looper中创建MessageQueue成员变量并把Looper对象绑定到当前线程中。当调用Handler的sendMessage(对象)方法的时候就将Message 对象添加到了Looper创建的MessageQueue队列中,同时给Message指定了target 对象,其实这个target对象就是Handler对象。主线程默认执行了Looper.looper()方法,该方法从Looper的成员变量MessageQueue中取出Message,然后调用Message的target对象的handleMessage()方法。这样就完成了整个消息机制。

事件分发机制

  1. 事件分发中的onTouch和onTouchEvent有什么区别,又该如何使用?

这两个方法都是在View的dispatchTouchEvent中调用的,onTouch优先于onTouchEvent执行。如果在onTouch方法中通过返回true将事件消费掉,onTouchEvent 将不会再执行。

另外需要注意的是,onTouch 能够得到执行需要两个前提条件,第一mOnTouchListener的值不能为空,第二当前点击的控件必须是enable的。因此如果你有一个控件是非enable的,那么给它注册onTouch 事件将永远得不到执行。对于这一类控件,如果我们想要监听它的touch 事件,就必须通过在该控件中重写onTouchEvent方法来实现。

  1. 请描述一下Android的事件分发机制

Android的事件分发机制主要是Touch事件分发,有两个主角:ViewGroup 和View。Activity的Touch事件事实上是调用它内部的ViewGroup的Touch 事件,可以直接当成ViewGroup处理。

View在ViewGroup内,ViewGroup也可以在其他ViewGroup内,这时候把内部的ViewGroup当成View来分析。

先分析ViewGroup的处理流程:首先得有个结构模型概念:ViewGroup 和View组成了一棵树形结构,最顶层为Activity的ViewGroup,下面有若干的ViewGroup节点,每个节点之下又有若干的ViewGroup节点或者View节点,依次类推。如图:

当一个Touch事件(触摸事件为例)到达根节点,即Acitivty的ViewGroup时,它会依次下发,下发的过程是调用子View(ViewGroup) 的dispatchTouchEvent 方法实现的。简单来说,就是ViewGroup 遍历它包含着的子View,调用每个View 的dispatchTouchEvent 方法,而当子View 为ViewGroup时,又会通过调用ViwGroup的dispatchTouchEvent 方法继续调用其内部的View的dispatchTouchEvent方法。上述例子中的消息下发顺序是这样的:①-②-⑤-⑥-⑦-③-④。dispatchTouchEvent方法只负责事件的分发,它拥有boolean类型的返回值,当返回为true时,顺序下发会中断。在上述例子中如果⑤的dispatchTouchEvent返回结果为true,那么⑥-⑦-③-④将都接收不到本次Touch事件。

  • Touch事件分发中只有两个主角:ViewGroup和View。ViewGroup包含onInterceptTouchEvent、dispatchTouchEvent、onTouchEvent 三个相关事件。View 包含dispatchTouchEvent、onTouchEvent 两个相关事件。其中ViewGroup 又继承于View。

  • ViewGroup和View组成了一个树状结构,根节点为Activity内部包含的一个ViwGroup。

  • 触摸事件由Action_Down、Action_Move、Aciton_UP 组成,其中一次完整的触摸事件中,Down 和Up 都只有一个,Move 有若干个,可以为0 个。

  • 当Acitivty 接收到Touch 事件时,将遍历子View 进行Down 事件的分发。ViewGroup 的遍历可以看成是递归的。分发的目的是为了找到真正要处理本次完整触摸事件的View,这个View 会在onTouchuEvent 结果返回true。

  • 当某个子View返回true时,会中止Down事件的分发,同时在ViewGroup 中记录该子View。接下去的Move 和Up 事件将由该子View 直接进行处理。由于子View是保存在ViewGroup中的,多层ViewGroup的节点结构时,上级ViewGroup保存的会是真实处理事件的View所在的ViewGroup 对象: 如ViewGroup0-ViewGroup1-TextView的结构中,TextView返回了true,它将被保存在ViewGroup1中,而ViewGroup1也会返回true,被保存在ViewGroup0中。当Move和UP事件来时,会先从ViewGroup0传递至ViewGroup1,再由ViewGroup1 传递至TextView。

  • 当ViewGroup中所有子View都不捕获Down事件时,将触发ViewGroup 自身的onTouch 事件。触发的方式是调用super.dispatchTouchEvent函数,即父类View的dispatchTouchEvent方法。在所有子View都不处理的情况下,触发Acitivity的onTouchEvent方法。

  • onInterceptTouchEvent有两个作用:1.拦截Down事件的分发。2.中止Up 和Move 事件向目标View 传递,使得目标View 所在的ViewGroup 捕获Up 和Move 事件。

Handler、Message、Looper、MessageQueue

  1. 相关概念的解释
    主线程(UI 线程)
    定义:当程序第一次启动时,Android 会同时启动一条主线程(Main Thread)
    作用:主线程主要负责处理与UI 相关的事件

Message(消息)
定义:Handler 接收和处理的消息对象(Bean 对象)
作用:通信时相关信息的存放和传递

ThreadLocal
定义:线程内部的数据存储类
作用:负责存储和获取本线程的Looper

MessageQueue(消息队列)
定义:采用单链表的数据结构来存储消息列表
作用:用来存放通过Handler 发过来的Message,按照先进先出执行

Handler(处理者)
定义:Message 的主要处理者
作用:负责发送Message 到消息队列&处理Looper 分派过来的Message

Looper(循环器)
定义:扮演Message Queue 和Handler 之间桥梁的角色
作用:消息循环:循环取出Message Queue 的Message 消息派发:将取出的Message 交付给相应的Handler

  1. Message、Handler、MessageQueen、Looper的之间的关系?

首先,是这个MessageQueen,MessageQueen是一个消息队列,它可以存储Handler发送过来的消息,其内部提供了进队和出队的方法来管理这个消息队列,其出队和进队的原理是采用单链表的数据结构进行插入和删除的,即enqueueMessage()方法和next()方法。这里提到的Message,其实就是一个Bean 对象,里面的属性用来记录Message 的各种信息。

然后,是这个Looper,Looper 是一个循环器,它可以循环的取出MessageQueen 中的Message,其内部提供了Looper 的初始化和循环出去Message的方法,即prepare()方法和loop()方法。在prepare()方法中,Looper 会关联一个MessageQueen,而且将Looper存进一个ThreadLocal中,在loop()方法中,通过ThreadLocal取出Looper,使用MessageQueen的next()方法取出Message后,判断Message是否为空,如果是则Looper阻塞,如果不是,则通过dispatchMessage()方法分发该Message到Handler中,而Handler执行handlerMessage()方法,由于handlerMessage()方法是个空方法,这也是为什么需要在Handler中重写handlerMessage()方法的原因。这里要注意的是Looper只能在一个线程中只能存在一个。这里提到的ThreadLocal,其实就是一个对象,用来在不同线程中存放对应线程的Looper。

最后,是这个Handler,Handler是Looper和MessageQueen的桥梁,Handler内部提供了发送Message的一系列方法,最终会通过MessageQueen的enqueueMessage()方法将Message 存进MessageQueen 中。我们平时可以直接在主线程中使用Handler,那是因为在应用程序启动时,在入口的main方法中已经默认为我们创建好了Looper。

  1. 为什么在子线程中创建Handler 会抛异常?

Handler的工作是依赖于Looper的,而Looper(与消息队列)又是属于某一个线程(ThreadLocal是线程内部的数据存储类,通过它可以在指定线程中存储数据,其他线程则无法获取到),其他线程不能访问。因此Handler就是间接跟线程是绑定在一起了。因此要使用Handler必须要保证Handler所创建的线程中有Looper对象并且启动循环。因为子线程中默认是没有Looper的,所以会报错。

HandlerThread

  1. HandlerThread 作用
    当系统有多个耗时任务需要执行时,每个任务都会开启一个新线程去执行耗时任务,这样会导致系统多次创建和销毁线程,从而影响性能。为了解决这一问题,Google提供了HandlerThread,HandlerThread是在线程中创建一个Looper 循环器,让Looper轮询消息队列,当有耗时任务进入队列时,则不需要开启新线程,在原有的线程中执行耗时任务即可,否则线程阻塞。

  2. HanlderThread 的优缺点
    HandlerThread 本质上是一个线程类,它继承了Thread;
    HandlerThread 有自己的内部Looper 对象,可以进行looper 循环;
    通过获取HandlerThread 的looper 对象传递给Handler 对象,可以在handleMessage()方法中执行异步任务。
    建HandlerThread后必须先调用HandlerThread.start()方法,Thread会先调用run方法,创建Looper对象。

HandlerThread 优点是异步不会堵塞,减少对性能的消耗

HandlerThread 缺点是不能同时继续进行多任务处理,需要等待进行处理,处理效率较低

HandlerThread 与线程池不同,HandlerThread 是一个串行队列,背后只有一个线程。

IntentService

它本质是一种特殊的Service,继承自Service 并且本身就是一个抽象类
它可以用于在后台执行耗时的异步任务,当任务完成后会自动停止
它拥有较高的优先级,不易被系统杀死(继承自Service的缘故),因此比较适

合执行一些高优先级的异步任务它内部通过HandlerThread和Handler实现异步操作

创建IntentService 时,只需实现onHandleIntent 和构造方法,
onHandleIntent 为异步方法,可以执行耗时操作
即使我们多次启动IntentService,但IntentService的实例只有一个,这跟传统的Service是一样的,最终IntentService会去调用onHandleIntent执行异步任务。

当任务完成后,IntentService会自动停止,而不需要手动调用stopSelf()。另外,可以多次启动IntentService,每个耗时操作都会以工作队列的方式在IntentService中onHandlerIntent()回调方法中执行,并且每次只会执行一个工作线程。

子线程发消息到主线程进行更新UI,除了handler 和AsyncTask,还有什么?

  1. 用Activity 对象的runOnUiThread 方法更新在子线程中通过runOnUiThread()方法更新UI

  2. 用View.post(Runnable r)方法更新UI

子线程中能不能new handler?为什么?

不能,如果在子线程中直接new Handler() 会抛出异常java.lang.RuntimeException: Can’t create handler inside thread that has not called

在没有调用Looper.prepare() 的时候不能创建Handler, 因为在创建Handler 的源码中做了如下操作

Handler 的构造方法中

1
2
3
4
5
6
7
8
9
public static Looper myLooper() {
return sThreadLocal.get();
}
mLooper = Looper.myLooper();

if (mLooper == null) {
throw new RuntimeException("Can't create handler inside thread that has not called Looper.prepare()");
}