Android 零碎知识

使用WebView

如果要在一个 Activity 上显示图片, 可以用 imageView + ScrollView 组合。但如果是长图片,其实还可以用 WebView

但是 WebView 有内存泄漏的风险,使用时要谨慎。

阅读更多

Android笔记(八)使用RecyclerView

Android笔记(二) Activity和布局 中,使用一个 String[] 模拟了一些数据,然后写入到一个 TextView 或者 ScrollView 中。

但这样有两个弊端:

  • ScrollView是一次性将内容绘制完毕,如果数据量很大,会导致内存消耗。
  • 无法通过点击 String[] 里面的某一个 String 进入详细页面

于是我们引入了 RecyclerView 。想象一下,我们平时刷微博、刷知乎,随着我们不断地向下刷,数据是动态加载出来的。 这就是RecyclerView。 当然,如果在 RecyclerView 里面嵌套 CardView 就能显示很丰富的内容了。

阅读更多

Android笔记(七)连接网络

当我们在知乎上面搜索“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 为例

阅读更多

Android笔记(六) 运行时权限和内容提供器

什么是Content Provider ?

内容提供器(Content Provider)主要用于在不同的应用程序之间实现数据共享。它提供一套机制,允许在数据安全的条件下,让一个程序访问另一个程序的共享数据。比如,读取联系人app的电话号码数据等。

Content Provider可选择只对哪一部分数据进行共享,从而保证隐私。

但是在谈Content Provider之前,先来谈谈 Android 的运行时权限(Runtime permissions)


运行时权限(Runtime permissions)

Android 6.0 之后,Android 系统的权限分为两类:

  • 普通权限
  • 危险权限

普通权限在 AndroidManifest.xml 文件里直接声明,如下:

1
2
3
4
5
6
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.jerrysheh.englishquickchecker">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
...
</manifest>

危险权限则需要使用运行时权限(runtime permssions)。Android 6.0 引入了这个概念,简单地说,就是在应用程序运行的时候,由用户手动授权是否调用手机的某些数据(比如,麦克风、相机、电话)。

关于普通权限和危险权限的区别,以及分别有哪些权限,可以参考 Google 官方文档

申请运行时权限

可以调用ContextCompat.checkSelfPermission()方法来检查是否有相应的权限,如果有返回 PackageManager.PERMISSION_GRANTED,并且应用可以继续操作。如果没有,返回 PERMISSION_DENIED,且应用必须明确向用户要求权限。

可以调用shouldShowRequestPermissionRationale()方法来向用户解释需要某些权限。如果用户第一次拒绝了权限,第二个需要这个权限的时候,该方法返回true。

可以调用requestPermissions()来申请权限。(此时系统会显示标准对话框让用户选择是否授权,我们无法更改)

可以重写onRequestPermissionsResult() 方法来了解用户选择的结果。

开发模型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
// 没有权限。
if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.READ_CONTACTS)) {
// 用户拒绝过这个权限了,应该提示用户,为什么需要这个权限。
} else {
// 申请授权。
ActivityCompat.requestPermissions(thisActivity, new String[]{Manifest.permission.READ_CONTACTS}, MMM);
}
}

...

@Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
switch (requestCode) {
case MMM: {
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// 权限被用户同意,可以去放肆了。
} else {
// 权限被用户拒绝了,洗洗睡吧。
}
return;
}
}
}

可以看到,上面的逻辑还是比较复杂的。而且,由于OEM厂商会在定制ROM上各种改,导致权限获取不正常,以致出现各种各样的 bug。我本人就因为在一加5T上测试上面的代码时,点击了拒绝权限,但没有任何的 Toast 提示,白白浪费了4个小时的时间研究。后来用 AOSP 测试通过了,才发现是一加改了系统的权限通知框。

解决上述问题,推荐开源库:AndPermission

项目地址: https://github.com/yanzhenjie/AndPermission

使用方法可参考:Android 6.0 运行时权限管理最佳实践


访问其他程序中的数据

未完待续

创建内容提供器

未完待续

Android开发中的一些坑

Gradle 加载慢的问题

第一次加载项目很慢一直显示Building “XXXX” Gradle project info

解决办法:

打开{your project}/gradle/wrapper/gradle-wrapper.properties

查看distributionUrl中 gradle 版本

