超详细!Android 面试题大汇总与深度剖析

打印 上一主题 下一主题

主题 1837|帖子 1837|积分 5511

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
一、Java 与 Kotlin 根本

1. Java 的多态是如何实现的?

多态是指在 Java 中,同一个行为具有多个不同表现情势或形态的能力。它主要通过方法重载(Overloading)和方法重写(Overriding)来实现。


  • 方法重载:发生在同一个类中,方法名雷同,但参数列表不同(参数个数、范例或次序不同)。编译器在编译时,会根据调用方法时传入的参数来确定调用哪个重载版本的方法,这是一种静态绑定,也叫编译时多态。比方:
java
  1. public class Calculator {
  2.     public int add(int a, int b) {
  3.         return a + b;
  4.     }
  5.     public double add(double a, double b) {
  6.         return a + b;
  7.     }
  8. }
复制代码


  • 方法重写:发生在子类与父类之间,子类重写父类的方法,方法名、参数列表、返回值范例都必须雷同(返回值范例可以是父类返回值范例的子类,在 Java 5.0 及以上版本支持,称为协变返回范例)。在运行时,根据对象的实际范例来决定调用哪个类的重写方法,这是动态绑定,也叫运行时多态。比方:
java
  1. class Animal {
  2.     public void makeSound() {
  3.         System.out.println("Animal makes a sound");
  4.     }
  5. }
  6. class Dog extends Animal {
  7.     @Override
  8.     public void makeSound() {
  9.         System.out.println("Dog barks");
  10.     }
  11. }
  12. public class Main {
  13.     public static void main(String[] args) {
  14.         Animal animal1 = new Animal();
  15.         Animal animal2 = new Dog();
  16.         animal1.makeSound();// 输出:Animal makes a sound
  17.         animal2.makeSound();// 输出:Dog barks
  18.     }
  19. }
复制代码
2. Kotlin 中数据类(data class)的特点是什么?

Kotlin 的数据类是一种专门用于存储数据的类,它有以下特点:


  • 自动生成函数:编译器会自动为数据类生成 equals()、hashCode()、toString()、copy() 以及全部属性的 getter 和 setter(如果属性是可变的)。比方:
kotlin
  1. data class User(val name: String, val age: Int)
  2. val user = User("John", 25)
  3. println(user.toString())// 输出:User(name=John, age=25)
  4. val copiedUser = user.copy(age = 26)
  5. println(copiedUser)// 输出:User(name=John, age=26)
复制代码


  • 主构造函数至少有一个参数:这些参数会成为数据类的属性。
  • 属性必须是 val 或 var 修饰:通常利用 val 定义只读属性,var 定义可变属性。
  • 数据类不能是抽象、开放、密封大概内部的:不过数据类可以继承其他类或实现接口。
3. Java 中的非常处置处罚机制是怎样的?

Java 的非常处置处罚机制用于捕获和处置处罚步伐运行时出现的错误,保证步伐的健壮性。它主要包括以下几个部门:


  • 非常范例:分为受检非常(Checked Exception)和非受检非常(Unchecked Exception)。受检非常是编译时必须处置处罚的非常,比方 IOException、SQLException 等;非受检非常包括运行时非常(RuntimeException 及其子类)和错误(Error),运行时非常如 NullPointerException、IndexOutOfBoundsException 等,错误如 OutOfMemoryError、StackOverflowError 等,非受检非常在编译时不必要显式处置处罚。
  • try - catch - finally 块:try 块中放置可能会抛出非常的代码。当非常发生时,步伐会跳转到对应的 catch 块中实行非常处置处罚代码,catch 块可以有多个,用于捕获不同范例的非常。finally 块无论是否发生非常都会实行,通常用于释放资源等操纵。比方:
java
  1. try {
  2.     FileReader fileReader = new FileReader("nonexistent.txt");
  3. } catch (FileNotFoundException e) {
  4.     e.printStackTrace();
  5. } finally {
  6.     // 这里可以关闭文件流等资源
  7. }
复制代码


  • throws 声明:方法可以利用 throws 声明它可能抛出的非常,让调用者来处置处罚这些非常。比方:
java
  1. public void readFile() throws IOException {
  2.     FileReader fileReader = new FileReader("file.txt");
  3.     // 读取文件的代码
  4.     fileReader.close();
  5. }
复制代码


  • throw 语句:用于在代码中手动抛出一个非常。比方:
java
  1. if (age < 0) {
  2.     throw new IllegalArgumentException("Age cannot be negative");
  3. }
复制代码
4. Kotlin 中的空安满是如何实现的?

Kotlin 为空安全提供了强大的支持,主要通过以下几种方式实现:


  • 可空范例与非可空范例:在 Kotlin 中,范例默认是非可空的,比方 String 范例的变量不能赋值为 null。如果必要变量可以为 null,则要利用可空范例,即在范例后面加上 ?,如 String?。比方:
kotlin
  1. var name: String = "John"
  2. // name = null // 这行代码会报错,因为 name 是非可空类型
  3. var nullableName: String? = null
复制代码


  • 安全调用操纵符(?.) :用于在调用对象的方法或访问属性时,先检查对象是否为 null。如果对象为 null,则不会实行后续操纵,而是返回 null。比方:
kotlin
  1. val length = nullableName?.length
  2. println(length)// 输出:null
复制代码


  • Elvis 操纵符(?:) :用于在对象可能为 null 的情况下提供一个默认值。比方:
kotlin
  1. val result = nullableName?.length?: -1
  2. println(result)// 输出:-1
复制代码


  • 安全转换操纵符(as?) :用于将一个对象转换为指定范例,如果转换失败则返回 null,而不是抛出 ClassCastException。比方:
kotlin
  1. val obj: Any = "string"
  2. val str: String? = obj as? String
  3. println(str)// 输出:string
复制代码


  • 非空断言操纵符(!!) :用于将可空范例转换为非可空范例,如果对象为 null,则会抛出 NullPointerException。一般不发起过多利用,因为它粉碎了空安全机制。比方:
kotlin
  1. val nonNullableName: String = nullableName!!
复制代码
二、Android 根本组件

1. Activity 的生命周期方法有哪些?它们的实行次序是怎样的?

Activity 的生命周期方法主要有以下几个,实行次序如下:


  • onCreate(Bundle savedInstanceState) :在 Activity 第一次创建时调用,用于初始化 Activity 的布局、绑定数据等。比方:
java
  1. @Override
  2. protected void onCreate(Bundle savedInstanceState) {
  3.     super.onCreate(savedInstanceState);
  4.     setContentView(R.layout.activity_main);
  5.     // 初始化其他组件和数据
  6. }
复制代码


  • onStart() :当 Activity 即将可见时调用。此时 Activity 还未出如今前台,不可交互。
  • onResume() :Activity 进入前台并开始与用户交互时调用。这是 Activity 生命周期中用户可以与之交互的阶段。
  • onPause() :当 Activity 失去焦点但仍可见时调用,比方启动了一个对话框式的 Activity。通常用于保存持久数据、停止动画等操纵。比方:
java
  1. @Override
  2. protected void onPause() {
  3.     super.onPause();
  4.     // 保存数据到数据库等操作
  5. }
复制代码


  • onStop() :当 Activity 不再可见时调用,比如跳转到其他 Activity 或按了 Home 键。此时 Activity 处于后台。
  • onDestroy() :Activity 被销毁前调用,用于释放资源,如取消注册的广播汲取器、关闭数据库连接等。比方:
java
  1. @Override
  2. protected void onDestroy() {
  3.     super.onDestroy();
  4.     // 取消注册广播接收器
  5.     unregisterReceiver(mReceiver);
  6. }
复制代码


  • onRestart() :当 Activity 从停止状态重新启动时调用,在 onStart() 之前实行。
2. Service 的启动方式有几种?它们有什么区别?

Service 的启动方式主要有两种:


  • startService(Intent intent) :通过这种方式启动的 Service,会不停运行在后台,纵然启动它的组件(如 Activity)被销毁,Service 也不会停止。当调用 startService() 时,系统会调用 Service 的 onCreate() 方法(如果 Service 尚未创建),然后调用 onStartCommand(Intent intent, int flags, int startId) 方法。比方:
java
  1. Intent serviceIntent = new Intent(this, MyService.class);
  2. startService(serviceIntent);
复制代码
在 Service 中:
java
  1. public class MyService extends Service {
  2.     @Override
  3.     public void onCreate() {
  4.         super.onCreate();
  5.         // 初始化 Service,如创建线程等
  6.     }
  7.     @Override
  8.     public int onStartCommand(Intent intent, int flags, int startId) {
  9.         // 处理启动请求,可返回不同标志控制 Service 的行为
  10.         return START_STICKY;
  11.     }
  12.     @Override
  13.     public IBinder onBind(Intent intent) {
  14.         return null;
  15.     }
  16. }
复制代码


  • bindService(Intent intent, ServiceConnection conn, int flags) :通过这种方式启动的 Service,与启动它的组件(如 Activity)绑定在一起,当绑定的组件销毁时,Service 也会随之销毁。调用 bindService() 时,系统会调用 Service 的 onCreate() 方法(如果 Service 尚未创建),然后调用 onBind(Intent intent) 方法,返回一个 IBinder 对象给绑定的组件。组件通过 ServiceConnection 接口来与 Service 进行交互。比方:
java
  1. private ServiceConnection mConnection = new ServiceConnection() {
  2.     @Override
  3.     public void onServiceConnected(ComponentName name, IBinder service) {
  4.         // 获取 Service 的代理对象,进行交互
  5.     }
  6.     @Override
  7.     public void onServiceDisconnected(ComponentName name) {
  8.         // Service 与组件断开连接时调用
  9.     }
  10. };
  11. Intent bindIntent = new Intent(this, MyService.class);
  12. bindService(bindIntent, mConnection, Context.BIND_AUTO_CREATE);
复制代码
在 Service 中:
java
  1. public class MyService extends Service {
  2.     private final IBinder mBinder = new LocalBinder();
  3.     public class LocalBinder extends Binder {
  4.         public MyService getService() {
  5.             return MyService.this;
  6.         }
  7.     }
  8.     @Override
  9.     public IBinder onBind(Intent intent) {
  10.         return mBinder;
  11.     }
  12. }
复制代码
3. BroadcastReceiver 的注册方式有几种?动态注册和静态注册有什么区别?

BroadcastReceiver 的注册方式有两种:


  • 动态注册:在代码中通过 registerReceiver(BroadcastReceiver receiver, IntentFilter filter) 方法进行注册。比方:
java
  1. private BroadcastReceiver mReceiver = new BroadcastReceiver() {
  2.     @Override
  3.     public void onReceive(Context context, Intent intent) {
  4.         // 处理接收到的广播
  5.     }
  6. };
  7. IntentFilter filter = new IntentFilter();
  8. filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
  9. registerReceiver(mReceiver, filter);
复制代码
动态注册的长处是灵活性高,可以根据必要在运行时动态注册和取消注册广播汲取器,而且可以在不同的生命周期阶段进行操纵。缺点是当注册广播汲取器的组件销毁时,如果没有及时取消注册,可能会导致内存泄漏。另外,动态注册的广播汲取器只能汲取特定组件发送的广播(如果广播是在应用内发送)。


  • 静态注册:在 AndroidManifest.xml 文件中通过 <receiver> 标签进行注册。比方:
xml
  1. <receiver android:name=".MyReceiver">
  2.     <intent-filter>
  3.         <action android:name="android.intent.action.BOOT_COMPLETED" />
  4.     </intent-filter>
  5. </receiver>
复制代码
静态注册的长处是纵然应用没有运行,也能汲取特定的系统广播,如开机完成广播 BOOT_COMPLETED。缺点是相对静态,不敷灵活,一旦注册就会不停存在,除非卸载应用。而且过多的静态注册可能会增长应用的启动时间和资源消耗。
4. ContentProvider 的作用是什么?如何实现一个 ContentProvider?

