Android 面试题 应用程序结构 十

🔥 Intent 传递数据 🔥

ActivityServiceBroadcastReceiver之间的通信载体 Intent 来传递数据。而ContentProvider则是共享文件
Intent可传递的数据类型:

  • a. 8种基本数据类型(boolean byte char short int long float double)、String
  • b. Intent、Bundle
  • c. Serializable和Parcelable序列化对象(将对象转为字节流)及其对应数组、CharSequence 类型
  • d. ArrayList,泛型参数类型为:<Integer>、<? Extends Parcelable>、<Charsequence>、<String>
    序列化
Dart 复制代码
1.Serializable 方式来进行序列化
//构建一个实现了Serializable接口的类
public class XrData implements Serializable {
    public String name;
    public String phone;
}

//序列化

    Bundle bundle = new Bundle(); // Bundle主要用于传递数据,它保存的数据,是以key-value(键值对)的形式存在的
    bundle.putSerializable("DATA", entity); //1
    fragment.setArguments(bundle);

//反序列化
    mContact = (XrData) getArguments().getSerializable("DATA");
    mContact.name
    
2.Parcelable
这是安卓专用的序列化方式。Parcelable接口比Serializable接口效率更高。
因为其实现原理是把对象分解为Intent支持的数据类型,并通过Intent进行传输;
其数据可以保存在内存中,相对于Serializable将数据保存在磁盘,效率自然更高。

public class CutInfo implements Parcelable {

    private int offsetX;
    private int offsetY;

    public CutInfo() {}

    // 序列化
    @Override
    public void writeToParcel(Parcel dest, int flags) { //3
        dest.writeInt(this.offsetX);
        dest.writeInt(this.offsetY);
    }
    
    @Override
    public int describeContents() { //4
        return 0;
    }

    // 反序列化
    protected CutInfo(Parcel in) {
        this.offsetX = in.readInt();
        this.offsetY = in.readInt();
    }

    // 反序列化
    public static final Creator<CutInfo> CREATOR = new Creator<CutInfo>() { //5
        @Override
        public CutInfo createFromParcel(Parcel source) {
            return new CutInfo(source);
        }

        @Override
        public CutInfo[] newArray(int size) {
            return new CutInfo[size];
        }
    };
}

// ActivityA.java 实现序列化
button.setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
          Intent intent = new Intent(this, ActivityB.class);
          intent.putExtra("cut", new CutInfo(8, 8));
          startActivity(intent);
      }
  });
  
// ActivityB.java 获取并反序列化
 Intent intent = getIntent();
 CutInfo cut = intent.getParcelableExtra("cut");
 Log.d("ActivityB", cut.toString()); //会输出: "8, 8"

🔥 ContentProvider 进程间传递数据 🔥

进程间传递数据(底层用的还是binder)

原理:Binder简单的用法是进程A可以通过Binder获得进程B的本地代理,通过本地代理,就可以在进程A里面的调用进程B的方法。在ContentProvider的实现原理中,通过ContentResolver可以查找对应给定Uri的ContentProvider,返回对应的本地代理 BinderProxy,通过这个BinderProxy就可以调用insert、delete接口。

  • Content Provider使一个应用程序的指定数据集提供给其他应用程序。其他应用可以通过ContentResolver类从该内容提供者中获取或存入数据。(例如通信录信息)
  • ContentProvider使用URI来唯一标识其数据集,这里的URI以content://作为前缀,表示该数据由ContentProvider来管理。ContentProvider是以Uri的形式对外提供数据,ContentResolver是根据Uri来访问数据。

🔥 三者关系 🔥

ContentProvider、ContentResolver、ContentObserver 三者关系

  • ContentProvider:内容提供者,主要作用就是管理数据,比如最常见的增删改查操作,同时为这些数据的访问提供了统一的接口,实现进程间的数据传递和共享;
  • ContentResolver:内容解析者,ContentResolver可以为不同URI操作不同的ContentProvider中的数据,外部进程可以通过ContentResolver与ContentProvider进行交互。
  • ContentObserver:内容观察者,观察ContentProvider中的数据变化,有变化的时候执行特定操作。本人用的最多的是监听Settings数据库的变化。由于ContentObserver的生命周期不同步于Activity和Service等,因此,在不需要时,需要手动的调用unregisterContentObserver()去取消注册。

