安卓基础介绍


Android基础入门教程
安卓面试基础(如会必过)

1. Android的事件处理机制

1.1 基于监听的事件处理机制

由事件源、事件和事件监听器完成,有几种实现方式:
使用匿名内部类
使用内部类
使用外部类

1.2 基于回调的事件处理机制

Java回调机制解读
Java回调机制

2. 四大组件

1. Activity

Context的子类。

活动的启动流程

onCreate(): 首次被创建时触发,可以做一些初始化工作,如布局加载。
onStart(): 正在被启动,还没有显示在前台。
onRestart(): 正在重新启动,一般情况是活动从不可见重新变为可见状态时,被调用。
onResume(): 表示已经可见了,并且出现在前台并且开始活动。准备和用户交互的时候调用,此时活动一定在返回栈栈顶
onPause(): 表示正在停止,仍然可见。onPause中不可以进行耗时操作,会影响到新的活动的显示。在系统准备去启动或者恢复另一个活动的时候调用,通常该函数用于释放一些系统资源以及保存数据
onStop(): 表示活动即将停止,已经不可见了,位于后台。在活动完全不可见的时候调用
onDestroy(): 表示即将销毁,可以做一些回收资源的工作。

活动的回收和重建

被回收时保存状态数据:被回收前调用onSaveInstanceState(Bundle outState)方法保存数据,通过调用Bundle对象的putString(),putInt()等方法保存,传入key和value。
被重建时恢复状态数据:被重建时通过onCreate(Bundle savedInstanceState)方法恢复数据,通过调用Bundle对象的getString(“key”),getInt(“key”)等方法恢复状态数据。
通过Bundle在活动间传递数据:可以将数据保存在Bundle中,然后再将其保存在Intent中,实现活动间信息的传递。

活动的优先级

资源内存不足导致优先级低的互动会被杀死。从高到低:
前台活动
可见但非前台活动
后台活动

活动的三种运行状态

Resumed:活动状态。
Paused:暂停状态,可见但是不是最前台的活动。
Stopped:停止状态,此时Activity还在后台运行, 仍然在内存中保留Activity的状态, 并不是完全销毁。
Destroyed:销毁状态

活动的四种启动模式

可以通过在AndroidManifest.xml中通过给标签指定android:launchMode指定启动模式。

  1. 标准模式:standard
    每启动一次Activity, 就会创建一个新的Activity实例并置于栈顶。谁启动了这个Activity, 那么这个Activity就运行在启动它的那个Activity所在的栈中。
  2. 栈顶复用模式:singleTop
    如果需要新建的Activity位于任务栈栈顶, 那么此Activity的实例就不会重建, 而是重用栈顶的实例。
    如果栈顶不是新建的Activity,就会创建该Activity新的实例, 并放入栈顶。
  3. 栈内复用模式:singleTask
    该模式是一种单例模式, 即一个栈内只有一个该Activity实例。 该模式, 可以通过在AndroidManifest文件的Activity中指定该Activity需要加载到那个栈中, 即singleTask的Activity可以指定想要加载的目标栈。 singleTask和taskAffinity配合使用, 指定开启的Activity加入到哪个栈中。

在这种模式下, 如果Activity指定的栈不存在, 则创建一个栈, 并把创建的Activity压入栈内;
如果Activity指定的栈存在, 如果其中没有该Activity实例, 则会创建Activity并压入栈顶;
如果其中有该Activity实例, 则把该Activity实例之上的Activity杀死清除出栈, 重用并让该Activity实例处在栈顶, 然后调用onNewIntent()方法。
4. 单例模式:singleInstance
该种启动模式下,会启用一个新的返回栈管理此种活动,以满足不同应用共享某活动的情况。
作为栈内复用模式( singleTask) 的加强版,打开该Activity时, 直接创建一个新的任务栈, 并创建该Activity实例放入新栈中。 一旦该模式的Activity实例已经存在于某个
栈中, 任何应用再激活该Activity时都会重用该栈中的实例。

