使用 RecyclerView 可以实现动态加载数据。但如果我们有很多个页面,需要通过左右滑动来切换,就可以使用 ViewPager。
需要实现的效果:
使用 RecyclerView 可以实现动态加载数据。但如果我们有很多个页面,需要通过左右滑动来切换,就可以使用 ViewPager。
需要实现的效果:
如果要在一个 Activity 上显示图片, 可以用 imageView + ScrollView 组合。但如果是长图片,其实还可以用 WebView
但是 WebView 有内存泄漏的风险,使用时要谨慎。
在 Android笔记(二) Activity和布局 中,使用一个 String[] 模拟了一些数据,然后写入到一个 TextView 或者 ScrollView 中。
但这样有两个弊端:
于是我们引入了 RecyclerView 。想象一下,我们平时刷微博、刷知乎,随着我们不断地向下刷,数据是动态加载出来的。 这就是RecyclerView。 当然,如果在 RecyclerView 里面嵌套 CardView 就能显示很丰富的内容了。
当我们在知乎上面搜索“Android”的时候,可以看到地址栏的链接变化为:
https://www.zhihu.com/search?type=content&q=Android
其中,https://www.zhihu.com/search
是 BASE_URL, 问号?
后面的是参数。
这里的参数就是 type 为 content, q 为 Android 。
现在,我们要打造一个功能,用户在 EditText 上输入 Android , 我们的app 可以构造出 https://www.zhihu.com/search?type=content&q=Android
这样的 URL 出来。 并对该地址进行 HTTP 访问,然后获取 Response 结果。
以下将以 Github 的 API 为例
内容提供器(Content Provider)主要用于在不同的应用程序之间实现数据共享。它提供一套机制,允许在数据安全的条件下,让一个程序访问另一个程序的共享数据。比如,读取联系人app的电话号码数据等。
Content Provider可选择只对哪一部分数据进行共享,从而保证隐私。
但是在谈Content Provider之前,先来谈谈 Android 的运行时权限(Runtime permissions)
。
Android 6.0 之后,Android 系统的权限分为两类:
普通权限在 AndroidManifest.xml 文件里直接声明,如下:
1 | <manifest xmlns:android="http://schemas.android.com/apk/res/android" |
危险权限则需要使用运行时权限(runtime permssions)
。Android 6.0 引入了这个概念,简单地说,就是在应用程序运行的时候,由用户手动授权是否调用手机的某些数据(比如,麦克风、相机、电话)。
关于普通权限和危险权限的区别,以及分别有哪些权限,可以参考 Google 官方文档
可以调用ContextCompat.checkSelfPermission()
方法来检查是否有相应的权限,如果有返回 PackageManager.PERMISSION_GRANTED,并且应用可以继续操作。如果没有,返回 PERMISSION_DENIED,且应用必须明确向用户要求权限。
可以调用shouldShowRequestPermissionRationale()
方法来向用户解释需要某些权限。如果用户第一次拒绝了权限,第二个需要这个权限的时候,该方法返回true。
可以调用requestPermissions()
来申请权限。(此时系统会显示标准对话框让用户选择是否授权,我们无法更改)
可以重写onRequestPermissionsResult()
方法来了解用户选择的结果。
开发模型:
1 | if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) { |
可以看到,上面的逻辑还是比较复杂的。而且,由于OEM厂商会在定制ROM上各种改,导致权限获取不正常,以致出现各种各样的 bug。我本人就因为在一加5T上测试上面的代码时,点击了拒绝权限,但没有任何的 Toast 提示,白白浪费了4个小时的时间研究。后来用 AOSP 测试通过了,才发现是一加改了系统的权限通知框。
解决上述问题,推荐开源库:AndPermission
项目地址: https://github.com/yanzhenjie/AndPermission
使用方法可参考:Android 6.0 运行时权限管理最佳实践
未完待续
未完待续
第一次加载项目很慢一直显示Building “XXXX” Gradle project info
解决办法:
打开{your project}/gradle/wrapper/gradle-wrapper.properties
查看distributionUrl中 gradle 版本
去 https://services.gradle.org/distributions/ 下载相应版本的Gradle(官网地址:https://gradle.org/install)
放到以下目录即可
~/.gradle/wrapper/dists
C:\users\{user name}\.gradle\wrapper\dists
按照 Google 文档的开发模型,写的运行时权限模型代码,在一加手机5T上,点击拒绝后没有任何提示,也就是说依然返回了有权限的STATUE_CODE,但是什么都没发生。
1 | if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) { |
很多国产ROM都有这个坑。因此不要用上面的开发模型,推荐开源库:AndPermission
项目地址: https://github.com/yanzhenjie/AndPermission
使用方法可参考:Android 6.0 运行时权限管理最佳实践
查看Log:C:\Users\Jerrysheh\AppData\Local\Android\Sdk\tools\lib\monitor-x86_64\configuration\1520661867795.log
关键报错
1 | !ENTRY org.eclipse.osgi 4 0 2018-03-10 14:04:28.224 |
卸载 JRE 9。 (JDK 9 可以不用卸载) , 然后装 JDK 8 。
然后启动 Android Studio 时,使用管理员权限打开。
持久化技术指的是将内存中产生的瞬时数据保存到存储设备中,这样,当手机关机再重启,这些数据不会丢失。Android的持久化技术提供了一套机制,让数据在瞬时状态和持久状态之间进行转换。
Android 主要提供了 3 种数据持久化功能,分别是:
文件存储:顾名思义,用于保存文本或二进制数据等文件
SharedPreference存储:保存相对较小的键值集合
数据库存储:将数据保存到数据库
File 对象适合按照从开始到结束的顺序不跳过地读取或写入大量数据。 例如,它适合于图片文件或通过网络交换的任何内容。
早期的Android设备,由于内置存储空间非常有限(2011年买的Samung Galaxy S+只有 8 G 存储空间),因此通常都会外置SD卡。所以,Android的存储分为内部和外部。也有一些设备,虽然不支持SD卡,但依然人为地把存储空间分为外部和内部。一般来说,我们推荐把文件保存在内置存储当中,因为它始终可用,且只有自己的应用才能访问此处保存的文件,当自己的应用被卸载时,这些文件也会被移除。
如果要将文件写入外部存储中,请参考官方文档
无需任何权限,即可在内部存储中保存文件。
getFilesDir()
方法返回表示应用的内部目录的 File 。 用getCacheDir()
方法返回表示应用临时缓存文件的内部目录的 File。关于写入文件和写入缓存,请参考官方文档
首先定义一个 EditText
activity_main.xml
1 |
|
然后重写onCreate方法和onDestroy方法
Context类提供了一个 FileOutputStream 类型的 openFileOutput()
方法,用于输出数据到文件。
MainActivity.java
1 | public class MainActivity extends AppCompatActivity { |
这样当我们退出app,edittext的文本就会被自动保存起来。
类似于输出流,Context类也提供了一个 FileinputStream 类型的 openFileinput()
方法,用于输出数据到文件。
openFileinput(filename)
的参数是文件名,一旦指定,系统会自动从 /data/data//files/ 目录下加载这个文件,并返回一个 FileinputStream 对象。
1 | public class MainActivity extends AppCompatActivity { |
思路:
onCreate()
方法中调用 load()
来读取文件中的数据onDestroy()
方法中调用 saveText()
来保存数据到文件文件存储还是比较麻烦的,SharedPreference可以用键值对的方式来存储数据。
在 Android 中,有三种方法得到 SharedPreference 对象:
getSharePreference(filename, mode)
方法参数1是存储的文件名,目录在
/data/data/<package name>/share_prefs/
,参数2是模式,默认 MODE_PRIVATE
getPreferences(mode)
方法只有一个mode参数,因为这个方法会把当前类名作为 filename
static getDefaultSharedPreferences(Context)
方法广播接收器(Broadcast Receiver)允许你的应用接收来自各处的广播消息,比如开机广播,电池电量变化广播,时间或地区变化广播,以及来自电话、短信和其他app发出的广播消息等等。同样,我们的应用也可以向外发出广播消息。
Android中的广播可以分为以下两种:
标准广播(Normal Broadcast)
,一种完全异步执行的广播,当广播发出后,所有的广播接收器几乎同一时刻接收到这条广播信息,没有先后顺序之分。这种广播效率高,但无法截断。
有序广播(Ordered Broadcast)
,一种同步执行的广播,当广播发出后,同一时刻只有一个广播接收器收到这条广播,当这个广播接收器的逻辑执行完毕后,广播才会继续传递。这样一来,优先级高的广播接收器可以先收到广播,并且前面的广播接收器可以截断正在传递的广播。
Android笔记(三) Intent 和 Activity的生命周期、启动模式
Intent 是一个消息传递对象。它是 Android 程序中各组件之间进行交互的一种重要方式,它不仅可以指明当前组件想要执行的动作,还可以在不同组件之间传递数据。
关键词: 指明要执行的动作,传递数据
启动 Activity:
Activity 表示应用中的一个屏幕。通过将 Intent 传递给 startActivity()
,您可以启动新的 Activity 实例。Intent 描述了要启动的 Activity,并携带了任何必要的数据。
如果希望在 Activity 完成后收到结果,请调用 startActivityForResult()
。在 Activity 的 onActivityResult()
回调中,您的 Activity 将结果作为单独的 Intent 对象接收。
启动服务:
Service 是一个不使用用户界面而在后台执行操作的组件。通过将 Intent 传递给 startService()
,您可以启动服务执行一次性操作(例如,下载文件)。Intent 描述了要启动的服务,并携带了任何必要的数据。
如果服务旨在使用客户端-服务器接口,则通过将 Intent 传递给 bindService()
,您可以从其他组件绑定到此服务。
传递广播:
广播是任何应用均可接收的消息。系统将针对系统事件(例如:系统启动或设备开始充电时)传递各种广播。通过将 Intent 传递给 sendBroadcast()、sendOrderedBroadcast() 或 sendStickyBroadcast(),您可以将广播传递给其他应用。
显式 Intent 指的是明确地按名称(完全限定类名)指定要启动的组件。比如说,如果我们想在FirstActivity这个活动中打开SecondActivity,我们可以在FirstActivity中的一个按钮点击中调用StartActivity
,传入intent对象。
1 | Button buttonIntent = (Button) findViewById(R.id.button_intent); |
startActivity
,传入 intent 对象隐式 Intent 不会指定特定的组件,而是声明要执行的常规操作,从而允许其他应用中的组件来处理它。 例如,如需在地图上向用户显示位置,则可以使用隐式 Intent,请求另一具有此功能的应用在地图上显示指定的位置。
创建隐式 Intent 时,Android 系统通过将 Intent 的内容与在设备上其他应用的清单文件中声明的 Intent 过滤器进行比较,从而找到要启动的相应组件。 如果 Intent 与 Intent 过滤器匹配,则系统将启动该组件,并向其传递 Intent 对象。 如果多个 Intent 过滤器兼容,则系统会显示一个对话框,支持用户选取要使用的应用。
在AndroidManifest.xml中,把SecondActivity段修改如下
1 | <activity android:name=".SecondActivity"> |
在<intent-filter>中增添了action
和category
段,只有 action 和 category 同时匹配才能响应该Intent。每个Intent中只能指定一个action,但能指定多个category。
除了
action
和category
外,还有一个data
,请参考官方文档
修改按钮点击事件, new Intent对象,因为我们想启动能到响应为com.jerrysheh.hello.ACTION_START
这个action的活动,因此参数填入com.jerrysheh.hello.ACTION_START
。
1 | Button buttonIntent = (Button) findViewById(R.id.button_intent); |
或者,可以这样写,利用intent.setAction
方法。
1 | Button buttonIntent = (Button) findViewById(R.id.button_intent); |
如果intent.addCategory指定的Category没有一个活动能够匹配,那么程序会抛出异常。稍作修改,用resolveActivity()
方法来判断是否有应用能响应。假设没有活动匹配,就不启动startActivity()
;
1 | Button buttonIntent = (Button) findViewById(R.id.button_intent); |
new 一个 Intnet 对象后, 用 intent.setData(uri)
方法可以调用其他程序
比如调用浏览器打开 github
1 | Intent intent = new Intent("com.jerrysheh.hello.ACTION_START"); |
调用系统拨号拨打10010
1 | Intent intent = new Intent(Intent.ACTION_DIAL); |
可以在 AndroidManifest 的 <intent - filter>标签中配置 标签, 指定当前活动可以响应什么类型的数据。这样其他app响应这种数据的时候,Android系统会弹出选项,你的app会在可选列表里面
1 | <activity android:name=".SecondActivity"> |
这样, 你的app可以响应知乎网站的浏览器调用
可以用 intent 的 putExtra 方法向下一个活动传递数据。核心思想是,把数据存在String里,通过intent参数传递给下一个活动,下一个活动启动后从intent取出。
存放 (MainActivity.java)
1 | String data = "this is data" |
取出 (SecondActivity.java)
1 | Intent intent = getIntent(); |
Activity中有一个StartActivityForResult()
方法用于启动一个活动,但期望活动销毁后(通常是按下返回键或调用finish()
方法)返回一个结果给上一个活动。
MainActivity.java
1 | Intent intent = new Intent(MainActivity.this,SecondActivity.class); |
SecondActivity.java
1 | Intent intent = new Intent(); |
当然,返回数据后,会回调MainActivity的onActivityResult()
方法,因此我们还需要重写这个方法拿到SecondActivity返回来的数据。
1 |
|
上面的 intent 只能传 String, 如果我们有一个 javabean 对象需要传递,怎么做呢?
首先将实体类(bean)实现 Serializable 接口。注意: 如果 bean 里面嵌套了 bean,内部类也要声明为实现 Serializable 接口。
1 | public class bean implements Serializable { |
传递 activity
1 | Intent intent = new Intent(context, DetailActivity.class); |
接收 activity
1 | Bean detailBean = (Bean) getIntent().getSerializableExtra("name"); |
Android 用 任务(Task)来管理活动,一个Task就是一组存放在返回栈(Back Stack)里的活动的集合。系统总是会显示处于栈顶的活动给用户。
完整生存期
活动在onCraete()
和onDestroy()
之间经历的,就是一个完整生存期。
可见生存期
活动在onStart()
和onStop()
之间经历的,就是一个可见生存期。onStart()方法在活动从不可见变为可见时调用,onStop()反之。
前台生存期
活动在onResume()
和onPause()
之间经历的,就是一个前台生存期。onResume()方法在活动准备好和用户交互时调用。当系统准备去启动或恢复另一个活动时,onPause()将当前活动一些消耗CPU的资源释放,同时保存关键数据。
此外,还有一个onRestart()
,用于活动从停止状态变为运行状态之前调用,也就是活动被重新启动。
当系统内存不足时,用户按下back键返回到上一个Activity,有可能上一个Activity已经被系统回收,这时不会执行
onRestart()
,而是执行onCreate()
。遇到这种情况,如果上一个Activity有数据,那这些数据都丢失了,这是很影响用户体验的。解决办法是调用onSaveInstantState()
回调方法。具体参见《第一行代码》第二版p62,以及Activity Google官方文档(推荐)
Activity有四种启动模式,可以在 AndroidManifest.xml 的
1 | <activity |
android:launchMode
可填以下四种模式
标准模式,在MainActivity中启动MainActivity,会重复创建MainActivity的新实例。如创建了3个MainActivity的实例,需要按3次返回键才能完全退出。
singleTop
如果MainActivity已经在栈顶,启动MainActivity则不会重复创建新实例。但MainActivity不在栈顶,还是会创建新实例。
singleTask
无论MainActivity是否在栈顶,在整个应用程序上下文中只存在一个MainActivity实例。
singleInstance
单独创建一个返回栈存放该实例。解决不同应用共享一个返回栈的问题。
参考:Google官方文档
Activity 是用户可以执行的单一任务。负责创建新的窗口,供应用绘制和从系统中接收事件。
Activity 是用 Java 编写的,扩展自 Activity 类。
Activity 会创建视图来向用户显示信息,并使用户与 Activity 互动。视图是 Android UI 框架中的类。它们占据了屏幕上的方形区域,负责绘制并处理事件。Activity 通过读取 XML 布局文件确定要创建哪些视图(并放在何处)。这些 XML 文件存储在标记为 layouts 的 res 文件夹内。
Android 项目中的布局在 res/layouts 目录下的 XML 文件编写。
1 | <TextView |