安卓Android面试题汇总——超长15万字总结
一、Android四大组件
1、Activity与Fragment之间常见的几种通信方式
在 Android 开发中,Activity 与 Fragment 之间的通信是一个常见的需求。以下是几种常见的通信方式:
- 接口回调
在 Fragment 中定义一个接口,并在 Activity 中实现该接口。Fragment 调用接口中的方法来与 Activity 通信。
public class MyFragment extends Fragment { private FragmentListener mListener; public interface FragmentListener { void onMessageSent(String message); } @Override public void onAttach(Context context) { super.onAttach(context); if (context instanceof FragmentListener) { mListener = (FragmentListener) context; } else { throw new RuntimeException(context.toString() + " must implement FragmentListener"); } } // 在需要通信的地方调用 mListener.onMessageSent() }
在 Activity 中实现接口:
public class MyActivity extends AppCompatActivity implements MyFragment.FragmentListener { @Override public void onMessageSent(String message) { // 处理来自 Fragment 的消息 } }
- 使用 Bundle
通过 Bundle 传递数据。在创建 Fragment 时,将数据放入 Bundle 中,并在 Fragment 中获取。
// 在 Activity 中 Bundle bundle = new Bundle(); bundle.putString("key", "value"); MyFragment fragment = new MyFragment(); fragment.setArguments(bundle); // 在 Fragment 中 @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (getArguments() != null) { String value = getArguments().getString("key"); } }
- 使用 Handler
在 Activity 中创建一个 Handler,并将其传递给 Fragment。
// 在 Activity 中 Handler handler = new Handler(new Handler.Callback() { @Override public boolean handleMessage(Message msg) { // 处理消息 return true; } }); // 在 Fragment 中 @Override public void onAttach(Context context) { super.onAttach(context); if (context instanceof MyActivity) { Handler handler = ((MyActivity) context).handler; // 使用 handler 发送消息 } }
- 使用广播(Broadcast)
在 Activity 中注册广播接收器,在 Fragment 中发送广播。
// 在 Activity 中 BroadcastReceiver receiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { // 处理广播 } }; IntentFilter filter = new IntentFilter("custom_action"); registerReceiver(receiver, filter); // 在 Fragment 中 Intent intent = new Intent("custom_action"); intent.putExtra("key", "value"); getActivity().sendBroadcast(intent);
- 使用 EventBus
EventBus 是一个发布/订阅事件总线,可以简化组件间的通信。
// 在 Activity 中 EventBus.getDefault().register(this); @Subscribe public void onEvent(MessageEvent event) { // 处理事件 } // 在 Fragment 中 EventBus.getDefault().post(new MessageEvent("message"));
- 使用 ViewModel
ViewModel 是 Jetpack 提供的组件,可以在 Activity 和 Fragment 之间共享数据。
// 在 Activity 中 SharedViewModel viewModel = new ViewModelProvider(this).get(SharedViewModel.class); // 在 Fragment 中 SharedViewModel viewModel = new ViewModelProvider(requireActivity()).get(SharedViewModel.class); viewModel.getData().observe(getViewLifecycleOwner(), data -> { // 更新 UI });
2、LaunchMode的应用场景
在 Android 开发中,Activity 的启动模式(LaunchMode)有四种:standard、singleTop、singleTask 和 singleInstance。每种模式都有其特定的应用场景和使用方法。
- standard 模式
默认模式,每次启动 Activity 时都会创建一个新的实例,并将其放入任务栈中。
- 应用场景:适用于大多数普通页面,例如用户浏览不同的内容页面时,每次点击都会打开一个新的页面实例。
- singleTop 模式
如果要启动的 Activity 已经位于栈顶,则不会创建新的实例,而是复用栈顶的实例,并调用其 onNewIntent() 方法。
- 应用场景:适用于通知页面或详情页面,例如新闻客户端的新闻详情页面,避免多次点击通知创建多个实例。
- singleTask 模式
如果栈中已经存在该 Activity 的实例,则复用该实例,并将其上方的所有 Activity 移除。复用时会调用 onNewIntent() 方法。
- 应用场景:适用于应用的主界面或入口点,例如浏览器的主界面,确保每次启动时都回到主界面,并清除其上的其他页面。
- singleInstance 模式
在一个新的任务栈中创建该 Activity 的实例,并让多个应用共享该栈中的实例。任何应用再激活该 Activity 时都会重用该实例。
- 应用场景:适用于需要与应用分离的页面,例如闹钟提醒页面,确保每次启动时都进入同一个实例。
3、BroadcastReceiver 与 LocalBroadcastReceiver有什么区别
BroadcastReceiver 和 LocalBroadcastReceiver(通常通过 LocalBroadcastManager 实现)是 Android 中用于广播消息的两种机制。它们之间有一些关键区别:
BroadcastReceiver
- 跨应用广播:BroadcastReceiver 可以用于应用之间的通信,也可以用于应用与系统之间的通信。
- 注册方式:支持静态注册(在 AndroidManifest.xml 中声明)和动态注册(在代码中使用 registerReceiver() 方法)。
- 安全性:由于可以跨应用通信,必须考虑安全性,防止其他应用滥用广播。
- 性能:由于涉及跨进程通信,性能相对较低。
LocalBroadcastReceiver
- 应用内广播:LocalBroadcastReceiver 仅在应用内部发送和接收广播,不能跨应用通信。
- 注册方式:只支持动态注册,使用 LocalBroadcastManager 的 registerReceiver() 方法。
- 安全性:由于只在应用内部有效,不需要考虑外部应用的安全问题,数据更加安全。
- 性能:由于不涉及跨进程通信,性能更高。
示例代码
BroadcastReceiver
// 在 Activity 中 BroadcastReceiver receiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { // 处理广播 } }; IntentFilter filter = new IntentFilter("custom_action"); registerReceiver(receiver, filter); // 发送广播 Intent intent = new Intent("custom_action"); sendBroadcast(intent);
LocalBroadcastReceiver
// 在 Activity 中 LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(this); BroadcastReceiver receiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { // 处理广播 } }; IntentFilter filter = new IntentFilter("custom_action"); localBroadcastManager.registerReceiver(receiver, filter); // 发送广播 Intent intent = new Intent("custom_action"); localBroadcastManager.sendBroadcast(intent);
选择使用哪种广播
- 跨应用通信:使用 BroadcastReceiver。
- 应用内通信:使用 LocalBroadcastReceiver,因为它更安全且性能更高
4、详细介绍Context
在 Android 开发中,Context 是一个非常重要的概念,它提供了应用环境的全局信息。Context 类的实例在应用程序的各个组件(如 Activity、Service、BroadcastReceiver 和 ContentProvider)中都可以访问。以下是对 Context 的详细介绍:
Context 的作用
- 资源访问:通过 Context 可以访问应用的资源和类,例如字符串、颜色、布局文件等。
- 启动组件:可以通过 Context 启动新的 Activity、Service,发送广播等。
- 访问系统服务:通过 Context 可以获取系统级服务,例如 WindowManager、LayoutInflater、ActivityManager 等。
- 文件操作:可以通过 Context 进行文件的读写操作。
- 数据库操作:可以通过 Context 访问应用的数据库。
Context 的类型
- Application Context:应用程序级别的 Context,生命周期与应用程序相同。适用于需要全局访问的资源或服务。
- Activity Context:Activity 级别的 Context,生命周期与 Activity 相同。适用于需要与 Activity 相关的操作,例如启动新的 Activity 或者显示对话框。
使用场景
-
启动 Activity:
Intent intent = new Intent(context, NewActivity.class); context.startActivity(intent);
-
访问资源:
String appName = context.getString(R.string.app_name); Drawable icon = context.getDrawable(R.drawable.icon);
-
获取系统服务:
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
-
文件操作:
FileOutputStream fos = context.openFileOutput("filename", Context.MODE_PRIVATE); fos.write(data); fos.close();
注意事项
- 避免内存泄漏:在使用 Context 时要小心,特别是在长生命周期的对象中(如单例模式),避免持有 Activity 的 Context,应使用 Application 的 Context。
- 正确选择 Context:在需要与 UI 相关的操作时,使用 Activity 的 Context;在需要全局访问时,使用 Application 的 Context。
示例代码
以下是一个简单的示例,展示了如何在不同场景中使用 Context:
public class MyActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 使用 Activity 的 Context Intent intent = new Intent(this, AnotherActivity.class); startActivity(intent); // 使用 Application 的 Context Context appContext = getApplicationContext(); SharedPreferences prefs = appContext.getSharedPreferences("prefs", MODE_PRIVATE); } }
5、IntentFilter是什么,有哪些使用场景
IntentFilter(意图过滤器)是Android中的一个重要组件,用于过滤和处理隐式意图(Intent)。当用户执行某个操作时,Android系统会根据配置的IntentFilter来寻找能够响应该操作的组件(如Activity、Service或BroadcastReceiver)。
使用场景
- 启动Activity:当用户点击某个链接或文件时,系统会通过IntentFilter找到能够处理该链接或文件的Activity。例如,点击PDF文件时,系统会找到能够打开PDF文件的应用程序。
- 启动Service:某些后台服务可以通过IntentFilter来响应特定的操作或数据请求。
- 接收广播:BroadcastReceiver可以通过IntentFilter来接收特定的广播消息,如系统启动、网络变化等。
过滤规则
IntentFilter主要通过以下三个方面来匹配隐式意图:
-
动作(Action):通过标签指定。例如:
-
数据(Data):通过标签指定URI和数据类型。例如:
-
类别(Category):通过标签指定。例如:
示例代码
以下是一个简单的IntentFilter配置示例:
这个配置表示,当有一个包含http://www.example.com链接的隐式意图时,系统会启动ExampleActivity来处理该意图。
6、详细介绍startService和bindService的区别,生命周期以及使用场景
在Android开发中,startService和bindService是启动和绑定服务(Service)的两种主要方法。它们在使用场景和生命周期上有显著的区别。
(1)startService
使用场景
- 长时间运行的后台任务:适用于需要在后台长时间运行的任务,例如下载文件、播放音乐等。
- 独立运行:服务可以独立于启动它的组件运行,即使启动它的组件被销毁,服务仍然会继续运行。
生命周期
- 启动:通过调用startService(Intent)启动服务。
- onCreate():服务第一次启动时调用。
- onStartCommand(Intent, int, int):每次启动服务时调用。
- 运行:服务在后台运行,直到调用stopSelf()或stopService(Intent)停止服务。
- onDestroy():服务被停止时调用。
(2)bindService
使用场景
- 客户端-服务器通信:适用于需要与服务进行交互的场景,例如获取数据、控制播放等。
- 生命周期绑定:服务的生命周期与绑定它的组件(如Activity)绑定,当所有绑定的组件都解绑时,服务会被销毁。
生命周期
- 绑定:通过调用bindService(Intent, ServiceConnection, int)绑定服务。
- onCreate():服务第一次启动时调用。
- onBind(Intent):每次绑定服务时调用,返回一个IBinder对象用于客户端与服务的通信。
- 运行:服务在至少有一个组件绑定时运行。
- onUnbind(Intent):当所有绑定的组件解绑时调用。
- onDestroy():服务被销毁时调用。
(3)具体区别
- 启动方式:startService启动的服务独立运行,而bindService启动的服务与绑定的组件生命周期绑定。
- 生命周期:startService启动的服务在调用stopSelf()或stopService()之前一直运行,而bindService启动的服务在所有绑定的组件解绑后销毁。
- 交互方式:startService通常用于不需要与服务交互的场景,而bindService用于需要与服务进行交互的场景。
示例代码
startService
Intent intent = new Intent(this, MyService.class); startService(intent);
bindService
Intent intent = new Intent(this, MyService.class); bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
7、Service如何进行保活
在Android中,Service保活(保持服务运行)是一个常见的需求,特别是对于需要长时间运行的后台任务。以下是几种常见的保活方法:
(1)前台服务(Foreground Service)
通过调用startForeground()方法将Service设置为前台服务,并显示一个持续的通知。这种方法可以显著提高服务的优先级,减少被系统杀死的可能性。
public class MyService extends Service { @Override public int onStartCommand(Intent intent, int flags, int startId) { Notification notification = new Notification.Builder(this, CHANNEL_ID) .setContentTitle("Service Running") .setContentText("This is a foreground service") .setSmallIcon(R.drawable.ic_service) .build(); startForeground(1, notification); return START_STICKY; } }
(2)双进程守护(Dual Process Guard)
通过创建两个相互守护的服务,当一个服务被杀死时,另一个服务会重新启动它。这种方法可以提高服务的存活率。
public class MainService extends Service { private ServiceConnection connection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { } @Override public void onServiceDisconnected(ComponentName name) { startService(new Intent(MainService.this, GuardService.class)); bindService(new Intent(MainService.this, GuardService.class), connection, Context.BIND_IMPORTANT); } }; @Override public int onStartCommand(Intent intent, int flags, int startId) { bindService(new Intent(this, GuardService.class), connection, Context.BIND_IMPORTANT); return START_STICKY; } }
(3)广播接收器(Broadcast Receiver)
通过监听系统广播(如设备启动、网络变化等),在服务被杀死后重新启动服务。
public class RestartReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { context.startService(new Intent(context, MyService.class)); } }
(4) JobScheduler
使用JobScheduler来定期启动服务,即使服务被杀死,也可以通过调度任务来重新启动服务。
public class MyJobService extends JobService { @Override public boolean onStartJob(JobParameters params) { startService(new Intent(this, MyService.class)); return true; } @Override public boolean onStopJob(JobParameters params) { return true; } }
(5)Wake Lock
使用WakeLock保持CPU运行,防止设备进入休眠状态,从而保证服务的持续运行。
public class MyService extends Service { private PowerManager.WakeLock wakeLock; @Override public void onCreate() { super.onCreate(); PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE); wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MyApp::MyWakelockTag"); wakeLock.acquire(); } @Override public void onDestroy() { super.onDestroy(); if (wakeLock != null && wakeLock.isHeld()) { wakeLock.release(); } } }
这些方法可以帮助提高服务的存活率,但需要注意的是,过度使用保活技术可能会导致电池消耗增加和用户体验下降。因此,应该根据实际需求合理使用。
8、详细介绍下ContentProvider是如何实现数据共享的
ContentProvider 是Android中的一个组件,用于在不同应用程序之间共享数据。它提供了一种标准化的接口,使得应用程序可以通过统一的方式访问和操作数据。
ContentProvider的基本概念
ContentProvider负责管理结构化数据的访问,并提供一套定义数据安全的机制。它封装数据并提供跨进程访问的接口,使得不同应用程序可以共享数据。
使用场景
- 跨应用数据共享:例如,联系人、媒体文件等数据可以通过ContentProvider在不同应用之间共享。
- 数据保护:通过权限控制,确保只有授权的应用程序可以访问共享的数据。
- 数据同步:与同步适配器配合使用,实现设备之间的数据同步。
实现步骤
(1)创建ContentProvider类
首先,需要创建一个继承自ContentProvider的类,并重写以下方法:
- onCreate():初始化ContentProvider。
- query():查询数据。
- insert():插入数据。
- update():更新数据。
- delete():删除数据。
- getType():返回数据的MIME类型。
public class MyContentProvider extends ContentProvider { @Override public boolean onCreate() { // 初始化操作 return true; } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { // 查询操作 return null; } @Override public Uri insert(Uri uri, ContentValues values) { // 插入操作 return null; } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { // 更新操作 return 0; } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { // 删除操作 return 0; } @Override public String getType(Uri uri) { // 返回MIME类型 return null; } }
(2)声明ContentProvider
在AndroidManifest.xml文件中声明ContentProvider,并指定authorities属性来唯一标识它。
(3)使用ContentResolver访问数据
其他应用程序可以通过ContentResolver来访问ContentProvider提供的数据。ContentResolver提供了统一的接口来执行CRUD操作。
ContentResolver resolver = getContentResolver(); Cursor cursor = resolver.query(Uri.parse("content://com.example.myprovider/data"), null, null, null, null);
URI和UriMatcher
ContentProvider使用URI来标识数据集。URI通常包含以下几部分:
- Scheme:固定为content://。
- Authority:唯一标识ContentProvider。
- Path:表示具体的数据路径。
使用UriMatcher类来匹配和解析URI,从而确定要操作的数据。
UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); uriMatcher.addURI("com.example.myprovider", "data", 1);
数据模型
ContentProvider通常使用基于数据库模型的表格来提供数据。每行代表一条记录,每列代表特定类型和含义的数据。
数据保护
通过在AndroidManifest.xml中配置权限,确保只有授权的应用程序可以访问ContentProvider。
ContentProvider通过标准化的接口和URI机制,实现了跨应用的数据共享和访问控制。它不仅提供了数据的增删改查功能,还确保了数据的安全性和一致性。
9、切换横竖屏时Activity的生命周期
在Android中,当设备从竖屏切换到横屏或从横屏切换到竖屏时,Activity的生命周期会经历一系列变化。默认情况下,Activity会被销毁并重新创建。以下是详细的生命周期变化过程:
默认情况下的生命周期变化
- onPause():Activity即将进入后台,暂停与用户的交互。
- onStop():Activity完全不可见。
- onDestroy():Activity被销毁。
- onCreate():Activity重新创建。
- onStart():Activity变为可见。
- onRestoreInstanceState():恢复之前保存的状态。
- onResume():Activity重新与用户交互。
使用android:configChanges属性
如果在AndroidManifest.xml中为Activity设置了android:configChanges属性,可以避免Activity被销毁和重新创建。常见的配置包括orientation、screenSize和keyboardHidden。
配置后的生命周期变化
当配置了android:configChanges属性后,屏幕方向变化时不会重新创建Activity,而是调用onConfigurationChanged()方法。
onConfigurationChanged(Configuration newConfig):当设备配置发生变化时(如屏幕方向变化),系统会调用此方法。
以下是一个示例,展示如何处理配置变化:
@Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) { // 处理横屏 } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) { // 处理竖屏 } }
总结
- 默认情况下:切换屏幕方向会导致Activity被销毁并重新创建,经历完整的生命周期。
- 使用android:configChanges属性:可以避免Activity被销毁,直接调用onConfigurationChanged()方法处理配置变化。
10、Activity中onNewIntent方法的调用时机和使用场景
onNewIntent 方法在 Android 开发中非常重要,尤其是在处理 Activity 的启动模式时。
调用时机
- SingleTop 模式:当 Activity 的启动模式为 SingleTop 且该 Activity 已经在栈顶时,再次启动该 Activity 会调用 onNewIntent 方法,而不是创建新的实例。
- SingleTask 和 SingleInstance 模式:当 Activity 的启动模式为 SingleTask 或 SingleInstance,且该 Activity 已经存在于任务栈中时,再次启动该 Activity 会调用 onNewIntent 方法。
- Intent Flags:使用 FLAG_ACTIVITY_SINGLE_TOP 标记启动 Activity 时,如果该 Activity 已经在栈顶,也会调用 onNewIntent 方法。
使用场景
- 通知处理:当应用接收到通知并需要打开一个已经存在的 Activity 时,可以使用 onNewIntent 方法来处理新的 Intent 数据,而不需要重新创建 Activity 实例。例如,新闻应用接收到多条新闻推送时,可以使用 SingleTop 模式和 onNewIntent 方法来更新当前显示的新闻内容。
- 单例模式:对于一些需要全局唯一实例的 Activity,例如浏览器的主界面,可以使用 SingleTask 模式和 onNewIntent 方法来确保每次启动时都使用同一个实例,并处理新的 Intent 数据。
- 数据更新:在某些情况下,Activity 需要根据新的 Intent 数据进行更新,例如用户在不同的地方点击同一个 Activity 的入口,可以通过 onNewIntent 方法来处理这些新的数据。
示例代码
@Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); setIntent(intent); // 更新 Intent processExtraData(); // 处理新的数据 } private void processExtraData() { Intent intent = getIntent(); // 在这里处理接收到的数据 }
11、Intent传输数据的大小有限制吗?如何解决
是的,Android 中通过 Intent 传输数据的大小是有限制的。具体来说,Intent 传输的数据大小受限于 Binder 机制,通常在 1MB 左右。
解决方法
-
使用文件存储:将大数据存储在文件中,然后通过 Intent 传递文件路径。在目标 Activity 中读取文件内容。
// 发送方 File file = new File(getFilesDir(), "data.txt"); Intent intent = new Intent(this, TargetActivity.class); intent.putExtra("file_path", file.getAbsolutePath()); startActivity(intent); // 接收方 String filePath = getIntent().getStringExtra("file_path"); File file = new File(filePath); // 读取文件内容
-
使用 ContentProvider:将数据存储在 ContentProvider 中,通过 URI 传递数据的引用。
// 发送方 Uri uri = ContentUris.withAppendedId(MyContentProvider.CONTENT_URI, dataId); Intent intent = new Intent(this, TargetActivity.class); intent.setData(uri); startActivity(intent); // 接收方 Uri uri = getIntent().getData(); Cursor cursor = getContentResolver().query(uri, null, null, null, null); // 处理数据
-
使用 SharedPreferences:将数据存储在 SharedPreferences 中,通过 Intent 传递键值对。
// 发送方 SharedPreferences sharedPreferences = getSharedPreferences("my_prefs", MODE_PRIVATE); SharedPreferences.Editor editor = sharedPreferences.edit(); editor.putString("key", "value"); editor.apply(); Intent intent = new Intent(this, TargetActivity.class); startActivity(intent); // 接收方 SharedPreferences sharedPreferences = getSharedPreferences("my_prefs", MODE_PRIVATE); String value = sharedPreferences.getString("key", null); // 处理数据
-
使用 EventBus:使用 EventBus 传递大数据对象。
// 发送方 EventBus.getDefault().postSticky(new DataEvent(data)); Intent intent = new Intent(this, TargetActivity.class); startActivity(intent); // 接收方 @Subscribe(sticky = true, threadMode = ThreadMode.MAIN) public void onDataEvent(DataEvent event) { // 处理数据 }
这些方法可以有效解决 Intent 传输大数据时的限制问题。
12、ContentProvider、ContentResolver、ContentObserver之间的关系
ContentProvider、ContentResolver 和 ContentObserver 是 Android 中用于数据共享和数据变化监听的重要组件。它们之间的关系如下:
ContentProvider
ContentProvider 是 Android 的四大组件之一,主要用于在不同应用之间共享数据。它提供了一种标准化的接口,使得其他应用可以通过 URI 访问和操作数据。ContentProvider 主要用于对外提供数据,数据源可以是文件、数据库等。
ContentResolver
ContentResolver 是用于与 ContentProvider 交互的接口。它提供了一组方法,用于执行基本的 CRUD(创建、读取、更新、删除)操作。通过 ContentResolver,应用可以访问其他应用的 ContentProvider 提供的数据。
ContentObserver
ContentObserver 是用于监听数据变化的观察者。当 ContentProvider 中的数据发生变化时,ContentObserver 会收到通知。开发者可以通过 ContentResolver 注册 ContentObserver 来监听特定 URI 的数据变化,并在数据变化时执行相应的操作。
关系总结
- 数据提供:ContentProvider 提供数据。
- 数据访问:ContentResolver 用于访问 ContentProvider 提供的数据。
- 数据监听:ContentObserver 用于监听 ContentProvider 中数据的变化。
示例代码
以下是一个简单的示例,展示了如何使用这三个组件:
// 注册 ContentObserver ContentResolver resolver = getContentResolver(); Uri uri = Uri.parse("content://com.example.provider/data"); ContentObserver observer = new ContentObserver(new Handler()) { @Override public void onChange(boolean selfChange) { super.onChange(selfChange); // 数据变化时的处理逻辑 } }; resolver.registerContentObserver(uri, true, observer); // 访问 ContentProvider 数据 Cursor cursor = resolver.query(uri, null, null, null, null); if (cursor != null) { while (cursor.moveToNext()) { // 处理数据 } cursor.close(); }
13、Activity加载的流程
Activity 的启动流程是 Android 系统中一个复杂且关键的过程,涉及多个系统组件和进程间的通信。以下是详细的启动流程:
- 用户操作
当用户点击应用图标或通过其他方式启动 Activity 时,启动过程开始。
- Launcher 进程
Launcher 进程(桌面应用)通过 startActivity 方法向 ActivityManagerService (AMS) 发送启动请求。这个请求包含了要启动的 Activity 的信息。
- ActivityManagerService (AMS)
AMS 是系统服务,负责管理所有 Activity 的生命周期。AMS 接收到启动请求后,会进行一系列检查,例如:
- 检查 Activity 是否已经在栈中。
- 检查启动模式(如 singleTop, singleTask 等)。
- 创建或复用进程
如果目标 Activity 所在的进程尚未创建,AMS 会请求 Zygote 进程 fork 一个新的应用进程。如果进程已经存在,则直接复用。
- Zygote 进程
Zygote 是 Android 系统中的一个守护进程,负责创建新的应用进程。它通过 fork 自己来创建新的进程,并初始化基本的应用环境。
- ActivityThread
新创建的应用进程会启动 ActivityThread,这是应用程序的主线程。ActivityThread 会初始化 Looper 和 Handler,并准备好处理消息队列。
- Application 对象
ActivityThread 创建 Application 对象,并调用其 onCreate 方法进行初始化。
- Instrumentation
Instrumentation 是一个用于监控应用与系统交互的类。它负责调用 Activity 的生命周期方法。ActivityThread 通过 Instrumentation 创建目标 Activity 实例,并调用其 onCreate 方法。
- Activity 创建
ActivityThread 调用 performLaunchActivity 方法,创建 Activity 实例,并调用 Activity 的 onCreate 方法。此时,Activity 正式启动并进入前台。
- Activity 显示
最后,Activity 的 onStart 和 onResume 方法被调用,Activity 显示在用户界面上。
流程图
用户操作 -> Launcher -> AMS -> Zygote -> ActivityThread -> Application -> Instrumentation -> Activity 创建 -> Activity 显示
示例代码
以下是一个简化的示例代码,展示了启动 Activity 的基本流程:
// Launcher 进程 Intent intent = new Intent(this, TargetActivity.class); startActivity(intent); // AMS 处理请求 public void startActivity(Intent intent) { // 检查和处理启动请求 // 创建或复用进程 } // ActivityThread 创建 Activity public Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) { Activity activity = new Activity(); activity.attach(...); activity.onCreate(...); return activity; }
二、Android异步任务和消息机制
1、HandlerThread的使用场景和用法
HandlerThread 是 Android 中的一个线程类,专门用于在后台执行耗时任务,同时避免阻塞 UI 线程。它封装了 Looper 和 Handler,简化了线程管理和消息处理。
使用场景
- 后台执行耗时任务:例如网络请求、数据库操作等,这些任务如果在主线程中执行会导致应用卡顿。
- 消息处理和线程间通信:HandlerThread 内部封装了 Looper 和 Handler,可以轻松实现消息的发送和处理,以及线程间的通信。
- 简化线程管理:通过 HandlerThread,开发者无需手动创建和管理线程,只需关注业务逻辑的实现。
用法示例
以下是一个简单的 HandlerThread 使用示例:
public class MyHandlerThread extends HandlerThread { private Handler handler; public MyHandlerThread(String name) { super(name); } @Override protected void onLooperPrepared() { super.onLooperPrepared(); handler = new Handler(getLooper()) { @Override public void handleMessage(Message msg) { // 在这里处理消息 // 可以执行耗时操作,然后将结果发送到UI线程 } }; } public void sendMessageToBackgroundThread() { if (handler != null) { handler.sendMessage(handler.obtainMessage()); } } }
注意事项
-
启动和停止 HandlerThread:
MyHandlerThread handlerThread = new MyHandlerThread("MyThread"); handlerThread.start(); // 启动 HandlerThread handlerThread.quit(); // 停止 HandlerThread
-
与 UI 线程交互:可以通过 Handler 将消息发送到 UI 线程:
Handler uiHandler = new Handler(Looper.getMainLooper()); uiHandler.post(new Runnable() { @Override public void run() { // 在UI线程中执行操作 } });
-
防止内存泄漏:在不再使用时,及时停止 HandlerThread,例如在 Activity 的 onDestroy() 方法中停止 HandlerThread。
HandlerThread 是在 Android 开发中处理耗时任务并与 UI 线程进行交互的有用工具。合理使用 HandlerThread 可以提高应用的响应性和用户体验。HandlerThread 在 Android 开发中有许多优点,但也有一些需要注意的缺点。以下是它的优缺点:
优点
- 简化线程管理:HandlerThread 封装了 Looper 和 Handler,简化了线程的创建和管理,使得开发者可以专注于业务逻辑。
- 避免 UI 线程阻塞:通过在后台线程执行耗时任务,HandlerThread 可以防止 UI 线程卡顿,提高应用的响应速度和用户体验。
- 线程间通信方便:HandlerThread 内部集成了 Handler,可以方便地在不同线程之间发送和处理消息。
- 生命周期管理:HandlerThread 的生命周期与应用组件(如 Activity、Service)可以很好地配合,便于在组件销毁时停止线程,避免资源浪费。
缺点
- 性能开销:尽管 HandlerThread 简化了线程管理,但它仍然会带来一定的性能开销,特别是在频繁创建和销毁线程的情况下。
- 复杂性增加:对于简单的任务,使用 HandlerThread 可能会增加代码的复杂性,不如直接使用 AsyncTask 或 Kotlin 协程来得简洁。
- 内存泄漏风险:如果不正确管理 HandlerThread 的生命周期,可能会导致内存泄漏。例如,未及时停止 HandlerThread 或未清理未处理的消息。
- 单一线程:HandlerThread 只提供一个线程,如果需要并行处理多个任务,可能需要创建多个 HandlerThread 或使用其他并发工具。
适用场景
HandlerThread 适用于需要在后台执行耗时任务并与 UI 线程进行交互的场景,但不适用于需要高并发或复杂线程管理的情况。在这些情况下,可以考虑使用其他并发工具,如 ExecutorService、Kotlin 协程或 RxJava。
2、IntentService的应用场景和使用方式
IntentService 是 Android 中用于处理后台任务的一个服务类,它继承自 Service,并在内部使用工作线程来处理耗时操作。
应用场景
- 后台数据处理:当应用需要在后台处理大量数据或执行耗时操作时,可以使用 IntentService 来避免阻塞主线程。例如,下载文件、处理图像等。
- 批量任务执行:当需要按照特定顺序执行一系列任务时,IntentService 的工作队列可以确保任务按顺序执行,避免并发问题。
- 定期任务:结合 AlarmManager,IntentService 可以用于实现定期执行的后台任务,如定时更新数据、发送推送通知等。
使用方式
-
创建 IntentService 子类:
public class MyIntentService extends IntentService { public MyIntentService() { super("MyIntentService"); } @Override protected void onHandleIntent(@Nullable Intent intent) { // 在这里处理耗时任务 if (intent != null) { String action = intent.getAction(); // 根据 action 执行不同的任务 } } }
-
在 AndroidManifest.xml 中声明服务:
-
启动 IntentService:
Intent intent = new Intent(context, MyIntentService.class); intent.setAction("com.example.ACTION_TASK"); context.startService(intent);
优点
- 后台任务处理:IntentService 在后台线程中执行任务,避免了在主线程中执行耗时操作,从而保证了用户界面的流畅性
- 任务队列管理:IntentService 使用工作队列来管理任务,确保任务按顺序执行
- 自动停止:当所有任务执行完毕后,IntentService 会自动停止,无需手动管理其生命周期
- 简单易用:开发者只需继承 IntentService 并重写 onHandleIntent() 方法即可,简化了后台任务的管理
缺点
- 任务串行执行:IntentService 中的任务只能依次执行,不能并行执行,可能导致性能瓶颈
- 不适合长期运行任务:由于 IntentService 在任务执行完毕后会自动停止,因此不适合用于执行需要长期运行的任务
- 通信限制:IntentService 在处理任务时与主线程或 Activity 之间的通信相对有限。
3、AsyncTask的优点和缺点
AsyncTask 是 Android 中用于简化后台任务处理的一个类。它提供了一种简单的方法来在后台线程中执行任务,并在任务完成后更新 UI 线程。
优点
- 简单易用:AsyncTask 封装了线程和 Handler,使得开发者可以轻松地在后台线程中执行任务,并在任务完成后更新 UI
- UI 更新方便:提供了 onPreExecute()、doInBackground()、onProgressUpdate() 和 onPostExecute() 方法,方便在任务执行前、执行中和执行后更新 UI
- 自动管理线程:AsyncTask 自动管理线程的创建和销毁,开发者无需手动处理线程的生命周期
缺点
- 生命周期问题:AsyncTask 的生命周期与 Activity 的生命周期不同步,可能导致内存泄漏或任务结果无法更新到销毁后的 Activity
- 性能限制:AsyncTask 适用于短时间的后台任务,不适合长时间运行的任务。长时间运行的任务可能会导致性能问题
- 并发限制:AsyncTask 默认是串行执行任务的,虽然可以通过 executeOnExecutor() 方法并行执行任务,但并发任务数量有限
- 内存泄漏风险:如果 AsyncTask 持有对 Activity 的引用,且未正确处理 Activity 的销毁,可能会导致内存泄漏
适用场景
AsyncTask 适用于需要在后台执行短时间任务并在任务完成后更新 UI 的场景,例如网络请求、文件下载、数据库操作等。对于更复杂或长时间运行的任务,可以考虑使用其他并发工具,如 ExecutorService、Kotlin 协程或 RxJava。
4、谈谈你对Activity.runOnUiThread的理解
Activity.runOnUiThread 是 Android 中用于在主线程(UI 线程)上执行代码的一种方法。它的主要作用是确保在非 UI 线程中执行的代码能够安全地更新 UI 元素。
基本原理
在 Android 中,只有主线程才能更新 UI。如果尝试在其他线程中直接更新 UI,会导致应用崩溃或出现其他不可预知的问题。runOnUiThread 提供了一种从非 UI 线程切换到 UI 线程执行代码的方式。
使用方法
runOnUiThread(new Runnable() { @Override public void run() { // 在这里更新 UI } });
这个方法接受一个 Runnable 对象,并将其代码放到主线程的消息队列中执行。如果当前线程已经是主线程,则直接执行 Runnable 的 run() 方法;否则,通过 Handler 将 Runnable 发送到主线程执行。
工作流程
-
检查当前线程:首先检查当前线程是否是主线程。
-
执行 Runnable:
- 如果是主线程,直接执行 Runnable 的 run() 方法。
- 如果不是主线程,通过 Handler 将 Runnable 发送到主线程的消息队列中执行。
示例代码
public class MyActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); new Thread(new Runnable() { @Override public void run() { // 在后台线程中执行耗时操作 runOnUiThread(new Runnable() { @Override public void run() { // 在主线程中更新 UI } }); } }).start(); } }
优点
- 简化 UI 更新:提供了一种简单的方法在非 UI 线程中更新 UI,避免了复杂的线程管理。
- 安全性:确保所有 UI 更新都在主线程中执行,避免了线程安全问题。
缺点
- 性能开销:频繁使用 runOnUiThread 可能会增加主线程的负担,影响应用性能。
- 代码复杂性:在复杂的多线程环境中,频繁切换线程可能会增加代码的复杂性和维护难度。
适用场景
runOnUiThread 适用于需要在后台线程执行耗时操作并在完成后更新 UI 的场景,例如网络请求、数据库操作等。
5、子线程能否更新UI?为什么?
在 Android 开发中,子线程不能直接更新 UI。原因主要有以下几点:
- 设计思想:Android 设计了主线程(UI 线程)来处理所有的 UI 操作,以确保用户界面的流畅性和一致性。如果允许子线程直接更新 UI,可能会导致并发问题,影响用户体验
- 线程安全:UI 操作通常涉及复杂的绘制和布局计算。如果多个线程同时操作 UI,可能会导致数据不一致和崩溃。因此,Android 强制要求只有创建 UI 的线程(通常是主线程)才能更新 UI
- 异常处理:如果在子线程中尝试更新 UI,会抛出 CalledFromWrongThreadException 异常。这是因为 Android 系统会检查当前线程是否是创建视图层次结构的原始线程
不过,可以通过以下几种方法在子线程中间接更新 UI:
- Handler:使用 Handler 将消息从子线程发送到主线程,然后在主线程中处理消息并更新 UI。
- runOnUiThread():在 Activity 中使用 runOnUiThread() 方法,将 UI 更新操作放在主线程中执行。
- AsyncTask:使用 AsyncTask,在 onPostExecute() 方法中更新 UI。
6、谈谈Handler机制和原理
Handler机制是Android中用于线程间通信的重要工具。它主要用于在子线程中执行一些耗时操作后,更新主线程的UI。
- Message:消息对象,包含了需要传递的数据。
- Handler:处理者,用于发送和处理消息。
- MessageQueue:消息队列,存储所有发送的消息。
- Looper:循环器,不断从MessageQueue中取出消息并交给Handler处理。
工作流程
- 创建Handler:在主线程中创建Handler实例。
- 发送消息:在子线程中通过Handler发送消息到MessageQueue。
- 消息入队:消息被加入到MessageQueue中等待处理。
- Looper循环:Looper不断从MessageQueue中取出消息。
- 处理消息:Looper将消息交给Handler的handleMessage方法进行处理。
示例代码
// 在主线程中创建Handler Handler handler = new Handler(Looper.getMainLooper()) { @Override public void handleMessage(Message msg) { // 处理消息 // 更新UI } }; // 在子线程中发送消息 new Thread(new Runnable() { @Override public void run() { Message msg = handler.obtainMessage(); msg.what = 1; handler.sendMessage(msg); } }).start();
7、为什么在子线程中创建Handler会抛异常
在子线程中创建Handler会抛出异常的原因是因为子线程默认没有与Looper关联。Handler需要一个Looper来处理消息队列中的消息,而主线程在启动时会自动创建一个Looper,但子线程不会。
详细原因
- Looper缺失:在子线程中,如果没有调用Looper.prepare()来初始化Looper,Handler就无法找到与之关联的Looper。
- 异常抛出:当你在子线程中创建Handler时,Handler的构造方法会尝试获取当前线程的Looper。如果没有找到,它会抛出RuntimeException,提示“Can’t create handler inside thread that has not called Looper.prepare()”
解决方法
在子线程中创建Handler前,需要手动初始化Looper:
new Thread(new Runnable() { @Override public void run() { // 初始化Looper Looper.prepare(); // 创建Handler Handler handler = new Handler() { @Override public void handleMessage(Message msg) { // 处理消息 } }; // 开始Looper循环 Looper.loop(); } }).start();
8、试从源码角度分析Handler的post和sendMessage方法的区别和应用场景
Handler的post和sendMessage方法在功能上有一些区别,尽管它们都用于将任务或消息添加到消息队列中。
post方法
post方法用于将一个Runnable对象添加到消息队列中。它的实现实际上是通过sendMessageDelayed方法来完成的。源码如下:
public final boolean post(Runnable r) { return sendMessageDelayed(getPostMessage(r), 0); } private static Message getPostMessage(Runnable r) { Message m = Message.obtain(); m.callback = r; return m; }
可以看到,post方法将Runnable包装成一个Message对象,并立即发送到消息队列中。
sendMessage方法
sendMessage方法用于将一个Message对象发送到消息队列中。它需要通过Handler的handleMessage方法来处理消息。源码如下:
public final boolean sendMessage(Message msg) { return sendMessageDelayed(msg, 0); }
sendMessage方法直接发送一个Message对象,并在handleMessage方法中处理。
区别和应用场景
-
实现方式:
- post方法适用于直接执行Runnable对象,不需要额外的消息处理逻辑。
- sendMessage方法适用于需要传递复杂数据并在handleMessage方法中处理的场景。
-
使用场景:
-
post方法:适用于简单的任务,如更新UI。
handler.post(new Runnable() { @Override public void run() { // 更新UI } });
-
sendMessage方法:适用于需要传递数据并进行条件判断的任务。
Message msg = handler.obtainMessage(); msg.what = 1; handler.sendMessage(msg);
-
总结来说,post方法更适合简单的、无需复杂处理的任务,而sendMessage方法则适用于需要传递数据并进行复杂处理的场景。
9、Handler中有Loop死循环,为什么没有阻塞主线程,原理是什么
Handler中的Looper.loop()方法确实是一个死循环,但它不会阻塞主线程。其原理主要涉及到消息队列的处理机制和底层的Linux系统调用。
消息队列和Looper机制
- 消息队列:MessageQueue是一个先进先出的队列,用于存储消息和任务。
- Looper:Looper不断从MessageQueue中取出消息并交给对应的Handler处理。
Looper.loop()的工作原理
Looper.loop()方法的核心是一个无限循环,它不断调用MessageQueue.next()来获取下一个消息。如果没有消息,MessageQueue.next()会进入阻塞状态,直到有新消息到达。这种阻塞是通过底层的Linux系统调用实现的。
为什么不会阻塞主线程
- 阻塞机制:当消息队列为空时,MessageQueue.next()会调用nativePollOnce()方法,这个方法会使线程进入休眠状态,释放CPU资源
- 唤醒机制:当有新消息到达时,系统会通过管道(pipe)或其他机制唤醒线程,使其继续处理消息
资源管理
由于线程在等待消息时会进入休眠状态,因此不会占用大量的CPU资源。这种机制确保了即使在没有消息时,主线程也不会被阻塞,从而保持应用的响应性。
示例代码
public static void main(String[] args) { Looper.prepareMainLooper(); // 初始化主线程的Looper Handler handler = new Handler() { @Override public void handleMessage(Message msg) { // 处理消息 } }; Looper.loop(); // 开始消息循环 }
三、Android Ul绘制相关
1、Android补间动画和属性动画的区别
Android中的补间动画和属性动画有几个关键区别,主要体现在作用对象、动画效果和实现方式上。
补间动画 (Tween Animation)
- 作用对象:只能作用于View对象。
- 动画效果:包括平移(Translate)、缩放(Scale)、旋转(Rotate)和透明度(Alpha)四种基本效果。
- 实现方式:通过设置初始和终止状态,利用插值器(Interpolator)计算中间状态,从而实现动画效果。
- 特点:只改变View的显示效果,不会真正改变View的属性。例如,View的位置在动画过程中看似改变了,但实际坐标并未改变,点击事件仍在原位置触发。
属性动画 (Property Animation)
- 作用对象:可以作用于任何对象,不仅限于View。
- 动画效果:除了补间动画的四种效果外,还可以实现更多复杂的动画效果。
- 实现方式:通过ValueAnimator或ObjectAnimator等类,直接操作对象的属性。需要对象提供相应属性的getter和setter方法。
- 特点:真正改变对象的属性。例如,View的位置在动画过程中实际改变了,点击事件会在动画结束后的新位置触发。
示例代码
补间动画:
TranslateAnimation translateAnimation = new TranslateAnimation(0, 100, 0, 100); translateAnimation.setDuration(1000); view.startAnimation(translateAnimation);
属性动画:
ObjectAnimator animator = ObjectAnimator.ofFloat(view, "translationX", 0f, 100f); animator.setDuration(1000); animator.start();
应用场景
- 补间动画:适用于简单的UI动画,不需要改变实际属性的场景。
- 属性动画:适用于需要精确控制和改变对象属性的复杂动画场景。
2、Window和DecorView是什么?DecorView又是如何和Window建立联系的
在 Android 开发中,Window 和 DecorView 是两个重要的概念。
Window
Window 是一个抽象类,代表一个窗口的外观和行为。它是视图的承载器,负责管理和显示视图。每个 Activity 都包含一个 Window,在 Activity 中实际持有的是 PhoneWindow 这个子类。
DecorView
DecorView 是