在之前的文章中我们介绍了 Activity 的生命周期,包括正常情况下 Activity 的生命周期,以及多个 Activity 交互和横竖屏切换 Activity 之间的生命周期,还介绍了如何启动系统的 Activity,Activity 之间简单的数据交互,以及传递大数据时遇到的问题,还有就是 Activity 的四种启动模式,如果你对 Activity 还不太了解,那么可以先阅读之前的两篇关于 Activity 的相关介绍:


Android Activity 完全解析(上)

Android Activity 完全解析(中)


以上两篇对我们刚才提到的知识点进行了相关的总结,今天我们一起来学习更多的 Activity 更多的功能技术,以便对 Activity 有一个更加深入地了解,好了,下面我们开始来演绎,我们对今天要分享的内容也列一个提纲,让大家对今天的内容有一个概览



我们来创建一个 ActivityTest 来完成今天的演绎


一、在活动中使用 Menu


手机和电脑不同,屏幕空间有限,因此充分利用手机的屏幕空间就显得特别重要,如果你的活动中有大量菜单要显示,这个时候界面设计就会比较尴尬,Android 给我们提供了一种方式,可以让菜单得到展示的同时,不占用屏幕的任何空间

首先,在 res 目录下新建一个 menu 文件夹,接着在文件夹下新建一个叫 main.xml 的菜单文件,代码如下:


<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/add"
android:title="add" />
<item
android:id="@+id/remove"
android:title="remove" />
</menu>

这里我们创建了两个菜单项,其中 <item> 就是用来创建具体的某一个菜单项的,然后指定相应的 id 和 title

接着我们回到 MainActivity 中来重写 onCreateOptionMenu() 方法如下:


    @Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return true;
}

通过 getMenuInflater() 方法得到 MenuInflater 对象,再调用 inflate() 方法。就可以该当前的活动创建菜单了,inflate() 方法接受两个参数,第一个参数用于指定我们通过那一个资源文件来创建菜单,第二个参数用于指定我们的菜单项将添加到那一个 Menu 对象当中,这里直接使用 onCreateOptionMenu() 方法中传入的 menu,并返回 true,如果返回 false,菜单将无法显示

接下来我们来定义菜单的响应事件,我们在 MainActivity 中重写 onOptionsItemSelected() 方法,代码如下:


    @Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.add:
Toast.makeText(MainActivity.this, "点击all了", Toast.LENGTH_SHORT).show();
break;
case R.id.remove:
Toast.makeText(MainActivity.this, "点击remove了", Toast.LENGTH_SHORT).show();
break;
default:
}
return true;
}

在 onOptionsItemSelected() 方法中,通过调用 item.getItemId 来判断我们点击的是那一个菜单项,然后给每一个菜单项加入相应的逻辑处理,这我们简单的弹一个 Toast,这样我们的菜单栏就创建完成了,我们来一起总结一下,首先,编写 menu 的菜单文件,然后复写 onCreateOptionMenu() 和 onOptionsItemSelected() 方法,在 onCreateOptionMenu() 方法中加载 menu 资源文件,onOptionsItemSelected() 方法中处理具体的每一项菜单的响应事件,总体如下:


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

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

/**
* 重写 onCreateOptionsMenu() 方法
*
* @param menu
* @return
*/
@Override
public boolean onCreateOptionsMenu(Menu menu) {
//通过 getMenuInflater() 方法得到MenuInflater对象
//并调用它的 inflate() 方法加载菜单资源文件
getMenuInflater().inflate(R.menu.main, menu);
return true;
}

/**
* 重写 onOptionsItemSelected() 方法
*
* @param item
* @return
*/
@Override
public boolean onOptionsItemSelected(MenuItem item) {
//调用item.getItemId()判断点击的是那一个菜项,并加入相应的处理逻辑
switch (item.getItemId()) {
case R.id.add:
Toast.makeText(MainActivity.this, "点击all了", Toast.LENGTH_SHORT).show();
break;
case R.id.remove:
Toast.makeText(MainActivity.this, "点击remove了", Toast.LENGTH_SHORT).show();
break;
default:
}
return true;
}
}