https://services.gradle.org/distributions/ 下载相应版本的Gradle(官网地址:https://gradle.org/install)

放到以下目录即可

  • Linux:~/.gradle/wrapper/dists
  • Windows:C:\users\{user name}\.gradle\wrapper\dists

运行时权限在部分Android手机上无效

问题

按照 Google 文档的开发模型,写的运行时权限模型代码,在一加手机5T上,点击拒绝后没有任何提示,也就是说依然返回了有权限的STATUE_CODE,但是什么都没发生。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
// 没有权限。
if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.READ_CONTACTS)) {
// 用户拒绝过这个权限了,应该提示用户,为什么需要这个权限。
} else {
// 申请授权。
ActivityCompat.requestPermissions(thisActivity, new String[]{Manifest.permission.READ_CONTACTS}, MMM);
}
}

...

@Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
switch (requestCode) {
case MMM: {
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// 权限被用户同意,可以去放肆了。
} else {
// 权限被用户拒绝了,洗洗睡吧。
}
return;
}
}
}

解决办法

很多国产ROM都有这个坑。因此不要用上面的开发模型,推荐开源库:AndPermission

项目地址: https://github.com/yanzhenjie/AndPermission

使用方法可参考:Android 6.0 运行时权限管理最佳实践


Android Device Monitor 打不开的问题

查看Log:C:\Users\Jerrysheh\AppData\Local\Android\Sdk\tools\lib\monitor-x86_64\configuration\1520661867795.log

关键报错

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
!ENTRY org.eclipse.osgi 4 0 2018-03-10 14:04:28.224
!MESSAGE Bundle reference:file:org.apache.ant_1.8.3.v201301120609/@4 not found.

!ENTRY org.eclipse.osgi 4 0 2018-03-10 14:04:28.255
!MESSAGE Bundle reference:file:org.apache.jasper.glassfish_2.2.2.v201205150955.jar@4 not found.

!ENTRY org.eclipse.osgi 4 0 2018-03-10 14:04:28.255
!MESSAGE Bundle reference:file:org.apache.lucene.core_2.9.1.v201101211721.jar@4 not found.

!ENTRY org.eclipse.osgi 4 0 2018-03-10 14:04:28.458
!MESSAGE Bundle reference:file:org.eclipse.help.base_3.6.101.v201302041200.jar@4 not found.

!ENTRY org.eclipse.osgi 4 0 2018-03-10 14:04:28.474
!MESSAGE Bundle reference:file:org.eclipse.help.ui_3.5.201.v20130108-092756.jar@4 not found.

!ENTRY org.eclipse.osgi 4 0 2018-03-10 14:04:28.474
!MESSAGE Bundle reference:file:org.eclipse.help.webapp_3.6.101.v20130116-182509.jar@4 not found.

!ENTRY org.eclipse.osgi 4 0 2018-03-10 14:04:28.474
!MESSAGE Bundle reference:file:org.eclipse.jetty.server_8.1.3.v20120522.jar@4 not found.

!ENTRY org.eclipse.osgi 4 0 2018-03-10 14:04:28.521
!MESSAGE Bundle reference:file:org.eclipse.ui.intro_3.4.200.v20120521-2344.jar@4 not found.

java.lang.IllegalStateException: Unable to acquire application service. Ensure that the org.eclipse.core.runtime bundle is resolved and started (see config.ini).
at org.eclipse.core.runtime.internal.adaptor.EclipseAppLauncher.start(EclipseAppLauncher.java:74)
at org.eclipse.core.runtime.adaptor.EclipseStarter.run(EclipseStarter.java:353)
at org.eclipse.core.runtime.adaptor.EclipseStarter.run(EclipseStarter.java:180)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.base/java.lang.reflect.Method.invoke(Unknown Source)
at org.eclipse.equinox.launcher.Main.invokeFramework(Main.java:629)
at org.eclipse.equinox.launcher.Main.basicRun(Main.java:584)
at org.eclipse.equinox.launcher.Main.run(Main.java:1438)
at org.eclipse.equinox.launcher.Main.main(Main.java:1414)

解决办法

卸载 JRE 9。 (JDK 9 可以不用卸载) , 然后装 JDK 8 。

然后启动 Android Studio 时,使用管理员权限打开。

Android笔记(五)持久化技术

什么是持久化技术

