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

相关推荐
__water32 分钟前
RHA《Unity兼容AndroidStudio打Apk包》
android·unity·jdk·游戏引擎·sdk·打包·androidstudio
一起搞IT吧3 小时前
相机Camera日志实例分析之五:相机Camx【萌拍闪光灯后置拍照】单帧流程日志详解
android·图像处理·数码相机
浩浩乎@3 小时前
【openGLES】安卓端EGL的使用
android
Kotlin上海用户组5 小时前
Koin vs. Hilt——最流行的 Android DI 框架全方位对比
android·架构·kotlin
zzq19965 小时前
Android framework 开发者模式下,如何修改动画过度模式
android
木叶丸5 小时前
Flutter 生命周期完全指南
android·flutter·ios
阿幸软件杂货间5 小时前
阿幸课堂随机点名
android·开发语言·javascript
没有了遇见5 小时前
Android 渐变色整理之功能实现<二>文字,背景,边框,进度条等
android
没有了遇见7 小时前
Android RecycleView 条目进入和滑出屏幕的渐变阴影效果
android
站在巨人肩膀上的码农7 小时前
去掉长按遥控器power键后提示关机、飞行模式的弹窗
android·安卓·rk·关机弹窗·power键·长按·飞行模式弹窗