安卓Android面试题汇总——超长15万字总结

06-01 1744阅读

一、Android四大组件

1、Activity与Fragment之间常见的几种通信方式

在 Android 开发中,Activity 与 Fragment 之间的通信是一个常见的需求。以下是几种常见的通信方式:

  1. 接口回调

在 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 的消息
    }
}
  1. 使用 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");
    }
}
  1. 使用 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 发送消息
    }
}
  1. 使用广播(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);
  1. 使用 EventBus

EventBus 是一个发布/订阅事件总线,可以简化组件间的通信。

// 在 Activity 中
EventBus.getDefault().register(this);
@Subscribe
public void onEvent(MessageEvent event) {
   
    // 处理事件
}
// 在 Fragment 中
EventBus.getDefault().post(new MessageEvent("message"));
  1. 使用 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。每种模式都有其特定的应用场景和使用方法。

  1. standard 模式

默认模式,每次启动 Activity 时都会创建一个新的实例,并将其放入任务栈中。

  • 应用场景:适用于大多数普通页面,例如用户浏览不同的内容页面时,每次点击都会打开一个新的页面实例。
 
  1. singleTop 模式

如果要启动的 Activity 已经位于栈顶,则不会创建新的实例,而是复用栈顶的实例,并调用其 onNewIntent() 方法。

  • 应用场景:适用于通知页面或详情页面,例如新闻客户端的新闻详情页面,避免多次点击通知创建多个实例。
 
  1. singleTask 模式

如果栈中已经存在该 Activity 的实例,则复用该实例,并将其上方的所有 Activity 移除。复用时会调用 onNewIntent() 方法。

  • 应用场景:适用于应用的主界面或入口点,例如浏览器的主界面,确保每次启动时都回到主界面,并清除其上的其他页面。
 
  1. singleInstance 模式

在一个新的任务栈中创建该 Activity 的实例,并让多个应用共享该栈中的实例。任何应用再激活该 Activity 时都会重用该实例。

  • 应用场景:适用于需要与应用分离的页面,例如闹钟提醒页面,确保每次启动时都进入同一个实例。
 
3、BroadcastReceiver 与 LocalBroadcastReceiver有什么区别

BroadcastReceiver 和 LocalBroadcastReceiver(通常通过 LocalBroadcastManager 实现)是 Android 中用于广播消息的两种机制。它们之间有一些关键区别:

BroadcastReceiver

安卓Android面试题汇总——超长15万字总结
(图片来源网络,侵删)
  1. 跨应用广播:BroadcastReceiver 可以用于应用之间的通信,也可以用于应用与系统之间的通信。
  2. 注册方式:支持静态注册(在 AndroidManifest.xml 中声明)和动态注册(在代码中使用 registerReceiver() 方法)。
  3. 安全性:由于可以跨应用通信,必须考虑安全性,防止其他应用滥用广播。
  4. 性能:由于涉及跨进程通信,性能相对较低。

LocalBroadcastReceiver

  1. 应用内广播:LocalBroadcastReceiver 仅在应用内部发送和接收广播,不能跨应用通信。
  2. 注册方式:只支持动态注册,使用 LocalBroadcastManager 的 registerReceiver() 方法。
  3. 安全性:由于只在应用内部有效,不需要考虑外部应用的安全问题,数据更加安全。
  4. 性能:由于不涉及跨进程通信,性能更高。

示例代码

安卓Android面试题汇总——超长15万字总结
(图片来源网络,侵删)

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

安卓Android面试题汇总——超长15万字总结
(图片来源网络,侵删)
// 在 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 的作用

  1. 资源访问:通过 Context 可以访问应用的资源和类,例如字符串、颜色、布局文件等。
  2. 启动组件:可以通过 Context 启动新的 Activity、Service,发送广播等。
  3. 访问系统服务:通过 Context 可以获取系统级服务,例如 WindowManager、LayoutInflater、ActivityManager 等。
  4. 文件操作:可以通过 Context 进行文件的读写操作。
  5. 数据库操作:可以通过 Context 访问应用的数据库。

Context 的类型

  1. Application Context:应用程序级别的 Context,生命周期与应用程序相同。适用于需要全局访问的资源或服务。
  2. Activity Context:Activity 级别的 Context,生命周期与 Activity 相同。适用于需要与 Activity 相关的操作,例如启动新的 Activity 或者显示对话框。

