第一章 四大组件

介绍一下四大组件?

  • Activity(活动)
    Activity是Android程序与用户交互的窗口,是Android构造块中最基本的一种。它为用户提供一个窗口,上面可以显示一些控件用于监听并处理用户的事件。
  • Service(服务)
    Service提供需在后台长期运行的服务,无用户界面。一个组件可以与一个Service进行绑定实现组件之间的交互。Service可以在后台执行很多任务,如处理网络事务,播放音乐,文件读写下载等等。
  • Content Provider(内容提供者)
    Content Provider是Android官方推荐的不同应用程序间进行数据交互&共享的方式。ContentProvider为存储和获取数据提供统一的接口,相当于数据的搬运工(中间者),真正的数据源为Sqlite/文件/XML/网络等。
  • BroadcastReceiver(广播接收器)
    BroadcastReceiver相当于一个全局监听器,用于接收应用间/应用内发出的广播信息,并作出响应。

Activity

生命周期

在这里插入图片描述
在这里插入图片描述
Activity生命周期包括四种状态、七种方法、两个异常:

  1. 四种状态
  • Running状态:处于栈的最顶端,此时它处于可见并可和用户交互的激活状态。
  • Paused状态:当Activity被另一个透明或者Dialog样式的Activity覆盖时的状态。它仍然可见,但失去了焦点,故不可与用户交互。
  • Stopped状态:当Activity完全不可见,处于后台时,但仍保留着当前状态和成员信息
  • Killed状态:当前界面被销毁,等待被系统回收
  1. 七个方法
  • onCreate():在Activity创建时调用。一般用来做一些初始化操作,如初始化布局setContentLayout()
  • onStart():在Activity即将显示界面时调用,但用户无法操作。一般也用于做一些初始化操作,但对于Activity而言,onCreate只执行一次,但onStart可执行多次。
  • onResume():在Activity获取焦点开始与用户交互时调用,此时Activity处于运行状态,位于栈顶。一般用于数据恢复、开启动画等
  • onPause():在当前Activity被其他Activity覆盖部分或锁屏时调用,此时Activity处于暂停状态,仍然可见,但失去焦点,不能与用户交互。一般用于关闭动画,注销广播等。并应进行状态保存与数据存储,但不适合做耗时操作。(为了让新的Activity尽快切换到前台)
  • onStop():在Activity对用户完全不可见时调用,此时Activity处于停止状态。此时进程优先级较低,当系统内存不足时,容易被杀死。一般用于进行资源回收。
  • onDestory():在Activity销毁时调用,常用于释放资源,Activity处于销毁状态后,将被清出内存。
  • onRestart():在Activity从停止状态再次启动时调用。onRestart一般是应用位于后台重新切换为前台调用,可用于进行数据刷新。
    其中onCreate() & onDestory()、onStart() & onStop()、onResume() & onPause()成对出现。
  1. 两个异常
  • 更高优先级的进程需要内存,但系统内存不足
    处于暂停/停止状态(低优先级)可能会被直接杀死onStop()->直接杀死进程(当前Activity)
    手动重启当前Activity->onCreate()->onStart()->onResume()->运行
  • 系统配置发生改变导致Activity意外销毁(如横竖屏切换、键盘事件等)
    Running->onSaveInstanceState()->onPause()->onStop()->onDestroy()->自动重启->onCreate()->onStart()->onRestoreInstanceState()->onResume()->Running
    在onSaveInstanceState()保存Activity状态。适合保存一些非持久数据,如布局状态、成员变量的值等,持久数据适合在onPause()与onStop()中通过数据库、sharedpreference保存
    在onRestoreInstanceState()/onCreate()恢复Activity状态。

补充:Activity各种实际场景下生命周期的变换
(1)横竖屏切换、键盘事件等系统配置(自动重启)
->onPause->onSaveInstanceState->onStop->onDestroy->onCreate->onStart->onRestoreInstanceState->onResume
(2)横竖屏切换总结

  • 设置
    a. 静态设置,即在Mainfest文件中配置screenOrientation属性
// 控制Activity为竖屏显示
android:screenOrientation="portrait"

b. 动态设置,即调用Activity的setRequestedOrientation(@ActivityInfo.ScreenOrientation int requestedOrientation)方法设置screenOrientation属性值

  • Activity生命周期
    关于Android横竖屏切换Activity是否会销毁重建,这个由Activity的configChanges属性控制。

a. Activity 不销毁重建
下方配置可以控制Activity在横竖屏切换时不销毁重建

android:configChanges="orientation|keyboardHidden|screenSize"

配置了android:configChanges="orientation|keyboardHidden|screenSize"横竖屏切换时Activity不会销毁重建,而是会回调Activity的onConfigurationChanged方法。
b. Activity销毁重建

  • 不配置configChanges属性
  • 设置android:configChanges=“orientation”
  • 设置android:configChanges=“orientation|keyboardHidden”

以上三种配置,横竖屏切换时Activity均会销毁重建,Activity的生命周期都会重新执行一次
onPause -> onStop -> onDestroy -> onCreate -> onStart -> onResume
(3)锁屏/息屏/Home/打开新Activity/处于后台,并手动重启
->onPause->-> onSaveInstanceState->onStop->onReStart->onStart->onResume
(4)Back键退出当前Activity
onPause->onStop->onDestroy
(5)Aactivity切换Bactivity
AActivity:->onPause()
BActivity:onCreate()->onStart()->onResume()
AActivity:onStop()
(6)Aactivity切换Bactivity(透明/对话框)
AActivity:->onPause()
BActivity:onCreate()->onStart()->onResume()
面试题
1、弹出普通Dialog和一个自定义Dialog视图的Activity(android:theme="@style/dialogstyle")生命周期有什么区别?
(1)弹出普通Dialog:Activity周期不发生变化。因为Dialog依附于 Activity, Activity仍位于前台。
(2)弹出Dialog视图的Activity:
原Activity:onPause()
Dialog样式Activity:onCreate()->onStart()->onResume()

2、两个Activity 之间跳转时必然会执行的是哪几个方法?
当在A Activity里面激活B Activity的时候, A会调用onPause()方法,然后B调用onCreate() ,onStart(), onResume()。
这个时候B覆盖了A的窗体, A会调用onStop()方法。
如果B是个透明的窗口,或者是对话框的样式, 就不会调用A的onStop()方法。
如果B已经存在于Activity栈中,B会调用onReStart()->onStart()->onResume()
故一定会执行A的onPause()和B的onStart()与onResume()。

参数传递

  1. 通过Intent传递
  • 使用putExtra,可直接传递单一基本数据类型,或用Bundle封装多种数据类型再传递或者传递经Serializable/Parcelable序列化对象
// 传递基本数据类型
// 发送数据
   Intent intent =new Intent(this,OtherActivity.class); 
   intent.putExtra("boolean_key", true);
   intent.putExtra("string_key", "string_value");
   startActivity(intent);
// 获取数据
   Intent intent=getIntent();
   intent.getBooleanExtra("boolean_key",false);
   intent.getStringExtra("string_key");

// 传递多种数据类型(Bundle)
// 发送数据
	Intent intent =new Intent(CurrentActivity.this,OtherActivity.class);
	Bundle bundle =new Bundle();
	bundle.putBoolean("boolean_key", true);
	bundle.putString("string_key", "string_value");
	intent.putExtra("key", bundle);// 封装对象
	startActivity(intent);// 启动新的 Activity
// 获取数据
	Intent intent =getIntent();
	Bundle bundle =intent.getBundleExtra("key");
	bundle.getBoolean("boolean_key");
	bundle.getString("string_key");

// 传递自定义对象
// 方式1:实现Serializable接口
public class Person implements Serializable{
private String name;
private int age;
public String getName() {
		return name;
	}
public void setName(String name) {
		this.name = name;
	}
public int getAge() {
		return age;
		}
public void setAge(int age) {
		this.age = age;
	}
}

Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
intent.putExtra("person_data", person);
startActivity(intent);

Person person = (Person) getIntent().getSerializableExtra("person_data");

// 方式2:实现Parcelable接口
public class Person implements Parcelable {
	private String name;
	private int age;
	
	@Override
	public int describeContents() {
		// TODO Auto-generated method stub
		return 0;
	}
 
	@Override
	public void writeToParcel(Parcel dest, int flags) {
		// TODO Auto-generated method stub
		dest.writeString(name);
		dest.writeInt(age);
	}
	public static final Parcelable.Creator<Person> CREATOR=new Parcelable.Creator<Person>() {
 
		@Override
		public Person createFromParcel(Parcel source) {
			// TODO Auto-generated method stub
			Person person=new Person();
			person.name=source.readString();
			person.age=source.readInt();
			return person;
		}
 
		@Override
		public Person[] newArray(int size) {
			// TODO Auto-generated method stub
			return new Person[size];
		}
	};
 
}

