GVKun编程网logo

Android开发学习笔记 -- 活动相关(android活动的作用)

9

对于想了解Android开发学习笔记--活动相关的读者,本文将提供新的信息,我们将详细介绍android活动的作用,并且为您提供关于2019年Android岗位BAT等大厂面试题知识点小结,2021年

对于想了解Android开发学习笔记 -- 活动相关的读者,本文将提供新的信息,我们将详细介绍android活动的作用,并且为您提供关于2019年Android岗位BAT等大厂面试题知识点小结,2021年Android开发学习路线、Android MediaPlayer音频播放器详解,2021年Android开发学习路线、Android Native 代码开发学习笔记、Android 开发学习笔记(五):Starting an Activity的有价值信息。

本文目录一览:

Android开发学习笔记 -- 活动相关(android活动的作用)

Android开发学习笔记 -- 活动相关(android活动的作用)

活动 Intent

活动简介

Activity包含用户界面的组建,主要用于与用户交互

手动创建过程

创建一个新的Empty ActivityGenerate Layout File选项是创建布局文件,也就是可视化的界面,Launcher Activity选项是将该活动设置为启动主活动

任何活动都要重写onCreate()方法,调用父类的onCreate()方法

创建一个layout文件夹存放布局文件,新建Layout resource file

onCreate()函数中添加

setContentView(R.layout.xxxlayout);

调用setContentView方法给当前活动加载一个布局,参数是布局文件的id

在AndroidManifest文件里注册,注册代码是

<application 
......
    <activity android:name=".FirstActivity"></activity>
</application>

如果是主活动,还需要配置<intent-filter>标签

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

销毁活动使用finish()

使用Toast

Toast.makeText(FirstActivity.this, "toast context", Toast.LENGTH_SHORT).show();

参数分别是:上下文,内容,时间长短

创建menu文件夹,新建Menu resource file,在文件中添加item

<menu .....>
    <item
        android:id="@+id/add_item"
        android:title="Add"/>
....
</menu>

然后重写onCreateOptionMenu()方法

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

再定义菜单响应事件

public boolean onoptionItemSelected(MenuItem item) {
    swich (item.getItemId()) {
    case R.id.add_item:
        .....
        bread;
    ...
    default:
    }
    return true;
}

活动之间的跳转

显式

首先创建活动,注册

在第一个活动中设置点击事件跳转

public void onClick(View v) {
    Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
    startActivity(intent);
}

intent表示就是意图

隐式

不明确指定要跳转的活动,而是指定条件,由系统分析该向哪一个活动跳转

指定的条件就是action和category信息,两者都满足的活动会是跳转目标

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

在第一个活动中添加

public void onClick(View v) {
    Intent intent = new Intent("com.example.activitytest.ACTION_START);
    startActivity(intent);
}

每个intent中只能指定一个,但category可以有多个

intent.addCategory("com.example.activitytest.MY_CATEGORY");

没有响应的活动程序会崩溃

更多隐式用法

还可以调用其他程序的活动,例如浏览器,拨号等等
以浏览器为例:

修改点击事件

Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("http://www.baidu.com));
startActivity(intent);

ACTION_VIEW是系统内置的动作,点击可以打开浏览器

可以在<intent-filter>里配置<data>标签,指定当前活动能响应什么类型的数据

  • android:scheme 协议
  • android:host 主机
  • android:path 主机名和端口后的部分
  • android:mimeType 可以处理的数据类型

数据传递

向下一个活动传递数据

intent中提供一系列putExtra()方法的重载,存放数据,启动第二个活动后,再从intent中取出来

String data = "hello";
Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
intent.putExtra("extra_data", data);
startActivity(intent);

取出数据的凭据是键值,在第二个活动中

Intent intent = getIntent();
String data = intent.getStringExtra("extra_data");

返回数据给上一个活动

使用startActivityForResult()来启动,会在活动销毁的时候返回一个结果给上一个活动

Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
startActivityForResult(intent, 1);

1是请求码,只要是唯一值就行了

在第二个活动中写入返回逻辑

Intent intent = new Intent();
intent.putExtra("data_return", "Hello Return");
setResult(RESULT_OK, intent);
finish();

setResult()两个参数,第一个是返回的状态结果,第二个是带有数据的intent

在第一个活动中重载onActivityResult()方法

    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        switch (requestCode) {
            case 1:
                if (resultCode == RESULT_OK) {
                    String returndata = data.getStringExtra("return_data");
                    Log.d("FirstActivity", returndata);
                }
                break;

            default:
        }
    }

请求码用来判断来源

活动的临时数据保存

Activity提供一个onSavedInstanceState()回调方法,在活动被回收之前一定会被调用,在该方法里面保存临时数据
使用Bundle类型参数

@Override
protected void onSavedInstanceState(Bundle outState) {
    super.onSavedInstanceState(outState);
    String tempData = "something";
    outState.putString("data_key", tempData);
}

在onCreate()方法有一个Bundle参数,可以从这里恢复数据

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        setContentView(R.layout.first_layout);

        if (savedInstanceState != null) {
            String tempData = savedInstanceState.getString("data_key");
            ...
        }
    }

活动的启动模式

一共4种,standard,singletop,singleTask,singleInstance

可以在AndroidManifest.xml中给activity指定android:launchMode属性选择启动模式

  • standard
    这是默认的启动模式,每次启动创建一个新的实例

  • singletop
    当启动活动时活动处于返回栈的栈顶,则直接使用,不会创建新的实例,但是如果不在栈顶就会创建新的实例

  • singleTask
    该活动在整个应用程序中只存在一个实例,启动之前会在返回栈中检查是否存在,如果发现存在,会把该活动之上的活动通通出栈,知道该实例在栈顶
    如果不存在则创建

  • singleInstance
    程序活动允许其他程序调用,创建单独的返回栈管理,不管哪个程序来访问该活动,共用同一个返回栈