到这里我们就彻底完成了,一起来看效果图:




二、使用显式 Intent 和隐式 Intent


Intent 是 Android 程序中各组件之间进行交互的重要方式,它不仅可以指明当前组件想要执行的动作,还可以在不同组件之间传递数据,Intent 一般可用于启动活动,启动服务,以及发送广播等场景,Intent 大致可以分为两种:显式 Intent 和隐式 Intent,下面我们来详细介绍


1)显式 Intent


Intent 有多个构造函数的重载,其中一个是 Intent(Context packageContext ,Class<?> cls),这个构造函数接受两个参数,Context 上下文,第二个参数 Class 则是指定想要启动的目标活动,通过这个函数我们就可以构建出 Intent 的意图,并通过 startActivity() 方法就可以启动目标活动

这里我们来创建 FristActivity 和 SecondActivity 来具体演示:


FristActivity 中的代码:


public class FristActivity extends AppCompatActivity {

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

/**
* 使用显式 Intent
*
* @param view
*/
public void jumpToSecondActivity(View view) {
Intent intent = new Intent(FristActivity.this, SecondActivity.class);
startActivity(intent);
}
}

activity_frist.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">

<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="jumpToSecondActivity"
android:text="butten1" />
</LinearLayout>

SecondActivity 中的代码:

public class SecondActivity extends AppCompatActivity {

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

由于上面的代码都特别简单,我们就不再过多的做介绍,直接来说明显示Intent的使用,我们首先构建出一个 Intent,传入 FirstActivity.this 作为上下文,传入 SeconActivity.class 作为目标活动,通过 startActivity() 方法来执行这个 Intent,这样一个完整的显式 Intent 使用我们就介绍完了

2)隐式 Intent


相比于显示 Intent,隐式 Intent 并不明确指出我们想要启动那一个活动,而是指定了一系列更为抽象的 action 和 category 等信息,然后交由系统去分析这个 Intent,并帮助我们找出合适的活动去启动

通过 <activity> 标签下配置 <intent - filter> 的内容,可以指定当前活动能够响应的 action 和 category,打开 AndroidManifest.xml,添加如下代码:


        <activity android:name=".SecondActivity">
<intent-filter>
<action android:name="com.example.qiudengjiao.activitytest.ACTION_START" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>

在 <action> 标签中我们指明了当前活动可以响应 com.example.qiudengjiao.activitytest.ACTION_START 这个 action,而 <category> 标签则包含了一些附加信息,更精确的指明了当前活动能够响应的 Intent 中还可能带有的 category,只有 <action> 和 <category> 中的内容同时能够匹配上 Intent 中指定的 action 和 category 时,这个活动才能够响应该 Intent


修改 FristActivity 中的点击事件代码如下:

    /**
* 使用隐式 Intent
*
* @param view
*/
public void jumpToSecondActivity(View view) {
Intent intent = new Intent("com.example.qiudengjiao.activitytest.ACTION_START");
startActivity(intent);
}

可以看到我们使用了 Intent 的另一个构造函数,直接将 action 的字符串传了进去,表明我们想要启动能够响应com.example.qiudengjiao.activitytest.ACTION_START 这个 action 的活动,那前面不是说要 <action> 和 <category> 中的内容同时能够匹配上才能响应吗?怎么没有看到哪里指定 category 呢?这是因为 android.intent.category.DEFAULT 是一种默认的 category,在调用 startActivity() 方法的时候会自动将这个 category 添加到 Intent 中

重新运行程序,我们同样成功的启动 SecondActivity,不同的是我们这次采用的是隐式 Intent 的方式来启动的说明我们配置的 <action> 和 <category> 已经生效了

每个 Intent 中只能指定一个 action,但是却能指定多个 category,我们刚才的实例中只有一个默认的 category,现在我们来增加一个:

代码如下:


    /**
* 使用隐式Intent
*
* @param view
*/
public void jumpToSecondActivity(View view) {
Intent intent = new Intent("com.example.qiudengjiao.activitytest.ACTION_START");
intent.addCategory("com.example.qiudengjiao.activitytest.MY_ACTEGORY");
startActivity(intent);
}