持久化技术指的是将内存中产生的瞬时数据保存到存储设备中,这样,当手机关机再重启,这些数据不会丢失。Android的持久化技术提供了一套机制,让数据在瞬时状态和持久状态之间进行转换。

Android 主要提供了 3 种数据持久化功能,分别是:

  • 文件存储:顾名思义,用于保存文本或二进制数据等文件

  • SharedPreference存储:保存相对较小的键值集合

  • 数据库存储:将数据保存到数据库


文件存储

File 对象适合按照从开始到结束的顺序不跳过地读取或写入大量数据。 例如,它适合于图片文件或通过网络交换的任何内容。

早期的Android设备,由于内置存储空间非常有限(2011年买的Samung Galaxy S+只有 8 G 存储空间),因此通常都会外置SD卡。所以,Android的存储分为内部和外部。也有一些设备,虽然不支持SD卡,但依然人为地把存储空间分为外部和内部。一般来说,我们推荐把文件保存在内置存储当中,因为它始终可用,且只有自己的应用才能访问此处保存的文件,当自己的应用被卸载时,这些文件也会被移除。

如果要将文件写入外部存储中,请参考官方文档

将文件保存在内部存储中

无需任何权限,即可在内部存储中保存文件。

  • 可以用 getFilesDir() 方法返回表示应用的内部目录的 File 。 用getCacheDir()方法返回表示应用临时缓存文件的内部目录的 File。

关于写入文件和写入缓存,请参考官方文档

保存文本示例

首先定义一个 EditText

activity_main.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">

<EditText
android:id="@+id/edit_text_name_input"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="input your name"
android:padding="4dp"
android:textSize="24sp" />

</LinearLayout>

然后重写onCreate方法和onDestroy方法

Context类提供了一个 FileOutputStream 类型的 openFileOutput()方法,用于输出数据到文件。

MainActivity.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class MainActivity extends AppCompatActivity {
private EditText edit;

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

// edit 实例
edit = findViewById(R.id.edit_text_name_input);

}

@Override
protected void onDestroy(){
super.onDestroy();

// 获取 edit 的内容
String inputText = edit.getText().toString();
save(inputText);
}

public void save(String inputText){
FileOutputStream outputStream;

try {
// 输出流
outputStream = openFileOutput("myName", Context.MODE_PRIVATE);
outputStream.write(inputText.getBytes());
outputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}

这样当我们退出app,edittext的文本就会被自动保存起来。

从保存的文件中读取数据

类似于输出流,Context类也提供了一个 FileinputStream 类型的 openFileinput()方法,用于输出数据到文件。

openFileinput(filename)的参数是文件名,一旦指定,系统会自动从 /data/data//files/ 目录下加载这个文件,并返回一个 FileinputStream 对象。

一个完整的示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
public class MainActivity extends AppCompatActivity {

private EditText dataEditText;
private String inputText;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
dataEditText = findViewById(R.id.editText);
inputText = load();

// TextUtils.isEmpty方法:当 inputText 为 null 或 空 时返回 true
if (!TextUtils.isEmpty(inputText)){
dataEditText.setText(inputText);
dataEditText.setSelection(inputText.length());
Toast.makeText(this, "恢复数据成功", Toast.LENGTH_SHORT).show();
}

}

// 从文件加载数据
private String load() {
StringBuilder content = new StringBuilder();
String line;

try (
FileInputStream fileInputStream = openFileInput("data");
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(fileInputStream))
){
while( (line = bufferedReader.readLine()) != null ){
content.append(line);
}
} catch (IOException IOe){
IOe.printStackTrace();
}
return content.toString();
}

@Override
protected void onDestroy() {
super.onDestroy();
String inputText = dataEditText.getText().toString();
saveText(inputText);
}

//保存数据到文件
private void saveText(String inputText) {
try(
FileOutputStream outputStream = openFileOutput("data",Context.MODE_PRIVATE);
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream))
){
bufferedWriter.write(inputText);
} catch ( IOException e){
e.printStackTrace();
}
}
}

思路:

  • onCreate()方法中调用 load() 来读取文件中的数据
  • onDestroy()方法中调用 saveText() 来保存数据到文件

SharedPreference存储

文件存储还是比较麻烦的,SharedPreference可以用键值对的方式来存储数据。

  • 保存数据时,给数据提供一个键
  • 读取数据时,根据键找到值

