Android开发 入门篇(一)

date: 2020-01-09 12:56:40


有些as的使用技巧、活动和部分控件的使用。
出自《第一行代码(第二版)》,用于自己学(chao)习(shu)记录

序言

新建项目的小问题

关于AS的一些小问题,如果如下报错

Error:Execution failed for task ':app:preDebugAndroidTestBuild'.
> Conflict with dependency 'com.android.support:support-annotations' in project ':app'. Resolved versions for app (26.1.0) and test app (27.1.1) differ. See https://d.android.com/r/tools/test-apk-dependency-conflicts.html for details.

build.gradleModule:app文件中的dependencies{…}中添加

androidTestCompile('com.android.support:support-annotations:26.1.0') {
    force = true
}

即可,如果版本是其他,按照版本修改相应数值即可。

build.gradle

AS中是用gradle构建项目的,gradle是一个先进的项目构建工具。使用基于Groovy的领域特定语言(DSL)声明项目配置,摒弃了基于XML的繁琐配置。
项目中有两个build.gradle文件,一个是外层的,一个是内层app目录下的。

外层gradle

buildscript {
    
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.0.0'
    }
}

allprojects {
    repositories {
        google()
        jcenter()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

repositories中声明了google()和jcenter(),jcenter()是一个代码托管仓库,里面有很多优秀的安卓开源项目,声明后可以轻松引用仓库中的开源项目。dependencies中声明了一个gradle插件,其实gradle不是专门为Android项目开发的,Java和C++中也可以使用,因此需要使用专门的插件。

app gradle

apply plugin: 'com.android.application'

android {
    compileSdkVersion 26
    defaultConfig {
        applicationId "com.example.k.androidpractie"
        minSdkVersion 15
        targetSdkVersion 26
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:26.1.0'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
    androidTestCompile('com.android.support:support-annotations:26.1.0') {
        force = true
    }
}

versionCode表示项目版本号,versionName表示项目的版本名,在后面会说到。
Buildype包用于指定生成安装文件的相关配置,通常包括debug和release,debug是测试版安装文件,release则为正式版安装文件,debug可以忽略不写的。
minifyEnabled指定是否混淆,proguardFiles则用于指定混淆规则,后面有两个文件,一个是SDK目录下的,有所有项目的混淆规则,第二个文件是当前项目根目录下的,可以编写当前项目特有的混淆规则。
dependencies可以指定当前项目所有的依赖关系。通常一个AS项目一共有3种依赖方式:本地依赖、库依赖和远程依赖。本地依赖是对本地Jar包或目录添加依赖,库依赖是对库模块添加依赖,远程依赖则可以对jcenter等仓库中的项目添加依赖。

日志工具

Android中的日志工具类是Log(android.util.Log),提供了五个相关方法:

  • Log.v():打印最为繁琐、意义最小的日志,对应级别是verbose,级别最低
  • Log.d():打印一些调试信息,有助于调试或者分析程序,对应级别是debug,比verbose高
  • Log.i():打印一些比较重要的数据,对应级别是info,比debug高
  • Log.w():打印一些警告信息,提示程序这里可能会有一些潜在风险,最好修复一下,对应级别是warn,比info高级
  • Log.e(),打印错误信息,比如程序进入到catch中了,级别是error

测试一下,在MainAvtivity.java中的onCreate()方法中添加一行

Log.d("MainActivity","this is a dubug message");

就可以输入debug信息了,如下图所示,其中第一个参数是tag,一般写当前类名,便于过滤,第二个参数就是需要输出的信息。
image-1
为了方便使用日志工具,可以在每个类开头设置一个变量

private static final String TAG="...";

之后Log中的第一个参数直接TAG即可。
logcat可以使用过滤功能,AS默认提供了两个过滤器,也可以自定义。点击Edit Fliter Configuration,写好tag就行,点击OK,可以看到就只显示我们需要的log了。
image-2
image-3
等等,日志大概常用的就是这些,剩下的自己捉摸吧。

活动

创建和使用

在此手动创建一个活动。
新建一个项目,不要选择Empty Activity,选择Add No Activity
进入后,右键app-java-com.example.k.androidpractice_1 -> New -> Activity -> Empty Activity,同时取消Generate Layout File(自动创建布局文件)和Launcher Activity(设置为当前项目主活动)。
image-4
image-5
之后新建一个布局文件
右键app/res -> New -> Dictory,新建一个名为layout的文件夹,然后右键layout文件夹,New -> Layout resource file,xml名为first_layout,默认为LinearLayout。
在xml中添加一个Button,修改代码如下

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">
    <Button
        android:id="@+id/Button_1"
        android:text="this is a button"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
</LinearLayout>

切换到design
image-6
之后需要在Activity中加载布局,在FirstActivity中添加如下一行
setContentView(R.layout.first_layout);
设置布局,传入布局id即可。
在AndroidManifest.xml中注册Activity,其实AS已经帮我们注册了FirstActivity这个活动,代码如下

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

    <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/AppTheme">
        <activity android:name=".FirstActivity"></activity>
    </application>

</manifest>

其中android:name中应为com.example.k.androidpractice_1.FirstActivity,因为外部有package,这里就简写了。
但是还是需要设置主活动,否则程序无法知道首先启动哪个Activity。
将Activity标签修改如下

<activity android:name=".FirstActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>

注意:如果没有指定一个Activity为主活动,程序还是可以运行的,只不过无法看到图标,一般是作为第三方服务提供的,比如支付宝的支付服务。
之后运行可以看到程序正常运行了。

Toast

Toast是一种很友好的提示方式,可以以短小的信息提示用户某些信息,在此对按钮进行监听以显示Toast。
修改MainActivity代码如下

Button Button=findViewById(R.id.Button_1);
        Button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(FirstActivity.this,"this is a message",Toast.LENGTH_SHORT).show();
            }
        });

