安卓开发整理

先来一个HelloWorld.

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:orientation="vertical"
    android:gravity="center">

    <TextView
        android:id="@+id/tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World" />
    
</LinearLayout>

事件响应代码为将英文修改成中文。

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
//        设置屏幕组件需要用的布局
        setContentView(R.layout.activity_main);
        TextView tv = findViewById(R.id.tv);
//        修改文本控件的文字
        tv.setText("你好,世界");
    }

}

最后在手机上显示的画面如下

创建第二个页面

在res/layout文件夹下面新建一个xml文件

在res/values的strings.xml文件中添加内容

<resources>
    <string name="app_name">OCR</string>
    <string name="text2">Activity Main2</string>
</resources>

activity_main2.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:orientation="vertical"
    android:gravity="center">

    <TextView
        android:id="@+id/tv2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/text2" />

</LinearLayout>

在清单文件AndroidManifest.xml文件中添加activity_main2的配置。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.guanjian.ocr">

    <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.OCR">
        <activity
            android:name=".MainActivity"
            android:exported="true"
            android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".MainActivity2" />
    </application>

</manifest>

在Activity1中添加一个按钮来跳转到Activity2。

<?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:orientation="vertical"
    android:gravity="center">

    <TextView
        android:id="@+id/tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World" />

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="跳转" />

</LinearLayout>

修改MainActivity的Java代码

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
//        设置屏幕组件需要用的布局
        setContentView(R.layout.activity_main);
        TextView tv = findViewById(R.id.tv);
//        修改文本控件的文字
        tv.setText("你好,世界");

        Button button = findViewById(R.id.button);
//        给button设定点击事件的侦听
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
//                创建一个意图对象
                Intent intent = new Intent();
                intent.setClass(MainActivity.this,MainActivity2.class);
//                实现跳转
                startActivity(intent);
            }
        });
    }

}

在Java主目录中创建MainActivity2的响应类。

public class MainActivity2 extends AppCompatActivity {

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

在手机上的运行效果如下

Activity生命周期

上图的说明可以见以下代码

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "ning";

    /**
     * 在页面载入的时候最先触发
     * @param savedInstanceState
     */
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d(TAG,"Activity onCreate");
//        设置屏幕组件需要用的布局
        setContentView(R.layout.activity_main);
        TextView tv = findViewById(R.id.tv);
//        修改文本控件的文字
        tv.setText("你好,世界");

        Button button = findViewById(R.id.button);
//        给button设定点击事件的侦听
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
//                创建一个意图对象
                Intent intent = new Intent();
                intent.setClass(MainActivity.this,MainActivity2.class);
//                实现跳转
                startActivity(intent);
            }
        });
    }

    /**
     * 在页面载入的时候第二个触发
     */
    @Override
    protected void onStart() {
        super.onStart();
        Log.d(TAG,"Activity onStart");
    }

    /**
     * 在页面载入的时候第三个触发,结束时页面可见
     */
    @Override
    protected void onResume() {
        super.onResume();
        Log.d(TAG,"Activity onResume");
    }

    /**
     * 在页面跳转离开的时候触发
     */
    @Override
    protected void onPause() {
        super.onPause();
        Log.d(TAG,"Activity onPause");
    }

    /**
     * 在页面完全消失的时候触发
     */
    @Override
    protected void onStop() {
        super.onStop();
        Log.d(TAG,"Activity onStop");
    }

    /**
     * 从其他页面返回该页面时首次执行
     * 然后执行onStart和onResume
     */
    @Override
    protected void onRestart() {
        super.onRestart();
        Log.d(TAG,"Activity onRestart");
    }

    /**
     * 从主界面返回安卓桌面的时候触发
     */
    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.d(TAG,"Activity onDestroy");
    }
}

当我们打开安卓app的时候会显示日志

D/ning: Activity onCreate
D/ning: Activity onStart
D/ning: Activity onResume

当我们点击跳转按钮会显示日志

D/ning: Activity onPause
D/ning: Activity onStop

当我们从跳转页面返回主界面时会显示日志

D/ning: Activity onRestart
D/ning: Activity onStart
D/ning: Activity onResume

当我们从主界面返回安卓桌面时会显示日志

D/ning: Activity onPause
D/ning: Activity onStop
D/ning: Activity onDestroy

当我们点击了跳转按钮立刻返回主界面时会显示日志

D/ning: Activity onPause
D/ning: Activity onResume