ContentProvider 主要用于在不同应用步伐之间共享数据,它提供了一种统一的方式来存储、检索和操纵数据。比方,系统的接洽人应用通过 ContentProvider 向外提供接洽人数据,其他应用可以通过 ContentProvider 访问这些数据。
实现一个 ContentProvider 主要步调如下:


  • 创建一个类继承自 ContentProvider:比方:
java
  1. public class MyContentProvider extends ContentProvider {
  2.     // 实现 ContentProvider 的抽象方法
  3. }
复制代码


  • 在 AndroidManifest.xml 中注册 ContentProvider
xml
  1. <provider
  2.     android:name=".MyContentProvider"
  3.     android:authorities="com.example.myprovider"
  4.     android:exported="true" />
复制代码


  • 实现 ContentProvider 的抽象方法

    • onCreate() :在 ContentProvider 创建时调用,用于初始化一些资源。比方:

java
  1. @Override
  2. public boolean onCreate() {
  3.     // 初始化数据库等资源
  4.     return true;
  5. }
复制代码


  • query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) :用于查询数据。比方:
java
  1. @Override
  2. public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
  3.     // 根据 uri 等参数查询数据库,并返回 Cursor
  4.     SQLiteDatabase db = mOpenHelper.getReadableDatabase();
  5.     return db.query(TABLE_NAME, projection, selection, selectionArgs, null, null, sortOrder);
  6. }
复制代码


  • insert(Uri uri, ContentValues values) :用于插入数据。比方:
java
  1. @Override
  2. public Uri insert(Uri uri, ContentValues values) {
  3.     SQLiteDatabase db = mOpenHelper.getWritableDatabase();
  4.     long id = db.insert(TABLE_NAME, null, values);
  5.     return ContentUris.withAppendedId(uri, id);
  6. }
复制代码


  • update(Uri uri, ContentValues values, String selection, String[] selectionArgs) :用于更新数据。比方:
java
  1. @Override
  2. public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
  3.     SQLiteDatabase db = mOpenHelper.getWritableDatabase();
  4.     return db.update(TABLE_NAME, values, selection, selectionArgs);
  5. }
复制代码


  • delete(Uri uri, String selection, String[] selectionArgs) :用于删除数据。比方:
java
  1. @Override
  2. public int delete(Uri uri, String selection, String[] selectionArgs) {
  3.     SQLiteDatabase db = mOpenHelper.getWritableDatabase();
  4.     return db.delete(TABLE_NAME, selection, selectionArgs);
  5. }
复制代码


  • getType(Uri uri) :返回给定 Uri 所代表的数据的 MIME 范例。比方:
java
  1. @Override
  2. public String getType(Uri uri) {
  3.     return "vnd.android.cursor.dir/vnd.example.items";
  4. }
复制代码
三、布局与 UI

1. Android 中有哪些常用的布局容器?它们的特点和适用场景是什么?



  • LinearLayout:线性布局,它可以让子视图在水平或垂直方向上依次分列。通过 androidrientation 属性设置分列方向,android:layout_weight 属性可以控制子视图的权重,实现灵活的布局。适用于简朴的线性分列场景,如一个垂直分列的按钮列表,或水中分列的图标和文字组合。比方:
xml
  1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2.     android:layout_width="match_parent"
  3.     android:layout_height="match_parent"
  4.     android:orientation="vertical">
  5.     <Button
  6.         android:layout_width="wrap_content"
  7.         android:layout_height="wrap_content"
  8.         android:text="Button 1" />
  9.     <Button
  10.         android:layout_width="wrap_content"
  11.         android:layout_height="wrap_content"
  12.         android:text="Button 2" />
  13. </LinearLayout>
复制代码


  • RelativeLayout:相对布局,子视图可以根据与其他视图的相对位置或父视图的位置进行布局。比方,可以设置一个视图在另一个视图的下方、右侧等。适用于布局较为复杂,子视图之间有相对位置关系的场景,比如一个包含头像、用户名和简介的用户信息展示区域,头像在左上角,用户名在头像右侧,简介在用户名下方。比方:
xml
  1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2.     android:layout_width="match_parent"
  3.     android:layout_height="match_parent">
  4.     <ImageView
  5.         android:id="@+id/iv_avatar"
  6.         android:layout_width="50dp"
  7.         android:layout_height="50dp"
  8.         android:src="@drawable/avatar" />
  9.     <TextView
  10.         android:id="@+id/tv_username"
  11.         android:layout_width="wrap_content"
  12.         android:layout_height="wrap_content"
  13.         android:layout_toRightOf="@id/iv_avatar"
  14.         android:text="John Doe" />
  15.     <TextView
  16.         android:id="@+id/tv_introduction"
  17.         android:layout_width="wrap_content"
  18.         android:layout_height="wrap_content"
  19.         android:layout_below="@id/tv_username"
  20.         android:text="This is an introduction" />
复制代码


  • FrameLayout:帧布局,全部子视图会默认放置在布局的左上角,后添加的视图会覆盖前面的视图。它比力适适用于表现单个视图,大概多个视图必要重叠表现的场景,比方在一个舆图界面上叠加标记点、信息窗口等。比如,实现一个带有加载动画的图片展示:
xml
  1. <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2.     android:layout_width="match_parent"
  3.     android:layout_height="match_parent">
  4.     <ImageView
  5.         android:layout_width="match_parent"
  6.         android:layout_height="match_parent"
  7.         android:src="@drawable/your_image" />
  8.     <ProgressBar
  9.         android:layout_width="wrap_content"
  10.         android:layout_height="wrap_content"
  11.         android:layout_gravity="center" />
  12. </FrameLayout>
复制代码


  • ConstraintLayout:约束布局,是一种强大的布局容器,通过设置视图之间的约束关系来确定视图的位置和大小。它可以替换相对布局和线性布局,而且在复杂布局中能减少布局嵌套,进步性能。比方,要实现一个包含多个视图且有复杂对齐和约束关系的界面,如电商商品详情页,商品图片、标题、代价、形貌等元素之间有多种对齐和间距要求,利用 ConstraintLayout 就非常合适。以下是一个简朴示例:
xml
  1. <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2.     xmlns:app="http://schemas.android.com/apk/res-auto"
  3.     android:layout_width="match_parent"
  4.     android:layout_height="match_parent">
  5.     <ImageView
  6.         android:id="@+id/imageView"
  7.         android:layout_width="150dp"
  8.         android:layout_height="150dp"
  9.         app:layout_constraintTop_toTopOf="parent"
  10.         app:layout_constraintStart_toStartOf="parent"
  11.         app:layout_constraintEnd_toEndOf="parent"
  12.         android:src="@drawable/ic_launcher_background" />
  13.     <TextView
  14.         android:id="@+id/textView"
  15.         android:layout_width="wrap_content"
  16.         android:layout_height="wrap_content"
  17.         app:layout_constraintTop_toBottomOf="@id/imageView"
  18.         app:layout_constraintStart_toStartOf="@id/imageView"
  19.         android:text="Some Text" />
  20. </androidx.constraintlayout.widget.ConstraintLayout>
复制代码


  • TableLayout:表格布局,以表格的情势分列子视图,每个子视图可以占据一个或多个单元格。通过 <TableRow> 标签来定义行,在 <TableRow> 内添加的视图会依次分列在该行。适用于必要展示表格化数据的场景,如课程表、简朴的报表等。不过由于其灵活性相对较低,在复杂布局中利用较少。示比方下:
xml
  1. <TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2.     android:layout_width="match_parent"
  3.     android:layout_height="match_parent">
  4.     <TableRow>
  5.         <TextView
  6.             android:layout_width="wrap_content"
  7.             android:layout_height="wrap_content"
  8.             android:text="Header 1" />
  9.         <TextView
  10.             android:layout_width="wrap_content"
  11.             android:layout_height="wrap_content"
  12.             android:text="Header 2" />
  13.     </TableRow>
  14.     <TableRow>
  15.         <TextView
  16.             android:layout_width="wrap_content"
  17.             android:layout_height="wrap_content"
  18.             android:text="Data 1" />
  19.         <TextView
  20.             android:layout_width="wrap_content"
  21.             android:layout_height="wrap_content"
  22.             android:text="Data 2" />
  23.     </TableRow>
  24. </TableLayout>
复制代码
2. 如何实现一个自定义 View?请形貌其步调。

实现一个自定义 View 通常包含以下几个步调:


  • 创建一个继承自 View 或其子类的类:可以直接继承 View,也可以根据需求继承 TextView、ImageView 等更具体的子类。比方:
java
  1. public class MyCustomView extends View {
  2.     // 后续添加代码
  3. }
复制代码


  • 定义构造函数:一般必要定义至少两个构造函数,一个是在代码中创建 View 时调用的构造函数,另一个是在 XML 布局中利用时调用的构造函数。如果必要支持自定义属性,还需添加第三个构造函数。比方:
java
  1. public MyCustomView(Context context) {
  2.     super(context);
  3. }
  4. public MyCustomView(Context context, @Nullable AttributeSet attrs) {
  5.     super(context, attrs);
  6. }
  7. public MyCustomView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
  8.     super(context, attrs, defStyleAttr);
  9. }
复制代码


  • 测量 View 的大小:重写 onMeasure(int widthMeasureSpec, int heightMeasureSpec) 方法,通过 MeasureSpec 来确定 View 的宽度和高度。比方:
java
  1. @Override
  2. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  3.     int desiredWidth = 200; // 假设默认宽度
  4.     int desiredHeight = 200; // 假设默认高度
  5.     int widthMode = MeasureSpec.getMode(widthMeasureSpec);
  6.     int widthSize = MeasureSpec.getSize(widthMeasureSpec);
  7.     int heightMode = MeasureSpec.getMode(heightMeasureSpec);
  8.     int heightSize = MeasureSpec.getSize(heightMeasureSpec);
  9.     int width;
  10.     int height;
  11.     if (widthMode == MeasureSpec.EXACTLY) {
  12.         width = widthSize;
  13.     } else if (widthMode == MeasureSpec.AT_MOST) {
  14.         width = Math.min(desiredWidth, widthSize);
  15.     } else {
  16.         width = desiredWidth;
  17.     }
  18.     if (heightMode == MeasureSpec.EXACTLY) {
  19.         height = heightSize;
  20.     } else if (heightMode == MeasureSpec.AT_MOST) {
  21.         height = Math.min(desiredHeight, heightSize);
  22.     } else {
  23.         height = desiredHeight;
  24.     }
  25.     setMeasuredDimension(width, height);
  26. }
复制代码


  • 布局 View:重写 onLayout(boolean changed, int left, int top, int right, int bottom) 方法,确定 View 内部子视图的位置(如果有子视图)。对于简朴的自定义 View,通常不必要重写这个方法,因为没有子视图。但如果是自定义的复合 View(包含多个子 View),则必要在此方法中对每个子视图进行布局。比方:
java
  1. @Override
  2. protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
  3.     // 如果有子视图,计算子视图的位置并调用子视图的 layout 方法
  4.     // 例如:childView.layout(childLeft, childTop, childRight, childBottom);
  5. }
复制代码


  • 绘制 View:重写 onDraw(Canvas canvas) 方法,利用 Canvas 和 Paint 等类来绘制 View 的内容。比方绘制一个圆形:
java
  1. @Override
  2. protected void onDraw(Canvas canvas) {
  3.     super.onDraw(canvas);
  4.     Paint paint = new Paint();
  5.     paint.setColor(Color.RED);
  6.     int radius = getWidth() / 2;
  7.     canvas.drawCircle(getWidth() / 2, getHeight() / 2, radius, paint);
  8. }
复制代码


  • 处置处罚触摸事件(可选) :如果必要处置处罚触摸事件,如点击、滑动等,可以重写 onTouchEvent(MotionEvent event) 方法。比方实现一个简朴的点击变色结果:
java
  1. private boolean isClicked = false;
  2. @Override
  3. public boolean onTouchEvent(MotionEvent event) {
  4.     if (event.getAction() == MotionEvent.ACTION_DOWN) {
  5.         isClicked = true;
  6.         invalidate();
  7.         return true;
  8.     } else if (event.getAction() == MotionEvent.ACTION_UP) {
  9.         isClicked = false;
  10.         invalidate();
  11.         return true;
  12.     }
  13.     return super.onTouchEvent(event);
  14. }
  15. @Override
  16. protected void onDraw(Canvas canvas) {
  17.     super.onDraw(canvas);
  18.     Paint paint = new Paint();
  19.     if (isClicked) {
  20.         paint.setColor(Color.GREEN);
  21.     } else {
  22.         paint.setColor(Color.RED);
  23.     }
  24.     int radius = getWidth() / 2;
  25.     canvas.drawCircle(getWidth() / 2, getHeight() / 2, radius, paint);
  26. }
复制代码


  • 利用自定义 View:在 XML 布局文件中利用自定义 View,必要指定完备的包名和类名。比方:
xml
  1. <com.example.yourapp.MyCustomView
  2.     android:layout_width="100dp"
  3.     android:layout_height="100dp"
  4.     android:layout_centerInParent="true" />
复制代码
大概在代码中创建并添加到布局中:
java
  1. MyCustomView customView = new MyCustomView(this);
  2. LinearLayout layout = findViewById(R.id.main_layout);
  3. layout.addView(customView);
复制代码
3. Android 中的动画有哪些范例?如何利用属性动画实现一个视图的淡入淡出结果?

Android 中的动画主要有以下几种范例:


  • 补间动画(Tween Animation) :通过对 View 的透明度、旋转、缩放宁静移等属性进行插值盘算,实现动画结果。包括 AlphaAnimation(透明度动画)、RotateAnimation(旋转动画)、ScaleAnimation(缩放动画)和 TranslateAnimation(平移动画)。可以在 XML 文件中定义,也可以在代码中创建。比方,在 XML 中定义一个透明度动画:
xml
  1. <alpha xmlns:android="http://schemas.android.com/apk/res/android"
  2.     android:fromAlpha="0.0"
  3.     android:toAlpha="1.0"
  4.     android:duration="1000" />
复制代码
在代码中利用:
java
  1. Animation animation = AnimationUtils.loadAnimation(this, R.anim.fade_in);
  2. view.startAnimation(animation);
复制代码


  • 帧动画(Frame Animation) :通过次序播放一系列图片来实现动画结果。必要在 XML 文件中定义动画列表,然后在代码中启动动画。比方,在 XML 中定义一个帧动画:
xml
  1. <animation-list xmlns:android="http://schemas.android.com/apk/res/android"
  2.     android:oneshot="false">
  3.     <item
  4.         android:drawable="@drawable/frame1"
  5.         android:duration="100" />
  6.     <item
  7.         android:drawable="@drawable/frame2"
  8.         android:duration="100" />
  9.     <item
  10.         android:drawable="@drawable/frame3"
  11.         android:duration="100" />
  12. </animation-list>
复制代码
在代码中利用:
java
  1. ImageView imageView = findViewById(R.id.image_view);
  2. AnimationDrawable animationDrawable = (AnimationDrawable) imageView.getDrawable();
  3. animationDrawable.start();
复制代码


  • 属性动画(Property Animation) :属性动画可以对任何对象的属性进行动画操纵,而不仅仅是 View。它更加灵活和强大。
利用属性动画实现一个视图的淡入淡出结果可以通过以下方式:
java
  1. // 淡入效果
  2. ObjectAnimator fadeIn = ObjectAnimator.ofFloat(view, "alpha", 0f, 1f);
  3. fadeIn.setDuration(1000);
  4. fadeIn.start();
  5. // 淡出效果
  6. ObjectAnimator fadeOut = ObjectAnimator.ofFloat(view, "alpha", 1f, 0f);
  7. fadeOut.setDuration(1000);
  8. fadeOut.start();
复制代码
也可以利用 AnimatorSet 来组合多个动画,实现更复杂的结果。比方,先淡入再淡出:
java
  1. AnimatorSet animatorSet = new AnimatorSet();
  2. ObjectAnimator fadeIn = ObjectAnimator.ofFloat(view, "alpha", 0f, 1f);
  3. ObjectAnimator fadeOut = ObjectAnimator.ofFloat(view, "alpha", 1f, 0f);
  4. animatorSet.playSequentially(fadeIn, fadeOut);
  5. animatorSet.setDuration(2000);
  6. animatorSet.start();
复制代码
4. 在 Android 中如何实现沉浸式状态栏?

实现沉浸式状态栏主要有以下几种方式:


  • Android 4.4(KitKat)及以上版本

    • 利用 WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS:在 Activity 的 onCreate() 方法中添加以下代码:

java
  1. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
  2.     getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
  3. }
复制代码
这种方式会使状态栏半透明,内容会延伸到状态栏下方,必要手动调解布局,确保内容不会被状态栏遮挡。可以通过在布局根视图添加 android:fitsSystemWindows="true" 来办理,比方:
xml
  1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2.     android:layout_width="match_parent"
  3.     android:layout_height="match_parent"
  4.     android:fitsSystemWindows="true"
  5.     android:orientation="vertical">
  6.     <!-- 其他视图 -->
  7. </LinearLayout>
复制代码


  • 利用 WindowInsets:从 Android 5.0(Lollipop)开始,可以利用 WindowInsets 来更好地处置处罚沉浸式状态栏。起首,在 styles.xml 中设置主题:
xml
  1. <style name="AppTheme" parent="Theme.MaterialComponents.Light.NoActionBar">
  2.     <item name="android:statusBarColor">@android:color/transparent</item>
  3.     <item name="android:windowDrawsSystemBarBackgrounds">true</item>
  4. </style>
复制代码
然后在 Activity 中获取 WindowInsets 并处置处罚:
java
  1. View decorView = getWindow().getDecorView();
  2. decorView.setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() {
  3.     @Override
  4.     public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {
  5.         // 处理 insets,例如调整布局
  6.         return insets.consumeSystemWindowInsets();
  7.     }
  8. });
复制代码


  • AndroidX 库支持:利用 AndroidX 的 CoordinatorLayout 和 AppBarLayout 等组件可以方便地实现沉浸式状态栏结果。比方,在布局中利用 AppBarLayout 并设置 fitsSystemWindows="true":
xml
  1. <androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2.     xmlns:app="http://schemas.android.com/apk/res-auto"
  3.     android:layout_width="match_parent"
  4.     android:layout_height="match_parent"
  5.     android:fitsSystemWindows="true">
  6.     <com.google.android.material.appbar.AppBarLayout
  7.         android:layout_width="match_parent"
  8.         android:layout_height="wrap_content"
  9.         android:fitsSystemWindows="true">
  10.         <com.google.android.material.appbar.MaterialToolbar
  11.             android:layout_width="match_parent"
  12.             android:layout_height="?attr/actionBarSize"
  13.             android:title="My App" />
  14.     </com.google.android.material.appbar.AppBarLayout>
  15.     <FrameLayout
  16.         android:layout_width="match_parent"
  17.         android:layout_height="match_parent"
  18.         app:layout_behavior="@string/appbar_scrolling_view_behavior">
  19.         <!-- 页面内容 -->
  20.     </FrameLayout>
  21. </androidx.coordinatorlayout.widget.CoordinatorLayout>
复制代码
同时,在主题中设置状态栏颜色为透明:
xml
  1. <style name="AppTheme" parent="Theme.MaterialComponents.Light.NoActionBar">
  2.     <item name="android:statusBarColor">@android:color/transparent</item>
  3. </style>
复制代码
如许可以实现一个具有沉浸式结果且布局合理的界面,当页面滚动时,状态栏的颜色和样式可以与页面内容有更好的交互结果。
四、数据存储

1. Android 中常用的数据存储方式有哪些?它们的优缺点是什么?



  • SharedPreferences

    • 长处:简朴易用,适合存储少量的键值对情势的配置信息,如用户的偏好设置(是否开启通知、字体大小等)。在应用内不同组件间共享数据方便,不必要额外的权限。
    • 缺点:只能存储根本数据范例(如 boolean、int、float、String 等)和 Set<String>,不适合存储复杂数据布局。数据存储在 XML 文件中,读取和写入操纵相对较慢,在多进程情况下利用可能会出现题目。
    • 示例代码:写入数据:

java
  1. SharedPreferences sharedPreferences = getSharedPreferences("MyPrefs", Context.MODE_PRIVATE);
  2. SharedPreferences.Editor editor = sharedPreferences.edit();
  3. editor.putBoolean("isNotificationEnabled", true);
  4. editor.apply();
复制代码
读取数据:
java
  1. SharedPreferences sharedPreferences = getSharedPreferences("MyPrefs", Context.MODE_PRIVATE);
  2. boolean isNotificationEnabled = sharedPreferences.getBoolean("isNotificationEnabled", false);
复制代码


  • 文件存储

    • 长处:可以存储任何范例的数据,包括二进制数据(如图片、音频等)和文本数据。对于一些不必要复杂查询和布局化存储的数据,文件存储是一种简朴直接的方式。
    • 缺点:没有内置的查询和索引功能,读取和写入文件时必要手动处置处罚文件的打开、关闭、读写位置等操纵,相对繁琐。如果文件过大,读取和写入的性能会受到影响。
    • 示例代码:写入文本文件:

java
  1. try {
  2.     FileOutputStream fos = openFileOutput("myfile.txt", Context.MODE_PRIVATE);
  3.     String data = "Hello, World!";
  4.     fos.write(data.getBytes());
  5.     fos.close();
  6. } catch (IOException e) {
  7.     e.printStackTrace();
复制代码


  • 文件存储(续)

    • 示例代码(续) :读取文本文件:

java
  1. try {
  2.     FileInputStream fis = openFileInput("myfile.txt");
  3.     ByteArrayOutputStream bos = new ByteArrayOutputStream();
  4.     byte[] buffer = new byte[1024];
  5.     int length;
  6.     while ((length = fis.read(buffer)) != -1) {
  7.         bos.write(buffer, 0, length);
  8.     }
  9.     String content = bos.toString();
  10.     fis.close();
  11.     bos.close();
  12. } catch (IOException e) {
  13.     e.printStackTrace();
  14. }
复制代码


  • SQLite 数据库

    • 长处:是一种轻量级的关系型数据库,适合存储布局化数据,如用户信息、订单数据等。支持复杂的查询操纵,如 SELECT、INSERT、UPDATE、DELETE 等,而且可以通过事件来保证数据的一致性和完备性。在 Android 系统中内置支持,利用方便。
    • 缺点:相比其他轻量级存储方式,SQLite 的学习成本较高,必要相识 SQL 语法。对于简朴的数据存储需求,利用 SQLite 可能会显得过于复杂。在多线程情况下利用时,必要注意线程安全题目。
    • 示例代码:创建数据库和表:

java
  1. public class MyDatabaseHelper extends SQLiteOpenHelper {
  2.     private static final String DATABASE_NAME = "mydb.db";
  3.     private static final int DATABASE_VERSION = 1;
  4.     public static final String TABLE_NAME = "users";
  5.     public static final String COLUMN_ID = "_id";
  6.     public static final String COLUMN_NAME = "name";
  7.     public static final String COLUMN_AGE = "age";
  8.     private static final String CREATE_TABLE =
  9.             "CREATE TABLE " + TABLE_NAME + " (" +
  10.                     COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
  11.                     COLUMN_NAME + " TEXT, " +
  12.                     COLUMN_AGE + " INTEGER)";
  13.     public MyDatabaseHelper(Context context) {
  14.         super(context, DATABASE_NAME, null, DATABASE_VERSION);
  15.     }
  16.     @Override
  17.     public void onCreate(SQLiteDatabase db) {
  18.         db.execSQL(CREATE_TABLE);
  19.     }
  20.     @Override
  21.     public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
  22.         db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
  23.         onCreate(db);
  24.     }
  25. }
复制代码
插入数据:
java
  1. MyDatabaseHelper dbHelper = new MyDatabaseHelper(this);
  2. SQLiteDatabase db = dbHelper.getWritableDatabase();
  3. ContentValues values = new ContentValues();
  4. values.put(MyDatabaseHelper.COLUMN_NAME, "John");
  5. values.put(MyDatabaseHelper.COLUMN_AGE, 25);
  6. long newRowId = db.insert(MyDatabaseHelper.TABLE_NAME, null, values);
复制代码
查询数据:
java
  1. MyDatabaseHelper dbHelper = new MyDatabaseHelper(this);
  2. SQLiteDatabase db = dbHelper.getReadableDatabase();
  3. String[] projection = {
  4.         MyDatabaseHelper.COLUMN_ID,
  5.         MyDatabaseHelper.COLUMN_NAME,
  6.         MyDatabaseHelper.COLUMN_AGE
  7. };
  8. Cursor cursor = db.query(
  9.         MyDatabaseHelper.TABLE_NAME,
  10.         projection,
  11.         null,
  12.         null,
  13.         null,
  14.         null,
  15.         null
  16. );
  17. while (cursor.moveToNext()) {
  18.     int id = cursor.getInt(cursor.getColumnIndexOrThrow(MyDatabaseHelper.COLUMN_ID));
  19.     String name = cursor.getString(cursor.getColumnIndexOrThrow(MyDatabaseHelper.COLUMN_NAME));
  20.     int age = cursor.getInt(cursor.getColumnIndexOrThrow(MyDatabaseHelper.COLUMN_AGE));
  21.     // 处理查询结果
  22. }
  23. cursor.close();
复制代码


  • Room 数据库

    • 长处:是 Android Jetpack 组件的一部门,在 SQLite 之上提供了一个抽象层,使得数据库操纵更加面向对象和便捷。它通过注解处置处罚器自动生成大量样板代码,减少了手动编写 SQL 语句的工作量。支持 LiveData 和 RxJava 等相应式编程方式,方便与 UI 进行数据绑定和实时更新。
    • 缺点:相比直接利用 SQLite,增长了一定的学习成本,必要相识 Room 的注解和架构设计。由于其自动生成代码的特性,在一些复杂查询场景下,可能必要泯灭更多时间来优化生成的代码。
    • 示例代码:定义实体类:

java
  1. @Entity(tableName = "users")
  2. public class User {
  3.     @PrimaryKey(autoGenerate = true)
  4.     public int id;
  5.     @ColumnInfo(name = "name")
  6.     public String name;
  7.     @ColumnInfo(name = "age")
  8.     public int age;
  9. }
复制代码
定义数据访问对象(DAO):
java
  1. @Dao
  2. public interface UserDao {
  3.     @Insert
  4.     void insert(User user);
  5.     @Query("SELECT * FROM users")
  6.     List<User> getAllUsers();
  7. }
复制代码
创建数据库:
java
  1. @Database(entities = {User.class}, version = 1)
  2. public abstract class AppDatabase extends RoomDatabase {
  3.     public abstract UserDao userDao();
  4.     private static volatile AppDatabase INSTANCE;
  5.     public static AppDatabase getDatabase(final Context context) {
  6.         if (INSTANCE == null) {
  7.             synchronized (AppDatabase.class) {
  8.                 if (INSTANCE == null) {
  9.                     INSTANCE = Room.databaseBuilder(
  10.                             context.getApplicationContext(),
  11.                             AppDatabase.class,
  12.                             "app_database"
  13.                     ).build();
  14.                 }
  15.             }
  16.         }
  17.         return INSTANCE;
  18.     }
  19. }
复制代码
利用数据库:
java
  1. AppDatabase db = AppDatabase.getDatabase(this);
  2. UserDao userDao = db.userDao();
  3. User user = new User();
  4. user.name = "John";
  5. user.age = 25;
  6. new Thread(() -> {
  7.     userDao.insert(user);
  8.     List<User> users = userDao.getAllUsers();
  9.     // 处理查询结果
  10. }).start();
复制代码


  • ContentProvider

    • 长处:主要用于在不同应用步伐之间共享数据,提供了一种尺度的接口来访问和操纵数据。通过 ContentResolver,其他应用可以方便地查询、插入、更新和删除数据,而不必要相识数据的具体存储方式。
    • 缺点:实现一个 ContentProvider 相对复杂,必要处置处罚权限管理、URI 剖析、数据操纵等多个方面。由于涉及到跨应用操纵,安全性和性能题目必要特别关注。
    • 示例代码:在 AndroidManifest.xml 中注册 ContentProvider:

xml
  1. <provider
  2.     android:name=".MyContentProvider"
  3.     android:authorities="com.example.myprovider"
  4.     android:exported="true" />
复制代码
在 MyContentProvider 类中实现数据操纵方法(如 query、insert 等):
java
  1. public class MyContentProvider extends ContentProvider {
  2.     @Override
  3.     public boolean onCreate() {
  4.         // 初始化操作
  5.         return true;
  6.     }
  7.     @Override
  8.     public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
  9.         // 处理查询请求
  10.         SQLiteDatabase db = mOpenHelper.getReadableDatabase();
  11.         return db.query(TABLE_NAME, projection, selection, selectionArgs, null, null, sortOrder);
  12.     }
  13.     @Override
  14.     public Uri insert(Uri uri, ContentValues values) {
  15.         // 处理插入请求
  16.         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
  17.         long id = db.insert(TABLE_NAME, null, values);
  18.         return ContentUris.withAppendedId(uri, id);
  19.     }
  20.     // 其他方法如 update、delete、getType 等也需实现
  21. }
复制代码
其他应用利用 ContentResolver 访问数据:
java
  1. ContentResolver resolver = getContentResolver();
  2. Uri uri = Uri.parse("content://com.example.myprovider/users");
  3. Cursor cursor = resolver.query(uri, null, null, null, null);
  4. while (cursor.moveToNext()) {
  5.     // 处理查询结果
  6. }
  7. cursor.close();
复制代码
2. 如何进行数据库的升级操纵?以 SQLite 为例说明。

在 SQLite 中进行数据库升级操纵,主要通过 SQLiteOpenHelper 类来实现。SQLiteOpenHelper 类有两个重要的方法:onCreate(SQLiteDatabase db) 和 onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)。
当应用首次创建数据库时,系统会调用 onCreate 方法,在该方法中可以创建数据库表、索引等布局。而当数据库版本号发生厘革(通常是应用升级时),系统会调用 onUpgrade 方法,在这个方法中进行数据库的升级操纵。
假设我们有一个简朴的数据库,包含一个 users 表,最初的表布局如下:
java
  1. public class MyDatabaseHelper extends SQLiteOpenHelper {
  2.     private static final String DATABASE_NAME = "mydb.db";
  3.     private static final int DATABASE_VERSION = 1;
  4.     public static final String TABLE_NAME = "users";
  5.     public static final String COLUMN_ID = "_id";
  6.     public static final String COLUMN_NAME = "name";
  7.     private static final String CREATE_TABLE =
  8.             "CREATE TABLE " + TABLE_NAME + " (" +
  9.                     COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
  10.                     COLUMN_NAME + " TEXT)";
  11.     public MyDatabaseHelper(Context context) {
  12.         super(context, DATABASE_NAME, null, DATABASE_VERSION);
  13.     }
  14.     @Override
  15.     public void onCreate(SQLiteDatabase db) {
  16.         db.execSQL(CREATE_TABLE);
  17.     }
  18.     // 最初没有 onUpgrade 方法,因为数据库首次创建不需要升级
  19. }
复制代码
如今假设我们要给 users 表添加一个 age 列,而且将数据库版本号提升到 2。我们必要修改 MyDatabaseHelper 类,如下:
java
  1. public class MyDatabaseHelper extends SQLiteOpenHelper {
  2.     private static final String DATABASE_NAME = "mydb.db";
  3.     private static final int DATABASE_VERSION = 2;
  4.     public static final String TABLE_NAME = "users";
  5.     public static final String COLUMN_ID = "_id";
  6.     public static final String COLUMN_NAME = "name";
  7.     public static final String COLUMN_AGE = "age";
  8.     private static final String CREATE_TABLE =
  9.             "CREATE TABLE " + TABLE_NAME + " (" +
  10.                     COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
  11.                     COLUMN_NAME + " TEXT, " +
  12.                     COLUMN_AGE + " INTEGER)";
  13.     public MyDatabaseHelper(Context context) {
  14.         super(context, DATABASE_NAME, null, DATABASE_VERSION);
  15.     }
  16.     @Override
  17.     public void onCreate(SQLiteDatabase db) {
  18.         db.execSQL(CREATE_TABLE);
  19.     }
  20.     @Override
  21.     public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
  22.         if (oldVersion < 2) {
  23.             // 添加 age 列
  24.             db.execSQL("ALTER TABLE " + TABLE_NAME + " ADD COLUMN " + COLUMN_AGE + " INTEGER");
  25.         }
  26.     }
  27. }
复制代码
在 onUpgrade 方法中,起首检查 oldVersion 和 newVersion,如果 oldVersion 小于要升级到的版本号(这里是 2),则实行升级操纵。在这个例子中,利用 ALTER TABLE 语句给 users 表添加了 age 列。
如果数据库布局厘革较大,比如必要删除旧表并创建新表,同时保存旧表中的数据,可以如许实现:
java
  1. @Override
  2. public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
  3.     if (oldVersion < 2) {
  4.         // 创建临时表
  5.         db.execSQL("CREATE TEMPORARY TABLE " + TABLE_NAME + "_temp AS SELECT * FROM " + TABLE_NAME);
  6.         // 删除旧表
  7.         db.execSQL("DROP TABLE " + TABLE_NAME);
  8.         // 创建新表
  9.         db.execSQL(CREATE_TABLE);
  10.         // 将临时表中的数据插入新表
  11.         db.execSQL("INSERT INTO " + TABLE_NAME + " (" + COLUMN_ID + ", " + COLUMN_NAME + ") " +
  12.                 "SELECT " + COLUMN_ID + ", " + COLUMN_NAME + " FROM " + TABLE_NAME + "_temp");
  13.         // 删除临时表
  14.         db.execSQL("DROP TABLE " + TABLE_NAME + "_temp");
  15.     }
  16. }
复制代码
如许就完成了 SQLite 数据库的升级操纵,确保在应用升级时数据库布局能够正确更新,同时尽可能保存原有数据。在实际应用中,升级操纵可能会更复杂,必要根据具体的业务需求和数据库布局厘革进行相应的调解。
3. Room 数据库相比 SQLite 有哪些优势?如安在项目中集成 Room 数据库?

Room 数据库相比 SQLite 的优势



  • 代码简洁与高效开发:Room 通过注解处置处罚器自动生成大量样板代码,如数据库访问对象(DAO)的实现、数据库创建和升级的逻辑等。开发人员只需定义实体类、DAO 接口和数据库类,并利用相应注解标记,无需手动编写复杂的 SQLite 操纵代码,大大进步了开发效率。比方,定义一个简朴的用户实体类和对应的 DAO:
java
  1. // 定义实体类@Entity(tableName = "users")
  2. public class User {
  3.     @PrimaryKey(autoGenerate = true)
  4.     public int id;
  5.     @ColumnInfo(name = "name")
  6.     public String name;
  7.     @ColumnInfo(name = "age")
  8.     public int age;
  9. }
  10. // 定义 DAO 接口@Dao
  11. public interface UserDao {
  12.     @Insert
  13.     void insert(User user);
  14.     @Query("SELECT * FROM users")
  15.     List<User> getAllUsers();
  16. }
复制代码
相比直接利用 SQLite,减少了大量繁琐的 SQLiteOpenHelper 子类编写以及 SQLiteDatabase 操纵代码。


  • 范例安全与编译时检查:Room 在编译期进行范例检查,能提前发现很多错误,如查询语句中的语法错误、参数范例不匹配等。比方,如果在 @Query 注解的查询语句中写错了表名或列名,编译器会直接报错,而不是在运行时才出现难以排查的错误,这使得代码更加健壮和可靠。
  • 支持相应式编程:Room 与 LiveData 和 RxJava 等相应式编程框架精密集成。利用 LiveData 时,当数据库数据发生厘革,相干的 LiveData 会自动更新,UI 可以实时反映数据厘革,无需手动处置处罚数据变动通知和 UI 更新逻辑。比方:
java
  1. @Dao
  2. public interface UserDao {
  3.     @Query("SELECT * FROM users")
  4.     LiveData<List<User>> getAllUsersLiveData();
  5. }
复制代码
在 UI 层观察这个 LiveData,数据一旦有更新,UI 会自动刷新。


  • 架构设计精良:Room 遵循 Android 官方推荐的架构设计原则,将数据访问层与业务逻辑层和 UI 层清晰分离,有利于代码的维护和扩展。它的数据库抽象层设计使得在不影响其他层代码的情况下,方便切换数据库实现(如从 SQLite 切换到其他数据库)。
在项目中集成 Room 数据库的步调



  • 添加依赖:在项目的 build.gradle 文件中添加 Room 相干依赖。对于 Gradle 项目,在 dependencies 块中添加:
groovy
  1. def room_version = "2.4.3"
  2. implementation "androidx.room:room-runtime:$room_version"
  3. annotationProcessor "androidx.room:room-compiler:$room_version"
  4. // 如果使用 LiveData
  5. implementation "androidx.room:room-ktx:$room_version"
  6. // 如果使用 RxJava
  7. implementation "androidx.room:room-rxjava2:$room_version"
复制代码


  • 定义实体类:创建 Java 或 Kotlin 类,并利用 @Entity 注解标记为数据库实体。在类中定义字段,并利用 @PrimaryKey、@ColumnInfo 等注解指定主键和列信息。比方:
java
  1. @Entity(tableName = "products")
  2. public class Product {
  3.     @PrimaryKey
  4.     public int productId;
  5.     @ColumnInfo(name = "product_name")
  6.     public String productName;
  7.     public double price;
  8. }
复制代码


  • 创建数据访问对象(DAO) :定义接口,并利用 @Dao 注解标记。在接口中定义方法,利用 @Insert、@Query、@Update、@Delete 等注解指定数据库操纵。比方:
java
  1. @Dao
  2. public interface ProductDao {
  3.     @Insert
  4.     void insert(Product product);
  5.     @Query("SELECT * FROM products")
  6.     List<Product> getAllProducts();
  7.     @Update
  8.     void update(Product product);
  9.     @Delete
  10.     void delete(Product product);
  11. }
复制代码


  • 创建数据库类:创建一个继承自 RoomDatabase 的抽象类,利用 @Database 注解指定实体类和数据库版本。在类中定义抽象方法来获取 DAO 实例。比方:
java
  1. @Database(entities = {Product.class}, version = 1)
  2. public abstract class AppDatabase extends RoomDatabase {
  3.     public abstract ProductDao productDao();
  4.     private static volatile AppDatabase INSTANCE;
  5.     public static AppDatabase getDatabase(final Context context) {
  6.         if (INSTANCE == null) {
  7.             synchronized (AppDatabase.class) {
  8.                 if (INSTANCE == null) {
  9.                     INSTANCE = Room.databaseBuilder(
  10.                             context.getApplicationContext(),
  11.                             AppDatabase.class,
  12.                             "app_database"
  13.                     ).build();
  14.                 }
  15.             }
  16.         }
  17.         return INSTANCE;
  18.     }
  19. }
复制代码


  • 利用 Room 数据库:在必要访问数据库的地方,获取 AppDatabase 实例,然后通过 DAO 实例实行
  • 利用 Room 数据库(续) :在必要访问数据库的地方,获取 AppDatabase 实例,然后通过 DAO 实例实行数据库操纵。比方,在一个 ViewModel 中插入数据:
java
  1. import androidx.lifecycle.ViewModel;
  2. import androidx.lifecycle.ViewModelProvider;
  3. import androidx.lifecycle.ViewModelProviders;
  4. import androidx.room.Room;
  5. import android.content.Context;
  6. import android.os.Bundle;
  7. import android.widget.Toast;
  8. import androidx.appcompat.app.AppCompatActivity;
  9. import androidx.lifecycle.LiveData;
  10. import java.util.List;
  11. import java.util.concurrent.ExecutorService;
  12. import java.util.concurrent.Executors;
  13. public class MainViewModel extends ViewModel {
  14.     private final AppDatabase appDatabase;
  15.     private final ExecutorService executorService;
  16.     public MainViewModel(Context context) {
  17.         appDatabase = AppDatabase.getDatabase(context);
  18.         executorService = Executors.newSingleThreadExecutor();
  19.     }
  20.     public void insertProduct(Product product) {
  21.         executorService.submit(() -> {
  22.             appDatabase.productDao().insert(product);
  23.         });
  24.     }
  25.     public LiveData<List<Product>> getAllProducts() {
  26.         return appDatabase.productDao().getAllProductsLiveData();
  27.     }
  28. }
复制代码
在 Activity 中利用 ViewModel 来操纵数据库:
java
  1. public class MainActivity extends AppCompatActivity {
  2.     private MainViewModel mainViewModel;
  3.     @Override
  4.     protected void onCreate(Bundle savedInstanceState) {
  5.         super.onCreate(savedInstanceState);
  6.         setContentView(R.layout.activity_main);
  7.         mainViewModel = new ViewModelProvider(this).get(MainViewModel.class);
  8.         Product product = new Product();
  9.         product.productId = 1;
  10.         product.productName = "Sample Product";
  11.         product.price = 10.99;
  12.         mainViewModel.insertProduct(product);
  13.         mainViewModel.getAllProducts().observe(this, products -> {
  14.             // 处理查询到的产品列表,例如更新 UI
  15.             for (Product p : products) {
  16.                 Toast.makeText(this, p.productName, Toast.LENGTH_SHORT).show();
  17.             }
  18.         });
  19.     }
  20. }
复制代码
通过上述步调,就完成了 Room 数据库在项目中的集成与根本利用。在实际项目中,还可以根据业务需求进一步优化,如添加事件处置处罚、复杂查询等功能。
4. 如安在 Android 应用中实现数据的加密存储?

在 Android 应用中实现数据的加密存储可以采用多种方式,以下介绍几种常见的方法:
利用 Android Keystore 系统

Android Keystore 系统提供了一种安全存储密钥的方式,这些密钥可以用于加密和解密数据。以下是一个利用 Android Keystore 结合 Cipher 类进行数据加密存储的示例:

  • 生成密钥
java
  1. KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
  2. keyGenerator.init(new KeyGenParameterSpec.Builder(
  3.         "my_key_alias",
  4.         KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
  5.        .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
  6.        .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
  7.        .build());
  8. SecretKey secretKey = keyGenerator.generateKey();
复制代码
这里生成了一个 AES 算法的密钥,利用 CBC 模式和 PKCS7 添补方式。密钥会存储在 Android Keystore 中,通过指定的别名(my_key_alias)进行访问。
2. 加密数据
java
  1. Cipher cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_CBC + "/" + KeyProperties.ENCRYPTION_PADDING_PKCS7);
  2. cipher.init(Cipher.ENCRYPT_MODE, secretKey);
  3. byte[] encryptedData = cipher.doFinal("data to be encrypted".getBytes());
复制代码
起首获取 Cipher 实例,并利用之前生成的密钥进行初始化,然后对数据进行加密,得到加密后的数据字节数组。
3. 存储加密数据
可以将加密后的数据存储到文件、SharedPreferences 或数据库中。比方,存储到文件:
java
  1. FileOutputStream fos = openFileOutput("encrypted_data.txt", Context.MODE_PRIVATE);
  2. fos.write(encryptedData);
  3. fos.close();
复制代码

  • 解密数据
java
  1. Cipher cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_CBC + "/" + KeyProperties.ENCRYPTION_PADDING_PKCS7);
  2. KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
  3. keyStore.load(null);
  4. Key key = keyStore.getKey("my_key_alias", null);
  5. cipher.init(Cipher.DECRYPT_MODE, key);
  6. FileInputStream fis = openFileInput("encrypted_data.txt");
  7. byte[] encryptedData = new byte[fis.available()];
  8. fis.read(encryptedData);
  9. fis.close();
  10. byte[] decryptedData = cipher.doFinal(encryptedData);
  11. String decryptedText = new String(decryptedData);
复制代码
在解密时,从 Android Keystore 中获取密钥,初始化 Cipher 为解密模式,读取存储的加密数据并进行解密,得到原始数据。
利用第三方加密库


  • Bouncy Castle:是一个广泛利用的开源加密库,提供了丰富的加密算法和工具。

    • 添加依赖:在 build.gradle 中添加依赖:

groovy
  1. implementation 'org.bouncycastle:bcprov-jdk15on:1.68'
复制代码


  • 示例代码:利用 AES 加密:
java
  1. import org.bouncycastle.crypto.CipherOutputStream;
  2. import org.bouncycastle.crypto.engines.AESEngine;
  3. import org.bouncycastle.crypto.modes.CBCBlockCipher;
  4. import org.bouncycastle.crypto.paddings.PKCS7Padding;
  5. import org.bouncycastle.crypto.params.KeyParameter;
  6. import org.bouncycastle.crypto.params.ParametersWithIV;
  7. import java.io.FileOutputStream;
  8. import java.security.SecureRandom;
  9. import java.util.Random;
  10. public class BouncyCastleEncryptionExample {
  11.     public static void main(String[] args) throws Exception {
  12.         byte[] key = new byte[16];
  13.         byte[] iv = new byte[16];
  14.         Random random = new SecureRandom();
  15.         random.nextBytes(key);
  16.         random.nextBytes(iv);
  17.         AESEngine engine = new AESEngine();
  18.         CBCBlockCipher cipher = new CBCBlockCipher(engine);
  19.         PKCS7Padding padding = new PKCS7Padding();
  20.         KeyParameter keyParam = new KeyParameter(key);
  21.         ParametersWithIV ivParam = new ParametersWithIV(keyParam, iv);
  22.         cipher.init(true, ivParam);
  23.         FileOutputStream fos = new FileOutputStream("encrypted_file.txt");
  24.         CipherOutputStream cos = new CipherOutputStream(fos, new org.bouncycastle.crypto.Cipher(padding, cipher));
  25.         cos.write("data to be encrypted".getBytes());
  26.         cos.close();
  27.         fos.close();
  28.     }
  29. }
复制代码
解密过程雷同,只是将 cipher.init(true, ivParam) 改为 cipher.init(false, ivParam)。

  • AES - CTR - Java:一个轻量级的 AES - CTR 模式加密库。

    • 添加依赖:在 build.gradle 中添加:

groovy
  1. implementation 'com.github.aelamre:aes-ctr-java:1.0.1'
复制代码


  • 示例代码
java
  1. import com.github.aelamre.aesctr.AESCTR;
  2. import java.nio.charset.StandardCharsets;
  3. import java.security.SecureRandom;
  4. import java.util.Random;
  5. public class AesCtrEncryptionExample {
  6.     public static void main(String[] args) throws Exception {
  7.         byte[] key = new byte[16];
  8.         byte[] iv = new byte[16];
  9.         Random random = new SecureRandom();
  10.         random.nextBytes(key);
  11.         random.nextBytes(iv);
  12.         AESCTR aesCtr = new AESCTR(key, iv);
  13.         String plaintext = "data to be encrypted";
  14.         byte[] encrypted = aesCtr.encrypt(plaintext.getBytes(StandardCharsets.UTF_8));
  15.         byte[] decrypted = aesCtr.decrypt(encrypted);
  16.         String decryptedText = new String(decrypted, StandardCharsets.UTF_8);
  17.     }
  18. }
复制代码
这些第三方库提供了更灵活和丰富的加密功能,但在利用时必要注意库的版本兼容性和安全性。在实际应用中,选择合适的加密方式和库要根据项目的具体需求、安全性要求以及性能思量来决定。同时,要遵循相干的安全规范和最佳实践,确保数据的安全存储。
利用 AndroidX Security 库

AndroidX Security 库提供了一些简化加密操纵的工具类和 API,有助于在 Android 应用中更方便地实现数据加密存储。

  • 添加依赖:在 build.gradle 文件中添加以下依赖,以利用 AndroidX Security 库中的加密功能:
groovy
  1. implementation 'androidx.security:security-crypto:1.1.0'
复制代码

  • 利用 EncryptedSharedPreferences:这是 AndroidX Security 库中用于加密 SharedPreferences 的工具。它基于 Android Keystore 系统来管理加密密钥。示例代码如下:
java
  1. // 生成加密密钥
  2. KeyGenParameterSpec keyGenParameterSpec = new KeyGenParameterSpec.Builder(
  3.         "my_shared_prefs_key",
  4.         KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
  5.        .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
  6.        .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
  7.        .setKeySize(256)
  8.        .build();
  9. KeyGenerator keyGenerator = KeyGenerator.getInstance(
  10.         KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
  11. keyGenerator.init(keyGenParameterSpec);
  12. SecretKey secretKey = keyGenerator.generateKey();
  13. // 创建 EncryptedSharedPreferences
  14. Context context = getApplicationContext();
  15. File encryptedSharedPrefFile = new File(context.getFilesDir(), "encrypted_prefs");
  16. EncryptedSharedPreferences encryptedSharedPreferences = EncryptedSharedPreferences.create(
  17.         context,
  18.         encryptedSharedPrefFile,
  19.         secretKey,
  20.         EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
  21.         EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
  22. );
  23. // 写入数据
  24. SharedPreferences.Editor editor = encryptedSharedPreferences.edit();
  25. editor.putString("username", "JohnDoe");
  26. editor.putInt("age", 30);
  27. editor.apply();
  28. // 读取数据
  29. String username = encryptedSharedPreferences.getString("username", "");
  30. int age = encryptedSharedPreferences.getInt("age", 0);
复制代码
在上述代码中,起首生成一个加密密钥,然后利用该密钥创建 EncryptedSharedPreferences。写入和读取数据的操纵与平凡的 SharedPreferences 雷同,但数据在存储时会被加密,读取时会自动解密。
数据库加密


  • SQLCipher:如果利用 SQLite 数据库,可以通过 SQLCipher 库来实现数据库加密。SQLCipher 是一个开源的 SQLite 扩展,为 SQLite 数据库文件提供透明的 256 位 AES 加密。

    • 添加依赖:在 build.gradle 文件中添加依赖:

groovy
  1. implementation 'net.zetetic:android-database-sqlcipher:4.4.3'
复制代码


  • 初始化数据库:在应用中初始化 SQLCipher 数据库,示例代码如下:
java
  1. // 初始化 SQLCipher
  2. SQLiteDatabase.loadLibs(context);
  3. String password = "my_database_password";
  4. SQLiteDatabase database = SQLiteDatabase.openOrCreateDatabase(
  5.         new File(context.getFilesDir(), "encrypted_database.db"),
  6.         password,
  7.         null
  8. );
  9. // 创建表
  10. database.execSQL("CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT, age INTEGER)");
  11. // 插入数据
  12. ContentValues values = new ContentValues();
  13. values.put("name", "Alice");
  14. values.put("age", 25);
  15. database.insert("users", null, values);
  16. // 查询数据
  17. Cursor cursor = database.query("users", null, null, null, null, null, null);
  18. if (cursor.moveToFirst()) {
  19.     int id = cursor.getInt(cursor.getColumnIndex("id"));
  20.     String name = cursor.getString(cursor.getColumnIndex("name"));
  21.     int age = cursor.getInt(cursor.getColumnIndex("age"));
  22.     // 处理查询结果
  23. }
  24. cursor.close();
  25. database.close();
复制代码
在上述代码中,通过 SQLiteDatabase.openOrCreateDatabase 方法利用密码打开或创建加密的 SQLite 数据库。全部对数据库的操纵(如创建表、插入数据、查询数据等)都会在加密状态下进行,确保数据在存储时的安全性。
在选择加密方式和库时,需综合考量应用的性能、安全性要求以及代码的可维护性。比方,对于简朴的少量数据加密,EncryptedSharedPreferences 可能是一个不错的选择;而对于大量布局化数据存储且对性能有较高要求时,SQLCipher 加密的 SQLite 数据库可能更合适。同时,定期更新加密库版本以修复潜伏的安全毛病也是保障数据安全的重要措施。
五、网络请求

1. 请简述 Android 中网络请求的几种方式,如 HttpURLConnection 和 OkHttp,并比力它们的优缺点。

HttpURLConnection

长处


  • 内置在 Java 尺度库中:从 Java SE 1.4 开始就存在,在 Android 平台上也能直接利用,无需额外引入第三方库,这对于一些对依赖库大小敏感的项目较为友爱,可减少应用的整体体积。
  • 跨平台性好:由于是 Java 尺度库的一部门,在不同的 Java 运行情况中表现一致,从桌面端到移动端,只要是支持 Java 的平台,都能利用雷同的代码逻辑进行网络请求,便于代码的复用和维护。
  • 根本功能齐全:支持常见的 HTTP 方法,如 GET、POST、PUT、DELETE 等,也能处置处罚 HTTP 相应头和相应体,能够满足大多数根本网络请求的需求。
缺点


  • 代码复杂:利用 HttpURLConnection 进行网络请求时,必要编写较多的样板代码。比方,设置请求头、处置处罚输入输出流、剖析相应数据等操纵都必要手动完成,代码量较大且容易出错。
java
  1. try {
  2.     URL url = new URL("https://example.com/api/data");
  3.     HttpURLConnection connection = (HttpURLConnection) url.openConnection();
  4.     connection.setRequestMethod("GET");
  5.     connection.setConnectTimeout(5000);
  6.     connection.setReadTimeout(5000);
  7.     int responseCode = connection.getResponseCode();
  8.     if (responseCode == HttpURLConnection.HTTP_OK) {
  9.         InputStream inputStream = connection.getInputStream();
  10.         BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
  11.         String line;
  12.         StringBuilder response = new StringBuilder();
  13.         while ((line = reader.readLine())!= null) {
  14.             response.append(line);
  15.         }
  16.         reader.close();
  17.         inputStream.close();
  18.         // 处理响应数据
  19.     } else {
  20.         // 处理错误响应
  21.     }
  22.     connection.disconnect();
  23. } catch (IOException e) {
  24.     e.printStackTrace();
  25. }
复制代码


  • 不支持异步操纵原生支持差:虽然可以通过在子线程中实行网络请求来实现异步,但必要手动管理线程池等操纵,在 Android 中如果不在子线程中实行网络请求,会抛出 NetworkOnMainThreadException。相比之下,当代的网络请求库在异步处置处罚方面更加便捷和高效。
  • 性能优化难度大:对于复杂的网络场景,如连接池管理、GZIP 压缩等优化操纵,HttpURLConnection 必要开发者手动实现,这对于开发者的技能要求较高,且容易出现性能题目。
OkHttp

长处


  • 简洁易用:OkHttp 提供了简洁的 API,大大减少了网络请求代码的编写量。比方,利用 OkHttp 进行 GET 请求:
java
  1. OkHttpClient client = new OkHttpClient();
  2. Request request = new Request.Builder()
  3.        .url("https://example.com/api/data")
  4.        .build();
  5. client.newCall(request).enqueue(new Callback() {
  6.     @Override
  7.     public void onFailure(Call call, IOException e) {
  8.         e.printStackTrace();
  9.     }
  10.     @Override
  11.     public void onResponse(Call call, Response response) throws IOException {
  12.         try (ResponseBody responseBody = response.body()) {
  13.             if (!response.isSuccessful()) {
  14.                 throw new IOException("Unexpected code " + response);
  15.             }
  16.             String responseData = responseBody.string();
  17.             // 处理响应数据
  18.         }
  19.     }
  20. });
复制代码
可以看到,代码布局更加清晰,逻辑更加简洁。


  • 强大的异步支持:OkHttp 内置了强大的异步请求机制,通过 enqueue 方法可以轻松将请求放入队列中异步实行,而且提供了 Callback 接口来处置处罚请求的结果,无需开发者手动管理线程,极大地进步了开发效率。
  • 性能优化精彩:OkHttp 支持连接池复用,减少了连接建立的开销,进步了网络请求的效率。同时,它自动处置处罚 GZIP 压缩,减少了数据传输量,进一步提升了性能。此外,OkHttp 还支持 HTTP/2 协议,相比 HTTP/1.1,在性能上有明显提升,如多路复用、头部压缩等功能,能够更快地传输数据。
  • 拦截器机制:OkHttp 的拦截器机制非常强大,可以方便地对请求和相应进行拦截和处置处罚。比方,可以利用拦截器添加公共请求头、记录请求日志、进行缓存处置处罚等。
java
  1. OkHttpClient client = new OkHttpClient.Builder()
  2.        .addInterceptor(new Interceptor() {
  3.             @Override
  4.             public Response intercept(Chain chain) throws IOException {
  5.                 Request request = chain.request();
  6.                 Request newRequest = request.newBuilder()
  7.                        .addHeader("Authorization", "Bearer your_token")
  8.                        .build();
  9.                 return chain.proceed(newRequest);
  10.             }
  11.         })
  12.        .build();
复制代码
缺点


  • 增长依赖库体积:由于 OkHttp 是第三方库,引入它会增长项目的依赖库体积,对于一些对应用体积要求极为苛刻的场景,可能必要谨慎思量。不过,随着 Android 应勤奋能的日益复杂,这点体积增长在大多数情况下是可以担当的。
  • 学习成本:对于初次打仗 OkHttp 的开发者,必要学习其特定的 API 和利用方式,如请求构建、相应处置处罚、拦截器机制等,相比利用 HttpURLConnection 有一定的学习成本,但从久远来看,掌握 OkHttp 能明显提升开发效率。
六、性能优化

1. 请简述 Android 应用性能优化的常见方向和方法。

布局优化


  • 减少布局嵌套:复杂的布局嵌套会增长视图的层级,导致测量和布局盘算的时间变长,影响性能。比方,利用 LinearLayout 时,避免过多的嵌套,可以通过 merge 标签来减少不须要的布局层级。比如在一个包含多个子视图的布局中,如果外层是一个 LinearLayout 且其唯一作用是作为容器,可改为 merge。
xml
  1. <!-- 优化前 -->
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3.     android:layout_width="match_parent"
  4.     android:layout_height="match_parent"
  5.     android:orientation="vertical">
  6.     <TextView
  7.         android:layout_width="wrap_content"
  8.         android:layout_height="wrap_content"
  9.         android:text="Title" />
  10.     <ListView
  11.         android:layout_width="match_parent"
  12.         android:layout_height="wrap_content" />
  13. </LinearLayout>
复制代码
xml
  1. <!-- 优化后 -->
  2. <merge xmlns:android="http://schemas.android.com/apk/res/android"
  3.     android:layout_width="match_parent"
  4.     android:layout_height="match_parent">
  5.     <TextView
  6.         android:layout_width="wrap_content"
  7.         android:layout_height="wrap_content"
  8.         android:text="Title" />
  9.     <ListView
  10.         android:layout_width="match_parent"
  11.         android:layout_height="wrap_content" />
  12. </merge>
复制代码

  • 利用合适的布局容器:根据布局需求选择合适的布局容器。比方,对于简朴的线性分列,LinearLayout 较为合适;对于复杂的相对位置布局,ConstraintLayout 能减少布局嵌套,进步性能。如在一个包含多个视图且有复杂对齐关系的界面中,利用 ConstraintLayout 可以有效优化布局。
xml
  1. <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2.     xmlns:app="http://schemas.android.com/apk/res-auto"
  3.     android:layout_width="match_parent"
  4.     android:layout_height="match_parent">
  5.     <ImageView
  6.         android:id="@+id/imageView"
  7.         android:layout_width="100dp"
  8.         android:layout_height="100dp"
  9.         app:layout_constraintTop_toTopOf="parent"
  10.         app:layout_constraintStart_toStartOf="parent" />
  11.     <TextView
  12.         android:id="@+id/textView"
  13.         android:layout_width="wrap_content"
  14.         android:layout_height="wrap_content"
  15.         app:layout_constraintTop_toBottomOf="@id/imageView"
  16.         app:layout_constraintStart_toStartOf="@id/imageView" />
  17. </androidx.constraintlayout.widget.ConstraintLayout>
复制代码

  • ViewStub 延迟加载:对于一些在特定条件下才必要表现的视图,可以利用 ViewStub。ViewStub 是一个轻量级的视图,在布局加载时不会占用过多资源,只有在调用 inflate() 方法时才会加载其指向的布局资源。比方,在一个用户信息界面中,有一个 “更多信息” 按钮,点击后才表现详细信息布局,可将详细信息布局利用 ViewStub 来实现。
xml
  1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2.     android:layout_width="match_parent"
  3.     android:layout_height="match_parent"
  4.     android:orientation="vertical">
  5.     <TextView
  6.         android:layout_width="wrap_content"
  7.         android:layout_height="wrap_content"
  8.         android:text="Basic User Info" />
  9.     <ViewStub
  10.         android:id="@+id/more_info_stub"
  11.         android:layout_width="match_parent"
  12.         android:layout_height="wrap_content"
  13.         android:layout="@layout/more_user_info_layout" />
  14.     <Button
  15.         android:id="@+id/more_info_button"
  16.         android:layout_width="wrap_content"
  17.         android:layout_height="wrap_content"
  18.         android:text="More Info" />
  19. </LinearLayout>
复制代码
在代码中:
java
  1. Button moreInfoButton = findViewById(R.id.more_info_button);
  2. ViewStub moreInfoStub = findViewById(R.id.more_info_stub);
  3. moreInfoButton.setOnClickListener(new View.OnClickListener() {
  4.     @Override
  5.     public void onClick(View v) {
  6.         if (moreInfoStub!= null) {
  7.             moreInfoStub.inflate();
  8.         }
  9.     }
  10. });
复制代码
内存优化


  • 避免内存泄漏

    • 正确利用 Context:在 Android 中,Context 的利用不当是导致内存泄漏的常见缘故原由。比方,在一个 Activity 中创建了一个静态的内部类,而且在该类中持有了 Activity 的 Context,由于静态类的生命周期比 Activity 长,会导致 Activity 无法被正常回收,从而造成内存泄漏。应尽量利用 Application Context 或弱引用持有 Context。

java
  1. // 错误示例
  2. public class MyStaticClass {
  3.     private static Context context;
  4.     public MyStaticClass(Context context) {
  5.         this.context = context;
  6.     }
  7. }
复制代码
java
  1. // 正确示例,使用弱引用
  2. public class MyWeakRefClass {
  3.     private WeakReference<Context> contextRef;
  4.     public MyWeakRefClass(Context context) {
  5.         contextRef = new WeakReference<>(context);
  6.     }
  7.     public Context getContext() {
  8.         return contextRef.get();
  9.     }
  10. }
复制代码


  • 及时释放资源:对于一些必要手动释放的资源,如数据库连接、文件流、Bitmap 等,要确保在不再利用时及时关闭或回收。比方,在利用完 Cursor 后,应及时调用 close() 方法。
java
  1. Cursor cursor = db.query(tableName, projection, selection, selectionArgs, null, null, null);
  2. try {
  3.     // 处理 Cursor 数据
  4. } finally {
  5.     if (cursor!= null) {
  6.         cursor.close();
  7.     }
  8. }
复制代码

  • 优化对象创建:减少不须要的对象创建,对于一些频繁利用且创建成本较高的对象,可以思量利用对象池技能。比方,在一个游戏应用中,常常必要创建和销毁子弹对象,可创建一个子弹对象池,从池中获取和回收子弹对象,而不是每次都新建对象。
java
  1. public class BulletPool {
  2.     private final Stack<Bullet> bulletStack = new Stack<>();
  3.     public Bullet getBullet() {
  4.         if (bulletStack.isEmpty()) {
  5.             return new Bullet();
  6.         } else {
  7.             return bulletStack.pop();
  8.         }
  9.     }
  10.     public void recycleBullet(Bullet bullet) {
  11.         bullet.reset();
  12.         bulletStack.push(bullet);
  13.     }
  14. }
复制代码

  • 合理利用数据布局:根据数据的特点和操纵需求选择合适的数据布局。比方,如果必要频繁进行查找操纵,HashMap 比 ArrayList 效率更高;如果必要频繁进行插入和删除操纵,LinkedList 更合适。在一个存储用户信息且常常根据用户 ID 查找用户的场景中,利用 HashMap<Integer, User> 来存储用户信息会更高效。
java
  1. HashMap<Integer, User> userMap = new HashMap<>();
  2. userMap.put(1, new User("John", 25));
  3. User user = userMap.get(1);
复制代码
绘制优化


  • 减少过分绘制:过分绘制是指在屏幕的同一区域绘制了多次不须要的内容,这会消耗 GPU 资源,影响性能。可以通过 Android Studio 的布局检查器工具来查看和分析过分绘制情况。优化方法包括减少不须要的背景设置、利用 clipRect 等方法限制绘制区域。比方,在一个布局中,如果
  • 减少过分绘制 :某个视图有默认背景,同时又在代码中为其设置了雷同颜色的背景,这就造成了不须要的过分绘制,应避免这种情况。对于复杂的自定义视图,可利用 clipRect 方法,只绘制可见区域,减少不须要的绘制操纵。
java
  1. // 自定义视图中使用 clipRect 示例
  2. @Override
  3. protected void onDraw(Canvas canvas) {
  4.     super.onDraw(canvas);
  5.     Rect clipRect = new Rect();
  6.     canvas.getClipBounds(clipRect);
  7.     // 根据 clipRect 调整绘制逻辑,只绘制可见区域内容
  8. }
复制代码

  • 优化自定义 View 的绘制:在自定义 View 的 onDraw 方法中,应避免复杂的盘算和创建过多临时对象。因为 onDraw 方法可能会被频繁调用,过多的复杂操纵会严重影响性能。比方,盘算坐标、路径等操纵应尽量提前缓存结果,而不是每次绘制时都重新盘算。对于频繁利用的画笔(Paint)、路径(Path)等对象,应在初始化时创建并复用,而不是在 onDraw 方法内每次都新建。
java
  1. public class MyCustomView extends View {
  2.     private Paint mPaint;
  3.     private Path mPath;
  4.     public MyCustomView(Context context) {
  5.         super(context);
  6.         mPaint = new Paint();
  7.         mPaint.setColor(Color.RED);
  8.         mPath = new Path();
  9.     }
  10.     @Override
  11.     protected void onDraw(Canvas canvas) {
  12.         super.onDraw(canvas);
  13.         // 使用已创建的 mPaint 和 mPath 进行绘制操作
  14.         canvas.drawPath(mPath, mPaint);
  15.     }
  16. }
复制代码

  • 利用硬件加快:Android 从 3.0 版本开始支持硬件加快,开启硬件加快后,系统会将部门绘制操纵交给 GPU 处置处罚,从而进步绘制性能。可以在应用的主题(styles.xml)中全局开启硬件加快:
xml
  1. <style name="AppTheme" parent="Theme.MaterialComponents.Light.NoActionBar">
  2.     <item name="android:windowContentOverlay">@null</item>
  3.     <item name="android:windowDisablePreview">true</item>
  4.     <item name="android:windowDrawsSystemBarBackgrounds">true</item>
  5.     <item name="android:windowShowWallpaper">false</item>
  6.     <item name="android:hardwareAccelerated">true</item>
  7. </style>
复制代码
也可以针对单个 Activity 或 View 开启硬件加快。不过必要注意的是,某些复杂的绘制操纵在硬件加快模式下可能会出现兼容性题目,此时可通过关闭硬件加快或利用软件绘制来办理。
代码优化


  • 避免在主线程实行耗时操纵:Android 的主线程负责处置处罚 UI 绘制和用户交互,在主线程实行耗时操纵(如网络请求、数据库查询、复杂盘算等)会导致 UI 卡顿甚至 ANR(Application Not Responding)。应将耗时操纵放在子线程中实行,可以利用线程、线程池、AsyncTask 或 HandlerThread 等方式。比方,利用 AsyncTask 进行网络请求:
java
  1. private class NetworkTask extends AsyncTask<Void, Void, String> {
  2.     @Override
  3.     protected String doInBackground(Void... voids) {
  4.         // 执行网络请求操作
  5.         try {
  6.             URL url = new URL("https://example.com/api/data");
  7.             HttpURLConnection connection = (HttpURLConnection) url.openConnection();
  8.             connection.setRequestMethod("GET");
  9.             int responseCode = connection.getResponseCode();
  10.             if (responseCode == HttpURLConnection.HTTP_OK) {
  11.                 InputStream inputStream = connection.getInputStream();
  12.                 BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
  13.                 String line;
  14.                 StringBuilder response = new StringBuilder();
  15.                 while ((line = reader.readLine())!= null) {
  16.                     response.append(line);
  17.                 }
  18.                 reader.close();
  19.                 inputStream.close();
  20.                 return response.toString();
  21.             } else {
  22.                 return null;
  23.             }
  24.         } catch (IOException e) {
  25.             e.printStackTrace();
  26.             return null;
  27.         }
  28.     }
  29.     @Override
  30.     protected void onPostExecute(String result) {
  31.         if (result!= null) {
  32.             // 更新 UI
  33.             TextView textView = findViewById(R.id.textView);
  34.             textView.setText(result);
  35.         }
  36.     }
  37. }
  38. // 在适当的地方执行 AsyncTask
  39. new NetworkTask().execute();
复制代码

  • 利用高效算法和数据布局:在代码实现中,选择高效的算法和数据布局能明显提升性能。比方,在对大量数据进行排序时,利用快速排序(QuickSort)或归并排序(MergeSort)通常比冒泡排序(BubbleSort)效率更高;在必要频繁查找元素的场景下,利用 HashMap 或 HashSet 比遍历 ArrayList 查找要快得多。假设要从一个包含大量用户对象的列表中快速查找某个用户,利用 HashMap 存储用户对象,以用户 ID 作为键,能极大进步查找效率。
java
  1. // 使用 HashMap 存储用户对象
  2. HashMap<Integer, User> userHashMap = new HashMap<>();
  3. // 初始化 userHashMap
  4. for (User user : userList) {
  5.     userHashMap.put(user.getId(), user);
  6. }
  7. // 快速查找用户
  8. User targetUser = userHashMap.get(targetUserId);
复制代码

  • 优化循环和条件语句:在编写循环和条件语句时,应尽量减少不须要的盘算和判断。比方,在循环中,避免在每次迭代时都进行复杂的盘算,可以将其移到循环外部。对于条件判断,尽量将可能性高的条件放在前面,减少不须要的判断次数。在一个根据用户等级进行不同操纵的场景中:
java
  1. // 优化前
  2. if (user.getLevel() == 3) {
  3.     // 执行等级 3 的操作
  4. } else if (user.getLevel() == 2) {
  5.     // 执行等级 2 的操作
  6. } else if (user.getLevel() == 1) {
  7.     // 执行等级 1 的操作
  8. }
  9. // 优化后,假设等级 1 的用户最多
  10. if (user.getLevel() == 1) {
  11.     // 执行等级 1 的操作
  12. } else if (user.getLevel() == 2) {
  13.     // 执行等级 2 的操作
  14. } else if (user.getLevel() == 3) {
  15.     // 执行等级 3 的操作
  16. }
复制代码
资源优化


  • 图片优化:图片资源通常占据应用较大的存储空间和内存,对图片进行优化能有效提升应用性能。

    • 选择合适的图片格式:对于简朴的图形和图标,利用 WebP 格式,它在保证图片质量的同时,文件大小通常比 JPEG 和 PNG 更小。对于照片等一连色调的图像,JPEG 格式较为合适,可通过调解压缩比来平衡图片质量和文件大小。对于透明背景的图片,PNG 格式是不错的选择,但对于大尺寸的透明图片,也可思量转换为 WebP 格式以减少文件大小。
    • 图片压缩和缩放:在加载图片前,根据实际表现需求对图片进行压缩和缩放。比方,对于一个在列表中表现的小图片,无需加载原图,可以通过 BitmapFactory.Options 类设置采样率,减少内存占用。

java
  1. BitmapFactory.Options options = new BitmapFactory.Options();
  2. options.inSampleSize = 2; // 例如设置采样率为 2,图片宽高变为原来的一半
  3. Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.large_image, options);
复制代码


  • 利用图片加载库:如 Glide、Picasso 等,这些库具有图片缓存、异步加载、自动根据设备屏幕分辨率加载合适图片等功能,能有效优化图片加载性能。以 Glide 为例,加载图片非常简朴:
java
  1. Glide.with(this)
  2.       .load("https://example.com/image.jpg")
  3.       .into(imageView);
复制代码

  • 资源文件归并与压缩:将多个较小的资源文件(如音频、视频片断)归并为一个文件,减少资源文件的数目,降低系统资源管理的开销。同时,对资源文件进行压缩,如对音频文件利用合适的编码格式和压缩参数,在不影响音质的前提下减小文件大小。在一个包含多个短音效的应用中,可将这些音效归并为一个音频文件,并进行适当压缩,减少应用安装包大小和运行时的资源加载时间。
  • 动态加载资源:对于一些不常用或在特定条件下才必要的资源,采用动态加载的方式。比方,应用中的一些扩展功能模块,其资源可以在用户必要利用该功能时再进行下载和加载,而不是在应用安装时就全部包含在安装包中,如许能有效减小应用的初始安装包大小,进步应用的下载和安装速率。可通过 Android 的 AssetManager 结合网络请求实现资源的动态加载。
通过从布局、内存、绘制、代码、资源等多个方向进行全面优化,可以明显提升 Android 应用的性能,为用户提供更流通、高效的利用体验。
启动优化


  • 减少启动时的任务:应用启动时,应避免实行过多不须要的任务。比方,一些数据预加载操纵如果不是必须在启动时完成,可以延迟到后台线程或用户实际利用相干功能时再进行。在 Application 类的 onCreate 方法中,要仔细检查并精简所实行的代码。如果有第三方 SDK 的初始化操纵,评估其是否可以异步进行,避免阻塞主线程。比如,某些广告 SDK 的初始化可能耗时较长,可将其放到子线程中实行:
java
  1. public class MyApplication extends Application {
  2.     @Override
  3.     public void onCreate() {
  4.         super.onCreate();
  5.         new Thread(() -> {
  6.             // 异步初始化广告 SDK
  7.             AdSdk.init(this);
  8.         }).start();
  9.         // 其他必要的初始化操作
  10.     }
  11. }
复制代码

  • 优化布局加载:启动页面的布局应尽量简洁,减少复杂布局和大量视图的利用。如前文所述,通过减少布局嵌套、合理选择布局容器等方式来降低布局加载的时间。对于启动页面中可能必要动态更新的部门,思量采用 ViewStub 进行延迟加载,避免在启动时就加载全部内容。同时,对于启动页面中利用的图片资源,确保进行了优化,采用合适的图片格式和尺寸,以加快图片的加载速率。
  • 利用冷启动优化技能:对于冷启动(应用从关闭状态到首次启动),可以采用一些特定的优化技能。比方,利用 SplashScreen API(Android 12 及以上)来展示一个快速表现的启动画面,在这个画面背后进行真正的应用初始化工作,给用户一种快速启动的感知。在 styles.xml 中配置 SplashScreen:
xml
  1. <style name="Theme.MyApp" parent="Theme.MaterialComponents.Light.NoActionBar">
  2.     <item name="android:windowSplashScreenBackground">@color/splash_background</item>
  3.     <item name="android:windowSplashScreenAnimatedIcon">@drawable/ic_launcher_background</item>
  4.     <item name="android:windowSplashScreenBrandingImage">@drawable/ic_launcher_background</item>
  5. </style>
复制代码
而且在 Activity 中进行相应的设置:
java
  1. public class MainActivity extends AppCompatActivity {
  2.     @Override
  3.     protected void onCreate(Bundle savedInstanceState) {
  4.         super.onCreate(savedInstanceState);
  5.         SplashScreen splashScreen = installSplashScreen();
  6.         // 继续进行 Activity 的初始化工作
  7.         setContentView(R.layout.activity_main);
  8.     }
  9. }
复制代码

  • 多进程启动优化:对于一些大型应用,可以思量采用多进程架构来优化启动性能。将一些独立的功能模块放在单独的进程中启动,如许可以避免全部功能在一个进程中启动时资源竞争导致的启动缓慢。比方,将图片加载模块、数据库操纵模块平分别放在不同进程中,主进程专注于 UI 初始化和焦点业务逻辑,减少主进程启动时的负担。在 AndroidManifest.xml 中为组件指定进程:
xml
  1. <service
  2.     android:name=".MyImageLoaderService"
  3.     android:process=":image_loader_process" />
复制代码
网络优化


  • 合理设置网络请求参数:在进行网络请求时,合理设置请求参数可以减少数据传输量和请求时间。比方,对于分页请求,设置合适的每页数据量,避免一次请求过多数据。同时,根据业务需求,设置合理的超时时间,既保证请求能及时相应,又避免因超时时间过短导致不须要的重试。在利用 OkHttp 进行网络请求时,可以如许设置参数:
java
  1. OkHttpClient client = new OkHttpClient.Builder()
  2.       .connectTimeout(10, TimeUnit.SECONDS)
  3.       .readTimeout(15, TimeUnit.SECONDS)
  4.       .build();
  5. Request request = new Request.Builder()
  6.       .url("https://example.com/api/data?page=1&pageSize=20")
  7.       .build();
复制代码

  • 缓存机制:实现有效的缓存机制可以减少不须要的网络请求。对于一些不常常厘革的数据,如商品列表、消息资讯等,可以在当地缓存数据。在进行网络请求前,先检查当地缓存是否存在且有效,如果有效则直接利用缓存数据,避免重复请求网络。可以利用内存缓存(如 LruCache)和磁盘缓存(如 DiskLruCache)相结合的方式。以 LruCache 为例,实现一个简朴的内存缓存:
java
  1. private LruCache<String, Bitmap> mMemoryCache;
  2. @Override
  3. protected void onCreate(Bundle savedInstanceState) {
  4.     super.onCreate(savedInstanceState);
  5.     // 获取应用可用内存的 1/8 作为缓存大小
  6.     int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
  7.     int cacheSize = maxMemory / 8;
  8.     mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
  9.         @Override
  10.         protected int sizeOf(String key, Bitmap bitmap) {
  11.             return bitmap.getByteCount() / 1024;
  12.         }
  13.     };
  14. }
  15. public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
  16.     if (getBitmapFromMemoryCache(key) == null) {
  17.         mMemoryCache.put(key, bitmap);
  18.     }
  19. }
  20. public Bitmap getBitmapFromMemoryCache(String key) {
  21.     return mMemoryCache.get(key);
  22. }
复制代码

  • 网络请求归并:如果应用在短时间内必要发起多个相似的网络请求,可以思量将这些请求归并为一个请求。比方,在一个电商应用中,同时必要获取商品详情、商品批评数目、商品库存等信息,如果分别发起请求,会增长网络开销和延迟。可以设计一个接口,一次性获取这些相干数据,减少网络请求次数。在服务器端进行相应的接口设计,将多个数据查询逻辑整合,客户端只需发起一次请求:
java
  1. Request request = new Request.Builder()
  2.       .url("https://example.com/api/product/1?fields=detail,commentCount,stock")
  3.       .build();
复制代码

  • 利用 HTTP/3:随着网络技能的发展,HTTP/3 相比 HTTP/2 在性能上有进一步提升,如更低的延迟、更好的拥塞控制等。如果服务器支持 HTTP/3,应在应用中启用它。在利用 OkHttp 时,从 OkHttp 4.9.0 版本开始支持 HTTP/3,可以通过如下方式配置:
java
  1. OkHttpClient client = new OkHttpClient.Builder()
  2.       .protocol(Protocol.H3)
  3.       .build();
复制代码
通过上述多种网络优化方式,可以明显提升应用的网络性能,减少用户期待时间,进步应用的相应速率。
电量优化


  • 减少不须要的叫醒锁利用:叫醒锁(WakeLock)用于保持设备的 CPU 或屏幕处于叫醒状态,以便应用在后台实行任务。但如果利用不当,会导致设备电量消耗过快。在利用叫醒锁时,要确保只有在真正必要设备保持叫醒状态的情况下才获取,而且在任务完成后及时释放。比方,在进行文件下载任务时,获取部门叫醒锁(PowerManager.PARTIAL_WAKE_LOCK)以保持 CPU 运行,完成下载后立即释放:
java
  1. PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
  2. PowerManager.WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MyDownloadTask:WakeLockTag");
  3. wakeLock.acquire();
  4. // 执行文件下载任务
  5. wakeLock.release();
复制代码

  • 优化后台任务实行:对于后台任务,尽量归并或延迟实行,减少频繁叫醒设备。比方,应用中有多个定时任务,如定时更新数据、定时检查通知等,可以将这些任务归并为一个任务,在合适的时间间隔内实行,而不是每个任务都单独定时实行。利用 WorkManager 可以方便地管理后台任务,它会根据设备的状态(如电量、网络等)智能调度任务的实行,减少电量消耗。
java
  1. WorkRequest workRequest = new OneTimeWorkRequest.Builder(MySyncWorker.class)
  2.       .setConstraints(new Constraints.Builder()
  3.            .setRequiresBatteryNotLow(true)
  4.            .setRequiredNetworkType(NetworkType.CONNECTED)
  5.            .build())
  6.       .build();
  7. WorkManager.getInstance(this).enqueue(workRequest);
复制代码

  • 优化传感器利用:如果应用利用了传感器(如 GPS、加快率计等),要合理控制传感器的采样频率和利用时长。传感器通常比力耗电,过高的采样频率会导致电量快速消耗。比方,在一个运动记录应用中,如果不是实时必要高精度的位置信息,可以适当降低 GPS 传感器的采样频率。同时,在不必要利用传感器时,及时关闭传感器以节省电量:
java
  1. LocationManager locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
  2. Criteria criteria = new Criteria();
  3. String provider = locationManager.getBestProvider(criteria, true);
  4. LocationListener locationListener = new LocationListener() {
  5.     @Override
  6.     public void onLocationChanged(Location location) {
  7.         // 处理位置变化
  8.     }
  9.     // 其他方法实现
  10. };
  11. // 设置较低的更新频率,例如每 5 分钟更新一次位置
  12. locationManager.requestLocationUpdates(provider, 5 * 60 * 1000, 0, locationListener);
  13. // 在不需要时取消位置更新
  14. locationManager.removeUpdates(locationListener);
复制代码
通过从启动、网络、电量等多个方面进行性能优化,能够全方位提升 Android 应用的性能表现,为用户提供更优质、高效且省电的利用体验。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

何小豆儿在此

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表