menu

设置菜单大概需要三步:

  • 新建menu文件夹和编写main.xml中的item
  • 重写onCreateOptionsMenu(),设置我们写好的main.xml
  • 重写onOptionsItemSelected(),对菜单选项进行监听响应

在res文件夹下新建文件夹menu,右键menu文件夹,New -> Menuresourcefile,输入main点ok。在main.xml中添加如下代码

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

这里创建了两个菜单选项Add和Remove指定其id和显示内容。
需要重写onCreateOptionsMenu()方法,按ctrl+O重写方法,找到我们需要的,点击OK。
image-7
修改如下

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

其中getMenuInflater()方法获得MenuInflater对象,调用inflater()方法创建菜单。
之后需要定义菜单的响应事件,即监听菜单,需要重写onOptionsItemSelected()方法

public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()){
            case R.id.Add_Item:
                Toast.makeText(this,"这是Add",Toast.LENGTH_SHORT).show();
                break;
            case R.id.Remove_Item:
                Toast.makeText(this,"这是Remove",Toast.LENGTH_SHORT).show();
                break;
                default:break;
        }
        return true;
    }

如果想结束一个Activity,在代码中可以直接通过finish()方法实现,即按返回键的功能

Activity跳转

在程序启动之后指挥进入到主活动,如何跳转到其他活动呢?需要使用intent。

显示Intent

创建一个Activity,叫SecondActivity,勾选generate不选launcher。
在activity_second.xml中添加Button控件

<Button
        android:id="@+id/Button2"
        android:layout_height="wrap_content"
        android:layout_width="match_parent"
        android:text="这是second activity"
        />

修改FirstActivity中对Button的监听,Intent一个重载是第一个参数为启动活动的上下文,第二个参数为需要启动的目标活动

protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.first_layout);
        Button Button=findViewById(R.id.Button_1);
        Button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent=new Intent(FirstActivity.this,SecondActivity.class);
                startActivity(intent);
            }
        });
    }

隐式Intent

即为不明确启动某个Activity,而是指定一系列action或者category等信息,然后由系统找到合适的Activity启动。
修改AndroidManifest.xml中的SecondActivity标签

<activity android:name=".SecondActivity">
            <intent-filter>
                <action android:name="con.example.k.androidpractice_1.ACTION_START"/>
                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
        </activity>

其中action指明了该活动可以响应的action,category具体包含了一些附加信息。
修改FirstActivity中的按钮监听

Button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent=new Intent("con.example.k.androidpractice_1.ACTION_START");
                startActivity(intent);
            }
        });