Person person = (Person) getIntent().getParcelableExtra("person_data");
  • 使用startActivityForResult+setResult获取新Activity关闭后返回的数据
    FirstActivity.java
// FirstActivity.java
public class FirstActivity extends Activity{
	@override
	protected void onCreate(Bundle savedInstanceState){
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_first);
	}
	...
	onClick(View v){
		Intent intent = new Intent(FirstActivity.this,SecondActivity.class);
		// startActivityForResult(Intent intent, int requestCode)
		// intent:跳转页面 requestCode:自定义int类型数值,用于判断源Activity
		startActivityForResult(intent,MyConstants.REQUEST_CODE);
	}
	// 重写 onActivityResult(int requestCode,int resultCode,Intent data)
	// 处理从目的Activity 返回的数据
	@override 
	protected void onActivityResult(int requestCode, int resultCode,Intent data){
		super.onActivityResult(requestCode,resultCode,data);
		if(requestCode == MyConstants.REQUEST_CODE && resultCode == MyConstants.RESULT_OK){
			String data = data.getStringExtra(MyConstants.REQUEST_DATA);
			Log.i(data);
		}
	}
}

SecondActivity.java

public class SecondActivity extends Activity{
	@override
	protected void onCreate(Bundle savedInstanceState){
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_second);
	}
		...
	onClick(View v){
		Intent intent = new Intent();
		intent.putExtra(MyConstants.REQUEST_DATA,"Data From SecondActivity");
		// 在setResult后,要调用finish()销毁当前的Activity
		// 才能返回到原Activity,执行原Activity的onActivityResult()函数
		setResult(MyConstants.RESULT_OK,intent);
		finish();
	}
}
  1. 通过直接访问类的静态变量实现
  2. 在Application(单例模式)设置应用的全局变量,可在程序中通过getApplication随时调用
  3. 使用EventBus插件传输数据量较大的数据
    订阅者
	//使用EventBus的接收方法的Activity,需要注册监听
	EventBus.getDefault().register(this);
   /**
     * 注册onEvent()监听事件
     * 加入注解加入注解Subscribe并指定线程模型为主线程MAIN(默认为POSTING)
     */
    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onEvent(MyEvent event) {
        popOutToast("接收到Event:" + event.msg);
    }

发布者

EventBus.getDefault().post(new MyEvent("Event From Publisher"));
  1. 借助外部存储,如SharedPreference、Sqlite或者File等

启动过程

Activity的启动过程,我们可以从Context的startActivity说起,其实现是ContextImpl的startActivity,然后内部会通过Instrumentation来尝试启动Activity,这是一个跨进程过程,它会调用AMS的startActivity方法
当AMS校验完activity的合法性后,将activity入栈,并创建新的应用进程ActivityThread,这个过程是在ActivityStack里完成的,ActivityStack是运行在Server进程里的。
此时Server进程会通过ApplicationThread回调到我们的进程通知app进程ActivityThread绑定Application并启动Activity,这也是一次跨进程过程,而ApplicationThread就是一个binder,回调逻辑是在binder线程池中完成的,所以需要通过Handler H向主线程ActivityThread发送操作消息
绑定Application发送的消息是BIND_APPLICATION,对应的方法是handleBindApplication,该方法中对进程进行了配置,并创建及初始化了Application。启动Activity发送的消息是LAUNCH_ACTIVITY,对应的方法handleLaunchActivity,在这个方法里完成了Activity的创建和启动,回调Activity相关的周期方法。接着,在activity的onResume中,activity的内容将开始渲染到window上,然后开始绘制直到我们看见。

启动模式

Activity的启动模式有四种:standard、singleTop、singleTask和singleInstance。我们可以通过在AndroidManifest.xml的activity标签下通过launchMode属性指定想要设置的启动模式。

<activity android:name=".MainActivity"
			  android:launchMode="singleTop">...
  1. standard(标准模式)
    该启动模式为默认模式。标准模式下,只要启动一次Activity,不管该实例是否存在,系统都会在当前任务栈中新建一个Activity实例并将该实例置于栈顶。
    该模式用于正常打开一个新的页面。使用最多,最普通。
  2. singleTop(栈顶复用模式)
    栈顶复用模式下,如果要启动的Activity已经处于栈的顶部,那么此时系统不会创建新的实例,而是复用栈顶的实例,同时它的onNewIntent()方法会被执行,我们可以通过Intent进行传值。否则会创建一个新的实例。
    SingleTop适用于接受推送通知的内容显示页面,防止每点击一次通知重新打开重复页面。
  3. singleTask(栈内复用模式)
    栈内复用模式下,首先会根据taskAffinity去寻找对应的任务栈:
    1、如果不存在指定的任务栈,系统会新建对应的任务栈,并新建一个Activity实例压入栈中。
    2、如果存在指定的任务栈,则会查找该任务栈中是否存在该Activity实例
    a、如果不存在该实例,则会在该任务栈中新建一个Activity实例压入栈中。
    b、如果存在该实例,则将任务栈中该Activity实例之上的所有Activity出栈并将所需Activity置于栈顶。
    SingleTask这种启动模式最常使用的就是一个APP的首页,因为一般为一个APP的第一个页面,且长时间保留在栈中,所以最适合设置singleTask启动模式来复用。
  4. singleInstance(单例模式)
    单例模式拥有singleTask(栈内复用)所有特性外且该Activity实例单独占用一个任务栈,具有全局唯一性。该模式启动的activity在系统中是单例的。如果已存在,则将它所在的任务栈调度到前台,进行复用。
    适用于与程序分开,具有独立功能的页面,如闹铃提醒,电话拨号等。

任务栈 & 任务
Android 任务栈
可简单理解,一个应用程序对应一个任务,任务以栈的方式存储一系列与用户交互的Activity

状态保存 & 恢复

  • 需要保存/恢复Activity状态的场景
    当一些异常的场景导致某个activity变得"容易"被系统销毁(而不是被用户主动销毁(如点击BACK键))时,系统 会调用onSaveInstanceState方法来给用户提供一个存储现场的机会。
    这些场景包括:锁屏、点击home键、其他app进入前台、启动新的activity、(当前activity可能被销毁)横竖屏切换、由于内存不足app被杀死(一定被销毁)等。
    当该activity被系统销毁后重启回到前台时,系统会调用onRestoreInstanceState恢复Activity中数据。
  • 如何保存/恢复Activity状态
    我们通常在系统调用onSaveInstanceState(Bundle savedInstanceState)中,我们可以在该方法中用一组存储在Bundle对象中的键值对集合保存该Activity当前状态/需要恢复的数据。当我们重启该Activity时,上述的Bundle对象会作为实参传递给onCreate()与onRestoreInstanceState(Bundle savedInstanceState)方法, 我们可以从Bundle对象中取出保存的数据, 然后利用这些数据将activity恢复到被摧毁之前的状态.
@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
        savedInstanceState.putString("MyString", "Welcome back to Android");
        super.onSaveInstanceState(savedInstanceState);
}
 
@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
        String myString = savedInstanceState.getString("MyString");
}

Service

启动方式 & 生命周期

  1. Service 有2种启动方式
  • startService()方式启动服务,调用者与Service没有关联。只有当Service调用stopSelf()或调用者调用stopService()才能停止服务。
  • bindService()方式启动服务,调用者与Servie绑定,可以与Service进行交互。当所有调用者退出后,Service会自动停止。
  1. Service 有5种生命周期方法
回调描述
onStartCommand()其他组件(如活动)通过调用startService()来请求启动服务时,系统调用该方法。如果你实现该方法,你有责任在工作完成时通过stopSelf()或者stopService()方法来停止服务。
onBind()当其他组件想要通过bindService()来绑定服务时,系统调用该方法。如果你实现该方法,你需要返回IBinder对象来提供一个接口,以便客户来与服务通信。你必须实现该方法,如果你不允许绑定,则直接返回null。
onUnbind()当客户中断所有服务发布的特殊接口时,系统调用该方法。
onCreate()当服务通过onStartCommand()和onBind()被第一次创建的时候,系统调用该方法。该调用要求执行一次性安装。
onDestroy()当服务不再有用或者被销毁时,系统调用该方法。你的服务需要实现该方法来清理任何资源,如线程,已注册的监听器,接收器等。
  1. Service的生命周期根据启动方式分3种情况
  • 只用startService启动服务:onCreate-> onStartCommand-> onDestory
  • 只用bindService绑定服务:onCreate-> onBind-> onUnBind-> onDestory
  • 同时用startService启动服务与用bindService绑定服务:onCreate-> onStartCommnad-> onBind-> onUnBind-> onDestory
    在这里插入图片描述