调用 addCategory() 方法来添加一个 category,这里我们指定了一个自定义的 category,值为com.example.qiudengjiao.activitytest.MY_ACTEGORY,这时候重新运行程序,发现程序崩溃,打印了如下日志:


错误的信息提示我们没有找到可以响应我们活动的 Intent,这就是我们刚刚在 Intent 增加了 category,而 SecondActivity 的 <intent - filter> 中并没有声明可以响应这个 category 的标签,所以就出现了没有任何活动可以响应的情况,现在我们在 <intent - filter> 再添加一个 category 的声明,如下所示:


        <activity android:name=".SecondActivity">
<intent-filter>
<action android:name="com.example.qiudengjiao.activitytest.ACTION_START" />

<category android:name="android.intent.category.DEFAULT" />
<category android:name="com.example.qiudengjiao.activitytest.MY_ACTEGORY" />
</intent-filter>
</activity>

这样就解决了程序崩溃的问题


3)更多隐式 Intent 的用法


上面我们学习了通过隐式 Intent 来启动活动的用法,但只是简单的介绍,现在我们接着来看更多隐式 Intent 的用法

使用隐式 Intent 我们不仅可以启动自己程序内的活动,还可以启动其他程序的活动,这使得 Android 多个应用程序之间的共享成为可能,比如说你的应用程序中需要展示一个网页,这时你没有必要去实现一个浏览器,而是调用系统的浏览器来打开这个网页就可以了,修改 FirstActivity 中按钮点击事件的代码,如下所示:


    /**
* 使用隐式 Intent
*
* @param view
*/
public void jumpToSecondActivity(View view) {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("https://www.baidu.com/"));
startActivity(intent);
}

这样我们就能调用系统的浏览器来打开我们需要打开的相应的网页,与此同时我们还可以在 <intent - filter> 标签中再配置一个 <data> 标签,用于更精确的指定当前活动能够响应什么类型的数据,<data> 标签中主要可以配置如下内容:
  • android: acheme,用于指定数据的协议部分,如上例中 http 部分
  • andriod: host,用于指定数据的主机名部分,如上例中的 www.baidu.com 部分
  • android: port,用于指定数据的端口部分,一般紧随在主机名之后
  • android: path,用于指定主机名和端口之后的部分,如一段网址中跟在域名之后的内容,
  • andorid: mimeType,用于指定可以处理的数据类型,允许使用通配符的方式进行指定
只有 <data> 标签中指定的内容和 Intent 中携带的 Data 完全一致时,当前活动才能够响应该 Intent,不过一般在 <data> 标签中都不会指定过多的内容,如上面浏览器,只需要指定 andorid:scheme 为 http,就可以响应所有的 http 协议的 Intent 了

还可以指定其他的协议,比如 tel 表示拨打电话,就可以调用系统拨打电话界面如下:


    /**
* 使用隐式 Intent
*
* @param view
*/
public void jumpToSecondActivity(View view) {
Intent intent = new Intent(Intent.ACTION_DIAL);
intent.setData(Uri.parse("tel:10086"));
startActivity(intent);
}

首先指定了 Intent 的 action 是 Intent.ACTION_DIAL,这又是一个 Andoid 系统的内置动作,然后在 data 部分指定了协议 tel,点击按钮,调用界面如下:





三、返回数据给上一个活动


活动之间可以相互传递数据,我们之前的文章中也介绍了 Activity 如何传递数据,包括简单数据,Bundle 对象,已经 JavaBean 等的传递,但这些都是从上一个活动传递到下一个活动,今天我们来学习如何返回数据给上一个活动

既然可以传递数据给下一个活动,那么肯定也可以返回数据给上一个活动,不过不同的是返回上一个活动,按一下 back 键就可以了,并没有一个用于启动活动 Intent 来传递数据,通过文档我们发现,Activity 中还有一个 startActivityForResult() 方法也是用于启动活动的,这就是我们所需要的


startActivityForResult() 方法接受两个参数,第一个参数还是 Intent,第二个参数是请求码,用于在之后的回调中判断数据的来源,接下来我们来实战一下:


修改 FristActivity 按钮中的点击事件,代码如下:


    public void jumpToSecondActivity(View view) {
Intent intent = new Intent(FristActivity.this, SecondActivity.class);
startActivityForResult(intent, 1);
}

这里我们使用了 startActivityForResult() 方法来启动 SecondActivity,请求码只要是一个唯一的值就可以了,这里传入了 1,接下来我们在 SecondActivity 中注册点击事件,并在点击事件中添加返回数据的逻辑,代码如下:


    public void returnClick(View view) {
Intent intent = new Intent();
intent.putExtra("key", "Hello FristActivity");
setResult(RESULT_OK, intent);
finish();
}

可以看到,我们还是构建了一个 Intent,只不过这个 Intent 只是用来传递数据而已,紧接着把要传递的数据存放在 Intent 中,然后调用了 setResult() 方法,这个方法非常重要,是专门向上一个活动返回数据的,setResult() 方法接受两个参数,第一个参数用于向上一个活动返回处理结果,一般只使用 RESULT_OK 或 RESULT_CANCELED 这两个值,第二个参数则把带有数据的 Intent 传递回去,然后调用 finish() 方法销毁当前活动


由于我们是使用 startActivityForResult() 方法来启动 SecondActivity 的,在 SecondActivity 被销毁之后会回调上一个活动的 onActivityResult() 方法,因此我们需要在 FristActivity 中重写这个方法来得到返回的数据,代码如下:


    @Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {

switch (requestCode) {
case 1:
if (resultCode == RESULT_OK) {
String resultData = data.getStringExtra("key");
Log.i(TAG, resultData);
}
break;
default:
}
}

onActivityResult() 方法带有 3 个参数,第一个参数 requestCode,即我们在启动活动时传入的请求码,第二个参数 resultCode,即我们在返回数据时传入的处理结果,第三个参数 data,即携带着返回数据的 Intent,由于在一个活动中有可能调用 startActivityForResult() 方法去启动很多不同的活动,每一个活动返回数据都会回调到 onActivityResult() 这个方法中因此我们首先要做的就是通过检查 requestCode 的值来判断数据的来源,确定数据是从 SecondActivity 返回的之后,我们再通过 resultCode 的值来判断处理结果是否成功,最后从 data 中取值并打印出来,这样就完成了向上一个活动返回数据的工作,从新运行程序,在 FirstActivity 的界面点击按钮会打开 SecondActivity,然后在 SecondActivity 界面点击按钮,会回到 FirstActivity,来看看我们的日志打印情况:


这是我们看到,数据已经成功返回了,这时候还需要注意得是,如果用户不是通过点击按钮来返回的,而是通过 back 键来返回的,这样数据就没法返回了,不过这种情况也比较好处理,我们可以在 SecondActivity 中重写 onBackPressed() 方法来解决这个问题,代码如下:


    @Override
public void onBackPressed() {
Intent intent = new Intent();
intent.putExtra("key", "Hello FristActivity");
setResult(RESULT_OK, intent);
finish();
}

这样的话当用户按下 Back 键,就会去执行 onBackPressed() 方法中的代码,我们在这里添加返回数据的逻辑就行了


四、活动的实践技巧

到这里我们已经掌握了活动的很多知识,不过离完全灵活应用,还有一段距离,虽然知识点就是这么多,但运用技巧却是多重多样,接下来我们来学习几个技巧,这在以后的开发中将会非常受用


1)知晓当前是那一个活动


这个技巧,将会教会我们如何根据当前的界面就能判断出这是那一个活动,可能我们对这个问题比较迷惑,我们自己写的代码怎么会不知道是对应的那一个活动,但是,如果这个活动不是我们写的那,当我们进入一个新的公司,接手一份全新的代码,阅读别人的代码有一个很头疼的问题,就是当你需要在某一个界面上修改一些非常简单的东西时,却半天找不到这个界面对应的活动时那一个,掌握我们现在的这个技巧,这种问题以后将会非常简单