🔥 在AndroidManifest注册 🔥

在Android文件AndroidManifest.xml注册
authorities: 访问表的主机地址;
**exported:**是否允许对外应用程序访问,true允许,false不允许,默认false;

🔥 继承ContentProvider 🔥

Android系统为我们提供ContentProvider类,我们继承这个类实现onCreate()、getType()、insert()、delete()、update()、query();
onCreate(): 内容提供者初始化的时候调用,一般这里执行数据库创建、升级操作。
getType(): 根据给定的Uri返回一个MIME类型的数据。
insert(): Uri表示需要操作哪张表,ContentValues需要插入的值,插入成功返回一个Uri;
delete(): Uri表示需要操作哪张表,selection和selectionArgs表示筛选的条件,返回受影响的行。
update(): Uri表示需要操作哪张表,ContentValues需要插入的值,selection和selectionArgs表示筛选的条件,返回受影响的行。
**query():**Uri表示需要操作哪张表,projection需要查询表中那些字段,selection和selectionArgs表示筛选的条件,sortOrder对查询查询结果进行排序,将查询结果放到Cursor返回。

Dart 复制代码
public class PersonProvider extends ContentProvider {
    private static String TAG = PersonProvider.class.getSimpleName();

    //这里的AUTHORITY就是我们在AndroidManifest.xml中配置的authorities
    public static final String AUTHORITY = "com.dream.contentprovider";

    //常量UriMatcher.NO_MATCH表示不匹配任何路径的返回码,也就是说如果找不到匹配的类型,返回-1
    private static final UriMatcher URI_MATCHER;

    //匹配不同的表的code
    private static final int ALL_PERSON = 1;
    private static final int PERSON = 2;

    //数据库的名字
    public static final String DB_NAME = "test.db";
    //数据库版本
    public static final int DB_VERSION = 1;
    //数据库表的名称
    private static final String TABLE_NAME = "person";

    private MyDBHelper dbHelper;
    private SQLiteDatabase db;

    static {
        //创建一个路径识别器
        URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
        //1.指定一个路径的匹配规则
        //如果路径满足content://com.dream.contentprovider.provider.PersonProvider/person,返回值就是(ALL_PERSON)=1
        URI_MATCHER.addURI(AUTHORITY, "/person", ALL_PERSON);
        //2.#号为通配符
        //如果路径满足content://com.dream.contentprovider.provider.PersonProvider/person/3,返回值就是(PERSON)=2
        URI_MATCHER.addURI(AUTHORITY, "/person/#", PERSON);
    }


    /**
     * 一般是对象第一次被创建时调用的方法
     *
     * @return
     */
    @Override
    public boolean onCreate() {
        dbHelper = new MyDBHelper(this.getContext(), DB_NAME, null, DB_VERSION);
        db = dbHelper.getReadableDatabase();
        return true;
    }