服务启动方式startServicebindService
方法参数Intent:用于启动服务Intent:用于启动服务
ServiceConnection:Activity 和 Service 建立连接时通信使用
服务周期启动服务后服务将一直在后台运行,即使 Activity 销毁依然存在假如没有先 startService,bindService后绑定的最后一个 Activity 销毁时,service也将销毁,且bindService后的Service 在系统 Running 任务管理器下是看不见的。但先startService,接着 bindService 时,系统 Running 任务管理器显示该服务,Service 解绑后,onDestroy并不会得到运行

适用场景(Service 与 Thread 对比)

Service和Thread均没有界面,在后台运行。

ServiceThread
运行线程主线程工作线程
依赖不依赖Activity,所有Activity都可以与该Service关联依赖某个Activity,在某个Activity创建进程,其他Activity无法获取
优先级提高进程的优先级,系统不容易回收进程在activity中开启的子线程按照优先级回收,易回收
适用场景长期在后台运行的操作activity中需要处理的耗时操作

Android 系统进程管理是按照一定规则的:应用程序一旦打开,为了下一次快速启动,关闭(清空任务栈)后进程不会停止。会带来内存不足的问题。Android系统有一套内存清理机制,根据进程优先级回收系统内存。服务的作用就是提高进程的优先级,使系统不容易回收进程。因此对于需要在后台长期运行的操作,不要在activity中开启子线程,应该创建服务,在服务里开启子线程。
如:长期在后台运行的没有界面的组件。如天气预报、股票显示(后台连接服务器的逻辑,每隔一段时间获取最新的(天气、股票)信息)、mp3播放器(后台长期播放音乐)等。

Service 分类 & 使用

  1. 不可交互的后台服务
    不可交互的后台服务即是普通的Service,通过startService()方式开启。Service的生命周期很简单,分别为onCreate、onStartCommand、onDestroy这三个。
    音乐播放器案例,继承Service类实现自定义Service,提供在后台播放音乐、暂停音乐、停止音乐的方法。
public class MyService extends Service {

    private final String TAG = "MyService";

    private MediaPlayer mediaPlayer;

    private int startId;

    public enum Control {
        PLAY, PAUSE, STOP
    }

    public MyService() {
    }

    @Override
    public void onCreate() {
        if (mediaPlayer == null) {
            mediaPlayer = MediaPlayer.create(this, R.raw.music);
            mediaPlayer.setLooping(false);
        }
        Log.e(TAG, "onCreate");
        super.onCreate();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        this.startId = startId;
        Log.e(TAG, "onStartCommand---startId: " + startId);
        Bundle bundle = intent.getExtras();
        if (bundle != null) {
            Control control = (Control) bundle.getSerializable("Key");
            if (control != null) {
                switch (control) {
                    case PLAY:
                        play();
                        break;
                    case PAUSE:
                        pause();
                        break;
                    case STOP:
                        stop();
                        break;
                }
            }
        }
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        Log.e(TAG, "onDestroy");
        if (mediaPlayer != null) {
            mediaPlayer.stop();
            mediaPlayer.release();
        }
        super.onDestroy();
    }

    private void play() {
        if (!mediaPlayer.isPlaying()) {
            mediaPlayer.start();
        }
    }

    private void pause() {
        if (mediaPlayer != null && mediaPlayer.isPlaying()) {
            mediaPlayer.pause();
        }
    }

    private void stop() {
        if (mediaPlayer != null) {
            mediaPlayer.stop();
        }
        stopSelf(startId);
    }

    @Override
    public IBinder onBind(Intent intent) {
        Log.e(TAG, "onBind");
        throw new UnsupportedOperationException("Not yet implemented");
    }

}

①Service不运行在一个独立的进程中,它同样执行在UI线程中,因此,在Service中创建了子线程来完成耗时操作。
②当Service关闭后,如果在onDestory()方法中不关闭线程,你会发现我们的子线程进行的耗时操作是一直存在的,此时关闭该子线程的方法需要直接关闭该应用程序。因此,在onDestory()方法中要进行必要的清理工作。
(2)在清单文件中声明Service,为其添加label标签,便于在系统中识别Service

      <service
          android:name=".MyService"
          android:label="@string/app_name" />

如果想配置成远程服务,加如下代码:

android:process="remote"

(3)Activity中在布局中添加三个按钮,用于控制音乐播放、暂停与停止

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    public void playMusic(View view) {
        Intent intent = new Intent(this, MyService.class);
        Bundle bundle = new Bundle();
        bundle.putSerializable("Key", MyService.Control.PLAY);
        intent.putExtras(bundle);
        startService(intent);
    }

    public void pauseMusic(View view) {
        Intent intent = new Intent(this, MyService.class);
        Bundle bundle = new Bundle();
        bundle.putSerializable("Key", MyService.Control.PAUSE);
        intent.putExtras(bundle);
        startService(intent);
    }

    public void stopMusic(View view) {
        Intent intent = new Intent(this, MyService.class);
        Bundle bundle = new Bundle();
        bundle.putSerializable("Key", MyService.Control.STOP);
        intent.putExtras(bundle);
        startService(intent);
        //或者是直接如下调用
        //Intent intent = new Intent(this, MyService.class);
        //stopService(intent);
    }
}
  1. 可交互的后台服务
    可交互的后台服务是指前台页面可以调用后台服务的方法,通过bindService()方式开启。Service的生命周期很简单,分别为onCreate、onBind、onUnBind、onDestroy这四个。
  • 创建服务类
    和普通Service不同在于这里返回一个代理对象,返回给前台进行获取,即前台可以获取该代理对象执行后台服务的方法
    @Override
    public IBinder onBind(Intent intent) {
        //返回MyBinder对象
        return new MyBinder();
    }
    //需要返回给前台的Binder类
    class MyBinder extends Binder implements IService{
        @Override
        public void showTip(){
            System.out.println("我是来此服务的提示");
        }
    }
  • 前台调用
    通过以下方式绑定服务:
bindService(mIntent,con,BIND_AUTO_CREATE);

当建立绑定后,onServiceConnected中的service便是Service类中onBind的返回值。如此便可以调用后台服务类的方法,实现交互。

private ServiceConnection con = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            BackService.MyBinder myBinder = (BackService.MyBinder) service;
            myBinder.showTip();
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

可参考回调方式实现与Activity交互案例

  1. 前台服务
    所谓前台服务只不是通过一定的方式将服务所在的进程级别提升了。前台服务会一直有一个正在运行的图标在系统的状态栏显示,非常类似于通知的效果。
    由于后台服务优先级相对比较低,当系统出现内存不足的情况下,它就有可能会被回收掉,所以前台服务就是来弥补这个缺点的,它可以一直保持运行状态而不被系统回收。
    创建服务类
    前台服务创建很简单,其实就在Service的基础上创建一个Notification,然后使用Service的startForeground()方法即可启动为前台服务。
public class ForeService extends Service{
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        beginForeService();
    }

    private void beginForeService() {
        //创建通知
        Notification.Builder mBuilder = new Notification.Builder(this)
                .setSmallIcon(R.mipmap.ic_launcher)
                .setContentText("2017-2-27")
                .setContentText("您有一条未读短信...");
        //创建点跳转的Intent(这个跳转是跳转到通知详情页)
        Intent intent = new Intent(this,NotificationShow.class);
        //创建通知详情页的栈
        TaskStackBuilder stackBulider = TaskStackBuilder.create(this);
        //为其添加父栈 当从通知详情页回退时,将退到添加的父栈中
        stackBulider.addParentStack(NotificationShow.class);
        PendingIntent pendingIntent = stackBulider.getPendingIntent(0,PendingIntent.FLAG_UPDATE_CURRENT);
        //设置跳转Intent到通知中
        mBuilder.setContentIntent(pendingIntent);
        //获取通知服务
        NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        //构建通知
        Notification notification = mBuilder.build();
        //显示通知
        nm.notify(0,notification);
        //启动前台服务
        startForeground(0,notification);
    }
}

启动前台服务

startService(new Intent(this, ForeService.class));

IntentService

  • 定义
    由于Service默认运行在主线程中,所以如果直接在服务中处理耗时操作,容易出现ANR。此时可引用IntentService。
    IntentService本质上是一个封装了HandlerThread+Service的异步框架,继承自Service。在使用完后会自动停止,适合需要在工作线程中按先后顺序,处理UI无关/后台 的耗时任务的场景,如离线下载。

不适用于多个数据同时请求的场景,因为所有的任务都在同一个Thread loop里执行,故按照先后顺序。

  • 使用
    (1)定义IntentService子类,构造方法传入线程名称,复写onHandleIntent()方法