我们还是在 ActivityTest 项目的基础上进行修改,首先我们需要新创建一个 BaseActivity 类,这里需要注意的是,我们这里创建的 BaseActivity 类并不需要在 AndroidManifest.xml 中注册,所以创建一个普通的 Java 类就可以了,让后让 BaseActivity 类继承 AppCompatActivity,并重写 onCreate() 方法,如下所示:

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

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.e(TAG, "onCreate: " + getClass().getSimpleName());
}
}

我们在 onCreate() 方法中获取了当前实例的类名,并通过 Log 打印出来,接下来我们要让 BaseAdtivity 成为我们项目中多有 Activity 的父类,这我们修改 FirstActivity 和 SecondActivity 的继承结构,让他们不再继承 AppCompatActivity,而是继承自 BaseActivity,而 BaseActivity 又继承自 AppCompatActivity,所有项目中现有活动的功能并不会受到影响我们从新运行来看打印的 Log




现在当进入到一个活动的界面,该活动类名就会被打印出来,这样我们就时时刻刻可以知道当前的额界面对应的是那一个活动了,其实我们在onResume() 中来打印更为合适,因为当从 FristActivity 开启 SecondActivity 后,我们点击返回按钮,返回FristActivity这种情况,其实FristActivity不会重新创建,也就是说不会再回调 onCreate() 方法,但肯定会回调 onResume() 方法,这也是关于生命周期回调的事情,大家可以考虑一下是不是这样更合适


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

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityManager.getInstance().addActivity(this);
}

@Override
protected void onResume() {
super.onResume();
Log.e(TAG, "onResume: " + getClass().getSimpleName());
}

@Override
protected void onDestroy() {
super.onDestroy();
ActivityManager.getInstance().removeActivity(this);
}
}

2)随时随地退出活动


如果你发现你的手机目前还停留在 SecondActivity,你会发现当前退出程序是非常不方便的,需要连续按 Back 键,这时我们考虑到如果我们的程序需要一个注销或退出功能该如何实现,必须要有一个随时随地都能退出的方案才行

这我们新建一个 ActivityManager 类作为活动管理器代码如下所示:


/**
* Activity 管理类
*/
public class ActivityManager {

//Activity存储实体
private static Stack<Activity> activityStack;
private static ActivityManager instance;

private ActivityManager() {
}

/**
* 单一实例
*/
public static ActivityManager getInstance() {

if (instance == null) {
synchronized (ActivityManager.class) {
if (instance == null) {
instance = new ActivityManager();
}
}
}
return instance;
}


/**
* 添加 Activity 到堆栈
*/
public void addActivity(Activity activity) {
if (activityStack == null) {
activityStack = new Stack<Activity>();
}
activityStack.add(activity);
}

/**
* 从堆栈移除 Activity
*/
public void removeActivity(Activity activity) {
if (activity != null) {
activityStack.remove(activity);
}
}

/**
* 获取当前 Activity(堆栈中最后一个压入的)
*/
public Activity currentActivity() {
Activity activity = activityStack.lastElement();
return activity;
}

/**
* 结束当前 Activity(堆栈中最后一个压入的)
*/
public void finishActivity() {
Activity activity = activityStack.lastElement();
finishActivity(activity);
removeActivity(activity);

}

/**
* 结束指定的 Activity
*/
public void finishActivity(Activity activity) {
if (activity != null) {
activityStack.remove(activity);
activity.finish();
}
}

/**
* 结束指定类名的 Activity
*/
public void finishActivity(Class<?> cls) {
for (Activity activity : activityStack) {
if (activity.getClass().equals(cls)) {
finishActivity(activity);
}
}
}

/**
* 结束所有 Activity
*/
public synchronized void finishAllActivity() {
for (int i = 0, size = activityStack.size(); i < size; i++) {
if (null != activityStack.get(i)) {
activityStack.get(i).finish();
}
}
activityStack.clear();
}
}

这里我们就对 Activity 有了相应的管理,接下来我们修改 BaseActivity 中的代码如下:

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

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.e(TAG, "onCreate: " + getClass().getSimpleName());
ActivityManager.getInstance().addActivity(this);
}