在 Android 中,有三种方法得到 SharedPreference 对象:

  • Context 类中的 getSharePreference(filename, mode) 方法

参数1是存储的文件名,目录在/data/data/<package name>/share_prefs/ ,参数2是模式,默认 MODE_PRIVATE

  • Activity 类中的 getPreferences(mode) 方法

只有一个mode参数,因为这个方法会把当前类名作为 filename

  • PreferenceManager 类中的 static getDefaultSharedPreferences(Context)方法

Android笔记(四) Broadcast

什么是 Broadcast Receiver

广播接收器(Broadcast Receiver)允许你的应用接收来自各处的广播消息,比如开机广播,电池电量变化广播,时间或地区变化广播,以及来自电话、短信和其他app发出的广播消息等等。同样,我们的应用也可以向外发出广播消息。

Android中的广播可以分为以下两种:

  • 标准广播(Normal Broadcast),一种完全异步执行的广播,当广播发出后,所有的广播接收器几乎同一时刻接收到这条广播信息,没有先后顺序之分。这种广播效率高,但无法截断。

  • 有序广播(Ordered Broadcast),一种同步执行的广播,当广播发出后,同一时刻只有一个广播接收器收到这条广播,当这个广播接收器的逻辑执行完毕后,广播才会继续传递。这样一来,优先级高的广播接收器可以先收到广播,并且前面的广播接收器可以截断正在传递的广播。

阅读更多

Android笔记(三) Intent 和 Activity的生命周期、启动模式

Intent

Intent 是一个消息传递对象。它是 Android 程序中各组件之间进行交互的一种重要方式,它不仅可以指明当前组件想要执行的动作,还可以在不同组件之间传递数据。

关键词: 指明要执行的动作传递数据

Intent 的基本使用场景

  • 启动 Activity
    Activity 表示应用中的一个屏幕。通过将 Intent 传递给 startActivity(),您可以启动新的 Activity 实例。Intent 描述了要启动的 Activity,并携带了任何必要的数据。

    如果希望在 Activity 完成后收到结果,请调用 startActivityForResult()。在 Activity 的 onActivityResult() 回调中,您的 Activity 将结果作为单独的 Intent 对象接收。

  • 启动服务
    Service 是一个不使用用户界面而在后台执行操作的组件。通过将 Intent 传递给 startService(),您可以启动服务执行一次性操作(例如,下载文件)。Intent 描述了要启动的服务,并携带了任何必要的数据。

    如果服务旨在使用客户端-服务器接口,则通过将 Intent 传递给 bindService(),您可以从其他组件绑定到此服务。

  • 传递广播
    广播是任何应用均可接收的消息。系统将针对系统事件(例如:系统启动或设备开始充电时)传递各种广播。通过将 Intent 传递给 sendBroadcast()、sendOrderedBroadcast() 或 sendStickyBroadcast(),您可以将广播传递给其他应用。

参阅:官方文档 - Intent 和 Intent 过滤器

使用显式Intent

显式 Intent 指的是明确地按名称(完全限定类名)指定要启动的组件。比如说,如果我们想在FirstActivity这个活动中打开SecondActivity,我们可以在FirstActivity中的一个按钮点击中调用StartActivity,传入intent对象。

1
2
3
4
5
6
7
8
Button buttonIntent = (Button) findViewById(R.id.button_intent);
buttonIntent.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(MainActivity.this,SecondActivity.class);
startActivity(intent);
}
});
  • 定义一个按钮
  • 在按钮点击事件中 new 一个 intent 对象
  • 调用 startActivity,传入 intent 对象

使用隐式Intent

隐式 Intent 不会指定特定的组件,而是声明要执行的常规操作,从而允许其他应用中的组件来处理它。 例如,如需在地图上向用户显示位置,则可以使用隐式 Intent,请求另一具有此功能的应用在地图上显示指定的位置。

创建隐式 Intent 时,Android 系统通过将 Intent 的内容与在设备上其他应用的清单文件中声明的 Intent 过滤器进行比较,从而找到要启动的相应组件。 如果 Intent 与 Intent 过滤器匹配,则系统将启动该组件,并向其传递 Intent 对象。 如果多个 Intent 过滤器兼容,则系统会显示一个对话框,支持用户选取要使用的应用。

在AndroidManifest.xml中,把SecondActivity段修改如下