只有当action和category同时匹配的时候才能正确响应活动,由于此时的category是DEFAULT,因此不写的话会默认为DEFAULT。
每个Intent只可以指定一个action,但是可以指定多个category。
然后一个例子,启动浏览器,代码如下

Button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent=new Intent(Intent.ACTION_VIEW);
                intent.setData(Uri.parse("http://www.baidu.com"));
                startActivity(intent);
            }
        });

如下所示,可以打开网页了,要注意网址一定要正确,比如必须要加http://,否则报错。其中INTENT.ACTION_VIEW是系统中的常量,值为android.intent.action.VIEW。
image-8
通过setData()传入数据,标签中可以添加标签,更加精确配置响应什么类型的数据,主要可以配置如下内容

  • android:sheme 数据的协议部分,如http
  • android:host 数据的主机部分,如www.baidu.com
  • android:port 数据的端口部分,一般跟在主机名后
  • android:path 用于指定主机名和端口之后的部分,如一段网址中跟在域名之后的内容
  • android:mimeType 指定可处理数据的类型,允许使用通配符方式指定。

只有标签中指定的内容和Intent中携带的数据一致的时候才能正确响应。
一个简单示例,以响应http协议。新建Third_Activity,添加一个Button3,修改AndroidManifest.xml

<activity android:name=".ThirdActivity">
            <intent-filter>
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:scheme="http"/>
            </intent-filter>
        </activity>

之后启动程序,点击按钮,可以看到已经识别到我们自定义的Activity了。
image-9

向下传递数据

大致思想就是先将数据存储到Intent中,再目标活动中取出数据即可。主要用到了putExtra()和getStringExtra()方法。

filename:FirstActivity.java
Button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String data="this is a data string";
                Intent intent=new Intent(FirstActivity.this,SecondActivity.class);
                intent.putExtra("extra_data",data);
                startActivity(intent);
            }
        });

filename:SecondActivity.java
setContentView(R.layout.activity_second);
        Intent intent=getIntent();
        String data=intent.getStringExtra("extra_data");
        Toast.makeText(SecondActivity.this,data,Toast.LENGTH_SHORT);

可以看到由Toast生成了,说明成功了。
image-10

返回数据给上一级

大致步骤为三步:

  • 调用startActivityForResult()启动Intent
  • 在待销毁活动中创建一个Intent,把数据放入其中,调用setResult()返回结果
  • 在上一级活动中重写onActivityResult()方法,从Intent中获取数据

需要使用到这么一个方法,startActivityForResult(),这个方法在销毁活动的时候会返回一个结果给上一级,有两个参数,第一个参数为Intent,第二个参数为一个请求码,保证其唯一即可。
在待销毁活动中创建一个Intent,不需要有参数,只是用来传递数据,通过putExtra把数据传入Intent中,调用setResult()返回Intent,有俩各个参数,第一个一般为RESULT_OK或者RESULT_CANCELED,之后调用finish()销毁。同时需要重写上一级活动中的onActivityResult()方法以得到数据,因为活动销毁后会调用上一级活动的这个方法。
修改代码

filename:FirstActivity.java
...
Button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String data="this is a data string";
                Intent intent=new Intent(FirstActivity.this,SecondActivity.class);
               startActivityForResult(intent,1);
            }
        });
...
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        switch (requestCode){
            case 1:
                if (resultCode==RESULT_OK){
                    String string=data.getStringExtra("data_return");
                    Toast.makeText(FirstActivity.this, string, Toast.LENGTH_SHORT).show();
                }
                break;
        }
    }

filename:SecondActivity.java
...
Button2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent=new Intent();
                intent.putExtra("data_return","hello,firstactivity");
                setResult(RESULT_OK,intent);
                finish();
            }
        });

可以看到成功获取到了数据。
image-11
由于可能会有多个Activity返回到同一个活动,因此需要在onActivityResult()中首先判断requestCode的值确定其来自于哪个活动(即为最开始的请求码),然后根据resultCode判断是否成功,之后从data中获取数据。
==============
如果是通过返回键返回的呢,重写待销毁活动的onBackPressed()方法即可。

活动生命周期

安卓中是通过任务来管理活动的,一个任务是一组存放在栈里的活动的集合,即返回栈。每当启动一个新的活动的时候,活动入栈处于栈顶位置,按下返回键或者调用finish()方法销毁一个方法的时候,栈顶的活动会出栈。系统总是显示栈顶的活动给用户。

活动状态

每个活动在生命周期最多有四个可能的活动状态

  • 运行状态:处于返回栈栈顶的活动处于运行状态
  • 暂停状态:活动不处于栈顶但是仍然可见的时候,处于暂停状态,不是所有活动都必须占满全部屏幕,比如弹出的对话框就只占据部分屏幕。
  • 停止状态:活动不处于栈顶且完全不可见的时候就处于停止状态,系统会保留相应状态和成员变量,但是不可靠,如果内存不够用的时候,这部分将被回收。
  • 销毁状态:当一个活动从栈顶移除后变成销毁状态,系统会优先回收这种状态的活动。

活动的生命期

Activity类中定义了七个回调方法,覆盖了生命周期中的每一个环节

  • onCreate():在活动第一次被创建的时候调用,实现布局的加载、事件绑定等
  • onStart():在活动由不可见变为可见的时候调用
  • onResume():在活动准备好和用户进行交互的时候调用,此时活动一定处于栈顶,且处于运行状态
  • onPause():在系统准备启动或恢复另一个活动时调用。一般在这里需要释放一些消耗cpu的资源,保存一些关键数据,要快,否则可能会影响新活动
  • onStop():在活动完全不可见的时候调用。若启动新活动是类似对话框的活动,使用onPause(),onStop()不会执行
  • onDestroy():在活动被销毁之前调用,之后活动会变为销毁状态
  • onRestart():活动由停止状态变为运行状态的时候调用该方法,即活动被重新启动

还可以分为三种生存期

  • 完整生存期:即onCreate()和onDestroy()之间所经历的是完整生命期,在onCreate()中进行初始化操作,在onDestroy()中进行内存的释放
  • 可见生存期:在onStart()和onStop()之间所经历的是可见生命期。在这个期间活动都是可见的,即便某些无法交互的时候也是可见的。应在onStart()中完成资源的加载,onStop()中进行资源的释放
  • 前台生存期:在onResume()和onPause()之间所经历的是前台生存期。在这个期间活动总是处于运行状态,可以和用户进行交互

image-12
当一个活动进入停止状态的时候,可能会被回收。如在活动A上启动活动B,此时A进入停止状态,但是由于内存不足,A被回收,从活动B返回之后,仍会正常显示活动A,只不过不会调用onRestart()方法,而是会调用onCreate()方法。比如正在文本框中输入了文字,然后启动了另一个活动,返回之后,打的字没了,就是说这个活动被重新创建了。
Activity类中提供一个onSaveInstanceState()回调方法,可以保证在活动被回收之前一定会被调用
可以在此时临时保存数据避免数据消失的问题。重写该方法,参数是一个Bundle,Bundle提供了一系列方法用于保存数据,如putString(),putInt()等,这些方法都有两个参数,第一个是键值,第二个是保存的内容值。

protected void onSaveInstanceState(Bundle outState){
	super.onSaveInstanceState(outState);
	String tempData="this is s temp data";
	outState.putString("data_key",tempData);
}

如何取出数据呢?onCreate()的参数也为Bundle,在该方法中从Bundle中取出数据

protected void onCreate(Bundle savedInstancedState){
	super.onCreate(saveInstanceState);
	Log.d(TAG,"onCreate);
	if (savedInstancedState != null){
		String tempData=savedInstancedState.getString("data_key");
		Log.d(TAG,tempData);
	}
}

Intent和Bundle有些类似,当然Intent可以和Bundle结合使用,把数据先保存到Bundle中,在把Bundle传入Intent中。

活动启动模式

在实际项目中需要根据需求合理设定不同的启动模式,一共有四种standard、singleTop、singleTask和singleInstance。
通过AndroidManifest.xml中中的android:launchMode属性进行设置。

  • standard:每当standard模式启动的活动,不会在意是否已启动,即不会在意是否处于返回栈,每次启动都会创建一个新的活动,而且打开几个活动就需要按几次返回
  • singleTop:在启动的时候如果发现返回栈栈顶就是该活动,则不会创建新的活动,不管启动几次活动都只需要按一次返回,因为栈顶始终是该活动。但是如果该活动不处于栈顶位置,再次启动的话则仍会创建新的实例
  • singleTask:使用singleTop可以解决重复创建栈顶活动的问题,但是如果该活动不处于栈顶,则会创建很多活动。而singleTask模式会在返回栈中先检查是否存在待启动活动的实例,不存在的创建一个实例,如果存在则将其置为栈顶,并弹出所有该活动之上的活动
  • singleInstance:启动活动的时候会直接创建一个新的返回栈。假设有一个活动是需要共享的,如果使用前三种启动模式,均会创建新的实例,由于需要共享,因此不可能新建实例,所以使用单独的返回栈管理这个活动。(eg:设置second为singleInstance模式,在first启动second,在second启动third,则first和third处于同一个栈,second处于一个栈,此时栈顶活动为third,按返回会回到first而不是second,因为first和third处于同一个栈,再次按返回之后才会回到second,因为该栈已空,再次返回后会退出

活动的应用

找到界面对应的活动

在实际中可能并不确定哪个界面对应的是哪个Activity。这时候需要进行一点修改就好了。
新建一个名为BaseActivity的普通java class,不需要注册为Activity,编写代码如下

public class BaseActivity extends AppCompatActivity{
	protected void onCreate(Bundle savedInstanceState){
		super.onCreate(savedInstanceState);
		Log.d("BaseActivity",getClass().getSimpleName());
	}
}

之后将项目中Activity类中继承的AppCompatActivity改为继承BaseActivity,此时项目中的Activity类依然继承AppCompatActivity,因为BaseActivity继承于AppCompatActivity。
之后再运行程序,每当启动一个活动日志就会打印出该活动对应的类名。

快速直接退出程序

当启动多个程序的时候需要直接退出程序,直接按返回键的话可能需要多次,要想一次退出的话可以使用一个集合作为Activity管理器来保存当前所有活动进行处理。

public class ActivityCollector{
	public static List<Activity> acticities=new ArrayList<>();
	public static void addActivity(Activity activity){
		acticities.add(activity);
	}
	public static void removeActivity(Activity activity){
		activities.remove(activity);
	}
	public static void finishAll(){
		for (Activity activity:activities){
			if (!activity.isFinishing()){
				activity.finish();
			}
		}
		activities.clear();
	}
}

之后需要修改一下上面的BaseActivity代码

public class BaseActivity extends AppCompatActivity{
	protected void onCreate(Bundle savedInstanceState){
		super.onCreate(savedInstanceState);
		Log.d("BaseActivity",getClass().getSimpleName());
		ActivityCollector.addActivity(this);
	}
	protected void onDestroy(){
		super.onDestroy();
		ActivityCollector.removeActivity(this);
	}
}

之后如果需要一次退出程序的话只需要调用ActivityCollector.finishAll()方法即可。

启动程序的好技巧

在启动另一个活动的时候,需要传递参数的话,如果不是我们写的活动,不太容易知道都需要什么参数之类的,因此修改一下代码,结构就会很清晰明了。

filename:SecondActivity.java
public class SecondActivity extends BaseAvtivity{
	public static void actionStart(Context context,String Data1,String Data2){
		Intent intent=new Intent(context,SecondActivity.class);
		intent.putExtra("param1",Data1);
		intent.putExtra("param2",Data2);
		context.startActivity(intent);
	}
	...
}

然后

filename:FirstActivity.java
public void onClick(View v){
	SecondActivity.actionActivity(FirstActivity.this,"123","456");
}

这样就很清楚了。

balabala

安卓只写过几个小程序,虽然现在只把活动看完了,但是感觉真的认识了不少东西和方法,比如生命周期生存期,之前只是知道现象但是具体不知道是什么,现在有了一个系统的总结。几句这样,明天继续。

OK,THANKS FOR READING.BYE BYE~

发布了14 篇原创文章 · 获赞 5 · 访问量 5184

猜你喜欢

转载自blog.csdn.net/qq_41037945/article/details/104124016