这里有一个值得说明的地方,当App隐藏于后台的时候,我们启动了非常占用内存的App,比如游戏,此时安卓系统会将该进程杀死释放内存给游戏使用。当我们再次进入该App的时候会显示D/ning: Activity onCreate而不是D/ning: Activity onRestart。这几个触发动作的具体应用如下

  1. onCreate:创建活动,把页面布局加载进内存,进入初始状态。
  2. onStart:开始活动,把活动页面显示在屏幕上,进入了就绪状态。
  3. onResume:恢复活动,活动页面进入活跃状态,能够与用户正常交互,例如允许响应用户的点击动作;允许用户输入文字等等。
  4. onPause:暂停活动,页面进入暂停状态,无法与用户进行正常交互。
  5. onStop:停止活动,页面将不在屏幕上显示。
  6. onDestroy:销毁活动,回收活动占用的系统资源,把页面从内存中清除。
  7. onRestart:重启活动,重新加载内存中的页面数据。
  8. onNewIntent:重用已有的活动实例。

Intent

Intent是各个组件之间信息沟通的桥梁,它用于Android各组件之间的通信,主要完成下列工作:

  1. 标明本次通信请求从哪里来,到哪里去,要怎么走。
  2. 发起方携带本次通信需要的数据内容,接收方从收到的意图中解析数据。
  3. 发起方若想判断接收方的处理结果,意图就要负责接收方传回应答的数据内容。
  • 显式Intent:直接指定来源活动与目标活动,属于精确匹配。它有3种构建方式
  1. 在Intent的构造函数中指定。
  2. 调用意图对象的setClass方法指定。
  3. 调用意图对象的setComponent方法指定。
            @Override
            public void onClick(View view) {
//                创建一个意图对象,第一种方式
//                Intent intent = new Intent(MainActivity.this,MainActivity2.class);
//                第二种方式
                Intent intent = new Intent();
                intent.setClass(MainActivity.this,MainActivity2.class);
//                第三种方式
//                Intent intent = new Intent();
//                它可以调用第三方的控件,使用包名(字符串)和类名(字符串)来调用
//                ComponentName component = new ComponentName(MainActivity.this,MainActivity2.class);
//                intent.setComponent(component);
//                实现跳转
                startActivity(intent);
            }
  • 隐式Intent:没有明确指定要跳转的目标活动,只给出一个动作字符串让系统自动匹配,属于模糊匹配。

现在我们在Activity2的页面中添加如下的布局

<?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:orientation="vertical"
    android:gravity="center">

    <TextView
        android:id="@+id/tv2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/text2" />

    <TextView
        android:id="@+id/tv3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="5dp"
        android:text="点击以下按钮将向号码12345发起请求" />

    <Button
        android:id="@+id/btn_dial"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="跳到拨号页面" />

</LinearLayout>

在Activity2的Java代码中添加

public class MainActivity2 extends AppCompatActivity implements View.OnClickListener {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);
        findViewById(R.id.btn_dial).setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.btn_dial:
                Intent intent = new Intent();
//                隐式跳转到拨号界面
                intent.setAction(Intent.ACTION_DIAL);
                Uri uri = Uri.parse("tel:12345");
                intent.setData(uri);
                startActivity(intent);
                break;
            default:
                break;
        }
    }
}

运行结果

  • 向下一个Activity传递数据

我们在Activity的主界面的布局文件中添加如下代码

<?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:orientation="vertical"
    android:gravity="center">

    <TextView
        android:id="@+id/tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World" />

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="跳转" />

    <TextView
        android:id="@+id/tv_send"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="今天天气很晴朗" />

    <Button
        android:id="@+id/button_send"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="发送信息" />

</LinearLayout>

在Activity的Java代码中添加

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "ning";
    private TextView tvSend;

    /**
     * 在页面载入的时候最先触发
     * @param savedInstanceState
     */
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d(TAG,"Activity onCreate");
//        设置屏幕组件需要用的布局
        setContentView(R.layout.activity_main);
        TextView tv = findViewById(R.id.tv);
//        修改文本控件的文字
        tv.setText("你好,世界");

        Button button = findViewById(R.id.button);
//        给button设定点击事件的侦听
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
//                创建一个意图对象,第一种方式
//                Intent intent = new Intent(MainActivity.this,MainActivity2.class);
//                第二种方式
                Intent intent = new Intent();
                intent.setClass(MainActivity.this,MainActivity2.class);
//                第三种方式
//                Intent intent = new Intent();
//                它可以调用第三方的控件,使用包名(字符串)和类名(字符串)来调用
//                ComponentName component = new ComponentName(MainActivity.this,MainActivity2.class);
//                intent.setComponent(component);
//                实现跳转
                startActivity(intent);
            }
        });
        tvSend = findViewById(R.id.tv_send);
        findViewById(R.id.button_send).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent(MainActivity.this,MainActivity2.class);