Tips

获取当前活动信息

怎么判断当前是哪一个活动?
创建一个新类BaseActivity,继承AppCompatActivity,重写onCreate()方法,加入语句

Log.d("BaseActivity", getClass().getSimpleName());

一次退出多个程序

新建一个ActivityCollector类作为活动管理器

public class ActivityCollector {

    public static List<Activity> activities = new ArrayList<>();

    public static void addActivity(Activity activity) {
        activities.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中修改onCreate和onDestroy

//onCreate()
ActivityCollector.addActivity(this);
//onDestroy()
ActivityCollector.removeActivity(this);

一键退出

button3.setonClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        ActivityCollector.finishAll();
    }
});

启动活动的最佳写法

封装成一个方法,数据写参数中

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);
}

2019年Android岗位BAT等大厂面试题知识点小结,2021年Android开发学习路线

2019年Android岗位BAT等大厂面试题知识点小结,2021年Android开发学习路线

内存回收机制与GC算法(各种算法的优缺点以及应用场景):

GC是通过对象是否存活来决定是否进行回收,最常用的判定算法是引用计数算法以及Java中使用的判定算法为根搜索算法(GC Roots Tracing)

常用的垃圾收集算法

a、标记——清除算法(Mark——Sweep)

首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象。

缺点:效率问题,标记和清除过程的效率不高;

b、复制算法(copying)

为了解决标记清除算法的效率问题,复制算法将可用的内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活的对象复制到另一块上,然后再把已使用过的内存空间一次清理掉。

优点:实现简单,运行高效

缺点:对内存空间的利用不高,可用内存变成一半,这代价过高

c、标记——整理算法(Mark——Compact)

与标记清除算法的标记阶段相同,但标记后会将所有存活的对象向一端移动,然后直接清理掉端边界以外的内存。这种算法一般用于老年代的内存回收上,因为老年代中对象的存活时间都比较长,可能存在100%存活的极端情况,因此不能选择copying算法来进行回收。

d、分代收集算法(Generational Collection)

这种算法只是根据对象的存活周期的不同将内存划分为几块,一般都划分为新生代和老年代,这样可以根据各个年代的特点采用最适当的收集算法。新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,因此选取复制算法,只需要付出少量存活对象的复制成本就可以完成收集;老年代中因为对象存活率高,没有额外的空间对它进行分配担保,就必须使用“标记——清除”或是“标记——整理”算法来进行回收。在之前的三种算法中已经有所描述。

详情:http://www.open-open.com/lib/view/open1380593930103.html

GC原理时机以及GC对象:

JVM 分别对新生代和旧生代采用不同的垃圾回收机制

Java中那些不可达的对象就会变成垃圾。那么什么叫做不可达?其实就是没有办法再引用到该对象了。主要有以下情况使对象变为垃圾:

1.对非线程的对象来说,所有的活动线程都不能访问该对象,那么该对象就会变为垃圾。

2.对线程对象来说,满足上面的条件,且线程未启动或者已停止。

GC将负责回收所有“不可达”对象的内存空间。

详情:https://segmentfault.com/a/1190000002579346

内存泄露场景及解决方法:

因为静态变量造成的内存泄漏;

Handler 的错误使用;

非静态内部类的静态实例的错误使用;

不正确使用线程,造成内存泄漏;

资源没有及时关闭;

详情例子解决方案参见:http://blog.csdn.net/adrian24/article/details/53248255

或者http://www.jianshu.com/p/51072faadf51

OOM的避免及解决方法:

高效加载大图片:

在展示高分辨率图片的时候,最好先将图片进行压缩。压缩后的图片大小应该和用来展示它的控件大小相近,在一个很小的ImageView上显示一张超大的图片不会带来任何视觉上的好处,但却会占用我们相当多宝贵的内存,而且在性能上还可能会带来负面影响。下面我们就来看一看,如何对一张大图片进行适当的压缩,让它能够以最佳大小显示的同时,还能防止OOM的出现。

使用图片缓存技术:

内存缓存技术对那些大量占用应用程序宝贵内存的图片提供了快速访问的方法。其中最核心的类是LruCache (此类在android-support-v4的包中提供) 。这个类非常适合用来缓存图片,它的主要算法原理是把最近使用的对象用强引用存储在 LinkedHashMap 中,并且把最近最少使用的对象在缓存值达到预设定值之前从内存中移除。

详情:http://blog.csdn.net/guolin_blog/article/details/9316683

######4.四大组件及生命周期;ContentProvider的权限管理(读写分离,权限控制-精确到表级,URL控制);Activity的四种启动模式对比;Activity状态保存于恢复

都是一些基础知识,详情归类下:

Activity:详细知识:http://blog.csdn.net/amazing7/article/details/51244219

Service:详细知识:http://blog.csdn.net/amazing7/article/details/51305911

ContentProvider:详细知识:http://blog.csdn.net/amazing7/article/details/51324022

broadcastReceiver:详细知识:http://blog.csdn.net/amazing7/article/details/51352139

ContentProvider的权限管理:http://blog.csdn.net/robertcpp/article/details/51337891

Activity的四种启动模式对比:http://blog.csdn.net/knlnzhao/article/details/8005277

Activity状态保存于恢复:http://blog.csdn.net/js331455217/article/details/40930157

######5.Fragment生命周期;Fragment状态保存

生命周期及相关:http://blog.csdn.net/amazing7/article/details/51282082

状态保存:http://www.jianshu.com/p/75dc2f51cd63

######6.startActivityForResult是哪个类的方法,在什么情况下使用,如果在Adapter中使用应该如何解耦

startActivityForResult是Activity类里的方法,在原Activity里通过Intent跳转到其他类再跳回到原Activity里时候,回传数据所用