@Override
protected void onDestroy() {
super.onDestroy();
ActivityManager.getInstance().removeActivity(this);
}
}

在 BaseActivity 中的 onCreate() 方法中调用 ActivityManager 的 addActivity() 方法将活动添加到活动管理器中,然后在 BaseActivity 中重写 onDestroy() 方法并调用 remove() 方法来销毁活动从管理器中移除,从此以后不管你在哪里想要退出程序,只需要调用 ActivityManager 中的 finishAllActivity() 方法即可


3)启动活动的最佳写法


前面我们也已经学习了启动活动的相关方法,通过 Intent 构建出当前的意图,然后调用 startActivity() 或 startActivityForResult() 方法来将活动启动起来,需要传递数据的话也可以通过 Intent 来完成

这里我们假设一个场景,假设 SecondActivity 中需要用到两个非常重要的字符串参数,启动 SecondActivity 的时候必须传递过来,那么我们很容易会写成如下的代码:


    public void jumpToSecondActivity(View view) {
Intent intent = new Intent(FristActivity.this, SecondActivity.class);
intent.putExtra("param1", "data1");
intent.putExtra("param2", "data2");
startActivity(intent);
}

当然这样写并没有声明问题,这是在实际开发中我们经常会有对接的问题出现,比如,SecondActivity 并不是由我们自己开发的,但我们自己要负责开发启动 SecondActivity 的功能,这时候我们并不知道启动 SecondActivity 这个活动需要传递呢个参数,这是我们只有两种处理情况,就是自己去阅读 SecondActivity 中的代码,要不就是去询问 SeondActivity 的开发人员,这样就构成了沟通成本,关键是还很麻烦,其实这里我们只要换一种写法即可解决这个问题,修改 SecondActivity 中的代码如下:


public class SecondActivity extends BaseActivity {
private static final String TAG = "SecondActivity";

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

public static void startSecondActivity(Context context, String data1, String data2) {
Intent intent = new Intent(context, SecondActivity.class);
intent.putExtra("param1", "data1");
intent.putExtra("param2", "data2");
context.startActivity(intent);
}
}

我们在 SecondActivity 中添加一个 startSecondActivity() 方法,在这个方法中完成了 Intent 的构建,另外所有 SecondActivity 中需要的参数都是通过 startSecondActivity() 方法传递过来的,然后把它们存储到 Intent 中,最后调用 startActivity() 方法启动 SecondActivity

这样写的好处在于,我们可以一目了然,SecondActivity 中所需要的数据在方法中全部体现出来,这样就省去了阅读 SecondActivity 中代码的时间,也不用去负责编写 SecondActivity 代码的同事,就可以非常清楚的知道需要有哪些数据进行传递,另外这样写还简化了启动活动中的代码,现在只需要一行代码就可以启动 SecondActivity,如下所示:


    public void jumpToSecondActivity(View view) {
SecondActivity.startSecondActivity(FristActivity.this, "data1", "data2");
}

养成一个良好的编码习惯还是很重要的通过这个例子我们看出来,这样可以节省同事过来询问我们的时间,好了,今天的内容就分享到这里


参考:郭神第一行代码


更多相关文章

  1. W/System.err:at java.net.PlainDatagramSocketImpl.bind(PlainDa
  2. Android Studio获取数字签名(SHA1)的方法
  3. Android Volley:使用方法总结及实例解析
  4. [Android]如何导入已有的外部数据库
  5. eclipse下运行EasyAR官方sample的方法
  6. Android App性能信息获取方法
  7. Android Studio 检测内存泄漏与解决方法
  8. Android ListView获取当前可视区域条目数据
  9. Java操作数据库之jdbc【原生方式】

随机推荐

  1. Android开发资源完全汇总(转MatthewChen
  2. Handler机制情景分析
  3. use '@foo' to launch a virtual device
  4. android 双向滑动 稍加改进,可做成QQHD效
  5. Android Studio 下载安装教程
  6. 【Unity3D】与Android相互传递消息
  7. Android开机动画过程
  8. android TextView 阴影效果,和使用style学
  9. android测试
  10. 沉浸式状态栏StatusBar