1
2
3
4
5
6
7
<activity android:name=".SecondActivity">
<intent-filter>
<action android:name="com.jerrysheh.hello.ACTION_START" />
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="com.jerrysheh.hello.MY_CATEGORY"/>
</intent-filter>
</activity>

在<intent-filter>中增添了actioncategory段,只有 action 和 category 同时匹配才能响应该Intent。每个Intent中只能指定一个action,但能指定多个category。

  • action:声明接受的 Intent 操作。
  • category:声明接受的 Intent 类别。

除了actioncategory外,还有一个data,请参考官方文档

修改按钮点击事件, new Intent对象,因为我们想启动能到响应为com.jerrysheh.hello.ACTION_START这个action的活动,因此参数填入com.jerrysheh.hello.ACTION_START

1
2
3
4
5
6
7
8
9
Button buttonIntent = (Button) findViewById(R.id.button_intent);
buttonIntent.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent("com.jerrysheh.hello.ACTION_START");
intent.addCategory("com.jerrysheh.hello.MY_CATEGORY");
startActivity(intent);
}
});

或者,可以这样写,利用intent.setAction方法。

1
2
3
4
5
6
7
8
9
10
Button buttonIntent = (Button) findViewById(R.id.button_intent);
buttonIntent.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent();
intent.setAction("com.jerrysheh.hello.ACTION_START");
intent.addCategory("com.jerrysheh.hello.MY_CATEGORY");
startActivity(intent);
}
});

如果intent.addCategory指定的Category没有一个活动能够匹配,那么程序会抛出异常。稍作修改,用resolveActivity()方法来判断是否有应用能响应。假设没有活动匹配,就不启动startActivity();

1
2
3
4
5
6
7
8
9
10
11
12
13
Button buttonIntent = (Button) findViewById(R.id.button_intent);
buttonIntent.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent("com.jerrysheh.hello.ACTION_START");
intent.addCategory("com.jerrysheh.hello.MY_CATEGORY");

if (intent.resolveActivity(getPackageManager()) != null) {
startActivity(intent);
}

}
});

Intent的更多用法

调用浏览器和拨号

new 一个 Intnet 对象后, 用 intent.setData(uri) 方法可以调用其他程序

比如调用浏览器打开 github

1
2
3
Intent intent = new Intent("com.jerrysheh.hello.ACTION_START");
intent.setData(Uri.parse("https://www.github.com"));
startActivity(intent);

调用系统拨号拨打10010

1
2
3
Intent intent = new Intent(Intent.ACTION_DIAL);
intent.setData(Uri.parse("tel:10010"));
startActivity(intent);

可以在 AndroidManifest 的 <intent - filter>标签中配置 标签, 指定当前活动可以响应什么类型的数据。这样其他app响应这种数据的时候,Android系统会弹出选项,你的app会在可选列表里面

1
2
3
4
5
6
7
8
<activity android:name=".SecondActivity">
<intent-filter>
<action android:name="com.jerrysheh.hello.ACTION_START" />
<category android:name="android.intent.category.DEFAULT"/>
<data android:schme="https" />
<data android:host="www.zhihu.com" />
</intent-filter>
</activity>

这样, 你的app可以响应知乎网站的浏览器调用


使用Intent传递数据

向下一个活动传递数据

可以用 intent 的 putExtra 方法向下一个活动传递数据。核心思想是,把数据存在String里,通过intent参数传递给下一个活动,下一个活动启动后从intent取出。

存放 (MainActivity.java)

1
2
3
4
String data = "this is data"
Intent intent = new Intent("com.jerrysheh.hello.ACTION_START");
intent.putExtra(Intent.EXTRA_TEXT, data);
startActivity(intent);

取出 (SecondActivity.java)

1
2
Intent intent = getIntent();
String data = intent.getStringExtra(Intent.EXTRA_TEXT);

返回数据给上一个活动

Activity中有一个StartActivityForResult()方法用于启动一个活动,但期望活动销毁后(通常是按下返回键或调用finish()方法)返回一个结果给上一个活动。

MainActivity.java

1
2
Intent intent = new Intent(MainActivity.this,SecondActivity.class);
StartActivityForResult(intent, 1);

SecondActivity.java

1
2
3
4
Intent intent = new Intent();
intent.putExtra("data_return", "this is back data");
setResult(RESULT_OK, intent);
finish();

