精华内容
下载资源
问答
  • android四大组件应用
    2022-08-04 17:33:36

            Android 中最重要的是四大組件,即 Activity 、 Service 、 ContentProvider 、 BroadCast 。 這4個組件分工明確,共同构成了可重用、灵活、低耦合的 Android 系统。

            Activity 負責 UI 元素的加載、頁面之間的跳轉。代表了一個頁面單元。

            Services 負責與 UI 無關的工作,例如在後台執行耗時操作。

            ContentProvider 負責存儲與共享數據。使得數據可以在多個應用間共享。

            BroadCast 負責在各個組件、應用之間進行通信,簡化了 Android 開發中的通信問題。

    更多相关内容
  • Android四大基本组件介绍与生命周期Android四大基本组件分别是Activity,Service服务,ContentProvider内容提供者,BroadcastReceiver广播接收器。  Android四大基本组件介绍与生命周期  Android四大基本组件分别是...
  • BroadcastReceiver(广播接收器),在Android开发中,BroadcastReceiver的应用场景非常多,属于Android四大组件之一。 Android 广播分为两个角色:广播发送者、广播接收者 一、 作用 用于监听 / 接收 应用发出的广播...
  • NULL 博文链接:https://androidtoast.iteye.com/blog/1169635
  • Android 四大组件

    千次阅读 2022-04-06 18:21:37
    Android四大组件 1、Activity 1.1 初始Activity Activity 是什么? Activity有什么作用? 用户和应用程序交互的接口 摆放各种空间的容器 怎样创建Activity? 继承Activity 重写onCreate()方法 为...

    Android四大组件

    1、Activity

    1.1、 初始Activity

    1. Activity 是什么?

    2. Activity有什么作用?

      1. 用户和应用程序交互的接口
      2. 摆放各种空间的容器
    3. 怎样创建Activity?

      1. 继承Activity

      2. 重写onCreate()方法

      3. 为Activity提供布局xml文件

      4. 清单文件中配置

    • 创建OtherActivity类,并继承Activity类

      package com.tinno.createactivity;
      
      import android.app.Activity;
      import android.os.Bundle;
      
      import androidx.annotation.Nullable;
      
      /**
       * 演示创建Activity
       * 1、继承 Activity
       * 2、重写onCreate() 方法
       * 3、提供xml布局文件 需要在onCreate()方法中调用setContentView() 方法加载xml布局
       * 4、配置
       */
      public class OtherActivity extends Activity {
      
          /**
           * 表示当Activity被创建时回调的方法,由系统框架调用
           * Bundle 键为 string 的 map 集合
           * @param savedInstanceState
           */
          @Override
          protected void onCreate(@Nullable Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              setContentView(R.layout.activity_other);
          }
      }
      
      
    • 为OtherActivity类提供布局文件xml文件activity_other.xml

      <?xml version="1.0" encoding="utf-8"?>
      <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
          android:layout_width="match_parent"
          android:layout_height="match_parent">
      
          <TextView
              android:id="@+id/tv_show"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:text="@string/tv_show"
              android:textSize="25sp"
              android:textColor="#00AA00"/>
      
      </RelativeLayout>
      
    • 在配置文件中配置,使OtherActivity启动 AndroidManifest.xml

      <?xml version="1.0" encoding="utf-8"?>
      <manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.tinno.createactivity">
      
          <application
              android:allowBackup="true"
              android:icon="@mipmap/ic_launcher"
              android:label="@string/app_name"
              android:roundIcon="@mipmap/ic_launcher_round"
              android:supportsRtl="true"
              android:theme="@style/Theme.TencentClass">
              <activity
                  android:name=".MainActivity"
                  android:exported="true">
      
                  
              </activity>
      
              <!--
                   android:name=“需要配置的包名.类名 必选属性”
                   android:label="表示应用程序列表中程序图标下方的文字"
                   android:icon="表示应用程序的图标"
               -->
              <activity android:name=".OtherActivity"
                  android:exported="true">
      
                  <intent-filter>
                      <action android:name="android.intent.action.MAIN" />
      
                      <category android:name="android.intent.category.LAUNCHER" />
                  </intent-filter>
              </activity>
          </application>
      
      </manifest>
      
    • 启动服务 出现绿色文字 (我是OtherActivity

    • 两个Activity之间的跳转

      MainActivity.java ==> 使用意图进行跳转

      package com.tinno.createactivity;
      
      import androidx.appcompat.app.AppCompatActivity;
      
      import android.content.Intent;
      import android.os.Bundle;
      import android.view.View;
      
      public class MainActivity extends AppCompatActivity {
      
          @Override
          protected void onCreate(Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              setContentView(R.layout.activity_main);
          }
      
          // 点击按钮启动OtherActivity
          public void onClick(View view){
              Intent intent = new Intent(MainActivity.this,OtherActivity.class);  //通过intent意图对象描述启动的Activity
              startActivity(intent);  // 启动
          }
      }
      

      MainActivity 的布局文件 activity_main.xml

      <?xml version="1.0" encoding="utf-8"?>
      <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
          xmlns:app="http://schemas.android.com/apk/res-auto"
          xmlns:tools="http://schemas.android.com/tools"
          android:layout_width="match_parent"
          android:layout_height="match_parent"
          tools:context=".MainActivity">
      
          <Button
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:text="点击跳转"
              android:onClick="onClick"
              tools:ignore="OnClick" />
      
      </RelativeLayout>
      

      配置 AndroidManifest.xml

      <?xml version="1.0" encoding="utf-8"?>
      <manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.tinno.createactivity">
      
          <application
              android:allowBackup="true"
              android:icon="@mipmap/ic_launcher"
              android:label="@string/app_name"
              android:roundIcon="@mipmap/ic_launcher_round"
              android:supportsRtl="true"
              android:theme="@style/Theme.TencentClass">
              <activity
                  android:name=".MainActivity"
                  android:exported="true">
      
                  <intent-filter>
                      <action android:name="android.intent.action.MAIN" />
      
                      <category android:name="android.intent.category.LAUNCHER" />
                  </intent-filter>
              </activity>
      
              <!--
                   android:name=“需要配置的包名.类名 必选属性”
                   android:label="表示应用程序列表中程序图标下方的文字"
                   android:icon="表示应用程序的图标"
               -->
              <activity android:name=".OtherActivity">
      
      
              </activity>
          </application>
      
      </manifest>
      
    • 启动服务

      点击跳转 --> 显示 我是OtherActivity

    在这里插入图片描述

    在这里插入图片描述

    1.2 、Activity的生命周期

    1. 什么是生命周期?
    2. 研究Activity的生命周期有什么作用?
    3. Activity生命周期的执行顺序?
    4. 横竖屏切换时Activity生命周期的如何变化?

    1、Activiy生命周期的方法

    MethodDescriptionKillableNext
    onCreate当启动新的Activity的时候调用NoonStart()
    onStart当Activity对用户即将可见时调用NoonResume()
    or onStop()
    onResume当Activity界面可与用户交互时调用NoonPause()
    onPause当系统要启动一个其他的Activity时调用,用户保存当前数据YesonResume()
    or onStop()
    onStop该Activity已经不可见时调用YesonRestart()
    or onDestroy()
    onRestart重新启动Activity时调用(此方法是重启留在缓存中的Activity)NoonStart()
    onDestroy当Activity被finish或手机内存不足被销毁时候调用Yesnothing
    1. onCreate():表示 activity 被创建调用的方法
    2. onStart():表示activity能够被用户看到时回调的方法
    3. onResume():表示activity获取用户焦点时 能与用户交互时调用
    4. onPause():表示activity失去用户焦点时回调的方法
    5. onStop():表示activity被完全遮挡时回调的方法
    6. onRestart():表示activity处于停止状态重新被启动时回调的方法
    7. onDestroy():表示activity被销毁时回调的方法

    2、Activity生命周期执行顺序

    在这里插入图片描述

    3、横竖屏切换时Activity生命周期如何变化?

    默认情况下 Activity 会关闭并且重新启动

    1. 表示设置activity固定方向: android:screenOrientation="portrait"
    2. 表示activity横竖屏切换时不会调用生命周期函数 (4.0版本之后)

    设置 android:configChanges 属性 --> orientation、keyboardHidden、screenSize 多个属性值用 | 分隔

    <activity android:name=".OtherActivity"
                android:configChanges="orientation|keyboardHidden|screenSize">
    
    </activity>
    

    1.3、Activity之间的传值

    1. Activity之间通过Intent传值
    2. Activity之间通过Bundle传值
    3. 使用Application全局对象传值
    4. 启动Activity回传数据

    1.3.1、Activity之间通过 Intent 传值

    使用putExtra() 进行发送方的数据传递

    getStringExtra()、getIntExtra()、getCharExtra() … 等方法进行接收方的数据获取

    发送方:

    ​ 以 key - value 对的形式存储需要传递的数据

    接收方:

    ​ 获取激活的 Intent 对象,根据 key 获取传递数据

    1. 定义两个Activity类,并为连个Activity类编写布局文件:

      MainActivity.java

      package com.tinno.activitypassvalue;
      
      import androidx.appcompat.app.AppCompatActivity;
      
      import android.content.Intent;
      import android.os.Bundle;
      import android.view.View;
      
      /**
       * 演示activity之间通过 Intent 传值
       */
      public class MainActivity extends AppCompatActivity {
      
          @Override
          protected void onCreate(Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              setContentView(R.layout.activity_main);
          }
      
          // 点击按钮传递数据到指定的 activity 中
          public void send(View view){
              Intent intent = new Intent(this,ResultActivity.class);
              //putExtra(String key,value) key表示唯一性标识当前值的健 , value为具体类型
              intent.putExtra("姓名","张三");
              intent.putExtra("性别",'男');
              intent.putExtra("年龄",30);
              intent.putExtra("成绩",98.5);
              intent.putExtra("bl",true);
              startActivity(intent);
          }
      }
      

      activity_main.xml

      <?xml version="1.0" encoding="utf-8"?>
      <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
          xmlns:app="http://schemas.android.com/apk/res-auto"
          xmlns:tools="http://schemas.android.com/tools"
          android:layout_width="match_parent"
          android:layout_height="match_parent"
          android:orientation="vertical">
      
          <Button
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:layout_gravity="center"
              android:text="点击传值"
              android:onClick="send"/>
      
      </LinearLayout>
      

      ResultActivity.java

      package com.tinno.activitypassvalue;
      
      import android.app.Activity;
      import android.content.Intent;
      import android.os.Bundle;
      import android.widget.TextView;
      
      import androidx.annotation.Nullable;
      
      public class ResultActivity extends Activity {
          private TextView tv;
          @Override
          protected void onCreate(@Nullable Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              setContentView(R.layout.activity_result);
      
              tv = (TextView) findViewById(R.id.tv_show);
              //1、获取激活组件的 Intent 对象
              Intent intent = getIntent();
              //2、根据 key 获取传递的数据
              String name = intent.getStringExtra("姓名");
              int age = intent.getIntExtra("年龄",0); //getIntExtra(表示获取数据的key,如果根据key没有获取数据显示的默认值)
              double score = intent.getDoubleExtra("成绩",0.0);
              char sex = intent.getCharExtra("性别",'男');
              boolean bl = intent.getBooleanExtra("bl",false);
              //3、将数据展示到 TextView控件中
              tv.setText("name: " + name + "\n age: " + age + "\n score: " + score + "\n sex: " + sex + "\n bl: " + bl);
          }
      }
      

      activity_result.xml

      <?xml version="1.0" encoding="utf-8"?>
      <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
          android:orientation="vertical"
          android:layout_width="match_parent"
          android:layout_height="match_parent">
      
          <TextView
              android:id="@+id/tv_show"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:textSize="25sp"
              android:textColor="#AA0000"
              android:layout_gravity="center"
              android:text="这是ResultActivity的布局文件"/>
      </LinearLayout>
      
    2. 将 Activity 在 AndroidManifest.xml中进行注册

      <?xml version="1.0" encoding="utf-8"?>
      <manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.tinno.activitypassvalue">
      
          <application
              android:allowBackup="true"
              android:icon="@mipmap/ic_launcher"
              android:label="@string/app_name"
              android:roundIcon="@mipmap/ic_launcher_round"
              android:supportsRtl="true"
              android:theme="@style/Theme.TencentClass">
              <activity
                  android:name=".MainActivity"
                  android:exported="true">
                  <intent-filter>
                      <action android:name="android.intent.action.MAIN" />
      
                      <category android:name="android.intent.category.LAUNCHER" />
                  </intent-filter>
              </activity>
              <activity android:name=".ResultActivity">
      
              </activity>
          </application>
      
      </manifest>
      
    3. 运行结果 :

      点击传值 按钮 --> 数据显示

    在这里插入图片描述

    1.3.2、Activity之间通过Bundle传值

    1.3.2.1、数据传递方:
    1. 新建一个Bundle类
    2. Bundle类中 key - value 键值对的形式存储数据
    3. 创建一个 Intent 对象,将 Bundle 存入 Intent 对象
    1.3.2.2、数据接收方
    1. 获取激活的 Intent 对象
    2. 获取传递的 Bundle 对象
    3. 根据 Bundle 中的 key 值获取指定的 value 值
    1.3.2.3、步骤如 1.3.1
    1. **定义两个Activity类,并为连个Activity类编写布局文件: **

      MainActivity.java

      package com.tinno.activitypassvaluebundle;
      
      import androidx.appcompat.app.AppCompatActivity;
      
      import android.content.Intent;
      import android.os.Bundle;
      import android.view.View;
      
      /**
       * 演示采用Bundle进行传值
       */
      public class MainActivity extends AppCompatActivity {
      
          @Override
          protected void onCreate(Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              setContentView(R.layout.activity_main);
          }
      
          // 点击按钮通过 Bundler 将数据传递到目标 activity
          public void send(View view){
              //1、创建 intent 意图对象
              Intent intent = new Intent(this,ResultActivity.class);
              //2、创建 bundle 对象,用来存储需要传递的数据
              Bundle bundle = new Bundle();
              //3、将需要传递的数据存储到 Bundle 对象中
              bundle.putString("name","李四");
              bundle.putInt("age",28);
              bundle.putDouble("score",95.6);
              bundle.putChar("sex",'女');
              //4、将 bundle 对象存储到 intent 对象中
              intent.putExtras(bundle);
              //5、启动activity
              startActivity(intent);
          }
      }
      

      activity_main.xml

      <?xml version="1.0" encoding="utf-8"?>
      <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
          xmlns:app="http://schemas.android.com/apk/res-auto"
          xmlns:tools="http://schemas.android.com/tools"
          android:layout_width="match_parent"
          android:layout_height="match_parent"
          tools:context=".MainActivity">
      
          <Button
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:text="点击传值"
              android:onClick="send"/>
      </RelativeLayout>
      

      ResultActivity.java

      package com.tinno.activitypassvaluebundle;
      
      import android.app.Activity;
      import android.content.Intent;
      import android.os.Bundle;
      import android.widget.TextView;
      
      import androidx.annotation.Nullable;
      
      public class ResultActivity extends Activity {
          private TextView tv_showInfo;
      
          @Override
          protected void onCreate(@Nullable Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              setContentView(R.layout.activity_result);
      
              tv_showInfo = (TextView) findViewById(R.id.tv_showInfo);
              //1、获取激活的 Intent 对象
              Intent intent = getIntent();
              //2、获取传递的 bundle 对象
              Bundle bundle = intent.getExtras();
              //3、在 bundle 根据 key 获取具体的数据
              String name = bundle.getString("name");
              int age = bundle.getInt("age");
              double score = bundle.getDouble("score");
              char sex = bundle.getChar("sex");
              //4、将数据展示到 TextView 中
              tv_showInfo.setText("name: " + name +"\n age: " + age + "\n score: " + score +"\n sex: " + sex);
          }
      }
      

      activity_result.xml

      <?xml version="1.0" encoding="utf-8"?>
      <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
          android:orientation="vertical"
          android:layout_width="match_parent"
          android:layout_height="match_parent">
      
          <TextView
              android:id="@+id/tv_showInfo"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:textSize="32sp"
              android:textColor="#AA00AA"
              android:textStyle="bold"/>
      </LinearLayout>
      
    2. 将 Activity 在 AndroidManifest.xml中进行注册

      <?xml version="1.0" encoding="utf-8"?>
      <manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.tinno.activitypassvaluebundle">
      
          <application
              android:allowBackup="true"
              android:icon="@mipmap/ic_launcher"
              android:label="@string/app_name"
              android:roundIcon="@mipmap/ic_launcher_round"
              android:supportsRtl="true"
              android:theme="@style/Theme.TencentClass">
              <activity
                  android:name=".MainActivity"
                  android:exported="true">
                  <intent-filter>
                      <action android:name="android.intent.action.MAIN" />
      
                      <category android:name="android.intent.category.LAUNCHER" />
                  </intent-filter>
              </activity>
              <activity android:name=".ResultActivity">
      
              </activity>
          </application>
      
      </manifest>
      
    3. 运行结果 :

      点击传值 按钮 --> 数据显示
      在这里插入图片描述

    1.3.3、使用 Application 全局对象传值

    发送方

    ​ 将数据存储到 Application

    接收方

    ​ 读取 Application 中的数据

    注意: Application 需要注册和配置

    1、创建类继承MyAppliction继承Appliction,将需要存储的数据定义为application的属性
    2、在发送的activity 中发送数据
    3、接收的 activity 中,获取数据操作
    4、需要进行响应的注册(在AndroidManifest.xml文件中进行注册)

    1. 定义两个Activity类,并为两个Activity类编写布局文件:

      MainActivity.java

      package com.tinno.activitypassvalueappliaction;
      
      import androidx.appcompat.app.AppCompatActivity;
      
      import android.app.Application;
      import android.content.Intent;
      import android.os.Bundle;
      import android.view.View;
      
      /**
       * 演示 Application 传递数据
       *
       * 1、创建类继承MyAppliction继承Appliction,将需要存储的数据定义为application的属性
       * 2、在发送的activity 中发送数据
       * 3、接收的 activity 中,获取数据操作
       * 4、需要进行响应的注册(在AndroidManifest.xml文件中进行注册)
       *      <application
       *         android:name="自定义Application包名.类名"
       *        />
       */
      public class MainActivity extends AppCompatActivity {
      
          private MyApplication application;
      
          @Override
          protected void onCreate(Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              setContentView(R.layout.activity_main);
          }
      
          /**
           * 点击按钮将数据存储到Application中
           * @param view
           */
          public void click(View view){
              application = (MyApplication) getApplication();
              application.setName("小明");
              application.setAge(18);
      
              startActivity(new Intent(this,ResultActivity.class));
          }
      }
      

      activity_main.xml

      <?xml version="1.0" encoding="utf-8"?>
      <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
          xmlns:app="http://schemas.android.com/apk/res-auto"
          xmlns:tools="http://schemas.android.com/tools"
          android:layout_width="match_parent"
          android:layout_height="match_parent"
          tools:context=".MainActivity"
          android:orientation="vertical">
      
          <Button
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:text="点击传值"
              android:onClick="click"
              android:layout_gravity="center"/>
      
      </LinearLayout>
      

      ResultActivity.java

      package com.tinno.activitypassvalueappliaction;
      
      import android.app.Activity;
      import android.os.Bundle;
      import android.widget.TextView;
      
      import androidx.annotation.Nullable;
      
      public class ResultActivity extends Activity {
          private TextView tv;
          private MyApplication application;
          @Override
          protected void onCreate(@Nullable Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              setContentView(R.layout.activity_result);
      
              tv = (TextView) findViewById(R.id.tv);
      
              application = (MyApplication) getApplication();
              String name = application.getName();
              int age = application.getAge();
      
              tv.setText("name: " + name + "\n age: " + age);
          }
      }
      

      activity_result.xml

      <?xml version="1.0" encoding="utf-8"?>
      <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
          android:orientation="vertical"
          android:layout_width="match_parent"
          android:layout_height="match_parent">
      
          <TextView
              android:id="@+id/tv"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:textColor="#00AA00"
              android:textSize="32sp"
              android:text="你好"
              android:layout_gravity="center"/>
      </LinearLayout>
      
    2. 定义一个自定义类 MyApplication 继承自 Application,用来作为传值的实体类

      MyApplication

      package com.tinno.activitypassvalueappliaction;
      
      import android.app.Application;
      
      public class MyApplication extends Application {
          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;
          }
      }
      
    3. AndroidManifest.xml对目标Activity进行注册和 application 进行注册

      <?xml version="1.0" encoding="utf-8"?>
      <manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.tinno.activitypassvalueappliaction">
      
          <application
              android:name=".MyApplication"
              android:allowBackup="true"
              android:icon="@mipmap/ic_launcher"
              android:label="@string/app_name"
              android:roundIcon="@mipmap/ic_launcher_round"
              android:supportsRtl="true"
              android:theme="@style/Theme.TencentClass">
              <activity
                  android:name=".MainActivity"
                  android:exported="true">
                  <intent-filter>
                      <action android:name="android.intent.action.MAIN" />
      
                      <category android:name="android.intent.category.LAUNCHER" />
                  </intent-filter>
              </activity>
              <activity android:name=".ResultActivity">
      
              </activity>
          </application>
      
      </manifest>
      
    4. 结果 -> 点击传值 按钮跳转到 ResultActivity中,并获取数据

    在这里插入图片描述

    1.3.4、启动Activity回传数据

    在这里插入图片描述

    1. 定义两个Activity类,并为两个Activity类编写布局文件:

      MainActivity.java

      package com.tinno.activitypassforresult;
      
      import androidx.annotation.Nullable;
      import androidx.appcompat.app.AppCompatActivity;
      
      import android.app.Activity;
      import android.content.Intent;
      import android.os.Bundle;
      import android.util.Log;
      import android.view.View;
      import android.widget.EditText;
      import android.widget.TextView;
      
      /**
       * 启动Activity回传数据
       */
      public class MainActivity extends AppCompatActivity {
      
          private EditText et_num1,et_num2;
          private TextView tv_result;
          private static final int REQUEST_CODE=1;
      
          @Override
          protected void onCreate(Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              setContentView(R.layout.activity_main);
      
              // 获取两个输入框的内容和结果框的内容
              et_num1 = (EditText) findViewById(R.id.et_num1);
              et_num2 = (EditText) findViewById(R.id.et_num2);
              tv_result = (TextView) findViewById(R.id.et_res);
          }
      
          // 点击按钮发送数据到目标的 activity
          public void onClick(View view){
              Intent intent = new Intent(this,ResultActivity.class);
              String num1 = et_num1.getText().toString();
              String num2 = et_num2.getText().toString();
              intent.putExtra("num1",num1);
              intent.putExtra("num2",num2);
              startActivityForResult(intent,REQUEST_CODE);    //startActivityForResult(请求的Intent对象,大于0的整数请求码)
          }
      
          /**
           * 用来处理setResult()方法回传的数据
           * @param requestCode   本次请求的请求码
           * @param resultCode    结果码
           * @param data          回传的意图对象
           */
          @Override
          protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
              super.onActivityResult(requestCode, resultCode, data);
              if (requestCode == REQUEST_CODE && resultCode == Activity.RESULT_OK){
                  String result = data.getStringExtra("info");
                  Log.e("xx", "onActivityResult: " + result );
                  tv_result.setText(result);
              }
          }
      }
      

      activity_main.xml

      <?xml version="1.0" encoding="utf-8"?>
      <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
          xmlns:app="http://schemas.android.com/apk/res-auto"
          xmlns:tools="http://schemas.android.com/tools"
          android:layout_width="match_parent"
          android:layout_height="match_parent"
          tools:context=".MainActivity"
          android:orientation="vertical">
      
          <EditText
              android:id="@+id/et_num1"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:layout_alignParentLeft="true"
              android:layout_alignParentTop="true"
              android:textSize="20sp"
              android:ems="3"/>
      
          <TextView
              android:id="@+id/et_add"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:layout_alignBaseline="@id/et_num1"
              android:layout_alignBottom="@id/et_num1"
              android:layout_toRightOf="@id/et_num1"
              android:textSize="20sp"
              android:text="+"/>
      
          <EditText
              android:id="@+id/et_num2"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:layout_alignBaseline="@id/et_add"
              android:layout_alignBottom="@id/et_add"
              android:layout_toRightOf="@id/et_add"
              android:textSize="20sp"
              android:ems="3"/>
      
          <TextView
              android:id="@+id/et_amount"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:layout_alignBaseline="@id/et_num2"
              android:layout_alignBottom="@id/et_num2"
              android:layout_toRightOf="@id/et_num2"
              android:textSize="20sp"
              android:text="="/>
      
          <TextView
              android:id="@+id/et_res"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:layout_alignBaseline="@id/et_amount"
              android:layout_alignBottom="@id/et_amount"
              android:layout_toRightOf="@id/et_amount"
              android:textSize="20sp"
              android:text=""/>
      
          <Button
              android:id="@+id/btn"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:layout_below="@+id/et_res"
              android:layout_marginTop="22dp"
              android:layout_toRightOf="@id/et_res"
              android:onClick="onClick"
              android:text="计算结果"/>
      
      </RelativeLayout>
      

      ResultActivity.java

      package com.tinno.activitypassforresult;
      
      import android.app.Activity;
      import android.content.Intent;
      import android.os.Bundle;
      import android.util.Log;
      import android.view.View;
      import android.widget.EditText;
      import android.widget.TextView;
      
      import androidx.annotation.Nullable;
      
      public class ResultActivity extends Activity {
          private TextView tv_num1,tv_num2;
          private EditText et_result;
      
          @Override
          protected void onCreate(@Nullable Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              setContentView(R.layout.activity_result);
      
              tv_num1 = (TextView) findViewById(R.id.textView1);
              tv_num2 = (TextView) findViewById(R.id.textView3);
      
              et_result = (EditText) findViewById(R.id.editText1);
      
              Intent intent = getIntent();
              tv_num1.setText(intent.getStringExtra("num1"));
              tv_num2.setText(intent.getStringExtra("num2"));
          }
      
          // 点击按钮将数据进行回传到发送 activity的对象中
          public void send(View view){
              String result = et_result.getText().toString(); //获取结果数据
              Log.e("xx", "获取的结果为: " + result );
              Intent intent = new Intent();
              intent.putExtra("info",result);
              setResult(Activity.RESULT_OK,intent);   //setResult(请求的结果码,表示回传的数据的intent对象)
              ResultActivity.this.finish();   //关闭当前 activity
          }
      }
      

      activity_result.xml

      <?xml version="1.0" encoding="utf-8"?>
      <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
          android:layout_width="match_parent"
          android:layout_height="match_parent">
      
          <TextView
              android:id="@+id/textView1"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:layout_alignParentLeft="true"
              android:layout_alignParentTop="true"
              android:layout_marginLeft="20dp"
              android:layout_marginTop="19dp"
              android:textSize="20sp"
              android:text="*"/>
      
          <TextView
              android:id="@+id/textView2"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:layout_alignBottom="@id/textView1"
              android:layout_marginLeft="28dp"
              android:layout_toRightOf="@id/textView1"
              android:textSize="20sp"
              android:text="+"/>
      
          <TextView
              android:id="@+id/textView3"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:layout_alignBottom="@id/textView2"
              android:layout_marginLeft="28dp"
              android:layout_toRightOf="@id/textView2"
              android:textSize="20sp"
              android:text="*"/>
      
          <TextView
              android:id="@+id/textView4"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:layout_alignBottom="@id/textView3"
              android:layout_marginLeft="28dp"
              android:layout_toRightOf="@id/textView3"
              android:textSize="20sp"
              android:text="="/>
      
          <EditText
              android:id="@+id/editText1"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:layout_alignBottom="@id/textView4"
              android:layout_alignParentRight="true"
              android:layout_marginRight="32dp"
              android:ems="3"/>
      
          <Button
              android:id="@+id/button1"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:layout_alignRight="@id/editText1"
              android:layout_below="@id/editText1"
              android:layout_marginTop="30dp"
              android:onClick="send"
              android:text="回传数据"/>
      </RelativeLayout>
      
    2. Androidmanifest.xml进行注册

      <?xml version="1.0" encoding="utf-8"?>
      <manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.tinno.activitypassforresult">
      
          <application
              android:allowBackup="true"
              android:icon="@mipmap/ic_launcher"
              android:label="@string/app_name"
              android:roundIcon="@mipmap/ic_launcher_round"
              android:supportsRtl="true"
              android:theme="@style/Theme.TencentClass">
              <activity
                  android:name=".MainActivity"
                  android:exported="true">
                  <intent-filter>
                      <action android:name="android.intent.action.MAIN" />
      
                      <category android:name="android.intent.category.LAUNCHER" />
                  </intent-filter>
              </activity>
              <activity android:name=".ResultActivity">
      
              </activity>
          </application>
      
      </manifest>
      
    3. 结果 ==> 在MainActivity中输入两个数字,点击计算结果按钮 跳转到 ResultActivity ,在ResultActivity进行结果计算,点击回传数据按钮,将数据传回到 MainActivity 中

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    1.4、Task、Back Stack

    Task:

    多个Activity一起完成一项工作时,它们的集合被称作一个Task。

    Back Stack:

    一个Task的所有的Activity被放置在一个stack结构中,根据他们启动的顺序被添加。Stack不会进行重新排列,只会在打开新Activity时添加其到栈顶,或finish时从栈顶移除。所以Activity在此stack中表现为“last in,fisrt out”,因为上述特点,多个Activity在打开和关闭时,stack表现出“回退栈”这样的效果

    https://blog.csdn.net/ckq5254/article/details/79474827

    1. 定义两个Activity类,并为两个Activity类编写布局文件:

      MainActivity.java

      package com.tinno.taskdemo;
      
      import androidx.appcompat.app.AppCompatActivity;
      
      import android.content.Intent;
      import android.os.Bundle;
      import android.view.View;
      
      /**
       * 演示Task
       */
      public class MainActivity extends AppCompatActivity {
      
          @Override
          protected void onCreate(Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              setContentView(R.layout.activity_main);
          }
      
          public void click(View view){
              startActivity(new Intent(this,OtherActivity.class));
          }
      }
      

      activity_main.xml

      <?xml version="1.0" encoding="utf-8"?>
      <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
          xmlns:app="http://schemas.android.com/apk/res-auto"
          xmlns:tools="http://schemas.android.com/tools"
          android:layout_width="match_parent"
          android:layout_height="match_parent"
          tools:context=".MainActivity">
      
          <Button
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:text="点击启动OtherActivity"
              android:layout_alignParentRight="true"
              android:layout_alignParentLeft="true"
              android:onClick="click"/>
      
      </RelativeLayout>
      

      OtherActivity.java

      package com.tinno.taskdemo;
      
      import android.app.Activity;
      import android.content.Intent;
      import android.net.Uri;
      import android.os.Bundle;
      import android.provider.Settings;
      import android.view.View;
      
      import androidx.annotation.Nullable;
      
      public class OtherActivity extends Activity {
          @Override
          protected void onCreate(@Nullable Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              setContentView(R.layout.activity_other);
          }
      
          /**
           * 点击按钮打开发送短信的界面
           * @param view
           */
          public void click(View view){
              // 打开设置页面 Settings.ACTION_SETTINGS
              // 打开短信界面,发送短信 Intent.ACTION_SENDTO,Uri.parse("smsto:10000"))
              Intent intent = new Intent(Settings.ACTION_SETTINGS);
              startActivity(intent);
          }
      }
      

      activity_other.xml

      <?xml version="1.0" encoding="utf-8"?>
      <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
          android:layout_width="match_parent"
          android:layout_height="match_parent">
      
          <Button
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:text="点击启动设置页面"
              android:layout_alignParentLeft="true"
              android:layout_alignParentRight="true"
              android:onClick="click"/>
      </RelativeLayout>
      
    2. Androidmanifest.xml进行注册

      <?xml version="1.0" encoding="utf-8"?>
      <manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.tinno.taskdemo">
      
          <application
              android:allowBackup="true"
              android:icon="@mipmap/ic_launcher"
              android:label="@string/app_name"
              android:roundIcon="@mipmap/ic_launcher_round"
              android:supportsRtl="true"
              android:theme="@style/Theme.TencentClass">
              <activity
                  android:name=".MainActivity"
                  android:exported="true">
                  <intent-filter>
                      <action android:name="android.intent.action.MAIN" />
      
                      <category android:name="android.intent.category.LAUNCHER" />
                  </intent-filter>
              </activity>
              <activity android:name=".OtherActivity">
      
              </activity>
          </application>
      
      </manifest>
      
    3. 结果 点击启动OTHERACTIVITY按钮跳转到 OtherActivity中,在点击 点击启动设置页面 按钮 跳转到 系统设置页面
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述

    1.5、Activity的启动模式

    1.5.1、什么是启动模式

    Activity 启动时的策略

    **AndroidManifest.xml 中的标签的 android:lauchMode 属性设置 **

    1.5.2、启动模式有什么作用

    可以更好根据用户需求在Back Stack中管理Activity 提高用户体验

    当我们多次启动同一个Activity的时候,系统会创建多个实例,并把它们一一放入任务栈当中,当我们单击back键的时候,会发现这些Activity会一一回退。任务栈是一种“后进先出”的栈结构,每次按一下back键就会有一个Activity出栈,直到栈空为止,当栈中无任何Activity的时候,系统就会回收这个任务栈。

    1.5.3、Activity 的启动模式

    • Standard:标准模式

      标准模式,这也是系统的默认模式。每次启动一个Activity都会重新创建一个新的实例,不管这个实例是否已经存在。一个任务栈中可以有多个实例,每个实例也可以属于不同的任务栈。在这种模式下,谁启动了这个Activity,那么这个Activity就运行在启动它的那个Activity所在的栈中。比如ActivityA启动了ActivityB(B是标准模式),那么B就会进入到A所在的栈中。

      例如:栈内情况为ABCD,其中ABCD为四个Activity,A位于栈底,D位于栈顶,这个时候假设要再次启动D,如果D的启动模式为standard,那么由于D被重新创建,导致栈内的情况变为ABCDD。

      在这里插入图片描述

    • SingleTop:栈顶复用模式

      栈顶复用模式,这种模式下,如果新的Activity已经位于任务栈顶,那么此Activity不会被重新创建,同时它的onNewIntent方法会被回调,通过此方法的参数我们可以取出当前请求的信息。需要注意的是,这个Activity的onCreate,onStart不会被系统调用,因为它并没有发生改变。如果新Activity的实例已经存在但是不是位于栈顶,那么这个Activity仍然会被重新创建。

      例如:栈内情况为ABCD,其中ABCD为四个Activity,A位于栈底,D位于栈顶,这个时候假设要再次启动D,如果D的启动模式为singleTop,栈内的情况变为ABCD。

      在这里插入图片描述

    • SingleTask:栈内复用模式

      栈内复用模式,这是一种单实例模式,在这种模式下,只要Activity在一个栈中存在,那么多次启动此Activity都不会创建实例,和singleTop一样,系统也会回调其onNewIntent。具体点说,当一个具有singleTask模式的Activity请求启动后,比如Activity A,系统首先会寻找是否存在A想要的任务栈,如果不存在,就会重新创建一个任务栈,然后创建A的实例后把A放到栈中。如果存在A所需的任务栈,这时要看A是否在栈中有实例存在,如果有实例存在,那么系统就会把A调到栈顶并调用它的onNewIntent方法,如果实例不存在,就创建A的实例并把A压入栈中:

      例如:1)比如目前任务栈S1中情况为ABC,这个时候Activity D 以singleTask模式请求启动,其所需要的任务栈为S2,由于S2和D的实例均不存在,所以系统会先创建任务栈S2,然后再创建D的实例并将其入栈到S2

      ​ 2)另外一种情况,假设D所需的任务栈为S1,其他情况如上面,那么由于S1已经存在,所以系统会直接创建D的实例并将其入栈到S1

      ​ 3)如果D所需的任务栈为S1,并且当前任务栈S1的情况为ADBC,根据栈内复用的原则,此时D不会重新创建,系统会把D切换到栈顶并调用其onNewIntent方法,同时由于singleTask默认具有clearTop的效果,会导致栈内所有在D上面的Activity全部出栈,于是最终S1中的情况为AD。

      在这里插入图片描述

    • SingleInstance:单实例模式

      这是一种特殊的singleTask模式,它除了具有singleTask模式的所有特性之外,还加强了意见,那就是具有此模式的Activity只能单独地位于一个任务栈中,比如Activity A是singleInstance模式,当A启动后,系统会为它创建一个新的任务栈,然后A独自在这个新的任务栈中,由于栈内复用的特性,后续的请求均不会创建新的Activity,除非这个特性的任务栈被系统销毁了。

      回退时展示的页面为 singleInstance 模式的 Activity

      在这里插入图片描述

      回退时展示页面为非 singleInstance 模式的 Activity

      在这里插入图片描述

      其实,不管回退时当前展示页面是何种模式的Activity,每次点击返回时都是先将当前展示页面所处的任务栈中的Activity弹栈,点击到当前任务栈中Activity全部弹完了,接着点击回去弹最后面展示出来的不是属于本任务栈的Activity所对应的任务栈(很拗口,看下图)。依次一直到任务退出。
      在这里插入图片描述

    代码演示

    1. 新建两个Activity 和 对应的 xml布局文件

      MainActivity.java

      package com.tinno.activitylauchmode;
      
      import androidx.appcompat.app.AppCompatActivity;
      
      import android.content.Intent;
      import android.os.Bundle;
      import android.view.View;
      
      /**
       * 演示Activity的启动模式
       */
      public class MainActivity extends AppCompatActivity {
      
          @Override
          protected void onCreate(Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              setContentView(R.layout.activity_main);
          }
      
          public void onClick(View view){
              Intent intent = null;
              switch (view.getId()){
                  case R.id.btn01: //启动第一个按钮
                      intent = new Intent(this,MainActivity.class);
                      break;
                  case R.id.btn02: //启动第二个按钮
                      intent = new Intent(this,OtherActivity.class);
                      break;
              }
              startActivity(intent);
          }
      }
      

      activity_main.xml

      <?xml version="1.0" encoding="utf-8"?>
      <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
          xmlns:app="http://schemas.android.com/apk/res-auto"
          xmlns:tools="http://schemas.android.com/tools"
          android:layout_width="match_parent"
          android:layout_height="match_parent"
          tools:context=".MainActivity">
      
          <Button
              android:id="@+id/btn01"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:text="启动Activity01"
              android:onClick="onClick"/>
      
          <Button
              android:id="@+id/btn02"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:text="启动Activity02"
              android:onClick="onClick"
              android:layout_below="@+id/btn01"/>
      
      </RelativeLayout>
      

      OtherActivity.java

      package com.tinno.activitylauchmode;
      
      import android.app.Activity;
      import android.content.Intent;
      import android.os.Bundle;
      import android.view.View;
      
      import androidx.annotation.Nullable;
      
      public class OtherActivity extends Activity {
          @Override
          protected void onCreate(@Nullable Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              setContentView(R.layout.activity_other);
          }
      
          public void click(View view){
              Intent intent = null;
              switch (view.getId()){
                  case R.id.button01:
                      intent = new Intent(this,MainActivity.class);
                      break;
                  case R.id.button02:
                      intent = new Intent(this,OtherActivity.class);
                      break;
              }
              startActivity(intent);
          }
      }
      

      activity_other.xml

      <?xml version="1.0" encoding="utf-8"?>
      <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
          android:layout_width="match_parent"
          android:layout_height="match_parent">
      
          <Button
              android:id="@+id/button01"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:text="启动Activity01"
              android:onClick="click"/>
      
          <Button
              android:id="@+id/button02"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:text="启动Activity02"
              android:onClick="click"
              android:layout_below="@+id/button01"/>
      </RelativeLayout>
      
    2. AndroidManifest.xml中进行注册,并设置启动方式

      <?xml version="1.0" encoding="utf-8"?>
      <manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.tinno.activitylauchmode">
      
          <application
              android:allowBackup="true"
              android:icon="@mipmap/ic_launcher"
              android:label="@string/app_name"
              android:roundIcon="@mipmap/ic_launcher_round"
              android:supportsRtl="true"
              android:theme="@style/Theme.TencentClass">
              <activity
                  android:name=".MainActivity"
                  android:exported="true">
                  <intent-filter>
                      <action android:name="android.intent.action.MAIN" />
      
                      <category android:name="android.intent.category.LAUNCHER" />
                  </intent-filter>
              </activity>
              <activity android:name=".OtherActivity" android:launchMode="singleInstance">
      
              </activity>
          </application>
      
      </manifest>
      

    2、ContentProvider(内容提供者)

    2.1、内容提供者原理

    ContentProvider(内容提供者)是 Android 的四大组件之一,管理 Android 以结构化方式存放的数据,以相对安全的方式封装数据(表)并且提供简易的处理机制和统一的访问接口供其他程序调用。

    Android 的数据存储方式总共有五种,分别是:Shared Preferences、网络存储、文件存储、外储存储、SQLite。但一般这些存储都只是在单独的一个应用程序之中达到一个数据的共享,有时候我们需要操作其他应用程序的一些数据,就会用到 ContentProvider。而且 Android 为常见的一些数据提供了默认的 ContentProvider(包括音频、视频、图片和通讯录等)。

    2.2、ContentProvider 使用详解

    2.2.1、URI(Uniform Resource Identifier)

    其它应用可以通过 ContentResolver 来访问 ContentProvider 提供的数据,而 ContentResolver 通过 uri 来定位自己要访问的数据,所以我们要先了解 uri。URI(Universal Resource Identifier)统一资源定位符,如果您使用过安卓的隐式启动就会发现,在隐式启动的过程中我们也是通过 uri 来定位我们需要打开的 Activity 并且可以在 uri 中传递参数。

    URI 为系统中的每一个资源赋予一个名字,比方说通话记录。每一个 ContentProvider 都拥有一个公共的 URI,用于表示 ContentProvider 所提供的数据。URI 的格式如下:

    // 规则
    [scheme:][//host:port][path][?query]
    // 示例
    content://com.wang.provider.myprovider/tablename/id:
    
    1. 标准前缀(scheme)——content://,用来说明一个Content Provider控制这些数据;
    2. URI 的标识 (host:port)—— com.wang.provider.myprovider,用于唯一标识这个 ContentProvider,外部调用者可以根据这个标识来找到它。对于第三方应用程序,为了保证 URI 标识的唯一性,它必须是一个完整的、小写的类名。这个标识在元素的authorities属性中说明,一般是定义该 ContentProvider 的包.类的名称;
    3. 路径(path)——tablename,通俗的讲就是你要操作的数据库中表的名字,或者你也可以自己定义,记得在使用的时候保持一致就可以了;
    4. 记录ID(query)——id,如果URI中包含表示需要获取的记录的 ID,则返回该id对应的数据,如果没有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("content://com.wang.provider.myprovider/tablename")

      再来看一个例子:

      http://www.baidu.com:8080/wenku/jiatiao.html?id=123456&name=jack
      

      uri 的各个部分在安卓中都是可以通过代码获取的,下面我们就以上面这个 uri 为例来说下获取各个部分的方法:

      • getScheme(): 获取 Uri 中的 scheme 字符串部分,在这里是 http
      • getHost(): 获取 Authority 中的 Host 字符串,即 www.baidu.com
      • getPost(): 获取 Authority 中的 Port 字符串,即 8080
      • getPath(): 获取 Uri 中 path 部分,即 wenku/jiatiao.html
      • getQuery(): 获取 Uri 中的 query 部分,即 id=15&name=du

    2.2.2、MIME

    MIME 是指定某个扩展名的文件用一种应用程序来打开,就像你用浏览器查看 PDF 格式的文件,浏览器会选择合适的应用来打开一样。Android 中的工作方式跟 HTTP 类似,ContentProvider 会根据 URI 来返回 MIME 类型,ContentProvider 会返回一个包含两部分的字符串。MIME 类型一般包含两部分,如:

    text/html
    text/css
    text/xml
    application/pdf
    

    分为类型和子类型,Android 遵循类似的约定来定义MIME类型,每个内容类型的 Android MIME 类型有两种形式:多条记录(集合)和单条记录。

    • 集合记录(dir):
    vnd.android.cursor.dir/自定义 
    
    • 单条记录(item):
    vnd.android.cursor.item/自定义 
    

    vnd 表示这些类型和子类型具有非标准的、供应商特定的形式。Android中类型已经固定好了,不能更改,只能区别是集合还是单条具体记录,子类型可以按照格式自己填写。

    在使用 Intent 时,会用到 MIME,根据 Mimetype 打开符合条件的活动。

    2.2.3、UriMatcher

    Uri 代表要操作的数据,在开发过程中对数据进行获取时需要解析 Uri,Android 提供了两个用于操作 Uri 的工具类,分别为 UriMatcher 和 ContentUris 。掌握它们的基本概念和使用方法,对一个 Android 开发者来说是一项必要的技能。

    UriMatcher 类用于匹配 Uri,它的使用步骤如下:

    • 将需要匹配的Uri路径进行注册,代码如下:
    //常量UriMatcher.NO_MATCH表示不匹配任何路径的返回码
    UriMatcher  sMatcher = new UriMatcher(UriMatcher.NO_MATCH);
    //如果match()方法匹配“content://com.wang.provider.myprovider/tablename”路径,返回匹配码为1
    sMatcher.addURI("content://com.wang.provider.myprovider", " tablename ", 1);
    //如果match()方法匹配content://com.wang.provider.myprovider/tablename/11路径,返回匹配码为2
    sMatcher.addURI("com.wang.provider.myprovider", "tablename/#", 2);
    

    此处采用 addURI 注册了两个需要用到的 URI;注意,添加第二个 URI 时,路径后面的 id 采用了通配符形式 “#”,表示只要前面三个部分都匹配上了就 OK。

    • 注册完需要匹配的 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;
    }
    

    2.2.4、ContentUris

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

    • 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); 
    
    • parseId(Uri uri) 则从路径中获取 ID 部分:
    Uri uri = Uri.parse("content://cn.scu.myprovider/user/7")
    
    //获取的结果为:7
    long personid = ContentUris.parseId(uri);
    

    2.3、ContentProvider 主要方法

    ContentProvider 是一个抽象类,如果我们需要开发自己的内容提供者我们就需要继承这个类并复写其方法,需要实现的主要方法如下:

    • public boolean onCreate(): 在创建 ContentProvider 时使用
    • public Cursor query():用于查询指定 uri 的数据返回一个 Cursor
    • public Uri insert():用于向指定uri的 ContentProvider 中添加数据
    • public int delete():用于删除指定 uri 的数据
    • public int update():用户更新指定 uri 的数据
    • public String getType():用于返回指定的 Uri 中的数据 MIME 类型

    数据访问的方法 insert,delete 和 update 可能被多个线程同时调用,此时必须是线程安全的。

    如果操作的数据属于集合类型,那么 MIME 类型字符串应该以 vnd.android.cursor.dir/ 开头,

    • 要得到所有 tablename 记录: Uri 为 content://com.wang.provider.myprovider/tablename,那么返回的MIME类型字符串应该为:vnd.android.cursor.dir/table。

    如果要操作的数据属于非集合类型数据,那么 MIME 类型字符串应该以 vnd.android.cursor.item/ 开头,

    • 要得到 id 为 10 的 tablename 记录,Uri 为 content://com.wang.provider.myprovider/tablename/10,那么返回的 MIME 类型字符串为:vnd.android.cursor.item/tablename 。

    方法使用示例

    使用 ContentResolver 对 ContentProvider 中的数据进行操作的代码如下:

    ContentResolver resolver = getContentResolver();
    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); 
    // 获取tablename表中所有记录
    Cursor cursor = resolver.query(uri, null, null, null, "tablename data");
    while(cursor.moveToNext()){
       Log.i("ContentTest", "tablename_id="+ cursor.getInt(0)+ ", name="+ cursor.getString(1));
    }
    // 把id为1的记录的name字段值更改新为zhang1
    ContentValues updateValues = new ContentValues();
    updateValues.put("name", "zhang1");
    Uri updateIdUri = ContentUris.withAppendedId(uri, 2);
    resolver.update(updateIdUri, updateValues, null, null);
    // 删除id为2的记录,即字段age
    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
       }
    }
    

    实例说明

    数据源是 SQLite, 用 ContentResolver 操作 ContentProvider。

    Constant.java(储存一些常量)

    public class Constant {  
          
        public static final String TABLE_NAME = "user";  
          
        public static final String COLUMN_ID = "_id";  
        public static final String COLUMN_NAME = "name";  
           
           
        public static final String AUTOHORITY = "cn.scu.myprovider";  
        public static final int ITEM = 1;  
        public static final int ITEM_ID = 2;  
           
        public static final String CONTENT_TYPE = "vnd.android.cursor.dir/user";  
        public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/user";  
           
        public static final Uri CONTENT_URI = Uri.parse("content://" + AUTOHORITY + "/user");  
    }  
    

    DBHelper.java (操作数据库)

    public class DBHelper extends SQLiteOpenHelper {  
      
        private static final String DATABASE_NAME = "finch.db";    
        private static final int DATABASE_VERSION = 1;    
      
        public DBHelper(Context context) {  
            super(context, DATABASE_NAME, null, DATABASE_VERSION);  
        }  
      
        @Override  
        public void onCreate(SQLiteDatabase db)  throws SQLException {  
            //创建表格  
            db.execSQL("CREATE TABLE IF NOT EXISTS "+ Constant.TABLE_NAME + "("+ Constant.COLUMN_ID +" INTEGER PRIMARY KEY AUTOINCREMENT," + Constant.COLUMN_NAME +" VARCHAR NOT NULL);");  
        }  
      
        @Override  
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)  throws SQLException {  
            // 这里知识简单删除并创建表格 
            // 如果需要保留原来的数据,需要先备份再删除
            db.execSQL("DROP TABLE IF EXISTS "+ Constant.TABLE_NAME+";");  
            onCreate(db);  
        }  
    }  
    

    MyProvider.java (自定义的 ContentProvider ) 

    public class MyProvider extends ContentProvider {    
        
        DBHelper mDbHelper = null;    
        SQLiteDatabase db = null;    
        
        private static final UriMatcher mMatcher;    
        static{    
            mMatcher = new UriMatcher(UriMatcher.NO_MATCH);      // 注册 uri   
            mMatcher.addURI(Constant.AUTOHORITY,Constant.TABLE_NAME, Constant.ITEM);    
            mMatcher.addURI(Constant.AUTOHORITY, Constant.TABLE_NAME+"/#", Constant.ITEM_ID);    
        }    
        
      
        @Override    
        public String getType(Uri uri) {      // 根据匹配规则返回对应的类型   
            switch (mMatcher.match(uri)) {    
            case Constant.ITEM:    
                return Constant.CONTENT_TYPE;    
            case Constant.ITEM_ID:    
                return Constant.CONTENT_ITEM_TYPE;    
            default:    
                throw new IllegalArgumentException("Unknown URI"+uri);    
            }    
        }    
        
        @Override    
        public Uri insert(Uri uri, ContentValues values) {    
            // TODO Auto-generated method stub    
            long rowId;    
            if(mMatcher.match(uri)!=Constant.ITEM){    
                throw new IllegalArgumentException("Unknown URI"+uri);    
            }    
            rowId = db.insert(Constant.TABLE_NAME,null,values);    
            if(rowId>0){    
                Uri noteUri=ContentUris.withAppendedId(Constant.CONTENT_URI, rowId);    
                getContext().getContentResolver().notifyChange(noteUri, null);    
                return noteUri;    
            }    
        
            throw new SQLException("Failed to insert row into " + uri);    
        }    
        
        @Override    
        public boolean onCreate() {    
            // TODO Auto-generated method stub    
            mDbHelper = new DBHelper(getContext());    
            db = mDbHelper.getReadableDatabase();    
        
            return true;    
        }    
        
        @Override    
        public Cursor query(Uri uri, String[] projection, String selection,    
                String[] selectionArgs, String sortOrder) {    
            // TODO Auto-generated method stub    
            Cursor c = null;    
            switch (mMatcher.match(uri)) {    
            case Constant.ITEM:    
                c =  db.query(Constant.TABLE_NAME, projection, selection, selectionArgs, null, null, sortOrder);    
                break;    
            case Constant.ITEM_ID:    
                c = db.query(Constant.TABLE_NAME, projection,Constant.COLUMN_ID + "="+uri.getLastPathSegment(), selectionArgs, null, null, sortOrder);    
                break;    
            default:    
                throw new IllegalArgumentException("Unknown URI"+uri);    
            }    
        
            c.setNotificationUri(getContext().getContentResolver(), uri);    
            return c;    
        }    
        
        @Override    
        public int update(Uri uri, ContentValues values, String selection,    
                String[] selectionArgs) {    
            // TODO Auto-generated method stub    
            return 0;    
        }
    
        @Override
        public int delete(Uri uri, String selection, String[] selectionArgs) {
            // TODO Auto-generated method stub
            return 0;
        }    
        
    }    
    

    MainActivity.java(ContentResolver操作)

    public class MainActivity extends Activity {
        private ContentResolver mContentResolver = null; 
        private Cursor cursor = null;  
             @Override
            protected void onCreate(Bundle savedInstanceState) {
                // TODO Auto-generated method stub
                super.onCreate(savedInstanceState);
                setContentView(R.layout.activity_main);
                
                   TextView tv = (TextView) findViewById(R.id.tv);
                    
                    mContentResolver = getContentResolver();  
                    tv.setText("添加初始数据 ");
                    for (int i = 0; i < 10; i++) {  
                        ContentValues values = new ContentValues();  
                        values.put(Constant.COLUMN_NAME, "fanrunqi"+i);  
                        mContentResolver.insert(Constant.CONTENT_URI, values);  
                    } 
                    
                    tv.setText("查询数据 ");
                    cursor = mContentResolver.query(Constant.CONTENT_URI, new String[]{Constant.COLUMN_ID,Constant.COLUMN_NAME}, null, null, null);  
                    if (cursor.moveToFirst()) {
                        String s = cursor.getString(cursor.getColumnIndex(Constant.COLUMN_NAME));
                        tv.setText("第一个数据: "+s);
                    }
            }
             
    }  
    

    最后在manifest申明 :

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

    2.4、ContentResolver实现对系统数据进行操作 --> (ContentResolver: 内容解析者)

    1. ContentResolver 实现系统数据的操作(联系人【查询】、媒体库文件、通话记录、短信记录)

    实例1:使用内容解析者读取手机的通话记录方法

    1. 创建布局文件,页面包括一个按钮和一个ListView,点击按钮读取手机的通话记录,并将读取的内容放在ListView上,代码如下:

      activity_main.xml

      <?xml version="1.0" encoding="utf-8"?>
      <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
          xmlns:app="http://schemas.android.com/apk/res-auto"
          xmlns:tools="http://schemas.android.com/tools"
          android:layout_width="match_parent"
          android:layout_height="match_parent"
          tools:context=".MainActivity"
          android:orientation="vertical">
      
          <Button
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:text="查询通话记录"
              android:layout_margin="5dp"
              android:onClick="queryLog"/>
      
          <ListView
              android:id="@+id/lv"
              android:layout_width="match_parent"
              android:layout_height="match_parent"/>
      
      </LinearLayout>
      

      list_item.xml

      <?xml version="1.0" encoding="utf-8"?>
      <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
          android:layout_width="match_parent"
          android:layout_height="match_parent"
          android:gravity="center"
          android:padding="10dp"
          android:orientation="horizontal">
      
          <TextView
              android:id="@+id/tv_number"
              android:layout_width="0dp"
              android:layout_height="wrap_content"
              android:layout_weight="1"
              android:text="number"/>
      
          <TextView
              android:id="@+id/tv_date"
              android:layout_width="0dp"
              android:layout_height="wrap_content"
              android:layout_weight="1"
              android:text="date"/>
      </LinearLayout>
      
    2. 在 MainActivity.java 类中对手机通话记录进行解析,并读取手机的通话记录,代码如下:

      package com.tinno.contentprovider_calllog;
      
      import androidx.appcompat.app.AppCompatActivity;
      
      import android.content.ContentResolver;
      import android.database.Cursor;
      import android.net.ParseException;
      import android.net.Uri;
      import android.os.Bundle;
      import android.provider.CallLog;
      import android.view.View;
      import android.widget.CursorAdapter;
      import android.widget.ListView;
      import android.widget.SimpleCursorAdapter;
      
      import java.text.SimpleDateFormat;
      import java.util.Date;
      
      /**
       * 演示查询通话记录
       */
      public class MainActivity extends AppCompatActivity {
      
          private ListView lv;
      
          // 内容解析者 --> 用来从内容提供者中获取数据的.
          private ContentResolver resolver;
      
          private String CALL_LOG_URI = "content://call_log/calls";
          // 要查询的列名
          private String[] columns = new String[]{
                  CallLog.Calls._ID,CallLog.Calls.NUMBER,CallLog.Calls.DATE,CallLog.Calls.TYPE
          };
      
          @Override
          protected void onCreate(Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              setContentView(R.layout.activity_main);
      
              resolver = getContentResolver();
      
              lv = (ListView) findViewById(R.id.lv);
      
      
          }
      
          // 查询通话记录
          public void queryLog(View view){
      
              /**
               * @Uri:统一资源标识符;url:统一资源定位符
               * @projection 需要查询的表中的列
               */
              Cursor cursor = resolver.query(Uri.parse(CALL_LOG_URI), columns, null, null, null);
      
              // 将查询出来的数据放入适配器中
              SimpleCursorAdapter adapter = new SimpleCursorAdapter(this,R.layout.list_item,cursor,new String[]{CallLog.Calls.NUMBER,CallLog.Calls.DATE},new int[]{R.id.tv_number,R.id.tv_date}, CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);
      
              // 显示在ListView上
              lv.setAdapter(adapter);
      
          }
      
          /**
           * @Description: long类型转换成日期
           *
           * @param lo 毫秒数
           * @return String yyyy-MM-dd HH:mm:ss
           */
          public static String longToDate(long lo){
              Date date = new Date(lo);
              SimpleDateFormat sd = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
              return sd.format(date);
          }
      
      }
      
    3. 在 Manifest 中加入读取手机通话记录的权限

       <!-- 添加读取通话记录的权限 -->
          <uses-permission android:name="android.permission.READ_CALL_LOG"/>
      
    4. 实现的效果

    在这里插入图片描述

    注意:

    当控制台报错

         Caused by: java.lang.SecurityException: Permission Denial: opening provider com.android.providers.contacts.CallLogProvider from ProcessRecord{f87e01b 6683:com.tinno.contentprovider_calllog/u0a502} (pid=6683, uid=10502) requires android.permission.READ_CALL_LOG or android.permission.WRITE_CALL_LOG
    

    如图所示:
    在这里插入图片描述

    需要在MainActivity中加入授权权限
    加入该方法

    @Override
     public void onRequestPermissionsResult(int requestCode, @NonNull String[] >permissions, @NonNull int[] grantResults) {
           super.onRequestPermissionsResult(requestCode, permissions, grantResults);
       }
    

    并在onCreate 加入该方法

    requestPermissions(new String[]{Manifest.permission.READ_CALL_LOG},1);
    

    如图所示
    在这里插入图片描述

    实例二:使用内容解析者读取手机短信记录

    1. 创建布局文件,页面包括一个按钮和一个ListView,点击按钮读取手机的短信记录,并将读取的内容放在ListView上,代码如下:

      activity_main.xml

      <?xml version="1.0" encoding="utf-8"?>
      <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
          xmlns:app="http://schemas.android.com/apk/res-auto"
          xmlns:tools="http://schemas.android.com/tools"
          android:layout_width="match_parent"
          android:layout_height="match_parent"
          android:orientation="vertical"
          tools:context=".MainActivity">
      
          <Button
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:text="查询短信记录"
              android:onClick="querySms"/>
      
          <ListView
              android:id="@+id/lv"
              android:layout_width="match_parent"
              android:layout_height="match_parent"/>
      
      </LinearLayout>
      

      list_item.xml

      <?xml version="1.0" encoding="utf-8"?>
      <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
          android:layout_width="match_parent"
          android:layout_height="match_parent"
          android:layout_gravity="center_vertical"
          android:padding="10dp"
          android:orientation="horizontal">
      
          <TextView
              android:id="@+id/tv_number"
              android:layout_width="0dp"
              android:layout_height="wrap_content"
              android:layout_weight="1"
              android:textColor="#F00"
              android:text="fddd"/>
      
          <TextView
              android:id="@+id/tv_content"
              android:layout_width="0dp"
              android:layout_height="wrap_content"
              android:layout_weight="1"
              android:textColor="#0f0"
              android:text="fddd"/>
      
          <TextView
              android:id="@+id/tv_state"
              android:layout_width="0dp"
              android:layout_height="wrap_content"
              android:layout_weight="1"
              android:textColor="#0ff"
              android:text="fddd"/>
      
      </LinearLayout>
      
    2. 在 MainActivity.java 类中对手机短信记录进行解析,并读取手机的通话记录,代码如下:

      package com.tinno.contentprovider_sms;
      
      import androidx.appcompat.app.AppCompatActivity;
      
      import android.annotation.SuppressLint;
      import android.content.ContentResolver;
      import android.content.Context;
      import android.database.Cursor;
      import android.net.Uri;
      import android.os.Bundle;
      import android.view.View;
      import android.view.ViewGroup;
      import android.widget.CursorAdapter;
      import android.widget.ListView;
      import android.widget.SimpleCursorAdapter;
      import android.widget.TextView;
      
      /**
       * 演示读取手机的短信内容
       */
      public class MainActivity extends AppCompatActivity {
      
          private ListView lv;
          private Cursor cursor;
      
          private ContentResolver resolver;
          private String SMS_URI = "content://sms";
      
          @Override
          protected void onCreate(Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              setContentView(R.layout.activity_main);
      
              lv = (ListView) findViewById(R.id.lv);
      
              resolver = getContentResolver();
          }
      
          // 查询短信记录
          public void querySms(View view){
              cursor = resolver.query(Uri.parse(SMS_URI), null, null, null, null);
      
      
              lv.setAdapter(new MyAdapter(this,cursor,CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER));
      
              // 关闭cursor
          }
      
          @Override
          protected void onDestroy() {
              super.onDestroy();
              if(cursor != null){
                  cursor.close();
              }
          }
      
          class MyAdapter extends CursorAdapter{
      
              public MyAdapter(Context context, Cursor c, int flags) {
                  super(context, c, flags);
              }
      
              // 创建一个视图 --> 引入ListView中要展示的子视图
              @Override
              public View newView(Context context, Cursor cursor, ViewGroup viewGroup) {
                  return getLayoutInflater().inflate(R.layout.list_item,null);
              }
      
              // 绑定数据的方法
              @Override
              @SuppressLint("Range")
              public void bindView(View view, Context context, Cursor cursor) {
                  TextView tvNumber = (TextView) view.findViewById(R.id.tv_number);
                  TextView tvContent = (TextView) view.findViewById(R.id.tv_content);
                  TextView tvType = (TextView) view.findViewById(R.id.tv_state);
      
                  String number = cursor.getString(cursor.getColumnIndex("address"));
                  String content = cursor.getString(cursor.getColumnIndex("body"));
                  tvNumber.setText(number);
                  tvContent.setText(content);
      
                  int type = cursor.getInt(cursor.getColumnIndex("type"));
                  if (type == 1){
                      tvType.setText("接收");
                  }else {
                      tvType.setText("发送");
                  }
              }
          }
      }
      
    3. 在 Manifest 中加入读取手机短信记录的权限

        <!-- 添加读取设备短信权限 -->
          <uses-permission android:name="android.permission.READ_SMS"/>
      
    4. 实现的效果
      在这里插入图片描述

    2.5、自定义ContentProvider

    自定义 ContentProvider 的流程一般如下

    1. 在分享数据的 APP 中创建一个类,继承 ContentProvider

    2. 按需实现对应的方法,不需要的直接空实现

      方法说明
      onCreate()只执行一次,用于初始化 Provider
      insert()插入
      delete()删除
      update()更新
      query()查询
      getType()获得 ContentProvider 数据的 MIME 类型
    3. AndroidManifest.xml 中注册自定义的 ContentProvider

      <provider
          <!-- 全限定类名 -->
          android:name = "cn.twle.android.bean.NameContentProvider" 
          <!-- 用于匹配的 URI -->
          android:authorities = "cn.twle.android.providers.msprovider"
          <!-- 是否共享数据 -->
          android:exported="true">
      </provider>
      
    4. 使用 UriMatcher 完成 Uri 的匹配

      1. 初始化 UriMatcher 对象

        private static UriMatcher matcher = new UriMatcher (UriMatcher.NO_MATCH);
        
      2. 使用静态代码块,通过 addURI() 方法将 uri 添加到 matcher

        static {
            matcher.addURI("cn.twle.android.providers.msprovider","test","1");
        }
        

        前两个参数构成 URI, 第三个参数:匹配后返回的标识码,如果不匹配返回 -1

      3. 在下面需要匹配 Uri 的地方使用 match() 方法

        switch( matcher.match(uri)) {
            case 1:
                break;
            case 2:
                break;
            default:
                break;
        }
        

        当然还可以使用通配符,比如 test/*test/# * 代表所有字符, # 代表数字

    5. 使用 ContentUris 类为 Uri 追加 id, 或者解析 Uri 中的 id

      1. withAppendedId(uri,id) 为路径添加 id 部分

        Uri nameUri = ContentUris.withAppendedId(uri,rowId);
        
      2. parseId(uri) 解析 uri 中的 id

        long nameId = ContentUris.parseId(uri);
        
    6. 然后在另一个工程中,调用 getContentResolver() 方法获得 Resolver 对象,再调用相应的操作方法,比如插入操作

      ContentValues values = new ContentValues();
      values.put("name","测试");
      
      Uri uri = Uri.parse("cn.twle.android.providers.msprovider/test");
      
      resolver.insert(uri,values);
      

    范例

    1. 创建一个 空的 Android 项目 cn.twle.android.CustomProvider

    2. MainActivity.java 同一目录下添加一个数据库创建类 DBOpenHelper.java

      package cn.twle.android.customprovider;
      
      import android.content.Context;
      import android.database.sqlite.SQLiteDatabase;
      import android.database.sqlite.SQLiteOpenHelper;
      
      public class DBOpenHelper extends SQLiteOpenHelper {
      
          final String CREATE_SQL = "CREATE TABLE test(_id INTEGER PRIMARY KEY AUTOINCREMENT,name)";
      
          public DBOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory,
                  int version) {
              super(context, name, null, 1);
          }
      
          @Override
          public void onCreate(SQLiteDatabase db) {
              db.execSQL(CREATE_SQL);
          }
      
          @Override
          public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
              // TODO Auto-generated method stub
      
          }
      
      }
      
    3. MainActivity.java 同一目录下添加一个自定义 ContentProvider 类,实现 onCreate()getType()

      NameContentProvider.java

      package cn.twle.android.customprovider;
      
      import android.content.ContentProvider;
      import android.content.ContentUris;
      import android.content.ContentValues;
      import android.content.UriMatcher;
      import android.database.Cursor;
      import android.database.sqlite.SQLiteDatabase;
      import android.net.Uri;
      
      public class NameContentProvider extends ContentProvider {
      
          //初始化一些常量
           private static UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);        
           private DBOpenHelper dbOpenHelper;
      
          //为了方便直接使用UriMatcher,这里addURI,下面再调用Matcher进行匹配
      
           static{  
               matcher.addURI("cn.twle.android.providers.msprovider", "test", 1);
           }
      
          @Override
          public boolean onCreate() {
              dbOpenHelper = new DBOpenHelper(this.getContext(), "test.db", null, 1);
              return true;
          }
      
          @Override
          public Cursor query(Uri uri, String[] projection, String selection,
                  String[] selectionArgs, String sortOrder) {
              return null;
          }
      
          @Override
          public String getType(Uri uri) {
              return null;
          }
      
          @Override
          public Uri insert(Uri uri, ContentValues values) {
      
              switch(matcher.match(uri))
              {
              //把数据库打开放到里面是想证明uri匹配完成
              case 1:
                  SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
                  long rowId = db.insert("test", null, values);
                  if(rowId > 0)
                  {
                      //在前面已有的Uri后面追加ID
                      Uri nameUri = ContentUris.withAppendedId(uri, rowId);
                      //通知数据已经发生改变
                      getContext().getContentResolver().notifyChange(nameUri, null);
                      return nameUri;
                  }
              }
              return null;
          }
      
          @Override
          public int delete(Uri uri, String selection, String[] selectionArgs) {
              return 0;
          }
      
          @Override
          public int update(Uri uri, ContentValues values, String selection,
                  String[] selectionArgs) {
              return 0;
          }
      
      }
      
    4. 修改 AndroidManifest.xml 中为 ContentProvider 进行注册

      <provider
          android:name="cn.twle.android.customprovider.NameContentProvider"
          android:authorities="cn.twle.android.providers.msprovider"
          android:exported="true" />
      

      说明:android:authorities 就是uri 地址,android:exported="true"可以被其他app访问

    5. 修改 activity_main.xml

      这里我们就不创建新项目了,直接用一个 App 完成所有的动作

      <?xml version="1.0" encoding="utf-8" ?>
      <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
          android:layout_width="match_parent"
          android:layout_height="match_parent"
          android:gravity="center_horizontal" 
          android:orientation="vertical" >
      
          <Button 
              android:text="插入数据"
              android:id="@+id/btn_insert" 
              android:layout_width="wrap_content" 
              android:layout_height="wrap_content" />
      
      </LinearLayout>
      
    6. 修改 MainActivity.java 实现 ContentResolver 的部分,点击按钮插入一条数据

      这里我们就不创建新项目了,直接用一个 App 完成所有的动作

      package cn.twle.android.customprovider;
      
      import android.content.ContentResolver;
      import android.content.ContentValues;
      import android.net.Uri;
      import android.support.v7.app.AppCompatActivity;
      import android.os.Bundle;
      import android.view.View;
      import android.widget.Button;
      import android.widget.Toast;
      
      public class MainActivity extends AppCompatActivity {
      
          private Button btn_insert;
      
          @Override
          protected void onCreate(Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              setContentView(R.layout.activity_main);
      
              btn_insert = (Button) findViewById(R.id.btn_insert);
      
              //读取contentprovider 数据  
              final ContentResolver resolver = this.getContentResolver();
      
              btn_insert.setOnClickListener(new View.OnClickListener() {
      
                  @Override
                  public void onClick(View v) {
                       ContentValues values = new ContentValues();
                       values.put("name", "测试");
                       Uri uri = Uri.parse("content://cn.twle.android.providers.msprovider/test");
                      resolver.insert(uri, values);
                      Toast.makeText(getApplicationContext(), "数据插入成功", Toast.LENGTH_SHORT).show();
      
                  }
              });
      
          }
      }
      
    7. 运行 APP ,点击插入数据,然后打开 file exploer 将 ContentProvider 的 db 数据库取出,用图形查看工具查看即可发现插入数据

    3、BroadcastReceiver(广播接收器)

    3.1. 定义

    即 广播,是一个全局的监听器,属于Android四大组件之一

    Android 广播分为两个角色:广播发送者、广播接收者


    3.2. 作用

    监听 / 接收 应用 App 发出的广播消息,并 做出响应


    3.3. 应用场景

    • Android不同组件间的通信(含 :应用内 / 不同应用之间)
    • 多线程通信
    • Android 系统在特定情况下的通信

    如:电话呼入时、网络可用时


    3.4. 实现原理

    4.1 采用的模型

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

    因此,Android将广播的发送者 和 接收者 解耦,使得系统方便集成,更易扩展

    4.2 模型讲解

    • 模型中有3个角色:
      1. 消息订阅者(广播接收者)
      2. 消息发布者(广播发布者)
      3. 消息中心(AMS,即Activity Manager Service
    • 示意图 & 原理如下

    img

    示意图


    3.5. 使用流程

    • 使用流程如下:

    img

    示意图

    • 下面,我将一步步介绍如何使用BroadcastReceiver

    即上图中的 开发者手动完成部分

    5.1 自定义广播接收者BroadcastReceiver

    • 继承BroadcastReceivre基类
    • 必须复写抽象方法onReceive()方法
    1. 广播接收器接收到相应广播后,会自动回调 onReceive() 方法
    2. 一般情况下,onReceive方法会涉及 与 其他组件之间的交互,如发送Notification、启动Service
    3. 默认情况下,广播接收器运行在 UI 线程,因此,onReceive()方法不能执行耗时操作,否则将导致ANR
    • 代码范例
      mBroadcastReceiver.java
    // 继承BroadcastReceivre基类
    public class mBroadcastReceiver extends BroadcastReceiver {
    
      // 复写onReceive()方法
      // 接收到广播后,则自动调用该方法
      // BroadcastReceiver: 也属于UI线程.onReceiver()方法中不能进行耗时操作,否则会导致ANR异常
      @Override
      public void onReceive(Context context, Intent intent) {
       //写入接收广播后的操作
        }
    }
    

    5.2 广播接收器注册

    注册的方式分为两种:静态注册、动态注册

    5.2.1 静态注册
    • 注册方式:在AndroidManifest.xml里通过****标签声明
    • 属性说明:
    <receiver 
        android:enabled=["true" | "false"]
    //此broadcastReceiver能否接收其他App的发出的广播
    //默认值是由receiver中有无intent-filter决定的:如果有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>
    

    当此 App首次启动时,系统会自动实例化mBroadcastReceiver类,并注册到系统中。

    5.2.2 动态注册
    • 注册方式:在代码中调用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);
         }
    }
    
    特别注意
    • 动态广播最好在ActivityonResume()注册、onPause()注销。
    • 原因:
      1. 对于动态广播,有注册就必然得有注销,否则会导致内存泄露

    重复注册、重复注销也不允许

    1. Activity生命周期如下:

    img

    Activity生命周期

    Activity生命周期的方法是成对出现的:

    • onCreate() & onDestory()
    • onStart() & onStop()
    • onResume() & onPause()

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

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

    img

    示意图

    5.3 广播发送者向AMS发送广播

    5.3.1 广播的发送
    • 广播 是 用”意图(Intent)“标识
    • 定义广播的本质 = 定义广播所具备的“意图(Intent)”
    • 广播发送 = 广播发送者 将此广播的“意图(Intent)”通过sendBroadcast()方法发送出去
    5.3.2 广播的类型

    广播的类型主要分为5类:

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

    具体说明如下:
    1. 普通广播(Normal Broadcast)
    即 开发者自身定义 intent的广播(最常用)。发送广播使用如下:

    public class MainActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
        }
    
        public void send(View view){
            Intent intent = new Intent();
            //对应BroadcastReceiver中intentFilter的action
            intent.setAction("BROADCAST_ACTION");
            // 发送广播 ----> mBroadcastReceiver
            sendBroadcast(intent);
        }
    }
    
    • 若被注册了的广播接收者中注册时intentFilteraction与上述匹配,则会接收此广播(即进行回调onReceive())。如下mBroadcastReceiver则会接收上述广播
    public class mBroadcastReceiver extends BroadcastReceiver {
    
        // BroadcastReceiver: 四大组件之一
        // BroadcastReceiver: 也属于UI线程.onReceiver()方法中不能进行耗时操作,否则会导致ANR异常
        @Override
        public void onReceive(Context context, Intent intent) {
            Toast.makeText(context, "接收到了广播", Toast.LENGTH_SHORT).show();
        }
    }
    
    <receiver 
        //此广播接收者类是mBroadcastReceiver
        android:name=".mBroadcastReceiver" >
        <!-- 用于接收网络状态改变时发出的广播 -->
        <!-- 意图过滤器 -->
        <intent-filter>
            <action android:name="BROADCAST_ACTION" />
        </intent-filter>
    </receiver>
    
    • 若发送广播有相应权限,那么广播接收者也需要相应权限
    • **中止广播 **
    public class Receiver02 extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            Toast.makeText(context, "这里是有序广播接收器", Toast.LENGTH_SHORT).show();
            
            // 中止广播
            abortBroadcast();
        }
    }
    

    2. 系统广播(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
    成功安装APKIntent.ACTION_PACKAGE_ADDED
    成功删除APKIntent.ACTION_PACKAGE_REMOVED
    重启设备Intent.ACTION_REBOOT
    屏幕被关闭Intent.ACTION_SCREEN_OFF
    屏幕被打开Intent.ACTION_SCREEN_ON
    关闭系统时Intent.ACTION_SHUTDOWN
    重启设备Intent.ACTION_REBOOT

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

    3. 有序广播(Ordered Broadcast)

    • 定义
      发送出去的广播被广播接收者按照先后顺序接收

    有序是针对广播接收者而言的

    • 广播接受者接收广播的顺序规则(同时面向静态和动态注册的广播接受者)

      1. 按照Priority属性值从大-小排序;
      2. Priority属性相同者,动态注册的广播优先;
      <receiver android:name=".MyReceiver" android:exported="true">
                  <!-- 意图过滤器 -->
      
                  <intent-filter android:priority="50">
                      <action android:name="receiver"/>
                  </intent-filter>
      
              </receiver>
      	
      	<!-- priority:优先级,值越大,约优先接收广播信息 -->
              <receiver android:name=".Receiver02" android:exported="true">
      
                  <intent-filter android:priority="100">
                      <action android:name="receiver"/>
                  </intent-filter>
              </receiver>
      
    • 特点

      1. 接收广播按顺序接收
      2. 先接收的广播接收者可以对广播进行截断,即后接收的广播接收者不再接收到此广播;
      3. 先接收的广播接收者可以对广播进行修改,那么后接收的广播接收者将接收到被修改后的广播
    • 具体使用
      有序广播的使用过程与普通广播非常类似,差异仅在于广播的发送方式:

    sendOrderedBroadcast(intent,null);
    

    4. 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 - 将全局广播设置成局部广播

      1. 注册广播时将exported属性设置为false,使得非本App内部发出的此广播不被接收;
      2. 在广播发送和接收时,增设相应权限permission,用于权限验证;
      3. 发送广播时指定该广播接收器所在的包名,此广播将只会发送到此包中的App内与之相匹配的有效广播接收器中。

      通过**intent.setPackage(packageName)**指定报名

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

    注:对于LocalBroadcastManager方式发送的应用内广播,只能通过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);
    

    5. 粘性广播(Sticky Broadcast)
    由于在Android5.0 & API 21中已经失效,所以不建议使用,在这里也不作过多的总结。

    5.3.3、普通广播和有序广播的区别
    • 普通广播:异步执行 --> 广播接收器接收广播没有先后顺序;效率较高,无法被拦截
    • 有序广播:同步执行 --> 同一时刻只会有一个广播接收器能够接收到信息;根据优先级有先后顺序;可以被拦截

    3.6. 特别注意

    对于不同注册方式的广播接收器回调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;

    4、Service

    4.1、Service概念及用途

    通常service用来执行一些耗时操作,或者后台执行不提供用户交互界面的操作。其他的应用组件可以启动Service,即便用户切换

    了其他应用,启动的Service仍可在后台运行。一个组件可以与Service绑定并与之交互,甚至是跨进程通信(IPC)。例如,一个

    Service可以在后台执行网络请求、播放音乐、执行文件读写操作或者与 content provider交互等。

    4.2、Service生命周期

    为了创建Service,需要继承Service类。并重写它的回调方法,这些回调方法反应了Service的生命周期,并提供了绑定Service的

    机制。最重要的Service的生命周期回调方法如下所示:

    • onStartCommand()

      当其他组件调用startService()方法请求启动Service时,该方法被回调。一旦Service启动,它会在后台独立运行。当Service执行完以后,需调用stopSelf() 或 stopService()方法停止Service。(若您只希望bind Service,则无需调用这些方法)

    • onBind()

      当其他组件调用bindService()方法请求绑定Service时,该方法被回调。该方法返回一个IBinder接口,该接口是Service与绑定的组件进行交互的桥梁。若Service未绑定其他组件,该方法应返回null。

    • onCreate()

      当Service第一次创建时,回调该方法。该方法只被回调一次,并在onStartCommand() 或 onBind()方法被回调之前执行。若Service处于运行状态,该方法不会回调。

    • onDestroy()

      当Service被销毁时回调,在该方法中应清除一些占用的资源,如停止线程、结束绑定注册的监听器或broadcast receiver 等。该方法是Service中的最后一个回调。

    img

    如果某个组件通过调用startService()启动了Service(系统会回调onStartCommand()方法),那么直到在Service中手动调用

    stopSelf()方法、或在其他组件中手动调用stopService()方法,该Service才会停止。

    如果某个组件通过调用bindService()绑定了Service(系统会回调onBind()方法),只要该组件与Service处于绑定状态,Service就

    会一直运行,当Service不再与组件绑定时,该Service将被destroy。

    上面两条路径并不是毫不相干的:当调用startService()后,您仍可以bind该Service。比如,当播放音乐时,需调用startService()启

    动指定播放的音乐,当需要获取该音乐的播放进度时,则需要调用bindService(),在这种情况下,直到Service被unbind ,调用

    stopService() 或stopSelf()都不能停止该Service。

    当系统内存低时,系统将强制停止Service的运行;若Service绑定了正在与用户交互的activity,那么该Service将不大可能被系统

    kill。如果创建的是前台Service,那么该Service几乎不会被kill。否则,当创建了一个长时间在后台运行的Service后,系统会降低

    该Service在后台任务栈中的级别——这意味着它容易被kill,所以在开发Service时,需要使Service变得容易被restart,因为一旦

    Service被kill,再restart它需要其资源可用时才行,当然这也取决于onStartCommand()方法返回的值。

    onStartCommand()方法必须返回一个整数,这个整数是一个描述了在系统的kill事件中,系统应该如何继续这个服务的值。

    onStartCommand()有4种返回值:

    • START_STICKY

      若系统在onStartCommand()执行并返回后kill了service,那么service会被recreate并回调onStartCommand()。注意不要重新传递最后一个Intent。相反,系统回调onStartCommand()时回传一个空的Intent,除非有 pending intents传递,否则Intent将为null。该模式适合做一些类似播放音乐的操作。

    • START_NOT_STICKY

      “非粘性的”。若执行完onStartCommand()方法后,系统就kill了service,不要再重新创建service,除非系统回传了一个pending intent。这避免了在不必要的时候运行service,您的应用也可以restart任何未完成的操作。

    • START_REDELIVER_INTENT

      若系统在onStartCommand()执行并返回后kill了service,那么service会被recreate并回调onStartCommand()并将最后一个Intent回传至该方法。任何 pending intents都会被轮流传递。该模式适合做一些类似下载文件的操作。

    • START_STICKY_COMPATIBILITY

      START_STICKY的兼容版本,但不保证服务被kill后一定能重启。

    4.3、Service的注册

    在manifest文件中注册service的方式如下:

    <manifest 
       ...
        <application
            ...
            <service android:name="com.hx.servicetest.MyService" /> 
        </application>
    </manifest>
    

    除此之外,在<\service>标签中还可以配置其他属性

    • android:name —>服务全限定类名(唯一不可缺省的)
    • android:label —>服务的名字,如果此项不设置,那么默认显示的服务名则为类名
    • android:icon —>服务的图标
    • android:permission —>申明此服务的权限,这意味着只有提供了该权限的应用才能控制或连接此服务
    • android:process —>表示该服务是否运行在另外一个进程,如果设置了此项,那么将会在包名后面加上这段字符串表示另一进程的名字
    • android:enabled —>如果此项设置为 true,那么 Service 将会默认被系统启动,不设置默认此项为 false
    • android:exported —>表示该服务是否能够被其他应用程序所控制或连接,不设置默认此项为 false

    Service的启动还可以使用隐式Intent,在<\service>中配置intent-filter即可,则Service可以响应带有指定action的Intent。

    <service android:name="com.hx.servicetest.MyRemoteService" >
             <intent-filter>  
                  <action android:name="com.hx.servicetest.MyAIDLService"/>  
             </intent-filter>
    </service>
    

    4.4、Service的启动

    有了 Service 类我们如何启动他呢,有两种方法:

    • Context.startService()
    • Context.bindService()

    当然,service也可以同时在上述两种方式下运行。这涉及到Service中两个回调方法的执行:onStartCommand()(通过start方式启

    动一个service时回调的方法)、onBind()(通过bind方式启动一个service回调的方法)。

    无论通过那种方式启动service(start、bind、start&bind),任何组件(甚至其他应用的组件)都可以使用service。并通过Intent

    传递参数。当然,您也可以将Service在manifest文件中配置成私有的,不允许其他应用访问。

    startService

    其他组件调用startService()方法启动一个Service。一旦启动,Service将一直运行在后台即便启动Service的组件已被destroy。通

    常,一个被start的Service会在后台执行单独的操作,也并不给启动它的组件返回结果。比如说,一个start的Service执行在后台下

    载或上传一个文件的操作,完成之后,Service应自己停止。

    一般使用如下两种方式创建一个start Service

    • 继承Service类

      请务必在Service中开启线程来执行耗时操作,因为Service运行在主线程中。

    • 继承IntentService类

      IntentService继承于Service,若Service不需要同时处理多个请求,那么使用IntentService将是最好选择:您只需要重写

      onHandleIntent()方法,该方法接收一个回传的Intent参数,您可以在方法内进行耗时操作,因为它默认开启了一个子线程,操

      作执行完成后也无需手动调用stopSelf()方法,onHandleIntent()会自动调用该方法。

    (1)继承Service类

    如果你需要在Service中执行多线程而不是处理一个请求队列,那么需要继承Service类,分别处理每个Intent。在Service中执行操

    作时,处理每个请求都需要开启一个新线程(new Thread()),并且同一时刻一个线程只能处理一个请求

    public class MyService extends Service {
        private HandlerThread mThread;
        private Handler mHandler;
    
        @Override  
        public void onCreate() {  
            super.onCreate();  
            MainActivity.showlog("onCreate()");
            initBackThread();
        }  
    
        private void initBackThread() {
            mThread = new HandlerThread("ServiceStartArguments");
            mThread.start();
            mHandler = new Handler(mThread.getLooper()) {
                @Override
                public void handleMessage(Message msg) {
                    //模拟耗时线程操作
                    MainActivity.showlog("processing...msg.arg1="+msg.arg1);
                    try {
                        Thread.sleep(5000);
                    } catch (Exception e) {
                        Thread.currentThread().interrupt();
                    }
                    MainActivity.showlog("stopSelf...msg.arg1="+msg.arg1);
                    //当所有操作完成后,服务自己停止
                    stopSelf(msg.arg1);
                }
            };
        }
    
        @Override  
        public int onStartCommand(Intent intent, int flags, int startId) {  
            MainActivity.showlog("onStartCommand()");
    
            Message msg = mHandler.obtainMessage();
            msg.arg1 = startId;
            mHandler.sendMessage(msg);
    
            return START_STICKY;
        }  
    
        @Override  
        public void onDestroy() {  
            super.onDestroy();  
            MainActivity.showlog("onDestroy()");  
        }  
    
        @Override  
        public IBinder onBind(Intent intent) {
            MainActivity.showlog("onBind()");
            return null;
        }    
    }  
    
    

    添加Button点击事件:

        @Override  
        public void onClick(View v) {  
            switch (v.getId()) {  
            case R.id.start_service:
                showlog("click Start Service button");
                Intent it1 = new Intent(this, MyService.class);  
                startService(it1);  
                break;  
            case R.id.stop_service: 
                showlog("click Stop Service button"); 
                Intent it2 = new Intent(this, MyService.class);  
                stopService(it2);  
                break;   
            default:  
                break;  
            }  
        }  
    

    这样的话,一个简单的带有Service功能的程序就写好了,现在我们将程序运行起来,并点击一下Start Service按钮,可以看到

    LogCat的打印日志如下:

    这里写图片描述

    流程:点击->onCreate->onStartCommand->耗时事件处理(5S)->onDestroy

    那么如果我连续两次点击Start Service按钮呢?这个时候的打印日志如下:

    这里写图片描述

    流程:第一次点击->onCreate->onStartCommand->耗时事件处理(5S)
    第二次点击->onStartCommand->耗时事件处理(5S)->onDestroy
    这里的耗时处理是借助HandlerThread来实现的,多个任务在同一个线程中,第一个事件处理完成后再进行第二个事件处理,直到最后一个任务处理完毕,才会停止Service,使用的是stopSelf(int startId);关于stopSelf的使用,可以参考后面 Service的销毁 一节内容。

    点击Start Service然后再点击Stop Service按钮就可以将MyService立即停止掉了,Log如下:

    这里写图片描述

    注:多个启动Service的请求可能导致onStartCommand()多次调用,但只需调用stopSelf() 、 stopService()这两个方法之一,就可

    立即停止该服务。

    上面如果我们的耗时任务时间够长,在MyService停止之前点击”返回”,Activity被干掉了,但是我们的服务仍然在运行,可以查看

    设置–>应用–>正在运行,截图如下:

    这里写图片描述

    (2)继承IntentService类

    在大多数情况下,start Service并不会同时处理多个请求,因为处理多线程较为危险,所以继承IntentService类带创建Service是个

    不错选择。

    使用IntentService的要点如下:

    • 默认在子线程中处理回传到onStartCommand()方法中的Intent;
    • 在重写的onHandleIntent()方法中处理按时间排序的Intent队列,所以不用担心多线程(multi-threading)带来的问题。
    • 当所有请求处理完成后,自动停止service,无需手动调用stopSelf()方法;
    • 默认实现了onBind()方法,并返回null;
    • 默认实现了onStartCommand()方法,并将回传的Intent以序列的形式发送给onHandleIntent(),您只需重写该方法并处理Intent即可。

    综上所述,您只需重写onHandleIntent()方法即可,当然,还需要创建一个构造方法,示例如下:

    public class MyIntentService extends IntentService {        
    
        public MyIntentService() {
            super("MyIntentService");
        }
    
        @Override  
        public void onHandleIntent(Intent intent) {
            //模拟耗时线程操作
            MainActivity.showlog("processing...");
            try {
                Thread.sleep(5000);
            } catch (Exception e) {
                Thread.currentThread().interrupt();
            } 
        }  
    }  
    

    如果您还希望在IntentService的继承类中重写其他生命周期方法,如onCreate()、onStartCommand() 或 onDestroy(),那么请先调

    用各自的父类方法以保证子线程能够正常启动。

    比如,要实现onStartCommand()方法,需返回其父类方法:

        @Override  
        public int onStartCommand(Intent intent, int flags, int startId) {  
            MainActivity.showlog("onStartCommand()");
            return super.onStartCommand(intent, flags, startId);  
        } 
    

    注:除onHandleIntent()外,onBind()方法也无需调用其父类方法。

    bindService

    上面学习了startService()启动 Service,不过这样的话Service和Activity的关系并不大,只是Activity通知了Service一下:“你可以启

    动了。”然后Service就去忙自己的事情了。那么有没有什么办法能让它们俩的关联更多一些呢?比如说在Activity中可以指定让

    Service去执行什么任务,当然可以,只需要让Activity和Service建立关联就好了。

    bindService()方法的意思是,把这个 Service 和调用 Service 的客户类绑起来,如果这个客户类被销毁,Service 也会被销毁。用

    这个方法的一个好处是,bindService() 方法执行后 Service 会回调 onBind() 方法,你可以从这里返回一个实现了 IBind 接口的

    类,在客户端操作这个类就能和这个服务通信了,比如得到 Service 运行的状态或其他操作。如果 Service 还没有运行,使用这个

    方法启动 Service 就会 onCreate() 方法而不会调用 onStartCommand()。

    观察MyService中的代码,你会发现有一个onBind()方法我们都没有使用到,这个方法其实就是用于和Activity建立关联的,重新写

    一个MyBindService,如下所示:

    public class MyBindService extends Service {    
        private MyBinder mBinder = new MyBinder();
    
        @Override  
        public void onCreate() {  
            super.onCreate();  
            MainActivity.showlog("onCreate()");  
        }  
    
        @Override  
        public int onStartCommand(Intent intent, int flags, int startId) {  
            MainActivity.showlog("onStartCommand()");
            return super.onStartCommand(intent, flags, startId);  
        }  
    
        @Override  
        public void onDestroy() {  
            super.onDestroy();  
            MainActivity.showlog("onDestroy()");  
        }  
    
        //Service自定义方法
        public void doSomethingInService(){  
            MainActivity.showlog("doSomethingInService()");  
        }
    
        //复写onBind方法,并且返回IBinder的实现类
        @Override  
        public IBinder onBind(Intent intent) {
            MainActivity.showlog("onBind()");
            return mBinder;  
        }  
    
        @Override  
        public boolean onUnbind(Intent intent) {  
            MainActivity.showlog("onUnbind()");  
            return super.onUnbind(intent);  
        } 
    
        //内部类,扩展自Binder类
        class MyBinder extends Binder {  
            //MyBinder自定义方法
            public void doSomethingInBinder() {  
                MainActivity.showlog("doSomethingInBinder()");  
            }
            public MyBindService getService(){
                return MyBindService.this;  
            }  
        }    
    } 
    

    这里我们新增了一个MyBinder类继承自Binder类,然后在MyBinder中添加了一个doSomethingInBinder()方法用于在后台执行任

    务,而且在Service中还写了一个doSomethingInService()方法,同样可以执行后台任务,其实这里只是打印了一行日志。

    接下来再修改MainActivity中的代码,让MainActivity和MyBindService之间建立关联,代码如下所示:

    public class MainActivity extends Activity implements OnClickListener {    
        private Button startService;    
        private Button stopService; 
        private Button startIntentService;    
        private Button stopIntentService;
        private Button bindService;    
        private Button unbindService;    
        private MyBindService.MyBinder myBinder;  
        private boolean isConnected = false;
    
        private ServiceConnection connection = new ServiceConnection() {   
            @Override  
            public void onServiceDisconnected(ComponentName name) {  
                showlog("onServiceDisconnected"); 
                isConnected = false;
            }  
    
            @Override  
            public void onServiceConnected(ComponentName name, IBinder iBinder) {
                showlog("onServiceConnected"); 
                myBinder = (MyBindService.MyBinder) iBinder;  
                myBinder.doSomethingInBinder();  
                MyBindService service = myBinder.getService();  
                service.doSomethingInService();  
                isConnected = true;  
            }  
        };  
    
        @Override  
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);  
            setContentView(R.layout.activity_main);  
            startService = (Button) findViewById(R.id.start_service);  
            stopService = (Button) findViewById(R.id.stop_service);
            startIntentService = (Button) findViewById(R.id.start_intent_service);  
            stopIntentService = (Button) findViewById(R.id.stop_intent_service);
            bindService = (Button) findViewById(R.id.bind_service);  
            unbindService = (Button) findViewById(R.id.unbind_service);  
            startService.setOnClickListener(this);  
            stopService.setOnClickListener(this);  
            startIntentService.setOnClickListener(this);  
            stopIntentService.setOnClickListener(this);
            bindService.setOnClickListener(this);  
            unbindService.setOnClickListener(this);  
        }  
    
        @Override  
        public void onClick(View v) {  
            switch (v.getId()) {  
            case R.id.start_service:
                showlog("click Start Service button");
                Intent it1 = new Intent(this, MyService.class);  
                startService(it1);  
                break;  
            case R.id.stop_service: 
                showlog("click Stop Service button"); 
                Intent it2 = new Intent(this, MyService.class);  
                stopService(it2);  
                break;  
            case R.id.start_intent_service:
                showlog("click Start IntentService button");
                Intent it3 = new Intent(this, MyIntentService.class);  
                startService(it3);  
                break;  
            case R.id.stop_intent_service: 
                showlog("click Stop IntentService button"); 
                Intent it4 = new Intent(this, MyIntentService.class);  
                stopService(it4);  
                break;  
            case R.id.bind_service:  
                showlog("click Bind Service button");
                Intent it5 = new Intent(this, MyBindService.class);  
                bindService(it5, connection, BIND_AUTO_CREATE);  
                break;  
            case R.id.unbind_service:
                showlog("click Unbind Service button"); 
                if(isConnected){  
                    unbindService(connection);  
                }  
                break;  
            default:  
                break;  
            }  
        }  
    
        public static void showlog(String info) {
            System.out.print("Watson "+info+"\n");
        }
    
    }
    

    可以看到,这里我们首先创建了一个ServiceConnection的匿名类,在里面重写了onServiceConnected()方法和

    onServiceDisconnected()方法,这两个方法分别会在Activity与Service建立关联和解除关联的时候调用。在onServiceConnected()

    方法中,我们又通过向下转型得到了MyBinder的实例,有了这个实例,Activity和Service之间的关系就变得非常紧密了。现在我们

    可以在Activity中根据具体的场景来调用MyBinder中的任何public方法,即实现了Activity指挥Service干什么Service就去干什么的功

    能。

    当然,现在Activity和Service其实还没关联起来了呢,这个功能是在Bind Service按钮的点击事件里完成的。可以看到,这里我们

    仍然是构建出了一个Intent对象,然后调用bindService()方法将Activity和Service进行绑定。bindService()方法接收三个参数,第一

    个参数就是刚刚构建出的Intent对象,第二个参数是前面创建出的ServiceConnection的实例,第三个参数是一个标志位,有两个

    flag, BIND_DEBUG_UNBINDBIND_AUTO_CREATE,前者用于调试,后者默认使用,这里传入BIND_AUTO_CREATE表示

    在Activity和Service建立关联后自动创建Service,这会使得MyService中的onCreate()方法得到执行,但onStartCommand()方法不

    会执行。

    现在让我们重新运行一下程序吧,在MainActivity中点击一下Bind Service按钮,LogCat里的打印日志如下图所示:

    这里写图片描述

    由于在绑定Service的时候指定的标志位是BIND_AUTO_CREATE,说明点击Bind Service按钮的时候Service也会被创建,这时应

    该怎么销毁Service呢?其实也很简单,点击一下Unbind Service按钮,将Activity和Service的关联解除就可以了。Log如下:

    这里写图片描述

    另外需要注意,任何一个Service在整个应用程序范围内都是通用的,即MyService不仅可以和MainActivity建立关联,还可以和任

    何一个Activity建立关联,而且在建立关联时它们都可以获取到相同的MyBinder实例。

    4.5、Service的销毁

    stopService/unbindService

    在Service的启动这一部分,我们已经简单介绍了销毁Service的方法。

    startService—>stopService

    bindService—>unbindService

    以上这两种销毁的方式都很好理解。

    但有几点需要注意一下:

    (1)你应当知道在调用 bindService 绑定到Service的时候,你就应当保证在某处调用 unbindService 解除绑定(尽管 Activity 被

    finish 的时候绑定会自动解除,并且Service会自动停止);

    (2)你应当注意使用 startService 启动服务之后,一定要使用 stopService停止服务,不管你是否使用bindService;

    (3)同时使用 startService 与 bindService 要注意到,Service 的终止,需要unbindService与stopService同时调用,才能终止

    Service,不管 startService 与 bindService 的调用顺序,如果先调用 unbindService 此时服务不会自动终止,再调用 stopService

    之后服务才会停止,如果先调用 stopService 此时服务也不会终止,而再调用 unbindService 或者之前调用 bindService 的

    Context 不存在了(如Activity 被 finish 的时候)之后服务才会自动停止;

    (4)当在旋转手机屏幕的时候,当手机屏幕在“横”“竖”变换时,此时如果你的 Activity 如果会自动旋转的话,旋转其实是 Activity

    的重新创建,因此旋转之前的使用 bindService 建立的连接便会断开(Context 不存在了),对应服务的生命周期与上述相同。

    (5)unbindService 解除绑定,参数为之前创建的 ServiceConnection 接口对象。另外,多次调用 unbindService 来释放相同的

    连接会抛出异常,因此我创建了一个 boolean 变量来判断是否 unbindService 已经被调用过。

    stopSelf

    对于StartService启动的服务,Service本身还提供了另外一个方法让自己停止—>stopSelf。

    若系统正在处理多个调用onStartCommand()请求,那么在启动一个请求时,你不应当在此时停止该Service。为了避免这个问题,

    您可以调用stopSelf(int)方法,以确保请求停止的Service是最新的启动请求。这就是说,当调用stopSelf(int)方法时,传入的ID代表

    启动请求(该ID会传递至onStartCommand()),该ID与请求停止的ID一致。则如果在调用stopSelf(int)之前,Service收到一个新

    的Start请求,ID将无法匹配,Service并不会停止。具体的例子参见上面Service启动一节。

        ...
        private void initBackThread() {
            mThread = new HandlerThread("ServiceStartArguments");
            mThread.start();
            mHandler = new Handler(mThread.getLooper()) {
                @Override
                public void handleMessage(Message msg) {
                    //线程操作
                    MainActivity.showlog("processing...msg.arg1="+msg.arg1);
                    try {
                        Thread.sleep(5000);
                    } catch (Exception e) {
                        Thread.currentThread().interrupt();
                    }
                    MainActivity.showlog("stopSelf...msg.arg1="+msg.arg1);
                    stopSelf(msg.arg1);
                }
            };
        }
    
        @Override  
        public int onStartCommand(Intent intent, int flags, int startId) {  
            MainActivity.showlog("onStartCommand()");
    
            Message msg = mHandler.obtainMessage();
            msg.arg1 = startId;
            mHandler.sendMessage(msg);
    
            return START_STICKY;
        }  
        ...
    

    4.6、Service和Thread的区别

    Thread我们大家都知道,是用于开启一个子线程,在这里去执行一些耗时操作就不会阻塞主线程的运行。而Service我们最初理解

    的时候,总会觉得它是用来处理一些后台任务的,一些比较耗时的操作也可以放在这里运行,这就会让人产生混淆了。但是,如果

    我告诉你Service其实是运行在主线程里的,你还会觉得它和Thread有什么关系吗?

    在MainActivity的onCreate()方法里加入一行打印当前线程id的Log:

    showlog("MainActivity thread id is " + Thread.currentThread().getId());
    

    同时在MyService的onCreate()方法里加入打印当前线程id的Log:

    MainActivity.showlog("MyService thread id is " + Thread.currentThread().getId());
    

    现在重新运行一下程序,并点击Start Service按钮,会看到如下Log信息:

    这里写图片描述

    可以看到,它们的线程id完全是一样的,由此证实了Service确实是运行在主线程里的,也就是说如果你在Service里编写了非常耗

    时的代码,程序也会出现ANR的。

    下面我详细的来解释一下:

    • Thread:Thread 是程序执行的最小单元,它是分配CPU的基本单位。可以用 Thread 来执行一些异步的操作。
    • Service:Service 是android的一种机制,当它运行的时候如果是Local Service,那么对应的 Service 是运行在主进程的 main 线程上的。如:onCreate,onStart 这些函数在被系统调用的时候都是在主进程的 main 线程上运行的。如果是Remote Service,那么对应的 Service 则是运行在独立进程的 main 线程上。 因此请不要把 Service 理解成线程,它跟线程半毛钱的关系都没有!

    Android的后台就是指,它的运行是完全不依赖UI的。即使Activity被销毁,或者程序被关闭,只要进程还在,Service就可以继续运行。比如说一些应用程序,始终需要与服务器之间始终保持着心跳连接,就可以使用Service来实现。你可能又会问,前面不是刚刚验证过Service是运行在主线程里的么?在这里一直执行着心跳连接,难道就不会阻塞主线程的运行吗?当然会,但是我们可以在Service中再创建一个子线程,然后在这里去处理耗时逻辑就没问题了。

    既然在Service里也要创建一个子线程,那为什么不直接在Activity里创建呢?这是因为Activity很难对Thread进行控制,当Activity被销毁之后,就没有任何其它的办法可以再重新获取到之前创建的子线程的实例。而且在一个Activity中创建的子线程,另一个Activity无法对其进行操作。但是Service就不同了,所有的Activity都可以与Service进行关联,然后可以很方便地操作其中的方法,即使Activity被销毁了,之后只要重新与Service建立关联,就又能够获取到原有的Service中Binder的实例。因此,使用Service来处理后台任务,Activity就可以放心地finish,完全不需要担心无法对后台任务进行控制的情况。你也可以在 Service 里注册 BroadcastReceiver,在其他地方通过发送 broadcast 来控制它,当然这些都是 Thread 做不到的。

    一个比较标准的Service就可以写成:

    @Override  
    public int onStartCommand(Intent intent, int flags, int startId) {  
        new Thread(new Runnable() {  
            @Override  
            public void run() {  
                // 开始执行后台任务  
            }  
        }).start();  
        return super.onStartCommand(intent, flags, startId);  
    }  
    
    class MyBinder extends Binder {  
    
        public void startDownload() {  
            new Thread(new Runnable() {  
                @Override  
                public void run() {  
                    // 执行具体的下载任务  
                }  
            }).start();  
        }    
    }  
    

    4.7、创建前台Service

    Service几乎都是在后台运行的,一直以来它都是默默地做着辛苦的工作。但是Service的系统优先级还是比较低的,当系统出现内存不足情况时,就有可能会回收掉正在后台运行的Service。如果你希望Service可以一直保持运行状态,而不会由于系统内存不足的原因导致被回收,就可以考虑使用前台Service。前台Service和普通Service最大的区别就在于,它会一直有一个正在运行的图标在系统的状态栏显示,下拉状态栏后可以看到更加详细的信息,非常类似于通知的效果,只有前台Service被destroy后,状态栏显示才能消失。当然有时候你也可能不仅仅是为了防止Service被回收才使用前台Service,有些项目由于特殊的需求会要求必须使用前台Service,比如说墨迹天气,它的Service在后台更新天气数据的同时,还会在系统状态栏一直显示当前天气的信息,如下图所示:
    这里写图片描述

    来看一下如何才能创建一个前台Service吧,其实并不复杂,修改MyService中的代码,如下所示:

        @Override  
        public void onCreate() {  
            super.onCreate();
            MainActivity.showlog("onCreate()");
            initBackThread();
    
            Intent notificationIntent = new Intent(this, MainActivity.class);  
          /*第二个参数现在不再使用了
            第四个参数描述:
            FLAG_CANCEL_CURRENT:如果当前系统中已经存在一个相同的PendingIntent对象,那么就将先将已有的PendingIntent取消,然后重新生成一个PendingIntent对象。
            FLAG_NO_CREATE:如果当前系统中存在相同的PendingIntent对象,系统将不会创建该PendingIntent对象而是直接返回null。
            FLAG_ONE_SHOT:该PendingIntent只作用一次。在该PendingIntent对象通过send()方法触发过后,PendingIntent将自动调用cancel()进行销毁,那么如果你再调用send()方法的话,系统将会返回一个SendIntentException。
            FLAG_UPDATE_CURRENT:如果系统中有一个和你描述的PendingIntent对等的PendingInent,那么系统将使用该PendingIntent对象,但是会使用新的Intent来更新之前PendingIntent中的Intent对象数据,例如更新Intent中的Extras*/
            PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);  
            Notification notification = new Notification.Builder(this)
                .setSmallIcon(R.drawable.ic_launcher)
                .setWhen(System.currentTimeMillis())
                .setTicker("有通知到来") 
                .setContentTitle("这是通知的标题") 
                .setContentText("这是通知的内容")
                .setOngoing(true)
                .setContentIntent(pendingIntent)
                .build();
            /*使用startForeground,如果id为0,那么notification将不会显示*/
            startForeground(1, notification);
        }
    

    这里只是修改了MyService中onCreate()方法的代码。可以看到,我们创建了一个Notification对象,然后设置了它的布局和数据,并在这里设置了点击通知后就打开MainActivity。然后调用startForeground()方法就可以让MyService变成一个前台Service,并会将通知的图片显示出来。

    现在重新运行一下程序,并点击Start Service或Bind Service按钮,MyService就会以前台Service的模式启动了,并且在系统状态栏会弹出一个通栏图标,下拉状态栏后可以看到通知的详细内容,如下图所示
    这里写图片描述

    可以调用stopForeground(Boolean bool)来移除前台Service。该方法需传入一个boolean型变量,表示是否也一并清除状态栏上的notification。该方法并不停止Service,如果停止正在前台运行的Service,那么notification 也会一并被清除。

    最后我们看一下进程的分类:

    • 前台进程 Foreground process
      • 当前用户操作的Activity所在进程
      • 绑定了当前用户操作的Activity的Service所在进程
      • 调用了startForeground()的Service 典型场景:后台播放音乐
    • 可见进程 Visible process
      • 处于暂停状态的Activity
      • 绑定到暂停状态的Activity的Service
    • 服务进程 Service process
      • 通过startService()启动的Service
    • 后台进程 Background process
      • 处于停止状态的Activity
    • 空进程 Empty process

    4.8、远程Service的使用

    什么是远程Service

    从上面可知,Service其实是运行在主线程里的,如果直接在Service中处理一些耗时的逻辑,就会导致程序ANR。让我们来验证一下吧,修改MyService代码,在onCreate()方法中让线程睡眠60秒,如下所示:

    @Override  
        public void onCreate() {  
            super.onCreate();  
            MainActivity.showlog("onCreate()");
    
            try {  
                Thread.sleep(60000);  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            } 
        }  
    

    点击一下Start Service按钮或Bind Service按钮,程序就会阻塞住无法进行任何其它操作,过一段时间后就会弹出ANR的提示框,如下图所示:

    这里写图片描述

    现在来看看远程Service的用法,如果将MyService转换成一个远程Service,还会不会有ANR的情况呢?让我们来动手尝试一下吧。将一个普通的Service转换成远程Service其实非常简单,只需要在注册Service的时候将它的android:process属性指定成:remote就可以了,代码如下所示:

    <service android:name="com.hx.servicetest.MyService" 
             android:process=":remote"/>
    

    重新运行程序,并点击一下Start Service按钮,你会看到控制台立刻打印了onCreate()的信息,而且主界面并没有阻塞住,也不会出现ANR。大概过了一分钟后,又会看到onStartCommand()打印了出来。
    为什么将MyService转换成远程Service后就不会导致程序ANR了呢?这是由于,使用了远程Service后,MyService已经在另外一个进程当中运行了,所以只会阻塞该进程中的主线程,并不会影响到当前的应用程序。

    那既然远程Service这么好用,干脆以后我们把所有的Service都转换成远程Service吧,还省得再开启线程了。其实不然,远程Service非但不好用,甚至可以称得上是较为难用。一般情况下如果可以不使用远程Service,就尽量不要使用它。
    下面就来看一下它的弊端吧,首先将MyService的onCreate()方法中让线程睡眠的代码去除掉,然后重新运行程序,并点击一下Bind Service按钮,你会发现程序崩溃了!为什么点击Start Service按钮程序就不会崩溃,而点击Bind Service按钮就会崩溃呢?这是由于在Bind Service按钮的点击事件里面我们会让MainActivity和MyService建立关联,但是目前MyService已经是一个远程Service了,Activity和Service运行在两个不同的进程当中,这时就不能再使用传统的建立关联的方式,程序也就崩溃了。

    调用远程Service

    那么如何才能让Activity与一个远程Service建立关联呢?这就要使用AIDL来进行跨进程通信了(IPC)。
    AIDL(Android Interface Definition Language)是Android接口定义语言的意思,它可以用于让某个Service与多个应用程序组件之间进行跨进程通信,从而可以实现多个应用程序共享同一个Service的功能。
    下面我们就来一步步地看一下AIDL的用法到底是怎样的。首先需要新建一个AIDL文件,在这个文件中定义好Activity需要与Service进行通信的方法。新建MyAIDLService.aidl文件,代码如下所示:

    package com.hx.servicetest;
    
    interface MyAIDLService {  
        int plus(int a, int b);  
        String toUpperCase(String str);  
    } 
    

    点击保存之后,在gen目录下通过aapt就会生成一个对应的Java文件,如下图所示:

    这里写图片描述

    然后我们写一个MyRemoteService,在里面实现我们刚刚定义好的MyAIDLService接口,如下所示:

    public class MyRemoteService extends Service {
    
        @Override  
        public void onCreate() {  
            super.onCreate();  
            MainActivity.showlog("onCreate()");  
        }  
    
        @Override  
        public int onStartCommand(Intent intent, int flags, int startId) {  
            MainActivity.showlog("onStartCommand()");
            return super.onStartCommand(intent, flags, startId);  
        }  
    
        @Override  
        public void onDestroy() {  
            super.onDestroy();  
            MainActivity.showlog("onDestroy()");  
        }  
    
        @Override  
        public IBinder onBind(Intent intent) {
            MainActivity.showlog("onBind()");
            return mBinder;  
        }  
    
        @Override  
        public boolean onUnbind(Intent intent) {  
            MainActivity.showlog("onUnbind()");  
            return super.onUnbind(intent);  
        } 
    
        MyAIDLService.Stub mBinder = new Stub() {         
            @Override  
            public String toUpperCase(String str) throws RemoteException {  
                if (str != null) {  
                    return str.toUpperCase();  
                }  
                return null;  
            }  
    
            @Override  
            public int plus(int a, int b) throws RemoteException {  
                return a + b;  
            }  
        }; 
    } 
    

    这里先是对MyAIDLService.Stub进行了实现,重写里了toUpperCase()和plus()这两个方法。这两个方法的作用分别是将一个字符串全部转换成大写格式,以及将两个传入的整数进行相加。然后在onBind()方法中将MyAIDLService.Stub的实现返回。这里为什么可以这样写呢?因为Stub其实就是Binder的子类,所以在onBind()方法中可以直接返回Stub的实现。

    接下来修改MainActivity中的代码,如下所示:

    public class MainActivity extends Activity implements OnClickListener {  
    
        private Button startService;    
        private Button stopService; 
        private Button startIntentService;    
        private Button stopIntentService;
        private Button bindService;    
        private Button unbindService;    
        private Button bindRemoteService;
    
        private MyAIDLService myAIDLService;   
        private ServiceConnection connection = new ServiceConnection() {  
            @Override  
            public void onServiceDisconnected(ComponentName name) {}  
    
            @Override  
            public void onServiceConnected(ComponentName name, IBinder service) {  
                myAIDLService = MyAIDLService.Stub.asInterface(service);  
                try {  
                    int result = myAIDLService.plus(7, 8);  
                    String upperStr = myAIDLService.toUpperCase("hello watson");  
                    showlog("result is " + result);  
                    showlog("upperStr is " + upperStr);  
                } catch (RemoteException e) {  
                    e.printStackTrace();  
                }  
            }  
        }; 
    
        @Override  
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState); 
            setContentView(R.layout.activity_main);  
            startService = (Button) findViewById(R.id.start_service);  
            stopService = (Button) findViewById(R.id.stop_service);
            startIntentService = (Button) findViewById(R.id.start_intent_service);  
            stopIntentService = (Button) findViewById(R.id.stop_intent_service);
            bindService = (Button) findViewById(R.id.bind_service);  
            unbindService = (Button) findViewById(R.id.unbind_service); 
            bindRemoteService = (Button) findViewById(R.id.bind_remote_service);  
            startService.setOnClickListener(this);  
            stopService.setOnClickListener(this);  
            startIntentService.setOnClickListener(this);  
            stopIntentService.setOnClickListener(this);
            bindService.setOnClickListener(this);  
            unbindService.setOnClickListener(this);
            bindRemoteService.setOnClickListener(this);
        }  
    
        @Override  
        public void onClick(View v) {  
            switch (v.getId()) {  
            case R.id.start_service:
                showlog("click Start Service button");
                Intent it1 = new Intent(this, MyService.class);  
                startService(it1);  
                break;  
            case R.id.stop_service: 
                showlog("click Stop Service button"); 
                Intent it2 = new Intent(this, MyService.class);  
                stopService(it2);  
                break;  
            case R.id.start_intent_service:
                showlog("click Start IntentService button");
                Intent it3 = new Intent(this, MyIntentService.class);  
                startService(it3);  
                break;  
            case R.id.stop_intent_service: 
                showlog("click Stop IntentService button"); 
                Intent it4 = new Intent(this, MyIntentService.class);  
                stopService(it4);  
                break;  
            case R.id.bind_service:  
                showlog("click Bind Service button");
                Intent it5 = new Intent(this, MyBindService.class);  
                bindService(it5, connection, BIND_AUTO_CREATE);  
                break;  
            case R.id.unbind_service:
                showlog("click Unbind Service button"); 
                if(isConnected == true){  
                    unbindService(connection);  
                }  
                break; 
            case R.id.bind_remote_service:
                showlog("click Bind Remote Service button");
                Intent it6 = new Intent(this, MyRemoteService.class);  
                bindService(it6, connection, BIND_AUTO_CREATE);
                break;
            default:  
                break;  
            }  
        }  
    
        public static void showlog(String info) {
            System.out.print("Watson "+info+"\n");
        }
    
    }
    

    我们只是修改了ServiceConnection中的代码。可以看到,这里首先使用了MyAIDLService.Stub.asInterface()方法将传入的IBinder对象传换成了MyAIDLService对象,接下来就可以调用在MyAIDLService.aidl文件中定义的所有接口了。这里我们先是调用了plus()方法,并传入了7和8作为参数,然后又调用了toUpperCase()方法,并传入hello world字符串作为参数,最后将调用方法的返回结果打印出来。
    现在重新运行程序,并点击一下Bind Remote Service按钮,可以看到打印日志如下所示:

    这里写图片描述

    由此可见,我们确实已经成功实现跨进程通信了,在一个进程中访问到了另外一个进程中的方法。
    不过你也可以看出,目前的跨进程通信其实并没有什么实质上的作用,因为这只是在一个Activity里调用了同一个应用程序的Service里的方法。而跨进程通信的真正意义是为了让一个应用程序去访问另一个应用程序中的Service,以实现共享Service的功能。那么下面我们自然要学习一下,如何才能在其它的应用程序中调用到MyRemoteService里的方法。

    看看这里我们bind Service的方式:

    Intent it6 = new Intent(this, MyRemoteService.class);  
    bindService(it6, connection, BIND_AUTO_CREATE);
    

    这里在构建Intent的时候是使用MyRemoteService.class来指定要绑定哪一个Service的,但是在另一个应用程序中去绑定Service的时候并没有MyRemoteService这个类,这时就必须使用到隐式Intent了。现在修改AndroidManifest.xml中的代码,给MyRemoteService加上一个action,如下所示:

    <service android:name="com.hx.servicetest.MyRemoteService" 
             android:exported="true" >
             <intent-filter>  
                  <action android:name="com.hx.servicetest.MyAIDLService"/>  
             </intent-filter>
    </service>
    

    这就说明,MyRemoteService可以响应带有com.hx.servicetest.MyAIDLService这个action的Intent。
    现在重新运行一下程序,这样就把远程Service端的工作全部完成了。
    然后创建一个新的Android项目,起名为ClientTest,我们就尝试在这个程序中远程调用MyRemoteService中的方法。
    ClientTest中的Activity如果想要和MyRemoteService建立关联其实也不难,首先需要将MyAIDLService.aidl文件从ServiceTest项目中拷贝过来,注意要将原有的包路径一起拷贝过来,完成后项目的结构如下图所示:

    这里写图片描述

    接下来打开MainActivity,在其中加入和MyRemoteService建立关联的代码,如下所示:

    public class MainActivity extends Activity implements OnClickListener {  
    
        private Button bindRemoteService;    
        private Button unbindRemoteService;    
        private boolean isConnected = false;
    
        private MyAIDLService myAIDLService;   
        private ServiceConnection connection = new ServiceConnection() {  
            @Override  
            public void onServiceDisconnected(ComponentName name) {
                isConnected = false;
            }  
    
            @Override  
            public void onServiceConnected(ComponentName name, IBinder service) {
                isConnected = true;
                myAIDLService = MyAIDLService.Stub.asInterface(service);  
                try {  
                    int result = myAIDLService.plus(13, 19);  
                    String upperStr = myAIDLService.toUpperCase("hello aidl service");  
                    showlog("result is " + result);  
                    showlog("upperStr is " + upperStr);  
                } catch (RemoteException e) {  
                    e.printStackTrace();  
                }  
            }  
        }; 
    
        @Override  
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState); 
            setContentView(R.layout.activity_main);
            bindRemoteService = (Button) findViewById(R.id.bind_service);  
            unbindRemoteService = (Button) findViewById(R.id.unbind_service);  
            bindRemoteService.setOnClickListener(this);  
            unbindRemoteService.setOnClickListener(this);
        }  
    
        @Override  
        public void onClick(View v) {  
            switch (v.getId()) {  
            case R.id.bind_service:
                Intent intent = new Intent("com.hx.servicetest.MyAIDLService");  
                bindService(intent, connection, BIND_AUTO_CREATE);  
                break;  
            case R.id.unbind_service:
                if(isConnected){  
                    unbindService(connection);  
                } 
                break;
            default:  
                break;  
            }  
        }  
    
        public static void showlog(String info) {
            System.out.print("Watson "+info+"\n");
        }  
    }
    

    这部分代码大家一定非常眼熟吧?没错,这和在ServiceTest的MainActivity中的代码几乎是完全相同的,只是在让Activity和Service建立关联的时候我们使用了隐式Intent,将Intent的action指定成了com.hx.servicetest.MyAIDLService。
    在当前Activity和MyService建立关联之后,我们仍然是调用了plus()和toUpperCase()这两个方法,远程的MyService会对传入的参数进行处理并返回结果,然后将结果打印出来。
    这样的话,ClientTest中的代码也就全部完成了,现在运行一下这个项目,点击Bind Remote Service按钮,此时就会去和远程的MyRemoteService建立关联,观察LogCat中的打印信息如下所示:
    这里写图片描述

    然后点击Unbind Remote Service按钮,此时会去和远程的MyRemoteService断开关联,MyRemoteService也会自定销毁,看Log吧:

    这里写图片描述

    展开全文
  • Android开发四大组件分别是:活动(Activity):用于表现功能。服务(Service):后台运行服务,不提供界面呈现。广播接收器(BroadcastReceiver):用于接收广播。内容提供商(ContentProvider):支持在多个应用中存储和...
  • Android四大组件

    千次阅读 2022-04-20 22:36:30
    Android 四大组件分别为:Activity、Service、BroadCast Receiver、Content ProviderActivity Activity是用户操作的可视化界面,它为用户提供了一个完成操作指令的窗口。需要在Activity创建时调用setContentView()...

    Android 四大组件分别为:

    Activity、Service、BroadCast Receiver、Content Provider

    Activity

              Activity是用户操作的可视化界面,它为用户提供了一个完成操作指令的窗口。 需要在Activity创建时调用setContentView()来完成界面的显示,为用户提供交互的入口。

    1、通常一个Activity就是一个屏幕

    2、Activity之间通过Intent进行通信

    3、安卓中每一个Activity都需要在AndroidMainFest.xml文件中声明

    Service

            Service(服务)是一个可以在后台执行长时间运行操作而没有用户界面的应用组件,即使当前应用被切换到后台,又或者用户打开了另一个App,服务仍然可以保持正常运行。

    1、启动服务的Activity被销毁也不会影响服务的运行,只有当进程被关闭时,Service也会被关闭

    2、服务并不会自己开启线程,所有的代码都是默认运行在主线程中,也就是说我们需要在服务的内部手动开启子线程,并在此执行耗时任务,否则就有可能出现主线程被阻塞的情况。

    Service的两种形式

            1、started(启动状态)

                    当应用组件(如 Activity)通过调用 startService() 启动服务时,服务即处于“启动”状态。一旦启动,服务即可在后台无限期运行,即使启动服务的组件已被销毁也不受影响,除非手动调用才能停止服务, 已启动的服务通常是执行单一操作,而且不会将结果返回给调用方。

            2、bound(绑定状态)

                    当应用组件通过调用 bindService() 绑定到服务时,服务即处于“绑定”状态。绑定服务提供了一个客户端-服务器接口,允许组件与服务进行交互、发送请求、获取结果,甚至是利用进程间通信 (IPC) 跨进程执行这些操作。 仅当与另一个应用组件绑定时,绑定服务才会运行。 多个组件可以同时绑定到该服务,但全部取消绑定后,该服务即会被销毁。

    Service在AndroidMainFest.xml中的声明

    <service android:enabled=["true" | "false"]
        android:exported=["true" | "false"]
        android:icon="drawable resource"
        android:isolatedProcess=["true" | "false"]
        android:label="string resource"
        android:name="string"
        android:permission="string"
        android:process="string" >
        . . .
    </service>
    • android:exported:代表是否能被其他应用隐式调用,其默认值是由service中有无intent-filter决定的,如果有intent-filter,默认值为true,否则为false。为false的情况下,即使有intent-filter匹配,也无法打开,即无法被其他应用隐式调用。
    • android:name:对应Service类名
    • android:permission:是权限声明
    • android:process:是否需要在单独的进程中运行,当设置为android:process=”:remote”时,代表Service在单独的进程中运行。注意“:”很重要,它的意思是指要在当前进程名称前面附加上当前的包名,所以“remote”和”:remote”不是同一个意思,前者的进程名称为:remote,而后者的进程名称为:App-packageName:remote。
    • android:isolatedProcess :设置 true 意味着,服务会在一个特殊的进程下运行,这个进程与系统其他进程分开且没有自己的权限。与其通信的唯一途径是通过服务的API(bind and start)。
    • android:enabled:是否可以被系统实例化,默认为 true,因为父标签也有enable属性,所以必须两个都为默认值 true 的情况下服务才会被激活,否则不会激活。

    参考来源:关于Android Service真正的完全详解,你需要知道的一切_zejian_的博客-CSDN博客_android service      

    展开全文
  • Android四大组件详解

    千次阅读 2021-10-18 18:36:14
    Android四大组件详解 文章目录Android四大组件详解四大组件的不同点一、Activity二、Service三、Broadcast Receiver四、Content Provider四大组件相同点4大组件的注册4大组件的激活4大组件的关闭android中的任务...

    Android四大组件详解


    Android四大组件分别是 活动(Activity)、服务(Service)、广播接收器(Broadcast Receiver)和内容提供器(Content Provider)

    • 活动(activity)是所有Android应用程序的门面,凡是在应用中能看到的东西,都是放在活动中的。

    • 服务(service)比较低调,无法看到,但是会在后台默默地运行;即使用户退出了应用,服务仍然是可以继续运行的。

    • 广播接收器(Broadcast Receiver)允许应用接受来自各自各处的广播消息,如电话、短信等;也可以向外发出广播消息。

    • 内容提供器(Content Provider)为应用程序之间共享数据提供了可能,比如需要读取系统电话薄中的联系人就需要通过Content Provider来实现。

    请添加图片描述

    四大组件的不同点

    一、Activity

    1.一个Activity通常是一个单独的屏幕(窗口);

    2.Activity之间通过Intent进行通信;

    3.每一个Activity都必须要在AndroidManifest.xml配置文件中声明,否则系统将不识别也不执行Activity。

    二、Service

    1.Service用于在后台完成用户指定操作。Service分为两种:

    ​ (1)started(启动):当应用程序组件(如activity)调用startService()方法启动服务时,服务处于started状态。 (2)bound(绑定):当应用程序组件调用bindService()方法绑定服务时,服务处于bound状态。

    2.startService()和bindService()的区别

    ​ (1)started service(启动服务)是由其他组件调用startService()方法启动的,这导致服务的onStartCommand()方法被调用。当服务是started状态时,其生命周期与启动它的组件无关,并且可以在后台无限期运行,即使启动服务的组件已经被销毁。因此,服务需要在完成任务后调用stopSelf()方法停止,或者由其他组件调用stopService()方法停止。

    ​ (2)使用bindService()方法启用服务,调用者与服务绑定在了一起,调用者一旦退出,服务也就终止,就有“不求同时生,必须同时死”的特点。

    3.需要在应用程序配置文件中声明全部的service,使用标签。

    4.Service通常位于后台运行,它一般不需要与用户交互,因此Service组件没有图形用户界面。Service组件需要继承Service基类。Service组件通常用于为其他组件提供后台服务或监控其他组件的运行状态。

    三、Broadcast Receiver

    1.你的应用可以使用它对外部事件进行过滤,只对感兴趣的外部事件(如当电话呼入时,或者数据网络可用时)进行接收并做出响应。广播接收器没有用户界面。然而,它们可以启动一个activity或serice来响应它们收到的信息,或者用NotificationManager来通知用户。通知可以用很多种方式来吸引用户的注意力,例如闪动背灯、震动、播放声音等。一般来说是在状态栏上放一个持久的图标,用户可以打开它并获取消息。

    2.广播接收者的注册有两种方法,分别是程序动态注册和AndroidManifest文件中进行静态注册。

    3.动态注册广播接收器特点是当用来注册的Activity关掉后,广播也就失效了。静态注册无需担忧广播接收器是否被关闭,只要设备是开启状态,广播接收器也是打开着的。也就是说哪怕app本身未启动,该app订阅的广播在触发时也会对它起作用。

    四、Content Provider

    1.android平台提供了Content Provider使一个应用程序的指定数据集提供给其他应用程序。其他应用可以通过ContentResolver类从该内容提供者中获取或存入数据。

    2.只有需要在多个应用程序间共享数据是才需要内容提供者。例如,通讯录数据被多个应用程序使用,且必须存储在一个内容提供者中。它的好处是统一数据访问方式。

    3.ContentProvider实现数据共享。ContentProvider用于保存和获取数据,并使其对所有应用程序可见。这是不同应用程序间共享数据的唯一方式,因为android没有提供所有应用共同访问的公共存储区。

    4.开发人员不会直接使用ContentProvider类的对象,大多数是通过ContentResolver对象实现对ContentProvider的操作。

    5.ContentProvider使用URI来唯一标识其数据集,这里的URI以content://作为前缀,表示该数据由ContentProvider来管理。

    四大组件相同点

    4大组件的注册

    4大基本组件都需要注册才能使用,每个Activity、service、Content Provider都需要在AndroidManifest文件中进行配置。AndroidManifest文件中未进行声明的activity、服务以及内容提供者将不为系统所见,从而也就不可用。而broadcast receiver广播接收者的注册分静态注册(在AndroidManifest文件中进行配置)和通过代码动态创建并以调用Context.registerReceiver()的方式注册至系统。需要注意的是在AndroidManifest文件中进行配置的广播接收者会随系统的启动而一直处于活跃状态,只要接收到感兴趣的广播就会触发(即使程序未运行)。

    4大组件的激活

    内容提供者的激活:当接收到ContentResolver发出的请求后,内容提供者被激活。而其它三种组件activity、服务和广播接收器被一种叫做intent的异步消息所激活。

    4大组件的关闭

    内容提供者仅在响应ContentResolver提出请求的时候激活。而一个广播接收器仅在响应广播信息的时候激活。所以,没有必要去显式的关闭这些组件。Activity关闭:可以通过调用它的finish()方法来关闭一个activity。服务关闭:对于通过startService()方法启动的服务要调用Context.stopService()方法关闭服务,使用bindService()方法启动的服务要调用Contex.unbindService()方法关闭服务。

    android中的任务(activity栈)

    (a)任务其实就是activity的栈,它由一个或多个Activity组成,共同完成一个完整的用户体验。栈底的是启动整个任务的Activity,栈顶的是当前运行的用户可以交互的Activity,当一个activity启动另外一个的时候,新的activity就被压入栈,并成为当前运行的activity。而前一个activity仍保持在栈之中。当用户按下BACK键的时候,当前activity出栈,而前一个恢复为当前运行的activity。栈中保存的其实是对象,栈中的Activity永远不会重排,只会压入或弹出。

    (b)任务中的所有activity是作为一个整体进行移动的。整个的任务(即activity栈)可以移到前台,或退至后台。

    (c)Android系统是一个多任务(Multi-Task)的操作系统,可以在用手机听音乐的同时,也执行其他多个程序。每多执行一个应用程序,就会多耗费一些系统内存,当同时执行的程序过多,或是关闭的程序没有正确释放掉内存,系统就会觉得越来越慢,甚至不稳定。为了解决这个问题,Android引入了一个新的机制,即生命周期(Life Cycle)。

    展开全文
  • Intent是信使,负责完成Android四大组件之间的信息传递,同类、不同类的组件无法直接传递对象,一旦需要沟通只能通过Intent(不建议通过静态变量或静态方法传递数据,容易造成数据异常、内存泄露等问题)。...
  • Android 四大组件面试

    千次阅读 2022-04-15 14:40:40
    Android 四大组件面试 前言 仅用于知识点简介,详细描述建议百度,主要是一些个人的理解总结,可以在面试时来回答,所以并不是很详细,只是总结~ 后续应该会不断更新 Activity、Service、BroatcastReceiver、...
  • Android四大组件是什么

    千次阅读 2021-05-26 07:49:03
    Android四大组件是:活动、服务、广播接收器、内容提供商。它们的英文名称是ACTIVITY、SERVICE、BroadcastReceiver、Content Provider。四个组件分别起到不同的作用,相互配合才能确保安卓系统的正常运行,因此是...
  • Android四大组件(activity task stack)

    千次阅读 2022-05-06 18:55:23
    服务也是Android四大组件之一。被称为是没有界面的activity;它拥有自己的生命周期,可以不随着activity的销毁而销毁。 2)创建方式 需要创建继承自Service类的自定义类,并在配置文件中使用标签完成注册,该类回复...
  • Android四大组件

    千次阅读 2022-07-24 22:19:40
    ContentProvider是Android四大应用组件之一,当前应用使用ContentProvider将数据库表数据操作暴露给其它应用访问,其它的应用需要使用ContentResolver来调用ContentProvider的方法,它们之间的调用是通过Uri来进行...
  • 你的应用可以使用它对外部事件进行过滤只对感兴趣的外部事件(如当电话呼入时,或者数据网络可用时)进行接收并做出响应。广播接收器没有用户界面。然而,它们可以启动一个activity或serice 来响应它们收到的信息,...
  • Android四大组件相关知识点

    千次阅读 2022-04-18 16:23:20
    activity是android四大组件之一,它提供屏幕进行交互。每个activity都会获得一个用于绘制其他界面的窗口。 一个应用通常由一个或多个activity组成,一般会指定一个activity作为主界面,activity有自己的什么周期,...
  • Android四大组件之Broadcast Receiver 作者:白璐 日期:2020/2/23 文章目录Android四大组件之Broadcast Receiver概述广播接收器(Broadcast Receiver)Broadcast Receiver简介Broadcast Receiver的注册一. 静态注册...
  • Android 四大组件之Activity

    千次阅读 2022-02-14 13:49:40
    Android 四大组件之Activity ​ Android四大组件分别是:活动(activity),用于表现功能;服务(service),后台运行服务,不提供界面呈现;广播接受者(Broadcast Receive),勇于接收广播;内容提供者...
  • Android四大组件之Service

    千次阅读 2021-11-11 10:22:55
    Android四大组件之一:Service是一个可以在后台执行长时间运行操作而不提供用户界面的应用组件; 服务是Android中实现程序后台的解决方案,不依赖任何用户界面,即使程序被切换到后台,或者用户打开了另外一个应用...
  • Android 四大组件基本理论讲解

    千次阅读 2020-08-27 10:37:41
    Activity是一个Android应用程序组件(也称为Android四大组件之一),它提供了一个屏幕,用户可以通过该屏幕进行交互以执行某些操作,例如拨打电话,拍照,发送电子邮件或查看地图。每个活动都有一个窗口,用于绘制其...
  • Android四大核心组件 Activity Activity是Android应用程序核心组件中最基本的一种,是用户与应用程序交互的窗口。类似于一个网站中的网页,可以互相跳转,并且可以有返回值(相当于转发是添加参数或者返回值)。 当...
  • Android四大组件之Activity

    千次阅读 2021-11-05 22:26:04
    Android 开发的四大组件分别是:活动(Activity),用于表现功能;服务(Service),后台运行服务,不提供界面呈现;广播接受者(Broadcast Receive),用于接收广播;内容提供者(Content Provider),支持多个应用...
  • Android面试题:四大组件

    千次阅读 2022-04-26 09:23:19
    1、Activity 与 Fragment 之间常见的几种通信方式 答:1.使用Bundle:在activity中建一个bundle,把要传的值存入bundle,然后通过fragment的setArguments...2.谈谈 Android 中几种 LaunchMode 的特点和应用场景?
  • Android四大组件之一服务(Service)

    万次阅读 2022-06-11 14:16:27
    1. Android四大组件之一,存在自己的生命周期2. 一种可以在后台执行长时间运行操作而没有用户界面的应用组件,需要在AndroidManifest.xml配置相关信息3. Service是Android中实现程序后台运行的解决方案,它非常...
  • Android 四大组件有哪些 功能是什么

    千次阅读 2020-11-19 09:06:13
    Android四大组件分别是: 1.activity 显示界面 2.service 服务 3.Broadcast Receiver 广播 4.Content Provider 数据通信 1. activity 显示页面: a.首先activity就是一个单独的窗口; 一个activity相当于我们...
  • android四大组件详解

    2011-12-30 14:42:11
    此文档涵盖了android四大组件,所有的基础应用介绍,并且还有补充说明
  • Android 一一 简述Android四大组件

    万次阅读 2017-11-30 21:42:31
    Android四大基本组件:Activity、BroadcastReceiver广播接收器、ContentProvider内容提供者、Service服务。 Activity:  应用程序中,一个Activity就相当于手机屏幕,它是一种可以包含用户界面的组件,主要用于和...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 31,383
精华内容 12,553
关键字:

android四大组件应用