Android基础之四大组件(Activity | Service | Broadcast Receiver | Content Provider)详解

目录

四大组件

Activity(活动)

Service(服务)

Broadcast Receiver(广播接收者)

Content Provider(内容提供者)

四大组件

Activity(活动)

简介

  • Activity相当于一个页面,可以在Activity中添加各种控件,例如Button,TextView等。
  • 一个Android程序(APP)可以由多个Activity组成
  • 提供了与用户交互的可视化界面(GUI)
  • Android中使用栈task来存储Acticity,及先进后出,栈顶及我们所看到的页面

生命周期

  • onCreat()--->onStart()--->onResume()--->onPause()--->onStop()--->onDestory()
  • onStop()--->onRestart()--->onStart()
  • 图解

启动模式(4种)

  • Standard(默认启动模式)

在该模式下,Activity可以有多个实例,每次启动一个Activity,无论栈Task里面是否已经存在该Activity的实例,都将创建一个新实例入栈

例如,Acticity A调用B,然后B调用A,这时栈的情况是ABA

  • SingleTop(栈顶复用模式)

当一个SingleTop实例位于栈顶时,再去启动它时,不需要创建新的实例,只需要调用newInstance()方法,但若不在栈顶,则会被重新创建

例如,A调用B,B调用B自己,情况是AB,若A调用B,B调用A,A再调用B,情况是BABA,同Standard

  • SingleTask(单任务模式)

如果启动的Acticity存在于栈Task中,则该Activity的实例会被移到栈顶(相当于将该Acticity之上的实例全部移出栈)

例如,原来Stack里面有ABCD,调用B,那就变成AB

  • SingleInstance(单实例模式)

一个Activity就是一个栈

启动方式

  • 为Activity指定所需任务栈的方式

在AndroidManifest.xml下,添加taskAffinity属性(默认情况下不指定该属性值,则该activity的指定栈为应用的包名)

<activity android:name=".SecondActivity"
    android:launchMode="singleTask"
    android:taskAffinity="com.example.laughter.task_1"/>
  • 为Activity指定启动模式

在AndroidManifest文件中指定lanchMode属性值

Intent intent = new Intent(MainActivity.this, SecondActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);

通过Intent中设置标志位来指定启动模式

<activity android:name=".SecondActivity"
        android:launchMode="singleTask"
        android:taskAffinity="com.example.laughter.task_1"/>

常见Flags标志位

· FLAG_ACTIVITY_NEW_TASK	指定singleTask模式
· FLAG_ACTIVITY_SINGLE_TOP	指定singleTop模式
· FLAG_ACTIVITY_CLEAR_TOP	同一栈中的所有位于该activity上方的都出栈

跳转方式

  • 显示启动

Intent内部直接声明要启动的activity所对应的class

Intent intent = new Intent(MainActivity.this, SecondActivity.class);
startActivity(intnet);
  • 隐式启动

分为两步走 第一步,在文件里配置activity属性

<activity android:name="com.example.android.tst.secondActivity"
            android:label = @string/title>
	    <intent-filter>
		    <action android:name="com.example.android.tst.secondActivity/>
		    <category android:name="android.intent.category.DEFAULT"/>
	    <intent-filter/>
</activity>
第二步,在需要跳转的地方	
Intent intent = new Intent("com.example.android.tst.secondActivity");
  • 跳转返回获取返回值
MainActivity: 
Intent intent = new Intent(MainActivity.this,OtherActivity.class);
intent.putExtra("a",a);
startActivityForResult(intent,1000);

// 在MainActivity中获取返回值
@Override
protected void onActivityResult(int requestCode, int resultCode	,Intent data) {
	super.onActivityResult(requestCode,resultCode,data);
	if(requestCode == 1000){
		if(resultCode == 1001){
			int c = data.getExtra("c",0);
		}
	}
}
在OtherActivity中设置返回值
Intent intent = new Intent();
intnet.putExtra("c",c);
setResult(1001,int);
finish();

Service(服务)

简介

  • Service是一个没有用户界面的后台执行耗时操作的应用组,它非常适合去执行那些不需要和用户交互而且还要求长期运行的任务。服务的运行不依赖于任何用户界面,即使程序被切换到后台,或者用户打开了另外一个应用程序,服务仍然能够保持正常运行
  • 服务依赖于启动它的进程,当该进程被杀死,该service也会被停止运行
  • IPC机制(跨进程通讯) 一个组件能绑定一个Service与之进行交互
  • Service可用于提升进程(每个进程就是一个应用)的优先级,从高到低依次为:前台(可见并可控),可见(局部可见却不可控),服务(存在正在运行的Service),后台(没有任何Activity可见),空进程(已退出的应用程序)

生命周期

  • startService()-->onCreate()-->onStartCommand()-->onDestory()
  • bindService()-->onCreate()-->onBind()-->onUnbind()-->onDestory()
  • 图解

  • 若我们希望服务一旦启动就立刻去执行任务,可以将逻辑写在onStartCommand()里面,反复调用startService()会导致onStartCommand()反复被执行
  • startService()只是启动Service,启动它的组件(如Activity)和Service并没有关联,只有当Service调用stopSelf()或者其他组件调用stopService()服务才会终止。
  • bindService()方法启动Service,其他组件可以通过回调获取Service的代理对象和Service交互,而这两方也进行了绑定,当启动方销毁时,Service也会自动进行unBind()操作,当发现所有绑定都进行了unBind时才会销毁Service。
  • service也是运行在主线程中的,若是要运行于其他线程,需要自行指定

注册服务

  • 每个服务都需要在AndroidManifest.xml中进行注册才能生效
<application
....>
...
    <service
			android:name=".MyService"
            android:enabled="true"
            android:exported="true">
	</service>
</application>

启动服务

Intent startIntent = new Intent(this,MyService.class);
startService(startIntent);

停止服务

Intent stopIntent = new Intent(this,MyService.class);
stopService(stopIntent);

启用服务

  • 前台服务(与普通服务的最大区别在于,它会一直有一个正在运行的图标在系统的状态栏中,下拉状态栏后可以看到更加详细的内容,非常类似于通知的效果)
public class MyService extends Service{
    Intent intent = new Intent(this, MainActivity.class);
    PendingIntent pi = PendingIntent.getActivity(this, 0 , intent, 0);
    Notification notification  = new NotificationCompat.Builder(this)
        .setContentTitle(" this is content titile")
        .setContentText("this is content text")
        .setWhen(System.currentTimeMillis())
        .setSmallIcon(R.mipmap.ic_launcher);
    .setLargeIcon(BitmapFactory.decodeResource(getResource(),R.mipmap.ic_launcher))
        .setContentIntent(pi)
        .build();
    // startForeground()方法是将MyService变成一个前台服务,并在系统状态栏显示出来
    startForeground(1,notification);
}
  • 使用子线程(将耗时操作放入子线程,规避ANR)
public class MyService extends Service{
    ...
        @Override
        public int onStartCommand(Intent intent , int flags, int startId){
        new Thread(new Runnable(){
            public void run(){
                //处理具体的逻辑
                //如此写的话需要手动调用stopService()或者stopSelf()方法去让服务器停止
                stopSelf();
            }
        }).start();
        return super.onStartCommand(intent, flags, startId);
    }
}

特性

  • 粘性:当Service被意外终止后,会在未来某一刻重启
  • 单例:一个Service类只会存在一个对象
  • 无界面

IntentService

  • IntentService是Service的子类,相较于Service,其最大特点是回调函数onHandleIntent()中可以直接进行耗时操作,无需再开新线程。其原理是IntentService的成员变量Handler在初始化时已属于工作线程,之后handleMessage,包括onHandleIntent等函数都运行在工作线程中;and还有一个特点,便是多次调用onHandleIntent(),多个耗时任务会按顺序依次执行,原理是其内置的Handler关联了任务队列,Handler通过looper去任务执行是顺序执行的。

如何使用

public class MyIntentService extends IntentService{
    
	public MyIntentService(){
        // 调用父类的有参构造方法
		super("MyIntentService");  
	}
    
	@Override
	protected void onHandleIntent(Intent intent){	
		// 打印当前的线程ID
		Log.e("mylog","Thread id is” + Thread.cuttentThread().getId();

		// 写一些需要处理的逻辑
	}
              
	@Override
	public void onDestory(){
		super.onDestory();
		Log.e("mylog","on Destory executed");
	}
}

Intent intent = new Intent(this, MyIntentService.class);
startServcie(intent);
// 并且,执行完毕会自动关闭

Broadcast Receiver(广播接收者)

简介

  • 在Android中,广播是一种广泛运用的在应用程序之间传输信息的机制,主要用于不用组件间通信(应用内/外);多线程通信;于Android系统的通信
  • 广播接收器没有用户界面
  • 自定义广播接收者,需要继承Broadcast Receiver类,且重写onReceive(),广播接收器收到相应广播后,会自动调用onReceive()方法,一般情况下,onReceive()会涉及与其他组件之间的交互,如 发送Notification,启动service等
  • 默认情况下,广播接收器运行在UI线程,因此,onReceive方法不能执行耗时操作,否则将导致ANR

注册方式

  • 静态注册(在AndroidManifest.xml中通过receiver标签进行注册)
<receiver 
	android:enable="true"/"false"
	
    //此broadcastReceiver 是否接受其他应用发出的广播
	//默认值时由receiver中有无intent-filter决定,如果有,默认true,否则默认false
	android:exported="true"/"false"
	android:icon="drawable resource"
	android:label="string resource"
	
    //继承BroadcastReceiver子类的类名
    android:name=".mBroadcastReceiver"
        
	//具有相应权限的广播发送者发送的广播才能被此BroadcastReceiver所接收;
    android:permission="string"
        
	//BroadcastReceiver运行所处的进程
	//默认为app的进程,可以指定独立的进程
	//注:Android四大基本组件都可以通过此属性指定自己的独立进程
    android:process="string" >

		//用于指定此广播接收器将接收的广播类型
		//本示例中给出的是用于接收网络状态改变时发出的广播
 		<intent-filter>
			<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
 		</intent-filter>
 </receiver>

示例:
 

<receiver 
   //此广播接收者类是mBroadcastReceiver
    android:name=".mBroadcastReceiver" >
     //用于接收网络状态改变时发出的广播
      <intent-filter>
          <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
      </intent-filter>
</receiver>
  • 动态注册(在代码中调用Context.registerReceiver()方法)
//实例化BroadcastReceiver子类 &  IntentFilter
mBroadcastReceiver mBroadcastReceiver = new mBroadcastReceiver();
IntentFilter intentFilter = new IntentFilter();

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

//动态注册:调用Context的registerReceiver()方法
registerReceiver(mBroadcastReceiver, intentFilter);

//动态注册广播后,需要在相应位置记得销毁广播
unregisterReceiver(mBroadcastReceiver);
动态注册最好在onResume()中注册,在onPause()中注销,否则会导致内存泄漏

注册方式的区别

  • 静态注册是常驻型广播,动态是非常驻型
  • 常驻在程序关闭后,仍可以被系统调用,非常驻组件结束=广播结束

广播类型

  • 普通广播
  • 系统广播
  • 有序广播
  • 粘性广播
  • app内广播

特别注意

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

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

Content Provider(内容提供者)

简介

  • 使一个应用程序的指定数据集提供给其他应用程序。其他应用可以通过ContentResolver类从该内容提供者中获取或存入数据
  • 对于每个应用程序来说,要想访问内容提供者的共享数据,需要借助ContentResolver类(可通过geContentResolver()获得该类实例)
  • ContentProvider实现数据共享。ContentProvider用于保存和获取数据,并使其对所有应用程序可见。这是不同应用程序间共享数据的唯一方式,因为android没有提供所有应用共同访问的公共存储区。
  • 不同于SQLiteDatabase,ContentResolver接收的是一个uri参数,被称之为内容URL,其给ContentProvider的数据建立了唯一标识符
它主要由两部分组成:authority 和 path 
authority 是用于对不同的应用程序做区分的,一般为了避免冲突,都会采用程序包名的方式进行命名。
path则是用于对同一应用程序中不同的表做区分,通常都会添加到authority后面:
	content://com.example.app.provider/table1
	content://com.example.app.provider/table2
    
而使用它作为参数的时候,需要将URL转换为URL对象:
	Uri uri = Uri.parse("content://com.example.app.provider/table1")
  • ContentProvider和调用者在同一个进程,ContentProvider的方法(query/insert/update/delete等)和调用者在同一线程中;
  • ContentProvider和调用者在不同的进程,ContentProvider的方法会运行在它自身所在进程的一个Binder线程中。

基本操作

  • 创建
public class MyProvider extends ContentProvider{
    @Override
    public boolean onCreate() {
        return false;
    }
    @Override
    public Cursor query(Uri uri, String[] projection, Stirng selection, String[] selectionArgs, String sortOrder){
        return null;
    }
    @Overrride
    public Uri insert(Uri uri , ContentValues values){
        return null;
    }
    @Override
    public int update(Uri uri, ContentValuse values, String selection, String[] selectionArgs){
        return 0;
    }
    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs){
        return 0;
    }
    @Override
    public String getType(Uri uri){
        return null;
    }
}
  • 查询

query()方法参数

对应SQL部分

描述

uri from

table_name

指定查询某个应用程序下的某个表

projection

select column1, column2

指定查询的列名

selection

where column=value

指定where约束条件

selectArgs

-

为where中的占位符提供具体的值

orderBy

order by column1, column2

指定查询结果的排序方式

Cursor cursor = getContentResolver().query(
	uri,
	projection,
	selection,
	selectionArgs,
	sortOrder
);
  • 取值
if(cursor != null){
    while(cursor.moveToNext()) {
        String column1 = cursor.getString(cursor.getColumnIndex("column1"));
        int column2 = cursor.getInt(cursor.getColumnIndex("column2"));
    }
    cursor.close();
}
  • 添加数据
ContentValues values = new ContentValues();
values.put(“column1”, "text");
values.put("column2", 1);
getContentResolver().insert(uri, values);
  • 更新数据
ContentValues valuse = new ContentValues();
valuse.put("column1", "");
getContentResolver().update(uri, values, "column1 = ? and column2 = ?", new String[]{"text", 1});
  • 删除数据
getContentResolver().delete(uri , "column2 = ?", new String[]{ "1"});

实例

  • 读取系统联系人
if(ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
    ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_CONTACTS}, 1);
}else {
    // 读取联系人
    readContacts();
}

private void readContacts(){
    Cursor cursor = null;
    try{
        // 查询联系人数据
        cursor = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,null,null,null,null);
        if(cursor != null){
            while(cursor.moveToNext()){
                // 获取联系人姓名
                String name = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
                // 获取联系人电话号码
                String number = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
                list.add(name+"n"+number);
            }
        }
    }catch(Exception e){
        e.printStackTrace()
    }finally{
        if(cursor != null){
            cursor.close();
        }
    }
}

@Override
public void onRequestPermissionResult(int requestCode, String[] permissions, int[] grantResults){
    switch(requestCode){
        case 1:
            if(grantResults.length >0 && grantResults[0] == PackageManager.PERMISSION_GRANTED){
                readContacts();
            }else {
                // 您拒绝了权限
            }
    }
}

uri主要格式

  • * : 表示匹配任意长度的任意字符
  • # : 表示匹配任意长度的数字
content://com.example.app.provider/table1
content://com.example.app.provider/table1/1

//一个能够匹配任意表的内容URI格式就可以写成:
content://com.example.app.provider/*

//一个能够匹配表中任意一行数据的内容URI格式就可以写成:
content://com.example.app.provider/table1/#

作用

  1. 封装。对数据进行封装,提供统一的接口,使用者完全不必关心这些数据是在DB,XML、Preferences或者网络请求来的。当项目需求要改变数据来源时,使用我们的地方完全不需要修改
  2. 提供一种跨进程数据共享的方式
本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>