详情:http://blog.csdn.net/sunchaoenter/article/details/6612039

在Adapter以及其他非Activity类使用的时候,可以将由原Activity类传入的Context强转为Activity类,再在原Activity里重写onActivityResult方法接受到返回值。

######7.AsyncTask原理及不足;IntentService原理

AsyncTask是一个轻量级的异步类,继承时候有三个泛型参数:

  1. Params,在执行AsyncTask时需要传入的参数,可用于在后台任务中使用;

  2. Progress,后台任务执行时,如果需要在界面上显示当前的进度,则使用这里指定的泛型作为进度单位;

  3. Result,当任务执行完毕后,如果需要对结果进行返回,则使用这里指定的泛型作为返回值类型。

需要重写四个方法(至少是2个,2和4):

  1. onPreExecute(),这个方法会在后台任务开始执行之间调用,用于进行一些界面上的初始化操作,比如显示一个进度条对话框等。

  2. doInBackground(Params…),这个方法中的所有代码都会在子线程中运行,我们应该在这里去处理所有的耗时任务。任务一旦完成就可以通过return语句来将任务的执行结果进行返回,如果AsyncTask的第三个泛型参数指定的是Void,就可以不返回任务执行结果。注意,在这个方法中是不可以进行UI操作的,如果需要更新UI元素,比如说反馈当前任务的执行进度,可以调用publishProgress(Progress…)方法来完成。

  3. onProgressUpdate(Progress…),当在后台任务中调用了publishProgress(Progress…)方法后,这个方法就很快会被调用,方法中携带的参数就是在后台任务中传递过来的。在这个方法中可以对UI进行操作,利用参数中的数值就可以对界面元素进行相应的更新。

  4. onPostExecute(Result),当后台任务执行完毕并通过return语句进行返回时,这个方法就很快会被调用。返回的数据会作为参数传递到此方法中,可以利用返回的数据来进行一些UI操作,比如说提醒任务执行的结果,以及关闭掉进度条对话框等。

AnsycTask执行任务时,内部会创建一个进程作用域的线程池来管理要运行的任务,也就就是说当你调用了AsyncTask.execute()后,AsyncTask会把任务交给线程池,由线程池来管理创建Thread和运行Therad,本质上是对Thread+Handler的良好封装,减少了开发者处理问题的复杂度,提高了开发效率。

AsyncTask缺陷:

最大的缺点:在使用多个异步操作和并需要进行Ui变更时,就变得复杂起来。

其他的参考下面的文章。

详情:http://blog.csdn.net/liuhe688/article/details/6532519

http://blog.csdn.net/boyupeng/article/details/49001215

IntentService原理

IntentService是继承于Service并处理异步请求的一个类,在IntentService内有一个工作线程来处理耗时操作,启动IntentService的方式和启动传统Service一样,同时,当任务执行完后,IntentService会自动停止,而不需要我们去手动控制。

详情:http://blog.csdn.net/qq_18402085/article/details/50753005

http://blog.csdn.net/ryantang03/article/details/8146154/

######8.AstncTask+HttpClient与AsyncHttpClient有什么区别

AsyncHttpClient来自android-async-http库是在Apache的HttpClient库的基础上开发构建而成的,这里的异步,是指它所有的网络请求都是在app的UI线程之外的独立工作线程中执行。而开发者通过利用Android的消息处理机制,把我们所需要编写的回调函数放在这个回调函数的创建线程中执行(一般就是UI线程),所以使用起来非常方便除了能应用在开发普通App上,还可以用来开发Service或后台线程,async-http-client库可以自已分辨是被用在哪一种应用下,不需要额外的设置。

详情:https://my.oschina.net/u/725054/blog/494494

http://blog.csdn.net/wangpeng047/article/details/19624529

######9.如何保证一个后台服务不被杀死;比较省电的方式是什么

服务不被杀死分3种来讨论

1.系统根据资源分配情况杀死服务

2.用户通过 settings -> Apps -> Running -> Stop 方式杀死服务

3.用户通过 settings -> Apps -> Downloaded -> Force Stop 方式杀死服务

第一种情况:

用户不干预,完全靠系统来控制,办法有很多。比如 onStartCommand() 方法的返回值设为 START_STICKY ,服务就会在资源紧张的时候被杀掉,然后在资源足够的时候再恢复。当然也可设置为前台服务,使其有高的优先级,在资源紧张的时候也不会被杀掉。

第二种情况:

用户干预,主动杀掉运行中的服务。这个过程杀死服务会通过服务的生命周期,也就是会调用 onDestory() 方法,这时候一个方案就是在 onDestory() 中发送广播开启自己。这样杀死服务后会立即启动。

当然,从理论上来讲这个方案是可行的,实验一下也可以。但有些情况下,发送的广播在消息队列中排的靠后,就有可能服务还没接收到广播就销毁了(这是我对实验结果的猜想,具体执行步骤暂时还不了解)。所以为了能让这个机制完美运行,可以开启两个服务,相互监听,相互启动。服务A监听B的广播来启动B,服务B监听A的广播来启动A。经过实验,这个方案可行,并且用360杀掉后几秒后服务也还是能自启的。到这里再说一句,如果不是某些功能需要的服务,不建议这么做,会降低用户体验。

第三种情况:

强制关闭就没有办法。这个好像是从包的level去关的,并不走完整的生命周期。所以在服务里加代码是无法被调用的。处理这个情况的唯一方法是屏蔽掉 force stop和 uninstall 按钮,让其不可用。方法自己去找吧。当然有些手机自带的清理功能就是从这个地方清理的,比如华为的清理。所以第三种情况我也没有什么更好的办法了。

详情:http://www.tuicool.com/articles/iu22QnF