public class myIntentService extends IntentService{
	public myIntentService(){
		// 1. 调用父类的构造函数,传入工作线程的名字
		super("myIntentService");
	}
	@override
	protected void onHandleIntent(Intent intent){
		// 2. 复写onHandleIntent()方法,根据intent请求处理 耗时操作
		String taskName = intent.getExtras().getString("taskName");
		// 从intent 中获取不同事务,根据不同事务进行处理
		switch(taskName){
			case "task1":
				Log.i("myIntentService","do task1");
			break;
			case "task2":
				Log.i("myIntentService","do task2");
			break;
			default:break;
		}
	}
	@override
	public void onCreate(){
		super.onCreate();
	}
	@override
	public int onStartCommand(Intent intent,int flags,int startId){
		// 复写onStartCommand方法,默认 = 将请求的Intent添加到工作队列中
		return super.onStartCommand(intent,flags,startId);
	}
	@override
	public void onDestroy(){
		super.onDestroy();
	}
	@override
	public IBinder onBind(Intent intent){
		// IntentService中,onBind()默认返回null
		return null;
	}
}

(2)在AndroidManifest.xml中注册服务

<service android:name=".myIntentService">
            <intent-filter >
                <action android:name="cn.scu.finch"/>
            </intent-filter>
        </service>

(3)在Activity中开启服务

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

            // 同一服务只会开启1个工作线程,在onHandleIntent()函数里,依次处理传入的Intent请求
            // 将请求通过Bundle对象传入到Intent,再传入到服务里
            // 请求1
            Intent i = new Intent("cn.scu.finch");	// 通过意图过滤器找到服务
            Bundle bundle = new Bundle();
            bundle.putString("taskName", "task1");
            i.putExtras(bundle);
            startService(i);	// 输出 do task1

            // 请求2
            Intent i2 = new Intent("cn.scu.finch");
            Bundle bundle2 = new Bundle();
            bundle2.putString("taskName", "task2");
            i2.putExtras(bundle2);
            startService(i2);	// 输出 do task2
			// 多次启动服务
            startService(i);  // 输出 do task1
        }
    }

输出结果
在这里插入图片描述
一个服务只会创建一次onCreate,只会开启一个工作线程。在onHandleIntent中依次处理传入的Intent

  • 源码分析
    在这里插入图片描述
    IntentService本质上是一个封装了HandlerThread+Service的异步框架。若启动IntentService 多次,但只创建一个工作线程,因此 每个耗时操作 则 以队列的方式 在 IntentService的 onHandleIntent回调方法中依次执行,执行完自动结束。
  • onCreate
    (1)IntentService 通过 HandlerThread 开启一个新的工作线程 ServiceThread
    (2)创建1个内部 Handler:ServiceHandler,并将ServiceHandler 与 ServiceThread 绑定,接受这个工作线程的消息队列中的消息,重写onHandleIntent()依次处理这些消息(根据Intent 的不同执行不同操作)。
@Override
public void onCreate() {
    super.onCreate();
    
    // 1. 通过实例化HandlerThread新建线程 & 启动;故 使用IntentService时,不需额外新建线程
    // HandlerThread继承自Thread,内部封装了 Looper
    // 创建HandlerThread类对象 = 创建Thread类对象 + 设置线程优先级 = 新开1个工作线程 + 设置线程优先级
    HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
    thread.start();
  
    // 2. 获得工作线程的 Looper & 维护自己的工作队列
    mServiceLooper = thread.getLooper();

    // 3. 新建mServiceHandler & 绑定上述获得Looper
    // 新建的Handler 属于工作线程 ->>分析1
    mServiceHandler = new ServiceHandler(mServiceLooper); 
}


   /** 
     * 分析1:ServiceHandler源码分析
     **/ 
     private final class ServiceHandler extends Handler {

         // 构造函数,与新建工作线程的looper(&MessegeQueue)绑定
         public ServiceHandler(Looper looper) {
         super(looper);
       }

        // IntentService的handleMessage()把从工作线程的Looper接收的消息交给onHandleIntent()处理
        @Override
         public void handleMessage(Message msg) {
  
          // onHandleIntent 方法在工作线程中执行
          // onHandleIntent() = 抽象方法,使用时需重写 ->>分析2
          onHandleIntent((Intent)msg.obj);
          // 执行完调用 stopSelf() 结束服务
          stopSelf(msg.arg1);

    }
}

   /** 
     * 分析2: onHandleIntent()源码分析
     * onHandleIntent() = 抽象方法,使用时需开发者重写
     **/ 
      @WorkerThread
      protected abstract void onHandleIntent(Intent intent);
  • onStartCommand
    IntentService将Intent传递给ServiceHandler & 依次插入到工作队列 & 逐个发送给onHandleIntent()

/** 
  * onStartCommand()源码分析
  * onHandleIntent() = 抽象方法,使用时需重写
  **/ 
  public int onStartCommand(Intent intent, int flags, int startId) {

    // 调用onStart()->>分析1
    onStart(intent, startId);
    return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
}

/** 
  * 分析1:onStart(intent, startId)
  **/ 
  public void onStart(Intent intent, int startId) {

  	// 1. 获得ServiceHandler消息的引用
    Message msg = mServiceHandler.obtainMessage();
    msg.arg1 = startId;

    // 2. 把 Intent参数 包装到 message 的 obj 发送消息中,
    //这里的Intent  = 启动服务时startService(Intent) 里传入的 Intent
    msg.obj = intent;

    // 3. 发送消息,即 添加到消息队列里
    mServiceHandler.sendMessage(msg);
}

IntentService只会创建一个工作任务队列,因此多次启动 IntentService 时,每一个耗时操作(通过Intent逐一发送请求)会以工作队列的方式在IntentService的onHandleIntent回调方法中执行,会按串行的方式顺序执行事件。
即 若一个任务正在IntentService中执行,此时你再发送1个新的任务请求,这个新的任务会一直等待直到前面一个任务执行完毕后才开始执行

  • IntentService & Service 区别
IntentServiceService
运行线程创建一个独立的工作线程处理异步任务(耗时操作)主线程
结束服务操作需手动调用stopService()处理完所有intent请求后,系统自动关闭服务
联系IntentService继承自Service
IntentService为Service的onBind()默认实现:return null
IntentService为Service的onStartCommand()提供默认实现:将请求的intent添加到队列
  • IntentService & 其他线程 区别
作用优先级
IntentService后台线程,提供服务(继承自Service)
其他线程工作线程,处理异步任务低,容易被系统杀死

Service和Activity 通信

Service与Activity有2种方式进行通信:

  1. bindService + 回调函数
    Activity调用bindService方法,绑定一个Service。通过实例化ServiceConnection接口内部类监听的方法获取Service中的Binder对象,并将该接口传给binderService方法。如果想实现主动通知Activity的,还可以在Service中添加回调方法。
    (1)新建一个回调接口,通过回调接口实现当Service中进度发生变化主动通知Activity更新UI
public interface OnProgressListener {
    void onProgress(int progress);
}

(2)新建一个Service类

public class MsgService extends Service {

    public static final int MAX_PROGRESS = 100;//进度条最大值
    private int progress = 0;//进度条进度值

    private OnProgressListener onProgressListener;//更新进度的回调接口
    
    public void setOnProgressListener(OnProgressListener onProgressListener) {
     	//注册回调接口的方法,供外部调用
        this.onProgressListener = onProgressListener;
    }

    public int getProgress() {
    //增加get()方法,供Activity调用,返回progress
        return progress;
    }

    /**
     * 模拟下载任务,每秒钟更新一次
     */
    public void startDownLoad(){
        new Thread(new Runnable() {
            
            @Override
            public void run() {
                while(progress < MAX_PROGRESS){
                    progress += 5;
                    
                    //进度发生变化通知调用方
                    if(onProgressListener != null){
                        onProgressListener.onProgress(progress);
                    }
                    
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    
                }
            }
        }).start();
    }

    @Override
    public IBinder onBind(Intent intent) {
        return new MsgBinder();//返回Binder对象
    }
    
    public class MsgBinder extends Binder{
        public MsgService getService(){
            return MsgService.this;//返回当前service对象
        }
    }

}

(3)Activity中新建一个ServiceConnection对象,它是一个接口,Activity与Service绑定后,在onServiceConnected回调方法中返回服务对象。
onServiceConnected用于执行Activity与Service绑定后执行相关操作。

public class MainActivity extends Activity {
    private MsgService msgService;
    private ProgressBar mProgressBar;
    

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //绑定Service
        Intent intent = new Intent("com.example.communication.MSG_ACTION");
        bindService(intent, conn, Context.BIND_AUTO_CREATE);