可以在启动Activity时, 通过Intent的addFlags()方法设置启动模式,启动模式对应的Flags:
(1)FLAG_ACTIVITY_NEW_TASK 其效果与指定Activity为singleTask模式一致。
(2)FLAG_ACTIVITY_SINGLE_TOP 其效果与指定Activity为singleTop模式一致。
(3)FLAG_ACTIVITY_CLEAR_TOP 具有此标记位的Activity, 当它启动时, 在同一个任务栈中所有位于它上面的Activity都要出栈。 如果和singleTask模式一起出现,若被启动的Activity已经存在栈中, 则清除其之上的Activity, 并调用该Activity的onNewIntent方法。 如果被启动的Activity采用standard模式, 那么该Activity连同之上的所有Activity出栈, 然后创建新的Activity实例并压入栈中。

2. Service服务

它和Activity一样都是Context的子类,只不过它没有UI界面, 是在后台运行的组件。
默认运行在主线程中,因此不要在Service中执行耗时的操作, 除非你在Service中创建了子线程来完成耗时操作。

服务分类

  1. 按照运行地点分类:
    本地服务:运行在相同进程上,不需要IPC就可以与此服务通信。
    远程服务:运行在独立的进程上,使用服务的进程被kill不会对服务进程造成影响,但是使用服务需要IPC机制。
  2. 按照运行类型分类:
    前台服务:会在通知栏显示onGoing的Notification,如音乐播放服务,当服务被终止时,通知栏的Notification也会消失。
    后台服务:默认的服务即后台服务,不会在通知栏显示。

所谓前台服务只不过是通过一定的方式将服务所在的进程级别提升了。
前台服务会一直有一个正在运行的图标在系统的状态栏显示, 非常类似于通知的效果。
由于后台服务优先级相对比较低, 当系统出现内存不足的情况下, 它就有可能会被回收掉, 所以前台服务就是来弥补这个缺点的, 它可以一直保持运行状态而不被系统回收。
3. 按照使用方式分类:
startService启动的服务
bindService启动的服务
两者同时使用启动的服务

服务的生命周期

  1. 通过startService启动服务
    onCreate()
    onStartCommand(): 每次客户端调用startService()方法启动该Service都会回调该方法( 多次调用) 。 一旦这个方法执行, service就启动并且在后台长期运行。 通过调用stopSelf()或stopService()来停止服务。
    running
    onDestroy()
  2. 通过bindService绑定服务
    onCreate()
    onBind():当组件调用bindService()想要绑定到service时(比如想要执行进程间通讯)系统调用此方法( 一次调用, 一旦绑定后, 下次再调用bindService()不会回调该方法) 。 在你的实现中, 你必须提供一个返回一个IBinder来以使客户端能够使用它与service通讯, 你必须总是实现这个方法, 但是如果你不允许绑定, 那么你应返回null。
    running
    onUnbind(): 当前组件调用unbindService(), 想要解除与service的绑定时系统调用此方法( 一次调用, 一旦解除绑定后, 下次再调用unbindService()会抛出异常) 。
    onDestroy()
    注意: 通过bindService启动的Service的生命周期依附于启动它的Context。 因此当前台调用bindService的Context销毁后, 那么服务会自动停止。

3. BroadcastReceiver广播接收器

实现原理

Android 中的广播使用了设计模式中的观察者模式: 基于消息的发布/订阅事件模型。
模型中有3个角色:

  1. 消息订阅者( 广播接收者)
  2. 消息发布者( 广播发布者)
  3. 消息中心( AMS , 即 Activity Manager Service )
    原理描述:
  4. 广播接收者 通过 Binder 机制在 AMS 注册
  5. 广播发送者 通过 Binder 机制向 AMS 发送广播
  6. AMS 根据 广播发送者 要求, 在已注册列表中, 寻找合适的广播接收者,寻找依据: IntentFilter / Permission
  7. AMS 将广播发送到合适的广播接收者相应的消息循环队列中;
  8. 广播接收者通过 消息循环 拿到此广播, 并回调 onReceive()
    特别注意: 广播发送者 和 广播接收者的执行 是 异步的, 发出去的广播不会关心有无接收者接收, 也不确定接收者到底是何时才能接收到;

使用流程

  1. 自定义广播接收者
    继承自BroadcastReceiver类,
    重写抽象方法onReceive(Context ctx, Intent intent)方法:默认情况下, 广播接收器运行在UI线程, 因此, onReceive方法不能执行耗时操作, 否则将导致ANR。
  2. 注册广播接收器
    静态注册:在AndroidManifest.xml里通过标签receiver声明,当此App首次启动时, 系统会自动实例化自定义广播接收器类, 并注册到系统中。
    动态注册:在某一个组件的代码中通过调用Context的registerReceiver(BroadcastReceiver, IntentFilter)方法进行动态注册。对于动态广播, 有注册就必然得有注销, 否则会导致内存泄露。

两种注册方式的对比:
静态注册:常驻,不受任何组件的生命周期影响,缺点是耗电,占内存。
动态注册:非常驻,灵活,跟随组件的生命周期变化,在组件结束前,必须移除广播接收器。
3. 广播发送者向AMS发送广播
广播的发送:广播是用Intent标识的,定义广播所具备的Intent,通过sendBroadcast(Intent)方法发送出去

广播的类型

(1) 普通广播

即开发者自身定义intent的广播( 最常用);

(2) 系统广播

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

(3) 有序广播

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

  1. 按照Priority属性值从大-小排序;
  2. Priority属性相同者, 动态注册的广播优先;
    特点:
  3. 接收广播按顺序接收
  4. 先接收的广播接收者可以对广播进行截断, 即后接收的广播接收者不再接收到此广播;
  5. 先接收的广播接收者可以对广播进行修改, 那么后接收的广播接收者将接收到被修改后的广播;
    接口:sendOrderedBroadcast(intent);
(4) App应用内广播 Local Broadcast

为什么需要应用内广播:
Android中的广播可以跨App直接通信( exported对于有intent-filter情况下默认值为true),就有可能出现应用间广播的冲突,冲突表现在:

  1. 效率性问题:其他App针对性发出与当前App中注册的广播接收器的intent-filter相匹配的广播, 由此导致当前App不断接收广播并处理;
  2. 安全性问题:其他App注册与当前App发送的广播一致的intent-filter的广播接收器用于接收广播, 获取广播具体信息;

解决方案:使用App应用内广播

  1. App应用内广播可理解为一种局部广播, 广播的发送者和接收者都同属于一个App;
  2. 相比于全局广播( 普通广播) , App应用内广播优势体现在:安全性高 & 效率高;

实现方式:

  1. 将全局广播设置成局部广播
    (1)注册广播接收器时将exported属性设置为false, 使得非本App内部发出的广播不被接收;
    (2)在广播发送和接收时, 增设相应权限permission, 用于权限验证;
    (3)发送广播时指定该广播接收器所在的包名, 此广播将只会发送到此包中的App内与之相匹配的有效广播接收器中。通过 intent.setPackage(packageName) 指定包名。
  2. 使用封装好的LocalBroadcastManager类
//注册应用内广播接收器
//步骤1: 实例化BroadcastReceiver子类 & IntentFilter
BroadcastReceiver 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, inten
tFilter);
//取消注册应用内广播接收器
localBroadcastManager.unregisterReceiver(mBroadcastReceiver);

//步骤5:发送应用内广播
Intent intent = new Intent();
intent.setAction(android.net.conn.CONNECTIVITY_CHANGE);
localBroadcastManager.sendBroadcast(intent);
粘性广播

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

4. 内容提供者

Android ContentProvider 基本原理和使用详解
Android ContentProvider详解

定义,是什么

四大组件之一,IPC通信的方式之一,管理 Android 以结构化方式存放的数据,以相对安全的方式封装数据(表)并且提供简易的处理机制和统一的访问接口供其他程序调用。
Android 的数据存储方式总共有五种,分别是:Shared Preferences、网络存储、文件存储、外储存储、SQLite。
有时候我们需要操作其他应用程序的一些数据,就会用到 ContentProvider。而且 Android 为常见的一些数据提供了默认的 ContentProvider(包括音频、视频、图片和通讯录等)。

作用,为什么

进程间进行数据交互 & 共享, 即跨进程通信。
主要用于在不同的应用程序之间实现数据共享的功能,提供了一套完整的机制,允许一个程序访问另一个程序中的数据,同时还能保证数据访问的安全性。

原理,机制

底层是采用Binder机制。

使用方法

要实现与其他的 ContentProvider 通信首先要查找到对应的 ContentProvider 进行匹配。
Android 中 ContenProvider 借助 ContentResolver 通过 Uri 与其他的 ContentProvider 进行匹配通信。

统一资源标识符 URI

定义:Uniform Resource Identifier , 即统一资源标识符。URI 为系统中的每一个资源赋予一个名字,比方说通话记录。每一个 ContentProvider 都拥有一个公共的 URI,用于表示 ContentProvider 所提供的数据。

作用:唯一标识 ContentProvider & 其中的数据,外界进程通过 URI 找到对应的ContentProvider & 其中的数据, 再进行数据操作。
URI分为系统预置和自定义两种,主要介绍自定义URI。
格式:[scheme:][//host:port][path][?query]
如:content://com.example.app.myprovider/tablename/1,以下对各部分进行介绍:

  1. 主题名(Schema):URI前缀,安卓CP中的URI固定为:content://,用来说明是一个Content Provider控制这些数据;

  2. URI所对应数据的唯一标识,授权信息(Authority):com.example.app.myprovider,用于唯一标识这个 ContentProvider,外部调用者可以根据这个标识来找到它。
    对于第三方应用程序,为了保证 URI 标识的唯一性,它必须是一个完整的、小写的类名。
    这个标识在provider元素的authorities属性中说明,一般是定义该 ContentProvider 的包.类的小写名称,如下:

  3. 路径(path),表名(tablename):通俗的讲就是你要操作的数据库中表的名字,或者你也可以自己定义;

  4. 记录(ID):表中的某个记录(若无指定,则返回表的全部记录)
    对于第三部分路径(path)做进一步的解释,用来表示要操作的数据,构建时应根据实际项目需求而定。如:
    操作tablename表中id为11的记录,构建路径:/tablename/11;
    操作tablename表中id为11的记录的name字段:tablename/11/name;
    操作tablename表中的所有记录:/tablename;
    操作来自文件、xml或网络等其他存储方式的数据,如要操作xml文件中tablename节点下name字段:/ tablename/name;

若需要将一个字符串转换成Uri,可以使用Uri类中的parse()方法,如:
Uri uri = URI.parse(“URI = http://www.baidu.com:8080/wenku/jiatiao.html?id=123456&name=jack”);
uri 的各个部分在安卓中都是可以通过代码获取的,下面我们就以这个 uri 为例来说下获取各个部分的方法:
uri.getScheme():获取 Uri 中的 scheme 字符串部分,在这里是 http
uri.getHost():获取 Authority 中的 Host 字符串,即 www.baidu.com
uri.getPort():获取 Authority 中的 Port 字符串,即 8080
uri.getPath():获取 Uri 中 path 部分,即 wenku/jiatiao.html
uri.getQuery():获取 Uri 中的 query 部分,即 id=15&name=du

URI中可以进行通配符匹配:
// : 匹配任意长度的任何有效字符的字符串,以下的URI 表示 匹配provider的任何内容
content://com.example.app.provider/

// # : 匹配任意长度的数字字符的字符串,以下的URI表示匹配provider中的table表的任意一行
content://com.example.app.provider/table/#

MIME

是什么:
MIME 类型一般包含两部分:类型和子类型,如:
text/html
text/css
text/xml
application/pdf
ContentProvider 会根据 URI 来返回 MIME 类型,符合MIME规范,ContentProvider 会返回一个包含两部分的字符串,第一部分标识该URI对应的数据属于多条记录(集合记录,dir)还是单条记录(item),第二部分是自定义类型,如下:
集合记录(dir):vnd.android.cursor.dir/自定义
单条记录(item):vnd.android.cursor.item/自定义
vnd 表示这些类型和子类型具有非标准的、供应商特定的形式。Android中类型已经固定好了,不能更改,只能区别是集合还是单条具体记录,子类型可以按照格式自己填写。

UriMatcher

Uri 代表要操作的数据,在对数据进行获取时需要在CP中对Uri进行解析和匹配,来判断访问者要访问的数据是否是在本CP中提供。
Android 提供了两个用于操作 Uri 的工具类,分别为 UriMatcher 和 ContentUris 。

  1. UriMatcher 类用于为CP添加Uri匹配,并在CP的被调用接口中针对入参Uri进行匹配。
    添加Uri匹配代码如下:
//常量UriMatcher.NO_MATCH表示不匹配任何路径的返回码
UriMatcher  sMatcher = new UriMatcher(UriMatcher.NO_MATCH);

// 针对欲访问tablename表所有数据的Uri,匹配成功,并返回匹配码为1
sMatcher.addURI("content://com.wang.provider.myprovider", " tablename ", 1);

// 针对欲访问tablename表某一行单条数据的Uri,匹配成功,并返回匹配码为2
sMatcher.addURI("com.wang.provider.myprovider", "tablename/#", 2);

此处采用 addURI 注册了两个需要用到的 URI;注意,添加第二个 URI 时,路径后面的 id 采用了通配符形式 “#”,表示 URI对应访问表的某一行单条数据。
注册完需要匹配的 Uri 后,可以使用 sMatcher.match(Uri) 方法对输入的 Uri 进行匹配,如果匹配就返回对应的匹配码,匹配码为调用 addURI() 方法时传入的第三个参数。

switch (sMatcher.match(Uri.parse("content://com.zhang.provider.yourprovider/tablename/100"))) {
    case 1:
        //match 1, todo something
        break;
    case 2: // 匹配成功
        //match 2, todo something
        break;
    default:
        //match nothing, todo something
        break;
}
ContentUris

ContentUris 类用于操作 Uri 路径后面的 ID 部分,它有两个比较实用的方法:withAppendedId(Uri uri, long id) 和 parseId(Uri uri)。

  1. withAppendedId(Uri uri, long id) 用于为路径加上 ID 部分
Uri uri = Uri.parse("content://cn.scu.myprovider/user")

//生成后的Uri为:content://cn.scu.myprovider/user/7
Uri resultUri = ContentUris.withAppendedId(uri, 7); 
  1. parseId(Uri uri) 则从路径中获取 ID 部分
Uri uri = Uri.parse("content://cn.scu.myprovider/user/7")

//获取的结果为:7
long personid = ContentUris.parseId(uri);
ContentProvider

分为系统CP和自定义CP

  1. 系统CP
    如何获取:通过系统CP对应的唯一的URI获取,注意添加对应的访问权限,使用完成后记得及时关闭cursor。
  2. 自定义CP
    (1). 需要在AM文件中注册:
    (2). 需要继承ContentProvider 抽象类,实现对应的方法,如下:
    public boolean onCreate():在创建 ContentProvider 时使用,完成数据库的创建、连接、文件加载等资源准备操作。
    初始化CP时才会被调用,用于创建和升级db,返回true表示初始化成功,false表示失败。只有当存在ContentResolver尝试访问我们程序中的数据时,CP才会被初始化。

public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder):用于查询指定 uri 的数据返回一个 Cursor
public Uri insert(Uri uri, ContentValues values):用于向指定uri的 ContentProvider 中添加数据
public int delete(Uri uri, String selection, String[] selectionArgs):用于删除指定 uri 的数据,返回被删除的行数
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs):用户更新指定 uri 的数据,返回被更新内容的行数
数据访问的方法 insert,delete 和 update 可能被多个线程同时调用,此时必须是线程安全的。

public String getType(Uri uri):用于返回指定的 Uri 对象所对应的表数据 MIME 类型。
一个URI所对应的数据的MIME类型主要由3部分组成,Android对这3个部分做了如下格式规定:

  1. 必须以vnd开头;
  2. 如果对应的数据属于集合类型,则后接android.cursor.dir/,如果属于单条数据,则后接android.cursor.item/;
  3. 最后接上vnd..
    则对于content://com.example.app.provider/table/1这个uri,其对应的MIME类型就应该写成:android.cursor.item/vnd.com.example.app.provider.table1;
ContentResolver

获取:ContentResolver resolver = getContentResolver();
构造Uri:Uri uri = Uri.parse(“content://com.wang.provider.myprovider/tablename”);

添加记录:
// 添加一条记录
ContentValues values = new ContentValues();
values.put(“name”, “wang1”);
values.put(“age”, 28);
resolver.insert(uri, values);

更新记录:
// 把id为1的记录的name字段值更改新为zhang1
ContentValues updateValues = new ContentValues();
updateValues.put(“name”, “zhang1”);
Uri updateIdUri = ContentUris.withAppendedId(uri, 1);
resolver.update(updateIdUri, updateValues, null, null);

删除记录:
// 删除id为2的记录
Uri deleteIdUri = ContentUris.withAppendedId(uri, 2);
resolver.delete(deleteIdUri, null, null);

监听数据变化

如果ContentProvider的访问者需要知道数据发生的变化,可以在ContentProvider发生数据变化时调用getContentResolver().notifyChange(uri, null)来通知注册在此URI上的访问者。只给出类中监听部分的代码:

public class MyProvider extends ContentProvider {
   public Uri insert(Uri uri, ContentValues values) {
      db.insert("tablename", "tablenameid", values);
      getContext().getContentResolver().notifyChange(uri, null);
   }
}

而访问者必须使用 ContentObserver 对数据(数据采用 uri 描述)进行监听,当监听到数据变化通知时,系统就会调用 ContentObserver 的 onChange() 方法:

getContentResolver().registerContentObserver(Uri.parse("content://com.ljq.providers.personprovider/person"),
       true, new PersonObserver(new Handler()));
public class PersonObserver extends ContentObserver{
   public PersonObserver(Handler handler) {
      super(handler);
   }
   public void onChange(boolean selfChange) {
      //to do something
   }
}

总结

使用CP实现跨进程数据访问的总流程:
内容提供者进程:

  1. 创建MyContentPorvider,继承ContentProvider父类
  2. 在MyContentProvider类中,通过UriMatcher为CP添加Uri匹配
  3. 重写MyContentProvider的相关方法
  4. 在AM中声明provider,并在标签中声明authority,必要时还应该有对应访问权限的声明用于保护
    内容访问者进程:
  5. 获取ContentResolver:ContentResolver resolver = context.getContentResolver();
  6. 构建并解析欲访问内容对应的Uri
  7. 通过调用ContentResolver相关接口进行内容访问

3. 应用的pid uid 包名 进程名

pid

PID:process identifier,进程id,PID是各个进程的身份标识,具有唯一性,不同进程的PID互相不同。程序已启动,系统将会为进程分配一个独一无二的PID,进程终止后PID会被系统回收,可能会被继续分配给新的进程,但是在android系统中一般不会把已经kill掉的进程ID重新分配给新的进程,新产生的进程ID一般比产生之前的所有进程ID都要大。

安卓中,同一个应用中的不同组件也可以通过设置具有不同的PID,只需要如下方式声明即可:

但是此种方式会隐形增加应用内组件之间通信的复杂性,不建议使用。

获取pid:

int pid = android.os.Process.myPid();
int pid = getCallingPid();

uid

UID:user identifier,用户ID。linux系统属于多用户系统,UID在linux中就是用户的ID,表明是哪个用户运行了这个进程,主要用于权限管理。而android系统属于单用户系统,此时UID的功能主要是实现数据共享。为了实现数据共享,android为每个应用分配了不同的UID。开发者可以通过设置使不同应用共享一个UID,相同UID的应用程序之间可以实现数据共享。
uid用于标识一个应用程序,在应用安装时被分配,并且在应用存在于手机期间,都不会改变。
一个应用程序只能有一个uid,多个应用可以使用sharedUserId 方式共享同一个uid,前提是这些应用的签名要相同。

获取uid:

int uid = android.os.Process.myUid();
int uid = getCallingUid();

在android中通过UID实现数据共享需要在共享程序a,b中的AM文件配置如下参数即可:
android:sharedUserId=“com.xxx.xxx”
注意,为保护共享数据的安全性,防止被开发者通过设置共享UID的方式恶意访问,android还要求共享数据的应用之间需要具有相同的签名,每个公司或者开发者的签名是唯一的,不容易被暴露,因此保证了共享数据的安全。

uid如何分配和uid的价值
完全暴露:通过在AM的组件(活动、服务、内容提供器)声明中设置exported=true。
权限提示暴露:通过在AM的组件(活动、服务、内容提供器)声明中设置访问权限要求。
私有暴露:通过sharedUserId+同一套签名的方式实现。

包名 进程名

Android应用开发中的进程名,包名,applicationId
包名是安卓应用apk的唯一标识,安卓系统管理应用时,也是以包名索引。
包名在AndroidManifest.xml中定义。在一个安卓应用程序工程创建之初,包名就已经确定。
包名更多的是一个编译时概念。由android IDE自动生成的类,比如R、BuildConfig,都是以AndroidManifest.xml中定义的包名来构造类名,譬如,包名为“com.android.helloworld”,对应生成“com.android.helloworld.R”“com.android.helloworld.BuildConfig”。
在需要使用的类中,需要imort,import语句也将保留在源代码中。

android应用在被启动时,对应的进程的进程名一般就是包名。
android应用一般是运行在自己的进程中,除非通过AndroidManifest.xml中application定义中通过android:process字段指定运行在其他进程中(这种机制有特殊限定条件)。

applicationId是Android Studio提供的一种新的机制,可以在app/build.gradle中定义,用于设置应用的包名。

Android Studio默认生成的app/build.gradle中,applicationId与AndroidManifest.xml中定义的包名一致。
但显然,这两个值是可以独立设置的,可以不同。
注意,在修改applicationId后,前面提到的IDE自动生成的类(譬如R、BuildConfig),包名不会被更新,仍然是AndroidManifest.xml中定义的包名,使用他们的类中的import语句也不需要更改。

但在修改applicationId之后,查看运行时进程名,会变成新的applicationId。
在程序内运行时调用Context.getPackageName(),得到的仍然是新的applicationId。
怀疑实现方式是build过程中,擦除并更新了AndroidManifest.xml中的package字段。
使用apktool对编译生成的apk反编译,查看AndroidManifest.xml,发现确实是修改成了applicationId的值,验证了上述假设。

如上分析,对于applicationId的理解已经清晰。
对于一个大的工程,修改包名的成本过高,几乎不可取。
而经常会出现一个工程需要出不同版本,譬如收费版和免费版。
这样就可以通过简单修改app/build.gradle中的applicationId来改变最终apk的包名,这样对于安卓手机来说,就是不同的app;
同时,又不影响到开发编译时的代码一致性。

最后,说一个这种机制的坑。对于上文提到的自动生成的Java类(譬如R、BuildConfig),如果不是通过正常的import,而是通过在运行时调用Context.getPackageName()得到包名拼接成类名再用反射获取类,这样就会遇到ClassNotFoundException。需注意尽量不要用这种方式。反射虽然能让人体会到超越Java数据封装规则之上的快感,但是也有各种暗礁等着。

修改应用包名、名称、版本号、Icon以及环境判断和打包

4. 权限

参考:Android 权限(一):权限大全
Android源码中所有权限定义的文件为:frameworks/base/core/res/AndroidManifest.xml

权限的作用

Android 中应用权限有助于保护对以下数据和操作的访问/执行权限,从而为保护用户隐私提供支持:

  • 受限数据,例如系统状态和用户的联系信息
  • 受限操作,例如连接到已配对的设备并录制音频

权限的类别

  • 安装时权限(install权限):安卓系统在应用安装时就会根据应用申请的权限,决策授予或者拒绝授予该应用此权限。在某些低版本的安卓SDK(<23)上,在应用安装时会让用户选择是否授予应用安装时权限,如果不授予,应用无法安装。最新的安卓SDK取消了安装时权限对用户的提醒,自动决策是否授予某些安装时权限。normal和signature级别的权限都是安装时权限。不会给用户提示界面,系统自动决定权限的赋予或拒绝。

  • 运行时权限(动态权限):运行时权限也称为危险(dangerous)权限,此类权限授予应用对受限数据的额外访问权限,或允许应用执行对系统和其他应用具有更严重影响的受限操作。因此,您需要先在应用中请求运行时权限然后才能访问受限数据或执行受限操作。保护级别为dangerous的权限为运行时权限,需要应用在运行过程中动态申请该权限,由用户在弹窗中决策是否授予应用这些权限。

  • 特殊权限:特殊权限与特定的应用操作相对应。只有平台和原始设备制造商 (OEM) 可以定义特殊权限。此外,如果平台和 OEM 想要防止有人执行功能特别强大的操作(例如通过其他应用绘图),通常会定义特殊权限。系统设置中的特殊应用访问权限页面包含一组用户可切换的操作。其中的许多操作都是以特殊权限的形式实现的。特殊权限旨在限制访问尤其敏感或与用户隐私没有直接关系的系统资源。这些权限不同于安装时权限和运行时权限。声明特殊权限的应用会显示在系统设置中的特殊应用权限页面内(图 1)。如需向应用授予特殊权限,用户必须转到此页面:设置 > 应用 > 特殊应用权限。与运行时权限不同,用户必须从系统设置中的特殊应用权限页面授予特殊权限。应用可以使用 intent 将用户转到该页面,这会暂停应用,并启动相应的设置页面,以便用户授予指定的特殊权限。用户返回到应用后,应用可以在 onResume() 函数中检查是否已获得相应权限。

权限的保护级别

  • normal普通权限:默认值。具有较低风险的权限,此类权限允许请求授权的应用访问隔离的应用级功能,对其他应用、系统或用户的风险非常小。 系统会自动向在安装时请求授权的应用授予此类权限,无需征得用户的明确许可。
  • signature签名权限:只有在请求授权的应用使用与声明权限的应用相同的证书进行签名时系统才会授予的权限。如果证书匹配,则系统会在不通知用户或征得用户明确许可的情况下自动授予权限。只有当应用A 与 定义权限的应用B 或 OS 使用相同的证书签名时,系统才会向应用授予签名权限
  • dangerous:具有较高风险的权限,此类权限允许请求授权的应用访问用户私人数据或获取可对用户造成不利影响的设备控制权。由于此类权限会带来潜在风险,因此系统可能不会自动向请求授权的应用授予此类权限。例如,应用请求的任何危险权限都可能会向用户显示并且获得确认才会继续执行操作,或者系统会采取一些其他方法来避免用户自动允许使用此类功能。
  • systemOrSignature:系统仅向位于 Android 系统映像的专用文件夹中的应用 或 使用与声明权限的应用相同的证书进行签名的应用授予的权限。不要使用此选项,因为 signature 保护级别应足以满足大多数需求,无论应用安装在何处,该保护级别都能正常发挥作用。

权限的操作

权限的声明:在AM文件中通过如下标签定义权限:

<permission
    android:name="XXXX"
    android:label="@string/my_permission_label"
    android:description="@string/my_permission_desc"
    android:protectionLevel="normal" />
  • 权限名称:权限的标识字符串。
  • 权限包名:拥有这个权限的包名。
  • 权限标签:权限的简介。
  • 权限描述:权限的具体描述,通常描述该权限用于执行哪些动作。
  • 保护级别:决定了该权限能否、将以何种方式授予申请该权限的应用。
    权限的使用:在AM文件中通过 uses-permission android:name=“XXXXX” /> 声明权限使用
    权限的查看:
  • 查看某应用的信息,其中包含了该应用的权限信息:dumpsys package pkgName
  • 查看某应用是否被授予某权限:dumpsys package check-permission pkgName permissionName,0表示授予,-1表示未授予
  • 代码通过pid和uid检查是否具有权限:context.checkPermission(MY_PERMISSION_NAME, pid, uid) == PackageManager.PERMISSION_GRANTED

5. 签名

android应用签名信息 android 应用签名是什么

6. queries标签

作用:管理软件包可见性。
参考:Manifest queries标签 / targetAPI动态获取 / 手机 /包可见性
参考:android queries属性

7. meta-data标签

参考:Android中AndroidManifests.xml 之meta-data
参考:android meta-data

作用

用于在安卓应用开发中,定义应用程序(application标签),或者应用的组件(活动、服务、广播接收器)的额外元数据信息,这些信息之后可以在程序中通过packageManager特定的接口获得。meta-data是以键值对的形式配置到应用的AM文件中的。

使用

所有的meta-data键值对在程序中将被包装成Bundle供组件使用,因此使用方式同Bundle。
在代码中获取元数据的接口有:metaData.getString()、getInt()、getBoolean()、getFloat()等。
metadata普通值由value属性给出,资源ID由resource属性给出。
meta-data标签包括的基本信息如下:
name:数据项的唯一名称。 为了确保名称的唯一性,可使用 Java 风格的命名规则 — 如“com.example.project.activity.fred”。
resource(可选):如果元数据的值是定义在资源文件中,则需要通过resourse属性给定,赋值为资源ID,之后在程序中通过name获得的元数据的value也是资源ID。
value(可选):普通类型的值可以通过value属性给定。

8. handler机制

对handler机制的基本作用、用法、时序流程进行介绍,针对handler机制中的内存泄漏问题讲解:一篇读懂Android Handler机制
Android-Handler机制详解
全面解析 | Android之Handler机制

需要掌握的:

  1. handler机制的核心目的,基本类,基本用法,基本时序流程?
  2. handler机制中容易出的内存泄漏问题是什么?怎么解决?
  3. messageQueue的真实数据结构是什么?不同的send一条数据的方法对应的数据结构操作的区别是啥?
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值