使用场景

  1. 启动 Activity:

    Intent intent = new Intent(context, NewActivity.class);
    context.startActivity(intent);
    
  2. 访问资源:

    String appName = context.getString(R.string.app_name);
    Drawable icon = context.getDrawable(R.drawable.icon);
    
  3. 获取系统服务:

    WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
    
  4. 文件操作:

    FileOutputStream fos = context.openFileOutput("filename", Context.MODE_PRIVATE);
    fos.write(data);
    fos.close();
    

注意事项

  1. 避免内存泄漏:在使用 Context 时要小心,特别是在长生命周期的对象中(如单例模式),避免持有 Activity 的 Context,应使用 Application 的 Context。
  2. 正确选择 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)。

使用场景

  1. 启动Activity:当用户点击某个链接或文件时,系统会通过IntentFilter找到能够处理该链接或文件的Activity。例如,点击PDF文件时,系统会找到能够打开PDF文件的应用程序。
  2. 启动Service:某些后台服务可以通过IntentFilter来响应特定的操作或数据请求。
  3. 接收广播:BroadcastReceiver可以通过IntentFilter来接收特定的广播消息,如系统启动、网络变化等。

过滤规则

IntentFilter主要通过以下三个方面来匹配隐式意图:

  1. 动作(Action):通过标签指定。例如:

     
  2. 数据(Data):通过标签指定URI和数据类型。例如:

     
  3. 类别(Category):通过标签指定。例如:

     

示例代码

以下是一个简单的IntentFilter配置示例:

    
        
        
        
    

这个配置表示,当有一个包含http://www.example.com链接的隐式意图时,系统会启动ExampleActivity来处理该意图。

6、详细介绍startService和bindService的区别,生命周期以及使用场景

在Android开发中,startService和bindService是启动和绑定服务(Service)的两种主要方法。它们在使用场景和生命周期上有显著的区别。

(1)startService

使用场景

  • 长时间运行的后台任务:适用于需要在后台长时间运行的任务,例如下载文件、播放音乐等。
  • 独立运行:服务可以独立于启动它的组件运行,即使启动它的组件被销毁,服务仍然会继续运行。

生命周期

  1. 启动:通过调用startService(Intent)启动服务。
  2. onCreate():服务第一次启动时调用。
  3. onStartCommand(Intent, int, int):每次启动服务时调用。
  4. 运行:服务在后台运行,直到调用stopSelf()或stopService(Intent)停止服务。
  5. onDestroy():服务被停止时调用。

(2)bindService

使用场景

  • 客户端-服务器通信:适用于需要与服务进行交互的场景,例如获取数据、控制播放等。
  • 生命周期绑定:服务的生命周期与绑定它的组件(如Activity)绑定,当所有绑定的组件都解绑时,服务会被销毁。

生命周期

  1. 绑定:通过调用bindService(Intent, ServiceConnection, int)绑定服务。
  2. onCreate():服务第一次启动时调用。
  3. onBind(Intent):每次绑定服务时调用,返回一个IBinder对象用于客户端与服务的通信。
  4. 运行:服务在至少有一个组件绑定时运行。
  5. onUnbind(Intent):当所有绑定的组件解绑时调用。
  6. 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会被销毁并重新创建。以下是详细的生命周期变化过程:

默认情况下的生命周期变化

  1. onPause():Activity即将进入后台,暂停与用户的交互。
  2. onStop():Activity完全不可见。
  3. onDestroy():Activity被销毁。
  4. onCreate():Activity重新创建。
  5. onStart():Activity变为可见。
  6. onRestoreInstanceState():恢复之前保存的状态。
  7. 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 的启动模式时。

调用时机

  1. SingleTop 模式:当 Activity 的启动模式为 SingleTop 且该 Activity 已经在栈顶时,再次启动该 Activity 会调用 onNewIntent 方法,而不是创建新的实例。
  2. SingleTask 和 SingleInstance 模式:当 Activity 的启动模式为 SingleTask 或 SingleInstance,且该 Activity 已经存在于任务栈中时,再次启动该 Activity 会调用 onNewIntent 方法。
  3. Intent Flags:使用 FLAG_ACTIVITY_SINGLE_TOP 标记启动 Activity 时,如果该 Activity 已经在栈顶,也会调用 onNewIntent 方法。

使用场景

  1. 通知处理:当应用接收到通知并需要打开一个已经存在的 Activity 时,可以使用 onNewIntent 方法来处理新的 Intent 数据,而不需要重新创建 Activity 实例。例如,新闻应用接收到多条新闻推送时,可以使用 SingleTop 模式和 onNewIntent 方法来更新当前显示的新闻内容。
  2. 单例模式:对于一些需要全局唯一实例的 Activity,例如浏览器的主界面,可以使用 SingleTask 模式和 onNewIntent 方法来确保每次启动时都使用同一个实例,并处理新的 Intent 数据。
  3. 数据更新:在某些情况下,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 左右。

解决方法

  1. 使用文件存储:将大数据存储在文件中,然后通过 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);
    // 读取文件内容
    
  2. 使用 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);
    // 处理数据
    
  3. 使用 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);
    // 处理数据
    
  4. 使用 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 系统中一个复杂且关键的过程,涉及多个系统组件和进程间的通信。以下是详细的启动流程:

  1. 用户操作

当用户点击应用图标或通过其他方式启动 Activity 时,启动过程开始。

  1. Launcher 进程

Launcher 进程(桌面应用)通过 startActivity 方法向 ActivityManagerService (AMS) 发送启动请求。这个请求包含了要启动的 Activity 的信息。

  1. ActivityManagerService (AMS)

AMS 是系统服务,负责管理所有 Activity 的生命周期。AMS 接收到启动请求后,会进行一系列检查,例如:

  • 检查 Activity 是否已经在栈中。
  • 检查启动模式(如 singleTop, singleTask 等)。
  1. 创建或复用进程

如果目标 Activity 所在的进程尚未创建,AMS 会请求 Zygote 进程 fork 一个新的应用进程。如果进程已经存在,则直接复用。

  1. Zygote 进程

Zygote 是 Android 系统中的一个守护进程,负责创建新的应用进程。它通过 fork 自己来创建新的进程,并初始化基本的应用环境。

  1. ActivityThread

新创建的应用进程会启动 ActivityThread,这是应用程序的主线程。ActivityThread 会初始化 Looper 和 Handler,并准备好处理消息队列。

  1. Application 对象

ActivityThread 创建 Application 对象,并调用其 onCreate 方法进行初始化。

  1. Instrumentation

Instrumentation 是一个用于监控应用与系统交互的类。它负责调用 Activity 的生命周期方法。ActivityThread 通过 Instrumentation 创建目标 Activity 实例,并调用其 onCreate 方法。

  1. Activity 创建

ActivityThread 调用 performLaunchActivity 方法,创建 Activity 实例,并调用 Activity 的 onCreate 方法。此时,Activity 正式启动并进入前台。

  1. 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,简化了线程管理和消息处理。

使用场景

  1. 后台执行耗时任务:例如网络请求、数据库操作等,这些任务如果在主线程中执行会导致应用卡顿。
  2. 消息处理和线程间通信:HandlerThread 内部封装了 Looper 和 Handler,可以轻松实现消息的发送和处理,以及线程间的通信。
  3. 简化线程管理:通过 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());
        }
    }
}

注意事项

  1. 启动和停止 HandlerThread:

    MyHandlerThread handlerThread = new MyHandlerThread("MyThread");
    handlerThread.start(); // 启动 HandlerThread
    handlerThread.quit();  // 停止 HandlerThread
    
  2. 与 UI 线程交互:可以通过 Handler 将消息发送到 UI 线程:

    Handler uiHandler = new Handler(Looper.getMainLooper());
    uiHandler.post(new Runnable() {
         
        @Override
        public void run() {
         
            // 在UI线程中执行操作
        }
    });
    
  3. 防止内存泄漏:在不再使用时,及时停止 HandlerThread,例如在 Activity 的 onDestroy() 方法中停止 HandlerThread。

HandlerThread 是在 Android 开发中处理耗时任务并与 UI 线程进行交互的有用工具。合理使用 HandlerThread 可以提高应用的响应性和用户体验。HandlerThread 在 Android 开发中有许多优点,但也有一些需要注意的缺点。以下是它的优缺点:

优点

  1. 简化线程管理:HandlerThread 封装了 Looper 和 Handler,简化了线程的创建和管理,使得开发者可以专注于业务逻辑。
  2. 避免 UI 线程阻塞:通过在后台线程执行耗时任务,HandlerThread 可以防止 UI 线程卡顿,提高应用的响应速度和用户体验。
  3. 线程间通信方便:HandlerThread 内部集成了 Handler,可以方便地在不同线程之间发送和处理消息。
  4. 生命周期管理:HandlerThread 的生命周期与应用组件(如 Activity、Service)可以很好地配合,便于在组件销毁时停止线程,避免资源浪费。