######10.如何通过广播拦截和abort一条短信;广播是否可以请求网络;广播引起anr的时间限制

目前找到方法都是适用于Android4.4默认短信应用以前的方法:

第一步:新建一个类继承broadcastReceiver,并重写onReceive()方法.

第二步:订阅短信的广播Intent,订阅方法有两种:

1:使用代码进行订阅

2:在AndroidManifest.xml文件中的节点中进行订阅

在Android中,每次广播消息到来时都会创建broadcastReceiver实例并执行onReceive() 方法,

onReceive() 方法执行完后,broadcastReceiver 的实例就会被销毁。拦截短信的方法就写在onReceive() 方法里即可。

详情:http://blog.csdn.net/jason0539/article/details/11720419

最后:学习总结——Android框架体系架构知识脑图(纯手绘xmind文档)

学完之后,若是想验收效果如何,其实最好的方法就是可自己去总结一下。比如我就会在学习完一个东西之后自己去手绘一份xmind文件的知识梳理大纲脑图,这样也可方便后续的复习,且都是自己的理解,相信随便瞟几眼就能迅速过完整个知识,脑补回来。

下方即为我手绘的Android框架体系架构知识脑图,由于是xmind文件,不好上传,所以小编将其以图片形式导出来传在此处,细节方面不是特别清晰。但可给感兴趣的朋友提供完整的Android框架体系架构知识脑图原件(包括上方的面试解析xmind文档)

除此之外,前文所提及的Alibaba珍藏版 Android框架体系架构 手写文档以及一本 《大话数据结构》 书籍等等相关的学习笔记文档,也皆可分享给认可的朋友!

——感谢大家伙的认可支持,Free Download请注意:点赞+点赞+点赞!!!
节方面不是特别清晰。但可给感兴趣的朋友提供完整的Android框架体系架构知识脑图原件(包括上方的面试解析xmind文档)
[外链图片转存中…(img-cpyXDdGA-1643772218773)]

除此之外,前文所提及的Alibaba珍藏版 Android框架体系架构 手写文档以及一本 《大话数据结构》 书籍等等相关的学习笔记文档,也皆可分享给认可的朋友!

——感谢大家伙的认可支持,Free Download请注意:点赞+点赞+点赞!!!
自行下载领取链接:【Git】

Android MediaPlayer音频播放器详解,2021年Android开发学习路线

Android MediaPlayer音频播放器详解,2021年Android开发学习路线

override fun onStopTrackingTouch(seekBar: SeekBar?) {

//拖动结束之后再设置,如果在onProgressChanged中设置会有杂音

mMediaPlayer?.seekTo(seekBar!!.progress)

tv_currentPosition.text = formatDuration(seekBar!!.progress)

}

})

btn_start.setonClickListener {

audioStart()

}

btn_pause.setonClickListener {

audioPause()

}

btn_seek.setonClickListener {

seek_bar.progress = (seek_bar.max * 0.8).roundToInt()

mMediaPlayer?.seekTo(seek_bar!!.progress)

tv_currentPosition.text = formatDuration(seek_bar!!.progress)

audioStart()

}

btn_restart.setonClickListener {

audioRestart()

}

}

主要 是一些播放器的监听事件和按钮操作事件。

https://blog.csdn.net/yechaoa

着重介绍两个:

1、setonPreparedListener

注意,在获取资源时长的时候,需要在播放器准备完成之后获取,否则会有异常

Attempt to call getDuration in wrong state: mPlayer=0x7244676280, mCurrentState=4

error (-38, 0)

并会回调OnErrorListener

然后设置显示,并把时长赋值给seek_bar的最大值。

2、setonSeekBarchangelistener

3个方法:

  • onProgressChanged 进度改变

  • onStartTrackingTouch 开始拖动

  • onStopTrackingTouch 停止拖动

我们需要在改变中改变后对当前播放时长进行更新,并在最后的位置进行播放操作。

如果程序上没有定位到指定播放位置这种操作的话,不要在onProgressChanged中执行播放操作,因为频繁的进度改变,频繁的调用播放,会有杂音

所以建议用户手动拖动来触发播放。

如果非要程序可以跳到指定位置播放的话,建议如下操作:

btn_seek.setonClickListener {

seek_bar.progress = (seek_bar.max * 0.8).roundToInt()

mMediaPlayer?.seekTo(seek_bar!!.progress)

tv_currentPosition.text = formatDuration(seek_bar!!.progress)

audioStart()

}

手动赋值progress ,并调用播放。

格式化播放时间

这个获取时长返回的是毫秒,所以我们还需要对其格式化操作。

/**

  • 格式化播放时间

*/

private fun formatDuration(duration: Int): String {

val d = duration / 1000

val minute = d / 60

val second = d % 60

val m: String = if (minute < 10) “0 m i n u t e " e l s e " minute" else " minute"else"minute”

val s: String = if (second < 10) “0 s e c o n d " e l s e " second" else " second"else"second”

return “ m : m: m:s”

}

做了一个判断,不足两位数则前位补0。

开始播放

===============================================================

/**

  • 开始播放

*/

private fun audioStart() {

mMediaPlayer?.run {

if (!this.isPlaying) {

start()

startTimer()

}

}

}

因为没有播放中的回调接口,所以这里启动一个Timer获取当前位置并更新UI

Timer更新UI

/**

  • 每隔一秒执行一次,更新当前播放时间

*/

private fun startTimer() {

mTimer = Timer().apply {

schedule(object : TimerTask() {

override fun run() {

//非ui线程不能更新view,所以这里赋值给seek_bar,在seek_bar的事件中去更新

seek_bar.progress = mMediaPlayer!!.currentPosition

//tv_currentPosition.text = formatDuration(mMediaPlayer!!.currentPosition)

}

}, 0, 1000)

}

}