当然,返回数据后,会回调MainActivity的onActivityResult()方法,因此我们还需要重写这个方法拿到SecondActivity返回来的数据。

1
2
3
4
5
6
7
8
9
10
11
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch(requestCode) {
case 1:
if (requestCode == RESULT_OK) {
String returnData = data.getStringExtra("data_return");
...
}
break;
}
}

使用 intent 传递对象

上面的 intent 只能传 String, 如果我们有一个 javabean 对象需要传递,怎么做呢?

首先将实体类(bean)实现 Serializable 接口。注意: 如果 bean 里面嵌套了 bean,内部类也要声明为实现 Serializable 接口。

1
2
3
4
5
6
7
8
9
public class bean implements Serializable {
int a;
int b;
String c;
Heybean d;
public static class Heybean implements Serializable {
...
}
}

传递 activity

1
2
3
Intent intent = new Intent(context, DetailActivity.class);
intent.putExtra("name",detailbean);
context.startActivity(intent);

接收 activity

1
Bean detailBean = (Bean) getIntent().getSerializableExtra("name");

Activity的生命周期

Android 用 任务(Task)来管理活动,一个Task就是一组存放在返回栈(Back Stack)里的活动的集合。系统总是会显示处于栈顶的活动给用户。

Activity的四种状态

  • 运行状态
  • 暂停状态(弹出式卡片,背景活动就是暂停状态)
  • 停止状态
  • 销毁状态

Activity的生存期

Toast

  • 完整生存期
    活动在onCraete()onDestroy()之间经历的,就是一个完整生存期。

  • 可见生存期
    活动在onStart()onStop()之间经历的,就是一个可见生存期。onStart()方法在活动从不可见变为可见时调用,onStop()反之。

  • 前台生存期
    活动在onResume()onPause()之间经历的,就是一个前台生存期。onResume()方法在活动准备好和用户交互时调用。当系统准备去启动或恢复另一个活动时,onPause()将当前活动一些消耗CPU的资源释放,同时保存关键数据。

此外,还有一个onRestart(),用于活动从停止状态变为运行状态之前调用,也就是活动被重新启动。

当系统内存不足时,用户按下back键返回到上一个Activity,有可能上一个Activity已经被系统回收,这时不会执行onRestart(),而是执行onCreate()。遇到这种情况,如果上一个Activity有数据,那这些数据都丢失了,这是很影响用户体验的。解决办法是调用onSaveInstantState()回调方法。具体参见《第一行代码》第二版p62,以及Activity Google官方文档(推荐)

Activity的启动模式

Activity有四种启动模式,可以在 AndroidManifest.xml 的 标签中修改

1
2
3
4
5
6
7
8
<activity
android:name=".SecondActivity"
android:launchMode="singleTop">
<intent-filter>
<action android:name="com.jerrysheh.hello.ACTION_START" />
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>

android:launchMode可填以下四种模式

  • standard

标准模式,在MainActivity中启动MainActivity,会重复创建MainActivity的新实例。如创建了3个MainActivity的实例,需要按3次返回键才能完全退出。

  • singleTop

    如果MainActivity已经在栈顶,启动MainActivity则不会重复创建新实例。但MainActivity不在栈顶,还是会创建新实例。

  • singleTask

无论MainActivity是否在栈顶,在整个应用程序上下文中只存在一个MainActivity实例。

  • singleInstance

    单独创建一个返回栈存放该实例。解决不同应用共享一个返回栈的问题。

    参考:Google官方文档

Android笔记(二) Activity和布局

Activity

Activity 是用户可以执行的单一任务。负责创建新的窗口,供应用绘制和从系统中接收事件。

Activity 是用 Java 编写的,扩展自 Activity 类。

Activity 会创建视图来向用户显示信息,并使用户与 Activity 互动。视图是 Android UI 框架中的类。它们占据了屏幕上的方形区域,负责绘制并处理事件。Activity 通过读取 XML 布局文件确定要创建哪些视图(并放在何处)。这些 XML 文件存储在标记为 layouts 的 res 文件夹内。

参阅Activity Google官方文档


布局 XML

Android 项目中的布局在 res/layouts 目录下的 XML 文件编写。

示例

1
2
3
4
5
6
7
8
9
10
11
<TextView
android:id="@+id/hello"
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="确定"/>
阅读更多