缺点

  1. 性能开销:尽管 HandlerThread 简化了线程管理,但它仍然会带来一定的性能开销,特别是在频繁创建和销毁线程的情况下。
  2. 复杂性增加:对于简单的任务,使用 HandlerThread 可能会增加代码的复杂性,不如直接使用 AsyncTask 或 Kotlin 协程来得简洁。
  3. 内存泄漏风险:如果不正确管理 HandlerThread 的生命周期,可能会导致内存泄漏。例如,未及时停止 HandlerThread 或未清理未处理的消息。
  4. 单一线程:HandlerThread 只提供一个线程,如果需要并行处理多个任务,可能需要创建多个 HandlerThread 或使用其他并发工具。

适用场景

HandlerThread 适用于需要在后台执行耗时任务并与 UI 线程进行交互的场景,但不适用于需要高并发或复杂线程管理的情况。在这些情况下,可以考虑使用其他并发工具,如 ExecutorService、Kotlin 协程或 RxJava。

2、IntentService的应用场景和使用方式

IntentService 是 Android 中用于处理后台任务的一个服务类,它继承自 Service,并在内部使用工作线程来处理耗时操作。

应用场景

  1. 后台数据处理:当应用需要在后台处理大量数据或执行耗时操作时,可以使用 IntentService 来避免阻塞主线程。例如,下载文件、处理图像等。
  2. 批量任务执行:当需要按照特定顺序执行一系列任务时,IntentService 的工作队列可以确保任务按顺序执行,避免并发问题。
  3. 定期任务:结合 AlarmManager,IntentService 可以用于实现定期执行的后台任务,如定时更新数据、发送推送通知等。

使用方式

  1. 创建 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 执行不同的任务
            }
        }
    }
    
  2. 在 AndroidManifest.xml 中声明服务:

     
  3. 启动 IntentService:

    Intent intent = new Intent(context, MyIntentService.class);
    intent.setAction("com.example.ACTION_TASK");
    context.startService(intent);
    

优点

  1. 后台任务处理:IntentService 在后台线程中执行任务,避免了在主线程中执行耗时操作,从而保证了用户界面的流畅性
  2. 任务队列管理:IntentService 使用工作队列来管理任务,确保任务按顺序执行
  3. 自动停止:当所有任务执行完毕后,IntentService 会自动停止,无需手动管理其生命周期
  4. 简单易用:开发者只需继承 IntentService 并重写 onHandleIntent() 方法即可,简化了后台任务的管理

缺点

  1. 任务串行执行:IntentService 中的任务只能依次执行,不能并行执行,可能导致性能瓶颈
  2. 不适合长期运行任务:由于 IntentService 在任务执行完毕后会自动停止,因此不适合用于执行需要长期运行的任务
  3. 通信限制:IntentService 在处理任务时与主线程或 Activity 之间的通信相对有限。
3、AsyncTask的优点和缺点

AsyncTask 是 Android 中用于简化后台任务处理的一个类。它提供了一种简单的方法来在后台线程中执行任务,并在任务完成后更新 UI 线程。

优点

  1. 简单易用:AsyncTask 封装了线程和 Handler,使得开发者可以轻松地在后台线程中执行任务,并在任务完成后更新 UI
  2. UI 更新方便:提供了 onPreExecute()、doInBackground()、onProgressUpdate() 和 onPostExecute() 方法,方便在任务执行前、执行中和执行后更新 UI
  3. 自动管理线程:AsyncTask 自动管理线程的创建和销毁,开发者无需手动处理线程的生命周期

缺点

  1. 生命周期问题:AsyncTask 的生命周期与 Activity 的生命周期不同步,可能导致内存泄漏或任务结果无法更新到销毁后的 Activity
  2. 性能限制:AsyncTask 适用于短时间的后台任务,不适合长时间运行的任务。长时间运行的任务可能会导致性能问题
  3. 并发限制:AsyncTask 默认是串行执行任务的,虽然可以通过 executeOnExecutor() 方法并行执行任务,但并发任务数量有限
  4. 内存泄漏风险:如果 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 发送到主线程执行。

工作流程

  1. 检查当前线程:首先检查当前线程是否是主线程。

  2. 执行 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();
    }
}

优点

  1. 简化 UI 更新:提供了一种简单的方法在非 UI 线程中更新 UI,避免了复杂的线程管理。
  2. 安全性:确保所有 UI 更新都在主线程中执行,避免了线程安全问题。

缺点

  1. 性能开销:频繁使用 runOnUiThread 可能会增加主线程的负担,影响应用性能。
  2. 代码复杂性:在复杂的多线程环境中,频繁切换线程可能会增加代码的复杂性和维护难度。

适用场景