这里要注意,非ui线程不能更新view,所以这里赋值给seek_bar,在seek_bar的onProgressChanged 回调中去更新。

暂停播放

===============================================================

/**

  • 暂停播放

*/

private fun audioPause() {

mMediaPlayer?.run {

if (this.isPlaying) {

pause()

cancelTimer()

}

}

}

同样,暂停的时候取消Timer,做到资源及时回收。

取消Timer

private fun cancelTimer() {

mTimer?.run {

cancel()

mTimer = null

}

}

暂停/继续 播放

===================================================================

/**

  • 暂停/继续 播放

*/

private fun audioToggle() {

mMediaPlayer?.run {

if (this.isPlaying) {

audioPause()

} else {

audioStart()

}

}

}

如果只有一个事件触发的话,可以这么来写。

重新播放

===============================================================

播放器并没有自带restart()方法,不过我们可以手动把播放位置改到初始值,并调用播放。

/**

  • 重新播放

*/

private fun audioRest


art() {

mMediaPlayer?.run {

//定位到指定位置,单位毫秒

seekTo(0)

audioStart()

seek_bar.progress = 0

{

audioStart()

}

}

}

如果只有一个事件触发的话,可以这么来写。

重新播放

===============================================================

播放器并没有自带restart()方法,不过我们可以手动把播放位置改到初始值,并调用播放。

/**

  • 重新播放

*/