        mProgressBar = (ProgressBar) findViewById(R.id.progressBar1);
        Button mButton = (Button) findViewById(R.id.button1);
        mButton.setOnClickListener(new OnClickListener() {
            
            @Override
            public void onClick(View v) {
                //开始下载
                msgService.startDownLoad();
            }
        });
    }

    ServiceConnection conn = new ServiceConnection() {
        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
        
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //返回一个MsgService对象
            msgService = ((MsgService.MsgBinder)service).getService();
            
            //注册回调接口来接收下载进度的变化
            msgService.setOnProgressListener(new OnProgressListener() {
                
                @Override
                public void onProgress(int progress) {
                    mProgressBar.setProgress(progress);
                }
            });
        }
    };

    @Override
    protected void onDestroy() {
        unbindService(conn);
        super.onDestroy();
    }
}
  1. 广播(推荐LocalBroadcastManager)
    Activity调用registerReceive注册广播接收器,通过startService启动一个Service,之后Service调用sendBoardcast向Activity发送广播。Activity则通过onReceive方法接收Service发送的消息。

ContentProvider

描述

ContentProvider主要用于在不同的应用程序之间实现数据共享的功能。
ContentProvider=中间者角色(搬运工),真正存储&操作数据的数据源为原来存储数据的方式(数据库(sqlite)、文件、XML、网络等等)
ContentProvider一般为存储和获取数据提供统一的接口,可以在不同的应用程序之间共享数据。
它的设计用意在于:
(1)对底层数据库的抽象
对数据进行封装,提供统一的接口。使用者不必关心这些数据来源于数据库、XML、Preferences或请求。当项目改变数据来源时,不会对使用代码产生影响。
(2)提供一种跨进程数据共享方式
数据在多个应用程序中共享,当一个应用程序改变共享数据时候,可用ContentResolver接口的notifyChange函数通知那些注册了监控该URI的ContentObserver对象,去通知其他应用程序共享数据被修改了,使得它们可以相应地执行一些处理。
(3)用安全的方式对数据进行封装
是ContentProvider为应用间的数据交互提供了一个安全的环境。它准许你把自己的应用数据根据需求开放给其他应用进行增、删、改、查。通过android:exported属性指示该服务是否能够被其他应用程序组件调用或跟它交互,通过permission属性对于需要开放的组件设置合理的权限,通过path-permission可开放部分uri进行共享。
在这里插入图片描述

使用

  1. 使用原理
    内容提供者是一种跨应用访问数据库的方式。一个应用可以通过内容提供者将自己的私有数据暴露出来,其他应用通过内容解析者对数据进行增删改查等操作。
  2. 使用场景
    由于ContentProvider是向其他应用暴露数据库接口,不能保证应用所定义的数据库的安全性。因此往往不用于自定义数据库。适用于获取系统数据库的接口,如短信数据库、联系人数据库。
  3. 实例
  • 自定义Sqlite数据库
    (1)创建数据库类 集成SQLiteOpenHelper
public class DBHelper extends SQLiteOpenHelper {

    // 数据库名
    private static final String DATABASE_NAME = "finch.db";

    // 表名
    public static final String USER_TABLE_NAME = "user";
    public static final String JOB_TABLE_NAME = "job";

    private static final int DATABASE_VERSION = 1;
    //数据库版本号

    public DBHelper(Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {

        // 创建两个表格:用户表 和职业表
        db.execSQL("CREATE TABLE IF NOT EXISTS " + USER_TABLE_NAME + "(_id INTEGER PRIMARY KEY AUTOINCREMENT," + " name TEXT)");
        db.execSQL("CREATE TABLE IF NOT EXISTS " + JOB_TABLE_NAME + "(_id INTEGER PRIMARY KEY AUTOINCREMENT," + " job TEXT)");
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)   {

    }
}

(2)自定义ContentProvider 子类,继承自ContentProvider。并在清单文件中配置内容提供者
MyProvider.java

public class MyProvider extends ContentProvider {

    private Context mContext;
    DBHelper mDbHelper = null;
    SQLiteDatabase db = null;
    // 设置ContentProvider的唯一标识,与清单文件 android:authority对应
    public static final String AUTHORITY = "cn.scu.myprovider";

    public static final int User_Code = 1;
    public static final int Job_Code = 2;

    // UriMatcher类 路径匹配器:在ContentProvider 中注册URI
    private static final UriMatcher mMatcher;
    static{
        mMatcher = new UriMatcher(UriMatcher.NO_MATCH);
        // 初始化
        mMatcher.addURI(AUTOHORITY,"user", User_Code);
        mMatcher.addURI(AUTOHORITY, "job", Job_Code);
        // 若URI资源路径 = content://cn.scu.myprovider/user ,则返回注册码User_Code
        // 若URI资源路径 = content://cn.scu.myprovider/job ,则返回注册码Job_Code
    }

    // 以下是ContentProvider的6个方法
    @Override
    public boolean onCreate() {

        mContext = getContext();
        // 在ContentProvider创建时对数据库进行初始化
        // 运行在主线程,故不能做耗时操作,此处仅作展示
        // 因为实现对数据库的增删改查操作,因此操作数据必须获得SQLiteDatabase对象
        mDbHelper = new DBHelper(getContext());
        db = mDbHelper.getWritableDatabase();

        // 初始化两个表的数据(先清空两个表,再各加入一个记录)
        db.execSQL("delete from user");
        db.execSQL("insert into user values(1,'Carson');");
        db.execSQL("insert into user values(2,'Kobe');");

        db.execSQL("delete from job");
        db.execSQL("insert into job values(1,'Android');");
        db.execSQL("insert into job values(2,'iOS');");

        return true;
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        // 根据URI匹配 URI_CODE,从而匹配ContentProvider中相应的表名
        String table = getTableName(uri);
        // 向该表添加数据,返回值代表插入的行数 index
        db.insert(table, null, values);
        // 当该URI的ContentProvider数据发生变化时,通知外界(即访问该ContentProvider数据的访问者)
        mContext.getContentResolver().notifyChange(uri, null);
        return uri;
        }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
                        String[] selectionArgs, String sortOrder) {
        // 根据URI匹配 URI_CODE,从而匹配ContentProvider中相应的表名
        String table = getTableName(uri);
        // 查询数据(即实现对数据库的查询操作)这里cursor不能关闭
        Cursor cursor = db.query(table,projection,selection,selectionArgs,null,null,sortOrder,null);
		return cursor;
    }
    
    @Override
    public int update(Uri uri, ContentValues values, String selection,
                      String[] selectionArgs) {
        // 更新操作,返回值代表影响的行数
        int updRows = db.update(table, values, selection, selectionArgs);
        return updRows;
    }
    
    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
    	// 删除操作,返回值代表影响的行数
        int delRows = db.delete(table, selection, selectionArgs);
        return delRows;
    }
    
    // 根据URI匹配 URI_CODE,从而匹配ContentProvider中相应的表名
    private String getTableName(Uri uri){
        String tableName = null;
        switch (mMatcher.match(uri)) {
            case User_Code:
                tableName = DBHelper.USER_TABLE_NAME;
                break;
            case Job_Code:
                tableName = DBHelper.JOB_TABLE_NAME;
                break;
        }
        return tableName;
        }
    }

AndroidManifest.xml

<provider android:name="MyProvider"
                android:authorities="cn.scu.myprovider"/>

(3)由于第一个应用的私有数据库已通过ContentProvider暴露,因此第二个应用可以使用内容解析者对数据进行操作

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // 设置URI,对user表进行操作(与定义的路径一致)
        Uri uri_user = Uri.parse("content://cn.scu.myprovider/user");
        // 插入表中数据 ContentValues
        ContentValues values = new ContentValues();
        values.put("_id", 3);
        values.put("name", "Iverson");
        // 获取ContentResolver(直接通过上下文获取) & 通过ContentResolver 根据URI 向ContentProvider中插入数据
        ContentResolver resolver =  getContentResolver();
        resolver.insert(uri_user,values);
        // 通过ContentResolver 向ContentProvider中查询数据
        Cursor cursor = resolver.query(uri_user, new String[]{"_id","name"}, null, null, null);
        while (cursor.moveToNext()){
            System.out.println("query book:" + cursor.getInt(0) +" "+ cursor.getString(1));
            // 将表中数据全部输出
        }
        cursor.close();
        // 关闭游标

        // 更改URI,对JOB表操作,从而匹配不同的URI CODE,从而找到不同的数据资源
        Uri uri_job = Uri.parse("content://cn.scu.myprovider/job");
        // 插入表中数据
        ContentValues values2 = new ContentValues();
        values2.put("_id", 3);
        values2.put("job", "NBA Player");

        // 获取ContentResolver
        ContentResolver resolver2 =  getContentResolver();
        // 通过ContentResolver 根据URI 向ContentProvider中插入数据
        resolver2.insert(uri_job,values2);
        // 通过ContentResolver 向ContentProvider中查询数据
        Cursor cursor2 = resolver2.query(uri_job, new String[]{"_id","job"}, null, null, null);
        while (cursor2.moveToNext()){
            System.out.println("query job:" + cursor2.getInt(0) +" "+ cursor2.getString(1));
            // 将表中数据全部输出
        }
        cursor2.close();
        // 关闭游标
}
}
  • 系统短信数据库
    系统短信目录位于com.android.provider.telephony内的mmssms.db(Provider 管理的私有数据库包括 com.android.provider.* 如applications、calendar、downloads等等)
    权限 -rw-rw---- 对一般用户不可读不可写不可执行(完全私有)=> 通过ContentProvider 暴露接口。