//                创建一个包裹对象
                Bundle bundle = new Bundle();
                bundle.putString("request_time", new Date().toString());
                bundle.putString("request_context",tvSend.getText().toString());
                intent.putExtras(bundle);
                startActivity(intent);
            }
        });
    }

    /**
     * 在页面载入的时候第二个触发
     */
    @Override
    protected void onStart() {
        super.onStart();
        Log.d(TAG,"Activity onStart");
    }

    /**
     * 在页面载入的时候第三个触发,结束时页面可见
     */
    @Override
    protected void onResume() {
        super.onResume();
        Log.d(TAG,"Activity onResume");
    }

    /**
     * 在页面跳转离开的时候触发
     */
    @Override
    protected void onPause() {
        super.onPause();
        Log.d(TAG,"Activity onPause");
    }

    /**
     * 在页面完全消失的时候触发
     */
    @Override
    protected void onStop() {
        super.onStop();
        Log.d(TAG,"Activity onStop");
    }

    /**
     * 从其他页面返回该页面时首次执行
     * 然后执行onStart和onResume
     */
    @Override
    protected void onRestart() {
        super.onRestart();
        Log.d(TAG,"Activity onRestart");
    }

    /**
     * 从主界面返回安卓桌面的时候触发
     */
    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.d(TAG,"Activity onDestroy");
    }
}

在Activity2中获取意图中的数据放入tv2中。

public class MainActivity2 extends AppCompatActivity implements View.OnClickListener {
    private TextView tv2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);
        findViewById(R.id.btn_dial).setOnClickListener(this);
        tv2 = findViewById(R.id.tv2);
//        从上一个页面的意图中获取包裹
        Bundle bundle = getIntent().getExtras();
        String requestTime = bundle.getString("request_time");
        String requestContext = bundle.getString("request_context");
        String desc = String.format("收到请求消息:\n请求时间:%s\n请求内容:%s",
                requestTime,requestContext);
        tv2.setText(desc);
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.btn_dial:
                Intent intent = new Intent();
//                隐式跳转到拨号界面
                intent.setAction(Intent.ACTION_DIAL);
                Uri uri = Uri.parse("tel:12345");
                intent.setData(uri);
                startActivity(intent);
                break;
            default:
                break;
        }
    }
}

运行结果

  • 向上一个Activity返回数据

现在我们要回一个信息给到主界面,说今天天气很热。

在Activity2中增加一个按钮,布局文件如下

<?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:orientation="vertical"
    android:gravity="center">

    <TextView
        android:id="@+id/tv2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/text2" />

    <TextView
        android:id="@+id/tv_response"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="待返回的消息为:今天天气很热" />

    <Button
        android:id="@+id/btn_response"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="发送返回信息" />

    <TextView
        android:id="@+id/tv3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="5dp"
        android:text="点击以下按钮将向号码12345发起请求" />

    <Button
        android:id="@+id/btn_dial"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="跳到拨号页面" />

</LinearLayout>

现在我们在Activity的主界面的Java代码中就不仅仅是普通的跳转到Activity2了,而是需要注册一个可以等待返回的ActivityResult。跳转的方式也不再是startActivity(intent);注意,以下代码都改成了Lambda表达式的形式,关于lambda表达式的内容可以参考Java函数式编程整理

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "ning";
    private TextView tvSend;
    private ActivityResultLauncher<Intent> register;

    /**
     * 在页面载入的时候最先触发
     * @param savedInstanceState
     */
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d(TAG,"Activity onCreate");
//        设置屏幕组件需要用的布局
        setContentView(R.layout.activity_main);
        TextView tv = findViewById(R.id.tv);
//        修改文本控件的文字
        tv.setText("你好,世界");

        Button button = findViewById(R.id.button);
//        给button设定点击事件的侦听
        button.setOnClickListener(view ->  {
//            创建一个意图对象,第一种方式
//            Intent intent = new Intent(MainActivity.this,MainActivity2.class);
//            第二种方式
            Intent intent = new Intent();
            intent.setClass(MainActivity.this,MainActivity2.class);
//            第三种方式
//            Intent intent = new Intent();
//            它可以调用第三方的控件,使用包名(字符串)和类名(字符串)来调用
//            ComponentName component = new ComponentName(MainActivity.this,MainActivity2.class);
//            intent.setComponent(component);
//            实现跳转
            startActivity(intent);
        });
        tvSend = findViewById(R.id.tv_send);
//        注册ActivityResult,并通过回调函数获取返回的信息
        register = registerForActivityResult(new StartActivityForResult(), result ->  {
            if (result != null) {
                Intent intent = result.getData();
                if (intent != null && result.getResultCode() == Activity.RESULT_OK) {
                    Bundle bundle = intent.getExtras();
                    String responseTime = bundle.getString("response_time");
                    String responseContext = bundle.getString("response_context");
                    String desc = String.format("收到返回消息:\n返回时间:%s\n返回内容:%s",
                            responseTime,responseContext);
                    tv.setText(desc);
                }
            }
        });
        findViewById(R.id.button_send).setOnClickListener(view ->  {
            Intent intent = new Intent(MainActivity.this,MainActivity2.class);
//                创建一个包裹对象
            Bundle bundle = new Bundle();
            bundle.putString("request_time", new Date().toString());
            bundle.putString("request_context",tvSend.getText().toString());
            intent.putExtras(bundle);
            register.launch(intent);
        });
    }

    /**
     * 在页面载入的时候第二个触发
     */
    @Override
    protected void onStart() {
        super.onStart();
        Log.d(TAG,"Activity onStart");
    }

    /**
     * 在页面载入的时候第三个触发,结束时页面可见
     */
    @Override
    protected void onResume() {
        super.onResume();
        Log.d(TAG,"Activity onResume");
    }

    /**
     * 在页面跳转离开的时候触发
     */
    @Override
    protected void onPause() {
        super.onPause();
        Log.d(TAG,"Activity onPause");
    }

    /**
     * 在页面完全消失的时候触发
     */
    @Override
    protected void onStop() {
        super.onStop();
        Log.d(TAG,"Activity onStop");
    }

    /**
     * 从其他页面返回该页面时首次执行
     * 然后执行onStart和onResume
     */
    @Override
    protected void onRestart() {
        super.onRestart();
        Log.d(TAG,"Activity onRestart");
    }

    /**
     * 从主界面返回安卓桌面的时候触发
     */
    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.d(TAG,"Activity onDestroy");
    }
}

Activity2中的返回信息的代码如下

public class MainActivity2 extends AppCompatActivity implements View.OnClickListener {
    private TextView tv2;
    private final String msg = "今天天气很热";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);
        findViewById(R.id.btn_dial).setOnClickListener(this);
        tv2 = findViewById(R.id.tv2);
//        从上一个页面的意图中获取包裹
        Bundle bundle = getIntent().getExtras();
        String requestTime = bundle.getString("request_time");
        String requestContext = bundle.getString("request_context");
        String desc = String.format("收到请求消息:\n请求时间:%s\n请求内容:%s",
                requestTime,requestContext);
        tv2.setText(desc);
        findViewById(R.id.btn_response).setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        Intent intent = new Intent();
        switch (view.getId()) {
            case R.id.btn_dial:
//                隐式跳转到拨号界面
                intent.setAction(Intent.ACTION_DIAL);
                Uri uri = Uri.parse("tel:12345");
                intent.setData(uri);
                startActivity(intent);
                break;
            case R.id.btn_response:
                Bundle bundle = new Bundle();
                bundle.putString("response_time",new Date().toString());
                bundle.putString("response_context",msg);
                intent.putExtras(bundle);
                setResult(Activity.RESULT_OK,intent);
//                页面返回跳转
                finish();
                break;
            default:
                break;
        }
    }
}

运行结果

运行时动态申请权限

安卓系统在6.0之前,只需要在清单文件中去配置权限就可以使用例如手机联系人、短信、相册等需要申请权限的应用。用户在安装的时候会进行提示。在安卓6.0之后不仅仅需要在清单文件中配置这些权限,而且会进行系统弹窗的询问,要使用这些权限需要在用户允许的情况下才可以使用。

  • Lazy模式

Lazy模式即懒汉式模式,当我们需要用到某个权限功能时才去请求权限。除此之外还有一个Hungry模式,即饿汉式模式,当我们打开App的时候,不管你有没有使用到某个权限功能,它都会对用户进行请求,让用户去一次性通过。

我们在Activity的布局文件中添加两个按钮去读取通讯录和发送短信。

<?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:orientation="vertical"
    android:gravity="center">

    <TextView
        android:id="@+id/tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World" />

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="跳转" />

    <TextView
        android:id="@+id/tv_send"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="今天天气很晴朗" />

    <Button
        android:id="@+id/button_send"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="发送信息" />

    <Button
        android:id="@+id/btn_contact"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="读写通讯录" />

    <Button
        android:id="@+id/btn_sms"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="发送短信" />

</LinearLayout>

新增一个查看是否已经授权的工具类

public class PermissionUtil {

    /**
     * 检查多个权限,返回true表示已经完全启用权限,返回false表示未完全启用权限
     * @param act Activity
     * @param permissions 权限
     * @param requestCode 权限编码
     * @return
     */
    public static boolean checkPermission(Activity act,String[] permissions,int requestCode) {
//        安卓6.0之后才开始使用动态权限管理,M就是6.0
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
//            默认授权
            int check = PackageManager.PERMISSION_GRANTED;
            for (String permission : permissions) {
                check = ContextCompat.checkSelfPermission(act,permission);
                if (check != PackageManager.PERMISSION_GRANTED) {
                    break;
                }
            }
//            未开启该授权,则请求系统弹窗,好让用户选择是否立即开启授权
            if (check != PackageManager.PERMISSION_GRANTED) {
                ActivityCompat.requestPermissions(act,permissions,requestCode);
                return false;
            }
        }
        return true;
    }

    /**
     * 判断用户是否授权
     * @param grantResults 授权结果,0已授权,-1未授权
     * @return 全部授权返回true,有一个未授权返回false
     */
    public static boolean checkGrant(int[] grantResults) {
        if (grantResults != null) {
            for (int grant : grantResults) {
                if (grant != PackageManager.PERMISSION_GRANTED) {
                    return false;
                }
            }
            return true;
        }
        return false;
    }
}

一个消息弹窗工具类

public class ToastUtil {
    /**
     * 弹窗消息
     * @param act
     * @param msg
     */
    public static void show(Activity act,String msg) {
        Toast toast = Toast.makeText(act,msg,Toast.LENGTH_LONG);
        toast.setGravity(Gravity.CENTER,0,0);
        toast.show();
    }
}

在清单文件中配置我们需要申请的权限

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.guanjian.ocr">

<!--    通讯录权限-->
    <uses-permission android:name="android.permission.READ_CONTACTS" />
    <uses-permission android:name="android.permission.WRITE_CONTACTS" />
<!--    短信权限-->
    <uses-permission android:name="android.permission.SEND_SMS" />
    <uses-permission android:name="android.permission.READ_SMS" />

    <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.OCR">
        <activity
            android:name=".MainActivity"
            android:exported="true"
            android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".MainActivity2" />
    </application>

</manifest>

Activity的整体Java代码如下,这里我们还没有真正去使用通讯录和短信功能,只是申请权限而已。

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "ning";
    private TextView tvSend;
    private ActivityResultLauncher<Intent> register;
//    通讯录权限
    private static final String[] PERMISSIONS_CONTACTS = new String[]{
            Manifest.permission.READ_CONTACTS,
            Manifest.permission.WRITE_CONTACTS
    };
//    短信权限
    private static final String[] PERMISSIONS_SMS = new String[]{
            Manifest.permission.SEND_SMS,
            Manifest.permission.READ_SMS
    };
//    通讯录权限编码
    private static final int REQUEST_CODE_CONTACTS = 1;
//    短信权限编码
    private static final int REQUEST_CODE_SMS = 2;

    /**
     * 在页面载入的时候最先触发
     * @param savedInstanceState
     */
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d(TAG,"Activity onCreate");
//        设置屏幕组件需要用的布局
        setContentView(R.layout.activity_main);
        TextView tv = findViewById(R.id.tv);
//        修改文本控件的文字
        tv.setText("你好,世界");

        Button button = findViewById(R.id.button);
//        给button设定点击事件的侦听
        button.setOnClickListener(view ->  {
//            创建一个意图对象,第一种方式
//            Intent intent = new Intent(MainActivity.this,MainActivity2.class);
//            第二种方式
            Intent intent = new Intent();
            intent.setClass(MainActivity.this,MainActivity2.class);
//            第三种方式
//            Intent intent = new Intent();
//            它可以调用第三方的控件,使用包名(字符串)和类名(字符串)来调用
//            ComponentName component = new ComponentName(MainActivity.this,MainActivity2.class);
//            intent.setComponent(component);
//            实现跳转
            startActivity(intent);
        });
        tvSend = findViewById(R.id.tv_send);
//        注册ActivityResult,并通过回调函数获取返回的信息
        register = registerForActivityResult(new StartActivityForResult(), result ->  {
            if (result != null) {
                Intent intent = result.getData();
                if (intent != null && result.getResultCode() == Activity.RESULT_OK) {
                    Bundle bundle = intent.getExtras();
                    String responseTime = bundle.getString("response_time");
                    String responseContext = bundle.getString("response_context");
                    String desc = String.format("收到返回消息:\n返回时间:%s\n返回内容:%s",
                            responseTime,responseContext);
                    tv.setText(desc);
                }
            }
        });
//        发送一个消息给下一个Activity
        findViewById(R.id.button_send).setOnClickListener(view ->  {
            Intent intent = new Intent(MainActivity.this,MainActivity2.class);
//                创建一个包裹对象
            Bundle bundle = new Bundle();
            bundle.putString("request_time", new Date().toString());
            bundle.putString("request_context",tvSend.getText().toString());
            intent.putExtras(bundle);
            register.launch(intent);
        });
//        使用通讯录
        findViewById(R.id.btn_contact).setOnClickListener(view -> {
            PermissionUtil.checkPermission(MainActivity.this,PERMISSIONS_CONTACTS,REQUEST_CODE_CONTACTS);
        });
//        发送短信
        findViewById(R.id.btn_sms).setOnClickListener(view -> {
            PermissionUtil.checkPermission(MainActivity.this,PERMISSIONS_SMS,REQUEST_CODE_SMS);
        });
    }

    /**
     * 在页面载入的时候第二个触发
     */
    @Override
    protected void onStart() {
        super.onStart();
        Log.d(TAG,"Activity onStart");
    }

    /**
     * 在页面载入的时候第三个触发,结束时页面可见
     */
    @Override
    protected void onResume() {
        super.onResume();
        Log.d(TAG,"Activity onResume");
    }

    /**
     * 在页面跳转离开的时候触发
     */
    @Override
    protected void onPause() {
        super.onPause();
        Log.d(TAG,"Activity onPause");
    }

    /**
     * 在页面完全消失的时候触发
     */
    @Override
    protected void onStop() {
        super.onStop();
        Log.d(TAG,"Activity onStop");
    }

    /**
     * 从其他页面返回该页面时首次执行
     * 然后执行onStart和onResume
     */
    @Override
    protected void onRestart() {
        super.onRestart();
        Log.d(TAG,"Activity onRestart");
    }

    /**
     * 从主界面返回安卓桌面的时候触发
     */
    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.d(TAG,"Activity onDestroy");
    }

    /**
     * 用户进行权限确认时触发
     * @param requestCode
     * @param permissions
     * @param grantResults
     */
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
//        判断用户对哪一个权限进行确认
        switch (requestCode) {
            case REQUEST_CODE_CONTACTS:
                if (PermissionUtil.checkGrant(grantResults)) {
                    Log.d(TAG,"通讯录授权获取成功");
                }else {
                    ToastUtil.show(this,"获取通讯录读写权限失败");
                }
                break;
            case REQUEST_CODE_SMS:
                if (PermissionUtil.checkGrant(grantResults)) {
                    Log.d(TAG,"收发短信权限获取成功");
                }else {
                    ToastUtil.show(this,"获取收发短信权限失败");
                }
                break;
            default:
                break;
        }
    }
}

运行结果

当我们点击允许,日志打印

D/ning: 通讯录授权获取成功

当我们点击禁止

如果我们拒绝过一次,再点发送短信按钮,此时就不会再询问你是否授权了,而是直接弹出获取收发短信权限失败的消息。当然这样是不友好的,可以使用隐式意图跳转到安卓手动授权界面,修改Activity的Java代码如下

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "ning";
    private TextView tvSend;
    private ActivityResultLauncher<Intent> register;
//    通讯录权限
    private static final String[] PERMISSIONS_CONTACTS = new String[]{
            Manifest.permission.READ_CONTACTS,
            Manifest.permission.WRITE_CONTACTS
    };
//    短信权限
    private static final String[] PERMISSIONS_SMS = new String[]{
            Manifest.permission.SEND_SMS,
            Manifest.permission.READ_SMS
    };
//    通讯录权限编码
    private static final int REQUEST_CODE_CONTACTS = 1;
//    短信权限编码
    private static final int REQUEST_CODE_SMS = 2;

    /**
     * 在页面载入的时候最先触发
     * @param savedInstanceState
     */
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d(TAG,"Activity onCreate");
//        设置屏幕组件需要用的布局
        setContentView(R.layout.activity_main);
        TextView tv = findViewById(R.id.tv);
//        修改文本控件的文字
        tv.setText("你好,世界");

        Button button = findViewById(R.id.button);
//        给button设定点击事件的侦听
        button.setOnClickListener(view ->  {
//            创建一个意图对象,第一种方式
//            Intent intent = new Intent(MainActivity.this,MainActivity2.class);
//            第二种方式
            Intent intent = new Intent();
            intent.setClass(MainActivity.this,MainActivity2.class);
//            第三种方式
//            Intent intent = new Intent();
//            它可以调用第三方的控件,使用包名(字符串)和类名(字符串)来调用
//            ComponentName component = new ComponentName(MainActivity.this,MainActivity2.class);
//            intent.setComponent(component);
//            实现跳转
            startActivity(intent);
        });
        tvSend = findViewById(R.id.tv_send);
//        注册ActivityResult,并通过回调函数获取返回的信息
        register = registerForActivityResult(new StartActivityForResult(), result ->  {
            if (result != null) {
                Intent intent = result.getData();
                if (intent != null && result.getResultCode() == Activity.RESULT_OK) {
                    Bundle bundle = intent.getExtras();
                    String responseTime = bundle.getString("response_time");
                    String responseContext = bundle.getString("response_context");
                    String desc = String.format("收到返回消息:\n返回时间:%s\n返回内容:%s",
                            responseTime,responseContext);
                    tv.setText(desc);
                }
            }
        });
//        发送一个消息给下一个Activity
        findViewById(R.id.button_send).setOnClickListener(view ->  {
            Intent intent = new Intent(MainActivity.this,MainActivity2.class);
//                创建一个包裹对象
            Bundle bundle = new Bundle();
            bundle.putString("request_time", new Date().toString());
            bundle.putString("request_context",tvSend.getText().toString());
            intent.putExtras(bundle);
            register.launch(intent);
        });
//        使用通讯录
        findViewById(R.id.btn_contact).setOnClickListener(view -> {
            PermissionUtil.checkPermission(MainActivity.this,PERMISSIONS_CONTACTS,REQUEST_CODE_CONTACTS);
        });
//        发送短信
        findViewById(R.id.btn_sms).setOnClickListener(view -> {
            PermissionUtil.checkPermission(MainActivity.this,PERMISSIONS_SMS,REQUEST_CODE_SMS);
        });
    }

    /**
     * 在页面载入的时候第二个触发
     */
    @Override
    protected void onStart() {
        super.onStart();
        Log.d(TAG,"Activity onStart");
    }

    /**
     * 在页面载入的时候第三个触发,结束时页面可见
     */
    @Override
    protected void onResume() {
        super.onResume();
        Log.d(TAG,"Activity onResume");
    }

    /**
     * 在页面跳转离开的时候触发
     */
    @Override
    protected void onPause() {
        super.onPause();
        Log.d(TAG,"Activity onPause");
    }

    /**
     * 在页面完全消失的时候触发
     */
    @Override
    protected void onStop() {
        super.onStop();
        Log.d(TAG,"Activity onStop");
    }

    /**
     * 从其他页面返回该页面时首次执行
     * 然后执行onStart和onResume
     */
    @Override
    protected void onRestart() {
        super.onRestart();
        Log.d(TAG,"Activity onRestart");
    }

    /**
     * 从主界面返回安卓桌面的时候触发
     */
    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.d(TAG,"Activity onDestroy");
    }

    /**
     * 用户进行权限确认时触发
     * @param requestCode
     * @param permissions
     * @param grantResults
     */
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
//        判断用户对哪一个权限进行确认
        switch (requestCode) {
            case REQUEST_CODE_CONTACTS:
                if (PermissionUtil.checkGrant(grantResults)) {
                    Log.d(TAG,"通讯录授权获取成功");
                }else {
                    ToastUtil.show(this,"获取通讯录读写权限失败");
                    jumpToSettings();
                }
                break;
            case REQUEST_CODE_SMS:
                if (PermissionUtil.checkGrant(grantResults)) {
                    Log.d(TAG,"收发短信权限获取成功");
                }else {
                    ToastUtil.show(this,"获取收发短信权限失败");
                    jumpToSettings();
                }
                break;
            default:
                break;
        }
    }

    /**
     * 跳转到安卓权限设置界面
     */
    private void jumpToSettings() {
        Intent intent = new Intent();
        intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
//        通过包名还跳转到我们自己的应用设置界面
        intent.setData(Uri.fromParts("package",getPackageName(),null));
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        startActivity(intent);
    }
}

运行结果

在这里点击权限,进入权限设置界面

在这里将信息权限设为允许就可以了。

  • Hungry模式

新建一个布局页面activity_main3.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:orientation="vertical"
    android:gravity="center">

    <Button
        android:id="@+id/btn_contact1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="读写通讯录" />

    <Button
        android:id="@+id/btn_sms1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="发送短信" />

</LinearLayout>

在清单中将主界面设置为Activity3

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.guanjian.ocr">
    <!-- 通讯录权限 -->
    <uses-permission android:name="android.permission.READ_CONTACTS" />
    <uses-permission android:name="android.permission.WRITE_CONTACTS" /> <!-- 短信权限 -->
    <uses-permission android:name="android.permission.SEND_SMS" />
    <uses-permission android:name="android.permission.READ_SMS" />

    <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.OCR">
        <activity
            android:name=".MainActivity" />
        <activity
            android:name=".MainActivity3"
            android:exported="true"
            android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".MainActivity2" />
    </application>

</manifest>

Activity3的Java代码如下

public class MainActivity3 extends AppCompatActivity implements View.OnClickListener {
    private static final String TAG = "ning";
//    所有权限
    private static final String[] PERMISSIONS = new String[]{
            Manifest.permission.READ_CONTACTS,
            Manifest.permission.WRITE_CONTACTS,
            Manifest.permission.SEND_SMS,
            Manifest.permission.READ_SMS
    };
    //    所有权限编码
    private static final int REQUEST_CODE_All = 1;
    //    通讯录权限编码
    private static final int REQUEST_CODE_CONTACTS = 2;
    //    短信权限编码
    private static final int REQUEST_CODE_SMS = 3;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main3);
        findViewById(R.id.btn_contact1).setOnClickListener(this);
        findViewById(R.id.btn_sms1).setOnClickListener(this);
//        在Activity创建的时候就进行权限判断
        PermissionUtil.checkPermission(this,PERMISSIONS,REQUEST_CODE_All);
    }

    @Override
    public void onClick(View view) {
//        不仅仅在启动的时候启动授权,在点击按钮的时候同样查看是否授权
        switch (view.getId()) {
            case R.id.btn_contact1:
                PermissionUtil.checkPermission(this,new String[]{PERMISSIONS[0],PERMISSIONS[1]},REQUEST_CODE_CONTACTS);
                break;
            case R.id.btn_sms1:
                PermissionUtil.checkPermission(this,new String[]{PERMISSIONS[2],PERMISSIONS[3]},REQUEST_CODE_SMS);
                break;
            default:
                break;
        }
    }

    /**
     * 用户进行权限确认时触发
     * @param requestCode
     * @param permissions
     * @param grantResults
     */
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        switch (requestCode) {
            case REQUEST_CODE_All:
                if (PermissionUtil.checkGrant(grantResults)) {
                    Log.d(TAG,"所有权限获取成功");
                }else {
                    for (int i = 0; i < grantResults.length; i++) {
//                        对于用户的每一个授权结果进行判断
                        if (grantResults[i] != PackageManager.PERMISSION_GRANTED) {
                            switch (permissions[i]) {
                                case Manifest.permission.READ_CONTACTS:
                                case Manifest.permission.WRITE_CONTACTS:
                                    ToastUtil.show(this,"获取通讯录读写权限失败");
                                    jumpToSettings();
                                    return;
                                case Manifest.permission.SEND_SMS:
                                case Manifest.permission.READ_SMS:
                                    ToastUtil.show(this,"获取收发短信权限失败");
                                    jumpToSettings();
                                    return;
                                default:
                                    break;
                            }
                        }
                    }
                }
                break;
            case REQUEST_CODE_CONTACTS:
                if (PermissionUtil.checkGrant(grantResults)) {
                    Log.d(TAG,"通讯录授权获取成功");
                }else {
                    ToastUtil.show(this,"获取通讯录读写权限失败");
                    jumpToSettings();
                }
                break;
            case REQUEST_CODE_SMS:
                if (PermissionUtil.checkGrant(grantResults)) {
                    Log.d(TAG,"收发短信权限获取成功");
                }else {
                    ToastUtil.show(this,"获取收发短信权限失败");
                    jumpToSettings();
                }
                break;
            default:
                break;
        }
    }

    /**
     * 跳转到安卓权限设置界面
     */
    private void jumpToSettings() {
        Intent intent = new Intent();
        intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
//        通过包名还跳转到我们自己的应用设置界面
        intent.setData(Uri.fromParts("package",getPackageName(),null));
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        startActivity(intent);
    }
}

在我们启动运行之前需要先将手机上的App卸载,否则之前授权过了,再次启动运行授权这里是无效的。

运行结果

当我们打开App的时候

如果我们禁止了上面的授权,当点击按钮时会跳转到安卓授权界面进行手工授权。

添加联系人

{{o.name}}
{{m.name}}

猜你喜欢

转载自my.oschina.net/u/3768341/blog/5569275