private fun audioRest[外链图片转存中…(img-JctATFNB-1642998219313)]
art() {

mMediaPlayer?.run {

//定位到指定位置,单位毫秒

seekTo(0)

audioStart()

seek_bar.progress = 0

Android Native 代码开发学习笔记

Android Native 代码开发学习笔记

本文提供排版更佳的PDF版本下载。

JNI,全称Java Native Interface,是用于让运行在JVM中的Java代码和运行在JVM外的Native代码(主要是C或者C++)沟通的桥梁。代码编写者即可以使用JNI从Java的程序中调用Native代码,又可以从Native程序中调用Java代码。这样,编程人员可以将低阶的代码逻辑包装到高阶的程序框架中,获得高性能高效率的同时保证了代码框架的高抽象性。

在Android中,仅有以下类库是允许在JNI中使用的:

  • libc (C library) headers
  • libm (math library) headers
  • JNI interface headers
  • libz (Zlib compression) headers
  • liblog (Android logging) header
  • OpenGL ES 1.1 (3D graphics library) headers (since 1.6)
  • A Minimal set of headers for C++ support

JNI本身仅仅是一个把两者融合的工具,作为编程者需要做的,就是在Java代码和Native代码中按照固定的格式告诉JNI如何调用对方。在Android中,有两种方式可以调用JNI,一种是Google release的专门针对Android Native开发的工具包,叫做NDK。去Android网站上下载该工具包后,就可以通过阅读里面的文档来setup一个新的包含Native代码的工程,创建自己的Android.mk文件,编译等等;另一种是完整的源码编译环境 ,也就是通过git从官方网站获取完全的Android源代码平台。这个平台中提供有基于make的编译系统。更多细节请参考这里。不管选择以上两种方法的哪一个,都必须编写自己的Android.mk文件,有关该文件的编写请参考相关文档。

下面通过一个简单的使用例子来讲解JNI。Android给C和C++提供的是两套不同的Native API,本文仅以C++举例说明。假设这么一个需求,Java代码需要打印一个字符串,而该字符串需要Native代码计算生成。对应的JNI流程是这样的:

1. 在准备打印字符串的Android类中,添加两段代码。

第一段是:

private native String getPrintStr();

这一行代码的目的是告诉JNI,这个Java文件中有这么一个函数,该函数是在Native代码中执行的,Native代码会返回一个字符串供Java代码来输出。

第二段是:

try {System.loadLibrary(“LIBNAME” }

catch (UnsatisfiedLinkError ule) {Log.e(TAG, “Could not load native library”);}

这两行代码是告诉JNI,你需要找的所有Native函数都在libLIBNAME.so这个动态库中。注意JNI会自动补全lib和so给LIBNAME,你只需要提供LIBNAME给loadLibrary就行了。在最后执行的时候,JNI会先找到这个动态库,然后找里面的OnLoad函数,具体注册流程由OnLoad函数接管。

关于如何确定这个LIBNAME,和如何定义OnLoad函数,下面就会讲。

2. 上面的第一步是告诉JNI,java代码需要和Native代码交互,同时把在哪里找,找什么都通知了。接下来的事情就由Native端接管。如果把上面的getPrintString函数申明比作原型,那么本地代码中的具体函数定义就应该和该原型匹配,JNI才能知道具体在哪里执行代码。具体来说,应该有一个对应的Native函数,有和Java中定义的函数同样的参数列表以及返回值。另外,还需要有某种机制让JNI将两者相互映射,方便参数和返回值的传递。在老版的JNI中,这是通过丑陋的命名匹配实现的,比如说在Java中定义的函数名是getPrintStr, 该函数属于package java.come.android.xxx,那么中对应Native代码中的函数名就应该是Java_com_android_xxx_getPrintStr。这样给开发人员带来了很多不便。可以用javah命令来生成对应Java code中定义函数的Native code版本header文件,从中得知传统的匹配方法是如何做的。具体过程如下:

  1. 通过SDK的方式编译Java代码。
  2. 找到Eclipse的工程目录,进入bin目录下。这里是编译出的java文件所对应的class文件所在。
  3. 假设包括Native函数调用的java文件属于com.android.xxx package,名字叫test.java,那么在bin下执行javah -jni com.android.xxx.test

执行完后,可以看到一个新生成的header文件,名字为com_android_xxx_test.h。打开后会发现已经有一个函数申明,函数名为java_com_android_xxx_test_getPrintStr。这个名字就包括了该函数所对应Java版本所在的包,文件以及名称。这就是JNI传统的确定名字的方法。

值得注意的是,header文件中不仅包含了基于函数名的映射信息,还包含了另一个重要信息,就是signature。一个函数的signature是一个字符串,描述了这个函数的参数和返回值。其中”()” 中的字符表示参数,后面的则代表返回值。例如”()V” 就表示void Func(); “(II)V” 表示 void Func(int, int); 数组则以”["开始,用两个字符表示。

具体的每一个字符的对应关系如下:

字符

Java类型

C类型

V

void

void

I

jint

int

Z

jboolean

boolean

J

jlong

long

D

jdouble

double

F

jfloat

float

B

jbyte

byte

C

jchar

char

S

jshort

short

上面的都是基本类型。如果Java函数的参数是class,则以"L"开头,以";"结尾,中间是用"/" 隔开的包及类名。而其对应的C函数名的参数则为jobject。 一个例外是String类,其对应的类为jstring。举例:

Ljava/lang/String; String jstring

Ljava/net/Socket; Socket jobject

如果JAVA函数位于一个嵌入类,则用$作为类名间的分隔符。例如 "(Ljava/lang/String;Landroid/os/FileUtils$FileStatus;)Z"

这个signature非常重要,是下面要介绍的新版命名匹配方法的关键点之一。所以,即使传统的命名匹配已经不再使用,javah这一步操作还是必须的,因为可以从中得到Java代码中需要Native执行的函数的签名,以供后面使用。

3. 在新版(版本号大于1.4)的JNI中,Android提供了另一个机制来解决命名匹配问题,那就是JNI_OnLoad。正如前面所述,每一次JNI执行Native代码,都是通过调用JNI_OnLoad实现的。下面的代码是针对本例的OnLoad代码:

/* Returns the JNI version on success, -1 on failure.

jint JNI_OnLoad(JavaVM* vm, void* reserved) {

JNIEnv* env = NULL;

jint result = -1;

if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {

LOGE("ERROR: GetEnv failed");

goto bail;

}

assert(env != NULL);

if (!register_Test(env)) {

LOGE("ERROR: Test native registration failed");

goto bail;

}

/* success -- return valid version number */

result = JNI_VERSION_1_4;

bail:??return result;

}

分析这个函数。首先,OnLoad通过GetEnv函数获取JNI的环境对象,然后通过register_Test来注册Native函数。register_Test的实现如下:

int register_Test(JNIEnv *env) {

const char* const ClassPathName ?= "com/android/xxx/test";

return registerNativeMethods(env, ClassPathName, TestMethods,

sizeof(TestMethods) / sizeof(TestMethods[0]));

}

在这里,ClassPathName是Java类的全名,包括package的全名。只是用 “/” 代替 ”.” 。然后我们把类名以及TestMethods这个参数一同送到registerNativeMethods这个函数中注册。这个函数是基于JNI_OnLoad的命名匹配方式的重点。

在JNI中,代码编写者通过函数signature名和映射表的配合,来告诉JNI_OnLoad,你要找的函数在Native代码中是如何定义的(signature),以及在哪定义的(映射表)。关于signature的生成和含义,在上面已经介绍。而映射表,是Android使用的一种用于映射Java和C/C++函数的数组,这个数组的类型是JNINativeMethod,定义为:

typedef struct {

const char* name;

const char* signature;

void* fnPtr;

} JNINativeMethod;

其中,第一个变量是Java代码中的函数名称。第二个变量是该函数对应的Native signature。第三个变量是该函数对应的Native函数的函数指针。例如,在上面register_Test的函数实现中,传给registerNativeMethods的参数TestMethods就是映射表,定义如下:

static JNINativeMethod TestMethods[] = {

{“getPrintStr”, “()Ljava/lang/String”, (void*)test_getPrintStr}

};

其中getPrintStr是在Java代码中定义的函数的名称,()Ljava/lang/String是签名,因为该函数无参数传入,并返回一个String。test_getPrintStr则是我们即将在Native code中定义的函数名称。该映射表和前面定义的类名ClassPathName一起传入registerNativeMethods:

static int registerNativeMethods(JNIEnv* env, const char* className, JNINativeMethod* ????Methods, int numMethods) {

jclass clazz;

clazz = env->FindClass(className);

if (clazz == NULL) {

LOGE(“Native registration unable to find class ‘%s’”, className);

return JNI_FALSE;

}

if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) {

LOGE(“RegisterNatives failed for ‘%s’”, className);

return JNI_FALSE;

}

return JNI_TRUE;

}

在这里,先load目标类,然后注册Native函数,然后返回状态。

可以看出,通过映射表方式,Java code中的函数名不须再和Native code中的函数名呆板对应。只需要将函数注册进映射表中,Native code的函数编写就有了很大的灵活性。虽说和前一种传统的匹配方法比,这种方式并没有效率上的改进,因为两者本质上都是从JNI load开始做函数映射。但是这一种register的方法极大降低了两边的耦合性,所以实际使用中会受欢迎得多。比如说,由于映射表是一个<名称,函数指针>对照表,在程序执行时,可多次调用registerNativeMethods()函数来更换本地函数指针,而达到弹性抽换本地函数的目的。

4. 接下来本应介绍test_getPrintStr。但在此之前,简单介绍Android.mk,也就是编译NDK所需要的Makefile,从而完成JNI信息链的讲解。Android.mk可以基于模版修改,里面重要的变量包括:

  • LOCAL_C_INCLUDES:包含的头文件。这里需要包含JNI的头文件。
  • LOCAL_SRC_FILES: 包含的源文件。
  • LOCAL_MODULE:当前模块的名称,也就是第一步中我们提到的LIBNAME。注意这个需要加上lib前缀,但不需要加.so后缀,也就是说应该是libLIBNAME。
  • LOCAL_SHARED_LIBRARIES:当前模块需要依赖的共享库。
  • LOCAL_PRELINK_MODULE:该模块是否被启动就加载。该项设置依具体程序的特性而定。

5. 至此,JNI作为桥梁所需要的所有信息均已就绪。JNI知道在调用Java代码中的getPrintStr函数时,需要执行Native代码。于是通过System.loadLibrary所加载的libLIBNAME.so找到OnLoad入口。在OnLoad中,JNI发现了函数映射表,发现getPrintStr对应的Native函数是test_getPrintStr。于是JNI将参数(如果有的话)传递给test_getPrintStr并执行,再将返回值(如果有的话)传回Java中的getPrintStr。

6. 用于最后测试的test_getPrintStr函数实现如下:

const jstring testStr = env->NewStringUTF(“hello, world”);

return testStr;

然后在Java代码中打印出返回的字符串即可。这个网页详细介绍了env可以调用的所有方法。

7. 关于测试时使用Log。调用JNI进行Native Code的开发有两种环境,完整源码环境以及NDK。两种环境对应的Log输出方式也并不相同,差异则主要体现在需要包含的头文件中。如果是在完整源码编译环境下,只要include 头文件(位于Android-src/system/core/include/cutils),就可以使用对应的LOGI、LOGD等方法了,当然LOG_TAG,LOG_NDEBUG等宏值需要自定义。如果是在NDK环境下编译,则需要include 头文件(位于ndk/android-ndk-r4/platforms/android-8/arch-arm/usr/include/android/),另外自己定义宏映射,例如:

#include

#ifndef LOG_TAG

#define LOG_TAG “MY_LOG_TAG”

#endif

#define LOGD(…) __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,__VA_ARGS__)

#define LOGI(…) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)

#define LOGW(…) __android_log_print(ANDROID_LOG_WARN,LOG_TAG,__VA_ARGS__)

#define LOGE(…) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)

#define LOGF(…) __android_log_print(ANDROID_LOG_FATAL,LOG_TAG,__VA_ARGS__)

另外,在Android.mk文件中对类库的应用在两种环境下也不相同。如果是NDK环境下,需要包括

LOCAL_LDLIBS := -llog

而在完整源码环境下,则需要包括

LOCAL_SHARED_LIBRARIES := libutils libcutils

8. 如果希望知道如何在Native中访问Java类的私有域和方法,请参考这篇文章

Random Posts

    visit the website for more great content.

    Android 开发学习笔记(五):Starting an Activity

    Android 开发学习笔记(五):Starting an Activity

    Starting an Activity

    不像其他的编程模式中,应用程序是由 main () 方法发起的,Android 系统启动代码在一个 activity 实例通过调用的回调方法,对应于特定阶段的生命周期。有一系列回调方法用于启动一个 activity,和一系列回调方法用以拆毁一个 activity。

    这一课将提供一个生命周期方法的总览,并且告诉你如何处理第一个回调方法,在创建一个新的 activity 实例时。

    了解生命周期回调(Understand the Lifecycle Callbacks)

    在一个 activity 的生命周期中,系统调用的一组核心的生命周期方法序列类似的一个阶梯金字塔。就是说,activity 生命周期的每一个阶段是一个单独的步骤的金字塔。为系统创建了一个新的 activity 实例,每个回调方法移动 activity 状态的一个步骤对顶部。顶部的塔是在该点的,activity 在前台运行,用户可以与它交互。


    当用户开始离开 activity 时,系统会调用其他的(函数)方法,把 activity 的状态从金字塔向下转移,以拆除这个 activity。在某些情况下,活动将只有一部分的方式下锥体等(如当用户切换到另一个应用程序),这一点让 activity 可以回到顶部(如果用户返回到 activity),并能恢复到用户离开时的样子。 

    下面是 Activity 生命周期的一个简单图示,就像一个金字塔一样。这个图示反映出了那些使得 activity 状态向上的回调方法,也反映了那些使得 activity 状态向下的回调方法。activity 也可以从暂停状态(Paused)或停止状态(Stopped)返回到 Resumed 状态。

    根据根据需要创建的 activity 的复杂程度,你可能不需要实现所有生命周期的方法。然而,重要的是你了解每一个执行,以确保您的应用程序的行为方式符合用户的期望。适当地实现你的 activity 生命周期中的方法,可以确保你的应用程序在以下几个方面表现的好一些:

    • 当用户正在使用你的程序时,突然接听电话或者切换到其它的程序,它不至于会崩溃。
    • 当用户不再使用你的程序时,它不会继续消耗宝贵的系统资源。
    • 当用户暂时离开你的程序,再返回时不会丢失用户的进展。
    • 当手机屏幕横向纵向切换时,它不会崩溃或者丢失用户的进度。

    activity 在不同的状态之间进行转变时,存在几状况。然而,这些状态中只有三种是静态的(activity 可以持续的状态)。换句话说,activity 可以在这三种状态之一,存在一段比较长的时间。这三种状态分别是:

    重返状态(Resumed
    在这个状态,activity 处于屏幕最前端,并且用户可以与之进行交互。(有时也被称为 “运行” 状态。

    暂停状态(Paused)
    在这个状态,activity 被其它的 activity 部分地遮挡住了。这个其它的 activity 位于屏幕最前端,要么是半透明的,要么没有占据整个屏幕。被暂停的 activity 无法接受用户的输入,也不能执行任何代码。

    停止状态(Stopped)
    在这种状态下,activity 是完全隐藏着的,并且用户不可见;这被认为是在背景下。在停止状态中,该 activity 的实例和它的所有状态信息,例如成员变量,都是保留的,但它不能执行任何代码。

    Activity 的其他状态(创建 Created 和开始 Started)都是短暂的。系统会通过调用下一个生命周期的回调方法,来使得它们快速转换到下一个状态。换句话说,系统在调用 onCreate () 方法之后,立马就调用 onStart () 方法,紧接着就是调用 onResume () 方法。

    这就是 Activity 最基本的生命周期。现在,你可以开始学习有关的一些具体生命周期行为了。

    指定应用程序的启动 Activity(Specify Your App''s Launcher Activity)

    当用户从手机主屏幕上选择你的应用程序图标时,Android 系统就会调用一个 Activity 的 onCreate () 方法。这个 Activity 就是你为自己的应用程序指定的那个 “launcher”(或 “main”)activity。它是你的的应用程序对外用户界面的主要入口。

    你可以在 Android 清单文件(AndroidManifest.xml)中指定哪个 activity 作为主要 activity。这个文件在你项目的根目录下

    你必须在清单文件中使用 <intent-filter> 来声明应用程序的主要 activity,同时指定 MAIN 动作和 LAUNCHER 类别。例如:

    <activity android:name=".MainActivity" android:label="@string/app_name">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>

    注意,当你使用 Android SDK 工具创建一个新的 Android 工程时,默认的工作文件中已经包含了一个 Activity 类,并且把它声明在清单文件里了。

    如果你的 activity 中有一个没有声明 MAIN 行为或者 LAUNCHER 类别,那么你的应用程序图标将不会出现在主屏幕的应用程序列表中

    创建一个新的实例(Create a New Instance)

    大多数应用程序包括几个不同的 activity,允许用户执行不同的动作。是否有一个活动的主要活动的创造,当用户点击您的应用程序图标或不同的活动,您的应用程序启动响应用户操作,系统创建新的 Activity 实例通过调用 onCreate () 方法。

    你必须实现 oncreate () 方法,它执行基本的应用程序启动逻辑,而这在 activity 的整个生命周期中应该只发生一次。例如,你的 onCreate () 方法实现应该定义用户界面,并可能实例化一些类范围的变量。

    例如,下面例子中的 onCreate () 方法显示出一些执行 activity 基本安装的代码,如宣布用户界面(定义在一个布局文件中),定义成员变量,并配置一些用户界面。

    TextView mTextView; // Member variable for text view in the layout
    
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    
        // Set the user interface layout for this Activity
        // The layout file is defined in the project res/layout/main_activity.xml file
        setContentView(R.layout.main_activity);
        
        // Initialize member TextView so we can manipulate it later
        mTextView = (TextView) findViewById(R.id.text_message);
        
        // Make sure we''re running on Honeycomb or higher to use ActionBar APIs
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
            // For the main activity, make sure the app icon in the action bar
            // does not behave as a button
            ActionBar actionBar = getActionBar();
            actionBar.setHomeButtonEnabled(false);
        }
    }

    一旦 onCreate () 执行完成,系统会接二联三地调用 onStart () 和 onResume () 方法。你的 activity 不会驻留在创建(Created)或开始(Started)状态。技术上,当 onStart () 方法被调用后,activity 就对用户可见了,但紧接着 onResume () 方法就被调用,activity 会保持在恢复(Resumed)状态,直到某个事件发生才引起状态改变,例如当接收到一个电话,或用户导航到其他的 activity,或设备的屏幕关闭。

    在接下来的其他课程中,你将会看到其他的启动方法,onStart () 和 onResume ()。它们在你的 acvitity 的生命周期中,被用来从暂停(Paused)或停止(Stopped)状态中恢复 activity。

    注意:onCreate () 方法包括一个参数,名为 savedInstanceState。它将在后面有关重建 Activity(Recreating an Activity)的课程中讨论。

    图 2 是另一个示例,它强调了 activity 生命周期结构中的三个主要的回调。在创建一个 activity 的新实例时,系统会依次调用这三个回调函数:onCreate (),onStart (),和 onResume ()。一旦这一系列回调完成,activity 就达到了恢复(Resumed)状态,用户可以与它交互,直到他们切换到一个不同的 activity。

    Activity 的销毁(Destroy the Activity)
    ------------------------------------------------------------------------------------------------------------------------------------------

    正如 activity 生命周期第一个回调是 onCreate (),其最后一个回调是 onDestroy ()。系统调用这个方法作为最后的信号,你的 activity 实例将被从系统内存中完成删除。

    大多数应用程序不需要实现这个方法,因为本地类引用已经随 activity 一起被销毁了,你的 activity 应该在 onPause () 和 onStop () 执行期间做一些清理活动。然而,如果你的 activity 在执行 onCreate () 方法时创建了后台运行的线程,或者是其他长期运行的资源(如果没有正确关闭则可能引发内存泄漏),那么你就应该在 onDestroy () 方法中杀了他们。

    @Override
    public void onDestroy() {
        super.onDestroy();  // Always call the superclass
        
        // Stop method tracing that the activity started during onCreate()
        android.os.Debug.stopMethodTracing();
    }
    注意:系统调用 ondestroy () 后它已经称为 onpause () 和 onstop () 在所有情况下只有一个:你打电话时 finish () 在 oncreate () 方法。在某些情况下,例如当你的活动可作为临时决定推出另一个活动,你可以称 finish () 内 oncreate () 破坏活动。在这种情况下,系统会立即要求 ondestroy () 没有要求任何其他生命周期的方法。

    关于Android开发学习笔记 -- 活动相关android活动的作用的问题就给大家分享到这里,感谢你花时间阅读本站内容,更多关于2019年Android岗位BAT等大厂面试题知识点小结,2021年Android开发学习路线、Android MediaPlayer音频播放器详解,2021年Android开发学习路线、Android Native 代码开发学习笔记、Android 开发学习笔记(五):Starting an Activity等相关知识的信息别忘了在本站进行查找喔。

    本文标签: