先来一个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。这几个触发动作的具体应用如下
- onCreate:创建活动,把页面布局加载进内存,进入初始状态。
- onStart:开始活动,把活动页面显示在屏幕上,进入了就绪状态。
- onResume:恢复活动,活动页面进入活跃状态,能够与用户正常交互,例如允许响应用户的点击动作;允许用户输入文字等等。
- onPause:暂停活动,页面进入暂停状态,无法与用户进行正常交互。
- onStop:停止活动,页面将不在屏幕上显示。
- onDestroy:销毁活动,回收活动占用的系统资源,把页面从内存中清除。
- onRestart:重启活动,重新加载内存中的页面数据。
- onNewIntent:重用已有的活动实例。
Intent
Intent是各个组件之间信息沟通的桥梁,它用于Android各组件之间的通信,主要完成下列工作:
- 标明本次通信请求从哪里来,到哪里去,要怎么走。
- 发起方携带本次通信需要的数据内容,接收方从收到的意图中解析数据。
- 发起方若想判断接收方的处理结果,意图就要负责接收方传回应答的数据内容。
- 显式Intent:直接指定来源活动与目标活动,属于精确匹配。它有3种构建方式
- 在Intent的构造函数中指定。
- 调用意图对象的setClass方法指定。
- 调用意图对象的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的时候
如果我们禁止了上面的授权,当点击按钮时会跳转到安卓授权界面进行手工授权。
添加联系人