runOnUiThread 适用于需要在后台线程执行耗时操作并在完成后更新 UI 的场景,例如网络请求、数据库操作等。

5、子线程能否更新UI?为什么?

在 Android 开发中,子线程不能直接更新 UI。原因主要有以下几点:

  1. 设计思想:Android 设计了主线程(UI 线程)来处理所有的 UI 操作,以确保用户界面的流畅性和一致性。如果允许子线程直接更新 UI,可能会导致并发问题,影响用户体验
  2. 线程安全:UI 操作通常涉及复杂的绘制和布局计算。如果多个线程同时操作 UI,可能会导致数据不一致和崩溃。因此,Android 强制要求只有创建 UI 的线程(通常是主线程)才能更新 UI
  3. 异常处理:如果在子线程中尝试更新 UI,会抛出 CalledFromWrongThreadException 异常。这是因为 Android 系统会检查当前线程是否是创建视图层次结构的原始线程

不过,可以通过以下几种方法在子线程中间接更新 UI:

  • Handler:使用 Handler 将消息从子线程发送到主线程,然后在主线程中处理消息并更新 UI。
  • runOnUiThread():在 Activity 中使用 runOnUiThread() 方法,将 UI 更新操作放在主线程中执行。
  • AsyncTask:使用 AsyncTask,在 onPostExecute() 方法中更新 UI。
6、谈谈Handler机制和原理

Handler机制是Android中用于线程间通信的重要工具。它主要用于在子线程中执行一些耗时操作后,更新主线程的UI。

  1. Message:消息对象,包含了需要传递的数据。
  2. Handler:处理者,用于发送和处理消息。
  3. MessageQueue:消息队列,存储所有发送的消息。
  4. Looper:循环器,不断从MessageQueue中取出消息并交给Handler处理。

工作流程

  1. 创建Handler:在主线程中创建Handler实例。
  2. 发送消息:在子线程中通过Handler发送消息到MessageQueue。
  3. 消息入队:消息被加入到MessageQueue中等待处理。
  4. Looper循环:Looper不断从MessageQueue中取出消息。
  5. 处理消息: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,但子线程不会。

详细原因

  1. Looper缺失:在子线程中,如果没有调用Looper.prepare()来初始化Looper,Handler就无法找到与之关联的Looper。
  2. 异常抛出:当你在子线程中创建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方法中处理。

区别和应用场景

  1. 实现方式:

    • post方法适用于直接执行Runnable对象,不需要额外的消息处理逻辑。
    • sendMessage方法适用于需要传递复杂数据并在handleMessage方法中处理的场景。
  2. 使用场景:

    • 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机制

  1. 消息队列:MessageQueue是一个先进先出的队列,用于存储消息和任务。
  2. Looper:Looper不断从MessageQueue中取出消息并交给对应的Handler处理。

Looper.loop()的工作原理

Looper.loop()方法的核心是一个无限循环,它不断调用MessageQueue.next()来获取下一个消息。如果没有消息,MessageQueue.next()会进入阻塞状态,直到有新消息到达。这种阻塞是通过底层的Linux系统调用实现的。

为什么不会阻塞主线程

  1. 阻塞机制:当消息队列为空时,MessageQueue.next()会调用nativePollOnce()方法,这个方法会使线程进入休眠状态,释放CPU资源
  2. 唤醒机制:当有新消息到达时,系统会通过管道(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)

  1. 作用对象:只能作用于View对象。
  2. 动画效果:包括平移(Translate)、缩放(Scale)、旋转(Rotate)和透明度(Alpha)四种基本效果。
  3. 实现方式:通过设置初始和终止状态,利用插值器(Interpolator)计算中间状态,从而实现动画效果。
  4. 特点:只改变View的显示效果,不会真正改变View的属性。例如,View的位置在动画过程中看似改变了,但实际坐标并未改变,点击事件仍在原位置触发。

属性动画 (Property Animation)

  1. 作用对象:可以作用于任何对象,不仅限于View。
  2. 动画效果:除了补间动画的四种效果外,还可以实现更多复杂的动画效果。
  3. 实现方式:通过ValueAnimator或ObjectAnimator等类,直接操作对象的属性。需要对象提供相应属性的getter和setter方法。
  4. 特点:真正改变对象的属性。例如,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 是

免责声明:我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理! 图片声明:本站部分配图来自人工智能系统AI生成,觅知网授权图片,PxHere摄影无版权图库和百度,360,搜狗等多加搜索引擎自动关键词搜索配图,如有侵权的图片,请第一时间联系我们。

目录[+]

取消
微信二维码
微信二维码
支付宝二维码