    /**
     * 让别人去调用返回结果
     *
     * @param uri           匹配条件
     * @param projection    选择的列
     * @param selection     查询条件
     * @param selectionArgs 查询条件的value
     * @param sortOrder     排序
     * @return
     */
    @Nullable
    @Override
    public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
        int type = URI_MATCHER.match(uri);
        switch (type) {
            case ALL_PERSON:
                return db.query(TABLE_NAME, projection, selection, selectionArgs, null, null, sortOrder);
            case PERSON:
                //获取具体某一行的数据
                String id = uri.getPathSegments().get(1);
                return db.query(TABLE_NAME, projection, "_id = ?", new String[]{id}, null, null, sortOrder);
            default:
                break;
        }
        return null;
    }

    //如果是单条记录应该返回以vnd.android.cursor.item/ 为首的字符串
    //如果是多条记录,应该返回vnd.android.cursor.dir/ 为首的字符串
    @Nullable
    @Override
    public String getType(@NonNull Uri uri) {
        int type = URI_MATCHER.match(uri);
        switch (type) {
            case ALL_PERSON:
                return "vnd.android.cursor.dir/vnd.com.dream.contentprovider.provider.person";
            case PERSON:
                return "vnd.android.cursor.item/vnd.com.dream.contentprovider.provider.person";
            default:
                return null;
        }
    }

    @Nullable
    @Override
    public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
        int type = URI_MATCHER.match(uri);
        switch (type) {
            case ALL_PERSON:
                long row = db.insert(TABLE_NAME, null, values);
                return ContentUris.withAppendedId(uri, row);
            case PERSON:
                long row2 = db.insert(TABLE_NAME, null, values);
                return ContentUris.withAppendedId(uri, row2);
            default:
                return null;
        }
    }

    @Override
    public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
        int type = URI_MATCHER.match(uri);
        int updateRow = 0;
        switch (type) {
            case ALL_PERSON:
                updateRow = db.delete(TABLE_NAME, selection, selectionArgs);
                break;
            case PERSON:
                //获取具体某一行的数据
                String id = uri.getPathSegments().get(1);
                updateRow = db.delete(TABLE_NAME, "_id = ?", new String[]{id});
                break;
            default:
                break;
        }
        return updateRow;
    }

    @Override
    public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
        int type = URI_MATCHER.match(uri);
        int updateRow = 0;
        switch (type) {
            case ALL_PERSON:
                updateRow = db.update(TABLE_NAME, values, selection, selectionArgs);
                break;
            case PERSON:
                //获取具体某一行的数据
                String id = uri.getPathSegments().get(1);
                updateRow = db.update(TABLE_NAME, values, "_id = ?", new String[]{id});
                break;
            default:
                break;
        }
        return updateRow;
    }
}

当APP启动的时候,onCreate()方法被执行,此时已经创建了数据库。

🔥 跨进程调用 🔥

private static final Uri STUDENT_ALL_URI = Uri.parse("content://" + PersonProvider.AUTHORITY + "/person");

拼接完整的地址为:"content://com.dream.contentprovider/person" 和**"content://com.dream.contentprovider/person/id"**,其中这个id表示表中数据的行数,id=0、1、 2 ...。当和UriMatcher 进行匹配返回匹配类型。不管是进程内还是跨进程之间通信关键是否URL正确,否则会出现如下错误:

复制代码
private static final Uri STUDENT_ALL_URI = Uri.parse("content://com.dream.contentprovider/person");

插入数据insert()

Dart 复制代码
 mUserProvider = getContentResolver();
 ContentValues values = new ContentValues();
 values.put("name", "张三");
 values.put("age", "18");
 values.put("sex", "男");
 mUserProvider.insert(STUDENT_ALL_URI, values);

删除数据delete()

Dart 复制代码
 mUserProvider = getContentResolver();
 mUserProvider.delete(STUDENT_ALL_URI, "name = ?", new String[]{"张三"});

查询数据query()

Dart 复制代码
 mUserProvider = getContentResolver();
 String result = "";
 Cursor cursor = mUserProvider.query(STUDENT_ALL_URI, null, null, null);
 if (cursor != null) {
     try {
          while (cursor.moveToNext()) {
              String id = cursor.getString(cursor.getColumnIndex("_id"));
              String name = cursor.getString(cursor.getColumnIndex("NAME"));
              String age = cursor.getString(cursor.getColumnIndex("AGE"));
              String sex = cursor.getString(cursor.getColumnIndex("SEX"));
              result = result + "\n" + "_id=" + id + " name=" + name + " sex=" + sex + "  age=" + age;
          }
     } finally {
         cursor.close();
         cursor = null;
         mTextView.setText(result);
     }
 }

记得关闭游标,防止内存泄漏

🔥 BroadcastReceiver 🔥

BroadcastReceiver (广播接收器),属于 Android 四大组件之一,在 Android 开发中,BroadcastReceiver 的应用场景非常多。今天,我将详细讲解关于BroadcastReceiver的所有广播类型,主要分为5类:

普通广播 (Normal Broadcast)
系统广播 (System Broadcast)
有序广播 (Ordered Broadcast)
粘性广播 (Sticky Broadcast)
App应用内广播(Local Broadcast)

🔥 普通广播(Normal Broadcast)🔥

即开发者自身定义intent的广播(最常用)。发送广播使用如下:

Dart 复制代码
Intent intent = new Intent();
//对应BroadcastReceiver中intentFilter的action
intent.setAction(BROADCAST_ACTION);
//发送广播
sendBroadcast(intent);

若被注册了的广播接收者中注册时 intentFilteraction 与上述匹配,则会接收此广播(即进行回调 onReceive())。如下mBroadcastReceiver则会接收上述广播

Dart 复制代码
<receiver 
    //此广播接收者类是mBroadcastReceiver
    android:name=".mBroadcastReceiver" >
    //用于接收网络状态改变时发出的广播
    <intent-filter>
        <action android:name="BROADCAST_ACTION" />
    </intent-filter>
</receiver>

若发送广播有相应权限,那么广播接收者也需要相应权限

🔥 系统广播(System Broadcast)🔥

Android中内置了多个系统广播:只要涉及到手机的基本操作(如开机、网络状态变化、拍照等等),都会发出相应的广播
每个广播都有特定的Intent - Filter(包括具体的action),Android系统广播action如下:

|---------------------------------------------|--------------------------------------|
| 系统操作 | action |
| 监听网络变化 | android.net.conn.CONNECTIVITY_CHANGE |
| 关闭或打开飞行模式 | Intent.ACTION_AIRPLANE_MODE_CHANGED |
| 充电时或电量发生变化 | Intent.ACTION_BATTERY_CHANGED |
| 电池电量低 | Intent.ACTION_BATTERY_LOW |
| 电池电量充足(即从电量低变化到饱满时会发出广播 | Intent.ACTION_BATTERY_OKAY |
| 系统启动完成后(仅广播一次) | Intent.ACTION_BOOT_COMPLETED |
| 按下照相时的拍照按键(硬件按键)时 | Intent.ACTION_CAMERA_BUTTON |
| 屏幕锁屏 | Intent.ACTION_CLOSE_SYSTEM_DIALOGS |
| 设备当前设置被改变时(界面语言、设备方向等) | Intent.ACTION_CONFIGURATION_CHANGED |
| 插入耳机时 | Intent.ACTION_HEADSET_PLUG |
| 未正确移除SD卡但已取出来时(正确移除方法:设置--SD 卡和设备内存--卸载SD卡) | Intent.ACTION_MEDIA_BAD_REMOVAL |
| 插入外部储存装置(如SD卡) | Intent.ACTION_MEDIA_CHECKING |
| 成功安装APK | Intent.ACTION_PACKAGE_ADDED |
| 成功删除APK | Intent.ACTION_PACKAGE_REMOVED |
| 重启设备 | Intent.ACTION_REBOOT |
| 屏幕被关闭 | Intent.ACTION_SCREEN_OFF |
| 屏幕被打开 | Intent.ACTION_SCREEN_ON |
| 关闭系统时 | Intent.ACTION_SHUTDOWN |
| 重启设备 | Intent.ACTION_REBOOT |

注:当使用系统广播时,只需要在注册广播接收者时定义相关的action即可,并不需要手动发送广播,当系统有相关操作时会自动进行系统广播

🔥 有序广播(Ordered Broadcast)🔥

定义

发送出去的广播被广播接收者按照先后顺序接收
广播接受者接收广播的顺序规则 (同时面向静态和动态注册的广播接受者)

按照Priority属性值从大-小排序;

Priority属性相同者,动态注册的广播优先;
特点

接收广播按顺序接收

先接收的广播接收者可以对广播进行截断,即后接收的广播接收者不再接收到此广播;

先接收的广播接收者可以对广播进行修改,那么后接收的广播接收者将接收到被修改后的广播
具体使用

有序广播的使用过程与普通广播非常类似,差异仅在于广播的发送方式:

Dart 复制代码
sendOrderedBroadcast(intent);

🔥 App应用内广播(Local Broadcast)🔥

背景

Android中的广播可以跨App直接通信(exported对于有intent-filter情况下默认值为true)
冲突

可能出现的问题:

其他App针对性发出与当前App intent-filter相匹配的广播,由此导致当前App不断接收广播并处理;

其他App注册与当前App一致的intent-filter用于接收广播,获取广播具体信息;

即会出现安全性 & 效率性的问题。
解决方案

使用App应用内广播(Local Broadcast)

1、App应用内广播可理解为一种局部广播,广播的发送者和接收者都同属于一个App。

2、相比于全局广播(普通广播),App应用内广播优势体现在:安全性高 & 效率高
具体使用1 - 将全局广播设置成局部广播

注册广播时将exported属性设置为false,使得非本App内部发出的此广播不被接收;

在广播发送和接收时,增设相应权限permission,用于权限验证;

发送广播时指定该广播接收器所在的包名,此广播将只会发送到此包中的App内与之相匹配的有效广播接收器中。

通过**intent.setPackage(packageName)**指定报名
具体使用2 - 使用封装好的LocalBroadcastManager类

使用方式上与全局广播几乎相同,只是注册/取消注册广播接收器和发送广播时将参数的context变成了LocalBroadcastManager的单一实例

注:对于LocalBroadcastManager方式发送的应用内广播,只能通过LocalBroadcastManager动态注册,不能静态注册

Dart 复制代码
//注册应用内广播接收器
//步骤1:实例化BroadcastReceiver子类 & IntentFilter mBroadcastReceiver 
mBroadcastReceiver = new mBroadcastReceiver(); 
IntentFilter intentFilter = new IntentFilter(); 

//步骤2:实例化LocalBroadcastManager的实例
localBroadcastManager = LocalBroadcastManager.getInstance(this);

//步骤3:设置接收广播的类型 
intentFilter.addAction(android.net.conn.CONNECTIVITY_CHANGE);

//步骤4:调用LocalBroadcastManager单一实例的registerReceiver()方法进行动态注册 
localBroadcastManager.registerReceiver(mBroadcastReceiver, intentFilter);

//取消注册应用内广播接收器
localBroadcastManager.unregisterReceiver(mBroadcastReceiver);

//发送应用内广播
Intent intent = new Intent();
intent.setAction(BROADCAST_ACTION);
localBroadcastManager.sendBroadcast(intent);

🔥 粘性广播(Sticky Broadcast)🔥

由于在Android5.0 & API 21中已经失效,所以不建议使用,在这里也不作过多的总结。

🔥 OnReceive 回调函数中Context 含义 🔥

对于不同注册方式的广播接收器回调OnReceive(Context context,Intent intent)中的context返回值是不一样的:

对于静态注册(全局+应用内广播) ,回调onReceive(context, intent)中的context返回值是:ReceiverRestrictedContext
对于全局广播的动态注册 ,回调onReceive(context, intent)中的context返回值是**:Activity Context** ;
对于应用内广播的动态注册 (LocalBroadcastManager方式),回调onReceive(context, intent)中的context返回值是:Application Context
对于应用内广播的动态注册 (非LocalBroadcastManager方式),回调onReceive(context, intent)中的context返回值是:Activity Context

相关推荐
code bean1 小时前
【C#】 C#中 nameof 和 ToString () 的用法与区别详解
android·java·c#
佛系小嘟嘟2 小时前
Android Studio Jetpack Compose毛玻璃特效按钮
android·ide·android studio
用户2018792831674 小时前
MagiskHidePropsConf 原理与实战故事
android
whysqwhw4 小时前
Egloo 项目结构分析
android
Wgllss4 小时前
大型异步下载器(二):基于kotlin+Compose+协程+Flow+Channel+ OKhttp 实现多文件异步同时分片断点续传下载
android·架构·android jetpack
yzpyzp5 小时前
KAPT 的版本如何升级,是跟随kotlin的版本吗
android·kotlin·gradle
泓博5 小时前
KMP(Kotlin Multiplatform)简单动画
android·开发语言·kotlin
柿蒂5 小时前
Flutter 拖动会比原生更省资源?分析 GPU呈现模式条形图不动的秘密
android·flutter
_一条咸鱼_5 小时前
Android Runtime内存管理全体系解构(46)
android·面试·android jetpack
猫头虎6 小时前
【Python系列PyCharm实战】ModuleNotFoundError: No module named ‘sklearn’ 系列Bug解决方案大全
android·开发语言·python·pycharm·bug·database·sklearn