linux 文件访问权限
Linux的文件访问权限分为 读、写、执行三种:
drwxr-xr-x意思如下:
第一位表示文件类型:
d是目录文件,l是链接文件,-是普通文件,p是管道。
后面分为三个三位来看,分别表示不同用户的权限:
第一个 rwx: root :r 是可读,w 是可写,x 是可执行,rwx 意思是可读可写可执行。
第二个 r-x: 一般用户(用户组):r-x 是可读可执行不可写。
第三个 r-x: 其他用户,r-x 是可读可执行不可写。
综合起来就是权限设置为:文件所有者(root)可读可写可执行,与文件所有者同属一个用户组的其他用户可读可执行,其它用户组可读可执行。
在这里插入图片描述

public void click(View v){
	// 由于短信数据库 系统通过ContentProvider 将接口暴露出来,因此可以直接通过ContentResolver 操作数据(SmsProvider extends ContentProvider 系统上层应用源码)
	Uri uri = Uri.parse("content://sms/");
	// 1. 往短信数据库里插入一条数据
	ContentValues values = new ContentValues();
	values.put("address","95555");
	values.put("body","您的余额为 0.00 元");
	values.put("data",System.currentTimeMillis());
	getContentResolver().insert(uri,values);
	// 2. 查询短信数据内容
	Cursor cursor = getContentResolver().query(uri,new String[]{"address","data","body"},null,null,null);
	while (cursor.moveToNext()){
            System.out.println("result:" + cursor.getString(0) +" "+ cursor.getString(1) + " " + cursor.getString(2));
}

记得申请权限

<uses-permission android:name="android.permission.WRITE_SMS" />
<uses-permission android:name="android.permission.READ_SMS" />

BroadcastReceiver

描述

  • BoardcastReceiver 简介
    BroadcastReceiver是一个全局监听器,用于监听应用间/应用内发出的广播消息,并作出响应。分为广播发送者和广播接受者。
    系统在特定场景会发送广播,如电量低、插入耳机、状态改变等等。每个应用程序都会收到;应用程序也可以发送广播用来通知其他APP状态变化;
    如果我们的应用程序想接收特定的广播并执行相关操作,便可注册一个BroadcastReceiver进行监听对应的广播,并在onReceive中执行操作。
  • BoardcaseReceiver 原理
    Android中广播使用设计模式中观察者模式,基于消息的发布/订阅事件模型。模型中有3个角色:消息订阅者(广播接受者)、消息发布者(广播发布者)、消息中心(AMS)
    在这里插入图片描述

分类

类型介绍使用
普通广播自定义广播为开发者自身定义的广播。开发者定义广播的intent,并通过sendBroadcast()方法发送。
系统广播Android中内置了多个系统广播:包括手机的状态变化与基本操作(如开机、网络状态变化、电量状况、拍照等等),都会发出相应的广播。(每个广播都有特定的Intent - Filter(包括具体的action))。当使用系统广播时,只需要在注册广播接收者时定义相关的action即可,并不需要手动发送广播,当系统有相关操作时会自动进行系统广播
有序广播有序广播通过sendOrderedBroadcast发送,发送出去的广播根据广播接受者的优先级Priority按先后顺序接收。广播在发送过程中可被优先级较高的接受者拦截并修改再传给下一接受者。sendOrderedBroadcast(intent);
无序广播无序广播直接通过sendBroadcast发送,发送的广播不可被拦截也无法被修改。sendBroadcast(intent);
全局广播在应用间、应用与系统间、应用内部进行通信的一种方式。默认BroadcastReceiver是跨应用广播
本地广播本地广播仅能在自己应用内发送、接收广播。即发送的广播只能在自身app传播,且接收不到其他app发送的广播。故更加安全与高效。(1)注册广播时将exported属性设置为false,使得非本App内部发出的此广播不被接收;(2)在广播发送和接收时,增设相应权限permission,用于权限验证;(3)发送广播时指定该广播接收器所在的包名,此广播将只会发送到此包中的App内与之相匹配的有效广播接收器中。(通过intent.setPackage(packageName)指定报名)(4)使用封装好的LocalBroadcastManager类

注册方式

静态注册动态注册
使用在AndroidManifest中通过标签声明,应用首次启动后,系统会自动实例化广播接收器实例并注册到广播系统中。在代码中调用Context.registerReceiver()方法完成注册。
特点广播常驻后台,不会随着其他组件的消亡而变化,当应用程序关闭后,如果有广播,应用程序仍会被系统调用。这样的话不仅占用内存,而且会增加应用的耗电量。广播非常驻后台,生命周期灵活可控。注册和注销的过程需要开发者自己手动完成。为了避免内存泄漏,当广播不再使用时,开发者需要手动注销广播。
场景适用于需要时刻监听广播的场景需要特定时刻监听广播

使用方式:发送\接受\屏蔽 广播

  1. 发送广播
    开发者自定义intent广播并发送
Intent intent = new Intent();
// 广播通过 意图(Intent) 标识
// 对应BroadcastReceiver中intentFilter的action,只有action相同的广播接受者才能收到广播
intent.setAction(BROADCAST_ACTION);
// 发送广播
sendBroadcast(intent);
  1. 接收广播
  • 静态/动态注册广播接收器
    静态注册——在AndroidManifest.xml里通过标签声明
    //此广播接收者类是mBroadcastReceiver
<receiver 
    android:name=".mBroadcastReceiver" >
	//用于指定此广播接收器将接收的广播类型
	//本示例中给出的是用于接收网络状态改变时发出的广播
    <intent-filter>
        <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
    </intent-filter>
</receiver>

通过过滤器匹配自定义广播:对于自定义广播接收者中注册时intentFilter的action与上述匹配,则会接收此广播(即进行回调onReceive())。如下mBroadcastReceiver则会接收对应action广播

    //此广播接收者类是mBroadcastReceiver
<receiver 
    android:name=".mBroadcastReceiver" >
    //用于过滤器为 BROADCAST_ACTION 的广播
    <intent-filter>
        <action android:name="BROADCAST_ACTION" />
    </intent-filter>
</receiver>

动态注册——在代码中调用Context.registerReceiver()方法

// 选择在Activity生命周期方法中的onResume()中注册
@Override
  protected void onResume(){
      super.onResume();

    // 1. 实例化BroadcastReceiver子类 &  IntentFilter
     mBroadcastReceiver mBroadcastReceiver = new mBroadcastReceiver();
     IntentFilter intentFilter = new IntentFilter();

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

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

// 注册广播后,要在相应位置记得销毁广播
// 即在onPause() 中unregisterReceiver(mBroadcastReceiver)
// 当此Activity实例化时,会动态将MyBroadcastReceiver注册到系统中
// 当此Activity销毁时,动态注册的MyBroadcastReceiver将不再接收到相应的广播。
 @Override
 protected void onPause() {
     super.onPause();
      //销毁在onResume()方法中的广播,否则会导致内存泄露
     unregisterReceiver(mBroadcastReceiver);
     }
}

在onResume()注册、onPause()注销是因为onPause()在App死亡前一定会被执行,从而保证广播在App死亡前一定会被注销,从而防止内存泄露。

不在onCreate() & onDestory() 或 onStart() & onStop()注册、注销是因为:
当系统因为内存不足(优先级更高的应用需要内存,请看上图红框)要回收Activity占用的资源时,Activity在执行完onPause()方法后就会被销毁,有些生命周期方法onStop(),onDestory()就不会执行。当再回到此Activity时,是从onCreate方法开始执行。
假设我们将广播的注销放在onStop(),onDestory()方法里的话,有可能在Activity被销毁后还未执行onStop(),onDestory()方法,即广播仍还未注销,从而导致内存泄露。
但是,onPause()一定会被执行,从而保证了广播在App死亡前一定会被注销,从而防止内存泄露。

  • 自定义广播接收者BroadcastReceiver
// 继承BroadcastReceivre基类
public class mBroadcastReceiver extends BroadcastReceiver {

  // 复写onReceive()方法
  // 接收到广播后,则自动调用该方法
  @Override
  public void onReceive(Context context, Intent intent) {
   // 写入接收广播后的操作
   // 默认情况下,广播接收器运行在UI线程,因此onReceive()方法不能执行耗时操作
    }
}
  1. 屏蔽广播
  • 注册广播时将exported属性设置为false,使得非本App内部发出的此广播不被接收;
  • 在广播发送和接收时,增设相应权限permission,用于权限验证,只用具有相应权限的广播发送者发送的广播才能被该BoardcastReceiver接收;
<receiver
//此broadcastReceiver能否接收其他App的发出的广播
//默认值是由receiver中有无intent-filter决定的:如果有intent-filter,默认值为true,否则为false  
	android:exported=["true" | "false"]
//具有相应权限的广播发送者发送的广播才能被此BroadcastReceiver所接收;
    android:permission="string">
//用于指定此广播接收器将接收的广播类型
//本示例中给出的是用于接收网络状态改变时发出的广播
 <intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
    </intent-filter>
</receiver>

发送广播时指定权限

// 发送广播,第二个参数标识接收消息的广播接收器需要BROADCAST_PERMISSION_DISC权限
// 权限为null,则说明广播监听者不需要任何权限便可监听
sendBroadcast(intent,BROADCAST_PERMISSION_DISC);
  • 发送广播时指定该广播接收器所在的包名,此广播将只会发送到此包中的App内与之相匹配的有效广播接收器中。
// 发送广播时指定包名
intent.setPackage(packageName)
  • 使用封装好的LocalBroadcastManager类(本地广播发送的广播只在自身app传播)
    App应用内广播可理解为一种局部广播,广播的发送者和接收者都同属于一个App。相比于全局广播(普通广播),App应用内广播优势体现在:安全性高 & 效率高(仅根据intent-filter过滤广播可能会造成隐私数据泄露等)
    使用方式上与全局广播几乎相同,只是注册/取消注册广播接收器和发送广播时将参数的context变成了LocalBroadcastManager的单一实例
//注册应用内广播接收器
//步骤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);

Context

理解 & 作用

Android应用模型是基于组件的应用设计模式,组件的运行要有一个完整的Android工程环境,(与Java不同,不能单单靠new出来对象就能运行)Context是维持Android程序中各组件(Activity、Service等)能够正常工作的一个核心功能类。
Context:语境、上下文。提供了关于应用环境全局信息的接口。我们可以通过这个接口获取 应用程序的资源和类 以及 进行应用级别的操作。如:启动Activity,弹出对话框,启动服务,发送广播,加载资源等等。

分类

Context 继承关系
Context是一个抽象类,它的具体实现类是ContextImpl,ContextWrapper是包装类。Activity,Application,Service都是继承自ContextWrapper,其初始化的过程中都会创建一个具体的ContextImpl实例,由ContextImpl实现Context中的方法。
ContextThemeWrapper继承自ContextWrapper,相对于ContextWrapper添加了与主题相关的接口。Application与Service直接继承自ContextWrapper,Activity直接继承自ContextThemeWrapper。

这里所说的主题就是指在AndroidManifest.xml中通过android:theme为Application元素或者Activity元素指定的主题。
当然,只有Activity才需要主题,Service是不需要主题的,因为Service是没有界面的后台场景,所以Service直接继承于ContextWrapper,Application同理。

因此对于一个应用程序,Context数量 = Activity数量 + Service数量 + 1(Application数量)
在这里插入图片描述
Context 作用域
在这里插入图片描述
Appliation/Service 不推荐使用的两种情况:

  1. 如果我们用ApplicationContext去启动一个LaunchMode为standard的Activity的时候会报错android.util.AndroidRuntimeException: Calling startActivity from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?
    这是因为非Activity类型的Context并没有所谓的任务栈,所以待启动的Activity就找不到栈了。解决这个问题的方法就是为待启动的Activity指定FLAG_ACTIVITY_NEW_TASK标记位,这样启动的时候就为它创建一个新的任务栈,而此时Activity是以singleTask模式启动的。所有这种用Application启动Activity的方式不推荐使用,Service同Application。
  2. 在Application和Service中去layout inflate也是合法的,但是会使用系统默认的主题样式,如果你自定义了某些样式可能不会被使用。所以这种方式也不推荐使用。

凡是跟UI相关的,都应该使用Activity做为Context来处理;其他的一些操作,Service,Activity,Application等实例都可以,当然了,注意Context引用的持有,防止内存泄漏。
不同种类Context的区别

Context类型Activity ContextApplication(Service) Context
父类不同ContextThemeWrapper(Activity相对于Application增加了UI界面的处理,如弹出Dialog)ContextWrapper
数量不同多个(Activity Context则随Activity启动而创建)一个(Application Context随Application启动而创建)
生命周期不同与Activity相关.故对于生命周期较长的对象应引用Application的Context防止内存泄露。与Application相关,随应用程序销毁而销毁
作用域不同Activity所持用的Context作用域最广,无所不能(继承自ContextThemeWrapper,在ContextWrapper基础上增加了主题操作)不适用于UI相关的操作,如Start an Activity或Layout Inflate
获取方式不同View.getContext()/Activity.thisActivity.getApplicationContext()

内存泄露

  1. 引起内存泄露的原因
    (1)错误的单例模式
public class Singleton {
    private static Singleton instance;
    private Context mContext;

    private Singleton(Context context) {
        this.mContext = context;
    }

    public static Singleton getInstance(Context context) {
        if (instance == null) {
            instance = new Singleton(context);
        }
        return instance;
    }
}

这是一个非线程安全的单例模式,instance作为静态对象,其生命周期要长于普通的对象,其中也包含Activity,假如Activity A去getInstance获得instance对象,传入this,常驻内存的Singleton保存了你传入的Activity A对象,并一直持有,即使Activity被销毁掉,但因为它的引用还存在于一个Singleton中,就不可能被GC掉,这样就导致了内存泄漏。
(2)View持有Activity引用

public class MainActivity extends Activity {
    private static Drawable mDrawable;

    @Override
    protected void onCreate(Bundle saveInstanceState) {
        super.onCreate(saveInstanceState);
        setContentView(R.layout.activity_main);
        ImageView iv = new ImageView(this);
        mDrawable = getResources().getDrawable(R.drawable.ic_launcher);
        iv.setImageDrawable(mDrawable);
    }
}

有一个静态的Drawable对象,当ImageView设置这个Drawable时,ImageView保存了mDrawable的引用,而ImageView传入的this是MainActivity的mContext,因为被static修饰的mDrawable是常驻内存的,MainActivity是它的间接引用,MainActivity被销毁时,也不能被GC掉,所以造成内存泄漏。

  1. 正确使用Context
    一般Context造成的内存泄漏,几乎都是当Context销毁的时候,却因为被引用导致销毁失败,而Application的Context对象可以理解为随着进程存在的,所以我们总结出使用Context的正确姿势:
  • 当Application的Context能搞定的情况下,并且生命周期长的对象,优先使用Application的Context。
  • 不要让生命周期长于Activity的对象持有到Activity的引用。
  • 尽量不要在Activity中使用非静态内部类,因为非静态内部类会隐式持有外部类实例的引用,如果使用静态内部类,将外部实例引用作为弱引用持有。

Intent

Intent表示目的、意图。Android通过Intent协助应用间,或应用内部组件(Activity,Service和Broadcast Receiver)间交互与通讯。用户可以通过Intent向Android组件发出一个意图,Intent负责对这个意图的动作、附加数据等进行描述。Android根据Intent的描述找到对应的组件,将Intent传入并完成组件的调用。
Intent作用主要包括2个

指定当前组件要完成的动作

根据intent寻找目标组件的方式分成两类

  1. 隐式意图
    通过在指定需启动组件所需满足的条件
    (1)在AndroidManifest.xml清单文件中配置启动目标组件的条件
    通过 AndroidManifest.xml文件下的<组件类型>(如< Activity >< Service > < BroadcastReceiver >)标签下的< intent -filter > 声明 需 匹配的条件,声明条件含:动作(Action)、类型(Category)、数据(Data)
// 为使SecondActivity能继续响应该Intent
// 我们需在AndroidManifest.xml文件下的<Activity>标签下配置<intent -filter>的内容
<activity android:name="com.test.SecondActivity">
<intent-filter >
      <action android:name="android.intent.action.ALL_APPS"/>
          <category android:name="android.intent.category.DEFAULT"/>
                <category android:name="com.example.intent_test.MY_CATEGORY"/>
            </intent-filter>
</activity>

(2)在Activity中发起意图

// 使FirstActivity启动SecondActivity(通过按钮)
      mybutton.setOnClickListener(new OnClickListener() {
    @Override
    public void onClick(View v) {
        // 1. 实例化1个隐式Intent对象
		Intent intent = new Intent();
		// 2. 指定action参数
		intent.setAction("android.intent.action.ALL_APPS");
    	// 3. 调用Intent中的addCategory()来添加一个category
    	// 注:每个Intent中只能指定1个action,但却能指定多个category
  		intent.addCategory("com.example.intent_test.MY_CATEGORY");
    	startActivity (intent);
    }
});
  1. 显式意图
    通过明确指定组件名
    明确指定组件名的方式:调用Intent的构造方法、Intent.setComponent()、Intent.setClass()
    通过 AndroidManifest.xml文件下的<组件类型 android:name=“组件名”>
// 使FirstActivity启动SecondActivity(通过按钮)
mybutton.setOnClickListener(new OnClickListener() {

    @Override
    public void onClick(View v) {
    // 1. 实例化显式Intent & 通过构造函数接收2个参数
    // new Intent(Context packageContext, Class<?> cls) ;
    // 参数1 = packageContext:启动活动的上下文,一般为当前Activity
    // 参数2 = cls:是指定要启动的目标活动
      Intent intent = new Intent(FirstActivity.this,SecondActivity.class);
    // 通过setClassName(String className) 指明类名
   	// intent.setClassName("com.test.SecondActivity");
    
    // 2. 通过Activity类的startActivity()执行该意图操作(接收一个Intent对象)
    // 将构建好的Intent对象传入该方法就可启动目标Activity
      startActivity (intent);
    }
});

Intent 构造方法
1、Intent() 空构造函数
2、Intent(Intent o) 拷贝构造函数
3、Intent(String action) 指定action类型的构造函数
4、Intent(String action, Uri uri) 指定Action类型和Uri的构造函数,URI主要是结合程序之间的数据共享ContentProvider
5、Intent(Context packageContext, Class<?> cls) 传入组件的构造函数,也就是上文提到的
6、Intent(String action, Uri uri, Context packageContext, Class<?> cls) 前两种结合体

通常开启自定义组件使用显式意图,开启系统应用时使用隐式意图。

  1. Intent 常见使用场景
  • 启动页面(Context.startActivity() 、Activity.startActivityForResult())
    (1)启动系统Activity
显示网页
   1. Uri uri = Uri.parse("http://google.com");  
   2. Intent it = new Intent(Intent.ACTION_VIEW, uri);  
   3. startActivity(it);

显示地图
   1. Uri uri = Uri.parse("geo:38.899533,-77.036476");  
   2. Intent it = new Intent(Intent.ACTION_VIEW, uri);   
   3. startActivity(it);   

打电话
   1. //叫出拨号程序 
   2. Uri uri = Uri.parse("tel:0800000123");  
   3. Intent it = new Intent(Intent.ACTION_DIAL, uri);  
   4. startActivity(it);  
   
   1. //直接打电话出去  
   2. Uri uri = Uri.parse("tel:0800000123");  
   3. Intent it = new Intent(Intent.ACTION_CALL, uri);  
   4. startActivity(it);  
   5. //用這個,要在 AndroidManifest.xml 中,加上  
   6. //<uses-permission id="android.permission.CALL_PHONE" /> 

传送SMS/MMS
   1. //调用短信程序 
   2. Intent it = new Intent(Intent.ACTION_VIEW, uri);  
   3. it.putExtra("sms_body", "The SMS text");   
   4. it.setType("vnd.android-dir/mms-sms");  
   5. startActivity(it); 
   
   1. //传送消息 
   2. Uri uri = Uri.parse("smsto://0800000123");  
   3. Intent it = new Intent(Intent.ACTION_SENDTO, uri);  
   4. it.putExtra("sms_body", "The SMS text");  
   5. startActivity(it); 

传送 Email
   1. Uri uri = Uri.parse("mailto:xxx@abc.com");  
   2. Intent it = new Intent(Intent.ACTION_SENDTO, uri);  
   3. startActivity(it); 

(2)启动自定义Activity

Intent it = new Intent(Activity.Main.this, Activity2.class);
startActivity(it);
  • 启动服务( Context.startService() 、Context.bindService() )
	//构建启动服务的Intent对象
	Intent startIntent = new Intent(this, MyService.class);
	//调用startService()方法-传入Intent对象,以此启动服务
	startService(startIntent);
  • 启动广播( Context.sendBroadcast()、Context.sendOrderedBroadcast())
public class mBroadcastReceiver extends BroadcastReceiver {

  //接收到广播后自动调用该方法
  @Override
  public void onReceive(Context context, Intent intent) {
    //写入接收广播后的操作
    }
}

(1)监听系统广播

  • 静态注册
<receiver
  //此广播接收者类是mBroadcastReceiver
  android:name=".mBroadcastReceiver" >
  //用于接收网络状态改变时发出的广播
  <intent-filter>
      <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
  </intent-filter>
</receiver>
  • 动态注册
@Override
protected void onResume() {
    super.onResume();
    mBroadcastReceiver mBroadcastReceiver = new mBroadcastReceiver();
    IntentFilter intentFilter = new IntentFilter();
    //设置接收广播的类型
    intentFilter.addAction(android.net.conn.CONNECTIVITY_CHANGE);
    registerReceiver(mBroadcastReceiver, intentFilter);
}

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

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

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

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

传递数据

Intent可传递的数据类型有3种

  • java的8种基本数据类型(boolean byte char short int long float double)、String以及他们的数组形式;
	  // 目的:将FristActivity中的一个字符串传递到SecondActivity中,并在SecondActivity中将Intent对象中的数据(FristActivity传递过来的数据)取出

	  // 1. 数据传递
	  // a. 创建Intent对象(显示Intent)
	  Intent intent = new Intent(FirstActivity.this,SecondActivity.class);     
	 
	  // b. 通过putExtra()方法传递一个字符串到SecondActivity;
	  // putExtra()方法接收两个参数:第一个是键,第二个是值(代表真正要传递的数据)
	  intent.putExtra("string_key","I come from FirstActivity");
	  intent.putExtra("boolean_key",false);
	  // c. 启动Activity
	  startActivity(intent);
	 
	  // 2. 数据取出(在被启动的Activity中)
	  // a. 获取用于启动SecondActivit的Intent
	  Intent intent = getIntent();
	  // b. 调用getStringExtra(),传入相应的键名,就可得到传来的数据
	  // 注意数据类型 与 传入时保持一致
	  String string_data = intent.getStringExtra("string_key");
	  boolean boolean_data = intent.getBooleanExtra("boolean_key");
  • Bundle类,Bundle是一个以键值对的形式存储可传输数据的容器;
	// 1. 数据传递
	  Intent intent = new Intent(FirstActivity.this,SecondActivity.class);     
	  Bundle bundle = new Bundle();
	  bundle.putString("name", "carson");
      bundle.putInt("age", 28);
	  intent.putExtras(bundle);
	  startActivity(intent);

	  // 2. 数据取出(在被启动的Activity中)
	  Intent intent = getIntent();
	  Bundle bundle = intent.getExtras();
	  // c. 通过bundle获取数据传入相应的键名,就可得到传来的数据
	  // 注意数据类型 与 传入时保持一致
	  String nameString = bundle.getString("name");
      int age = bundle.getInt("age");
  • 实现了Serializable和Parcelable接口的对象,这些对象实现了序列化。
    Serializable
public class User implements Serializable {
    ...
}

User user = new User();
Intent intent = new Intent(MyActivity.this,OthereActivity.class);
Bundle bundle = new Bundle();
bundle.putSerializable("user", user);
intent.putExtras(bundle);

Parcelable

public class User implements Parcelable {
    ...
}

User user = new User();
Intent intent = new Intent(MyActivity.this,OthereActivity.class);
Bundle bundle = new Bundle();
bundle.putParcelable("user", user);
intent.putExtras(bundle);

Application

Application代表应用程序,属于Android的一个系统组件。
Application特点

  1. 单例模式
    即每个App运行时,系统会自动创建并实例化Application对象,且应用程序中有且仅有一个Application对象。
  2. 全局实例
    不同的组件可以获取Application且获取的是同一个Application。
  3. 与App应用程序同生共死。
    Application的生命周期等于App的生命周期,与App同生共死。

Application获取方式

  • Context环境
application = (MyApplication)getApplicationContext();		//方法1
application = (MyApplication)getApplication();	//方法2
  • 非Context环境——单例模式(饿汉式)
public class MyApplication extends Application {
    private static MyApplication instance;
}
@Override
public void onCreate() {
    super.onCreate();
    instance = new MyApplication();
}
 // 获取Application
    public static Context getMyApplication() {
        return instance;
}

Application应用场景

应用场景调用生命周期方法
初始化资源,WebView预加载,推送服务注册,第三方插件加载等onCreate()
数据共享、数据缓存(设置全局共享变量、方法)onCreate()
获取应用程序当前内存使用情况(及时释放资源,避免被系统杀死/提高应用程序性能)onTrimMemory() & onLowMemory
监听 应用程序 配置信息的改变onConfigurationChanged()
监听应用程序内 所有Activity的生命周期registerActivityLifecycleCallbacks() & unregisterActivityLifecycleCallbacks()
  • 20
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

李一恩

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值