前言

刚入行安卓的萌新一枚,目前对工作内容,工作方向还处于混沌状态,搞个Android Studio和一个简单的app都搞了四天,期间的一些问题也缺少一套方法论去解决,完全是面向百度,没有解决问题的思路,写个博客总结一下这几天的一些坑和遇到的问题。用一个写的乱七八糟的demo去由浅入深稍微熟悉一下安卓四大组件,以及一个项目从开发到打包成apk的过程。

乱七八糟的Demo

本Demo写的时候属于是意识流写法,并没有明确的思路(软件工程还给老师了),现在重新学着规范,写一下文档。

项目要求

实际上是自己临时想的一些功能,似乎网上也有比较完善的实现代码。最初的设想是能具备以下功能:

  • 一款能够设置时间,定时打开手机钉钉打卡的app;
  • 能够自己设置时间,并且可以选择一天开启还是长期开启
  • 能够在设置中关闭定时开启的功能
  • 拓展功能,可以定时打开任意手机的app,通过安卓自带的一些访问手机应用程序的接口来实现此功能
    最后的结果是,失败了,虽然看上去很简单,或许实际也很简单,但是开发过程总是遇到各种问题,然后最重要的一个功能始终无法实现,暂时不了了之了,以后再来重新做吧(悲。文末会总结一下开发这个功能时候遇到的问题。现在先说说真正实现的功能。
    于是退而求其次,我选择用一个音乐播放器demo来研究四大组件。音乐播放器功能:
  • 播放,暂停,上下首
  • 显示歌曲信息(未实现,但是会贴上实现的代码,从别人完成的代码吸取经验)
  • 添加本地音乐(以后再实现吧。)

软件可行性分析

选择完项目之后的第一步,就是可行性分析。可行性分析包含四个要点:技术可行性,经济可行性,操作可行性,法律可行性。

  • 技术可行性,播放暂停上下首都是简单的状态切换判断功能,显示歌曲信息何添加本地音乐等都是跟数据库的一些操作有关,也是可以实现的。编程语言方面,我使用了Java,因为Kotlin不会。
  • 经济可行性:不要钱,所以从经济层面将是可以实行的。
  • 操作可行性:供用户操作的接口按钮实际上只有切歌,暂停,还有一个添加本地音乐。以从用户操作层面上来说,此次项目开发是可行的。
  • 法律可行性:没有盈利,仿的也是开源代码,但是有杰伦的歌(没有版权。

需求分析

项目要求已经在上面提到了
下面画一下简单的uml类图

概要设计

实现音乐播放部分的很简单,基本上就是MusicActivity和MyService实现的。MusicActivity利用广播对Myservice的行为进行控制。MyService实际上对音乐进行播放暂停等操作,由于实际的显示界面是由MusicActiviy展示的,所以如果需要对歌曲信息等进行展示,还需要MysService的广播将自己的歌曲信息以及状态反馈给MusicActivity。其具体实现过程放到后文讲。这一部分是音乐播放的功能,没有涉及到数据库操作,希望后续熟悉后能加入。这是音乐播放部分。
此外虽然没有完成但是依然保留的定时启动钉钉部分,主界面的按钮可以跳转到ClockActivity,具体是通过安卓自带的TimePicker来实现闹钟定时功能,然后通过pingIntent来传输给静态广播需要打开某app的请求。由于不知名原因,定时和启动功能均未能正确运行。
至此,安卓四大组件其三以及提到过了,分别是:

  • Activity(活动)
  • Service(服务)
  • Broadcast Receiver(广播 接收者)
    我们还未提到的第四大组件是content provider(内容提供者)

四大组件详解

在讲四大组件前先来看一下安卓架构
以下许多内容都是参考大佬的博客[点击跳转]干货很多(https://blog.csdn.net/joye123/article/details/116197862?utm_medium=distribute.pc_relevant.none-task-blog-2defaultbaidujs_utm_term~default-1-116197862-blog-91946872.pc_relevant_multi_platform_whitelistv3&spm=1001.2101.3001.4242.2&utm_relevant_index=4)


可以看到四大组件是属于应用程序框架层。而开发者进行安卓app开发基本上都是基于四大组件进行开发。
Activity:Activity是开发中最常用的,也是最复杂的一个组件。它是用户可以专注做一些事情的东西。它的主要功能就是可以和用户进行交互操作,所以几乎所有的Activity都会负责创建一个显示窗口,然后通过setContentView显示特定的UI。

Service:除了Activity,Service是第二复杂的组件。和Activity相比,Service是一种处于后台长时间运行的组件,它没有UI界面,不需要与用户交互。它被设计用来后台执行耗时任务或者为其他应用程序提供功能调用的服务。

BroadcastReceiver:广播接收者,这个组件比较简单,比较好理解了。类似于观察者模式,应用程序可以添加自己关心的广播事件,从而为用户提供更好的使用体验。这些广播可以是来自于操作系统、其他的应用程序、甚至是自己的应用程序。例如网络连接变化、电池电量变化、开机启动等。

ContentProvider:内容提供者,它被设计用来在不同的应用程序之间共享数据。例如电话程序中的联系人数据,就可以被其他应用程序读取。如果仅仅是在同一个程序中存取数据的话,用SQLiteDatabase接口就可以了。

系统管理四大组件

除了BroadcastReceiver可以动态注册外,四大组件在使用之前必须先在 AndroidManifest.xml清单文件中进行声明。我们这个demo中ClockActivity用的就是静态注册,需要在AndroidManifest.xml中声明,即使app关闭了,仍然可以在后台工作。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"package="com.example.note"><uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /><!-- 查询联系人 --><uses-permission android:name="android.permission.READ_CONTACTS" /><!-- 添加联系人 --><uses-permission android:name="android.permission.WRITE_CONTACTS" /><uses-permissionandroid:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"tools:ignore="ProtectedPermissions" /><applicationandroid:allowBackup="true"android:dataExtractionRules="@xml/data_extraction_rules"android:fullBackupContent="@xml/backup_rules"android:icon="@mipmap/ic_launcher"android:label="@string/app_name"android:roundIcon="@mipmap/ic_launcher_round"android:supportsRtl="true"android:theme="@style/Theme.Note"tools:targetApi="31"><activityandroid:name=".MessageActivity"android:exported="false" /><receiverandroid:name=".receiver.MyMusicReceiver"android:enabled="true"android:exported="true" /><activityandroid:name=".MusicActivity"android:exported="false" /><serviceandroid:name=".MyService"android:enabled="true"android:exported="true" /><activityandroid:name=".ClockActivity"android:exported="false" /><receiverandroid:name=".receiver.MyReceiver"android:enabled="true"android:exported="true"><intent-filter><action android:name="android.intent.action.MY_BROADCAST" /><category android:name="android.intent.category.DEFAULT" /></intent-filter></receiver><activityandroid:name=".SettingsActivity"android:exported="false"android:label="@string/title_activity_settings" /><activityandroid:name=".MainActivity"android:exported="false" /><activityandroid:name=".Splash"android:exported="true"><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter></activity>/ContentProvider声明
<providerandroid:name="androidx.core.content.FileProvider"android:authorities="${PACKAGE_NAME}.fileprovider"android:exported="false"android:grantUriPermissions="true"><meta-dataandroid:name="android.support.FILE_PROVIDER_PATHS"android:resource="@xml/sharesfilepaths" />
</provider></application></manifest>

在应用程序安装时,应用安装程序通过PackageInstaller服务解析应用安装包,并将AndroidManifest.xml中声明的四大组件信息保存到PackageManagerService中。

public class PackageManagerService extends IPackageManager.Stubimplements PackageSender {...//组件解析器,存储了系统所有应用程序的四大组件信息private final ComponentResolver mComponentResolver;}public class ComponentResolver {/** All available activities, for your resolving pleasure. */@GuardedBy("mLock")private final ActivityIntentResolver mActivities = new ActivityIntentResolver();/** All available providers, for your resolving pleasure. */@GuardedBy("mLock")private final ProviderIntentResolver mProviders = new ProviderIntentResolver();/** All available receivers, for your resolving pleasure. */@GuardedBy("mLock")private final ActivityIntentResolver mReceivers = new ActivityIntentResolver();/** All available services, for your resolving pleasure. */@GuardedBy("mLock")private final ServiceIntentResolver mServices = new ServiceIntentResolver();...
}

context类

另外一个重要的类是Context
Context是关于应用程序环境的全局信息接口,Context是一个抽象类,它的实现都是由系统类实现的。Context允许访问应用程序特定的资源和类,如

资源管理器AssetsManager、
包管理器PackageManager、
文本图片主题资源Resource、
主线程消息循环Looper
四大组件操作,如启动Activity、BroadcastReceiver,接收Intent
SharedPreferences操作
私有目录文件操作
数据库创建、删除操作
获取系统服务
权限操作


如图,四大组件中的Activity和Service都是Context下的子类,Context

//android.content.Contextpublic abstract class Context {public abstract AssetManager getAssets();public abstract Resources getResources();public abstract PackageManager getPackageManager();public abstract ContentResolver getContentResolver();public abstract Looper getMainLooper();public final CharSequence getText(@StringRes int resId) {return getResources().getText(resId);}public final String getString(@StringRes int resId) {return getResources().getString(resId);}public abstract String getPackageName();
}

Context的实现类为ContextImpl,ContextImpl为Activity或其他应用程序组件提供了基础的Context实现。

//android.app.ContextImplclass ContextImpl extends Context {//ContextImpl的构造方法是私有的,只能通过几个静态方法创建ContextImpl实例private ContextImpl(@Nullable ContextImpl container, @NonNull ActivityThread mainThread,@NonNull LoadedApk packageInfo, @Nullable String splitName,@Nullable IBinder activityToken, @Nullable UserHandle user, int flags,@Nullable ClassLoader classLoader, @Nullable String overrideOpPackageName) {...}....//创建系统应用的上下文@UnsupportedAppUsagestatic ContextImpl createSystemContext(ActivityThread mainThread) {LoadedApk packageInfo = new LoadedApk(mainThread);ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, null, 0, null, null);context.setResources(packageInfo.getResources());
context.mResources.updateConfiguration(context.mResourcesManager.getConfiguration(), ontext.mResourcesManager.getDisplayMetrics());return context;}//基于系统应用Context创建的用于UI的系统上下文,此上下文具有可以主题化的资源static ContextImpl createSystemUiContext(ContextImpl systemContext, int displayId) {final LoadedApk packageInfo = systemContext.mPackageInfo;ContextImpl context = new ContextImpl(null, systemContext.mMainThread, packageInfo, null,null, null, 0, null, null);context.setResources(createResources(null, packageInfo, null, displayId, null,packageInfo.getCompatibilityInfo()));context.updateDisplay(displayId);return context;}//创建普通应用级别的上下文,packageInfo指定了某个已安装的应用static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo,String opPackageName) {if (packageInfo == null) throw new IllegalArgumentException("packageInfo");ContextImpl context = new ContextImpl(null, mainThread, packageInfo,        null, null, null, 0,null, opPackageName);context.setResources(packageInfo.getResources());return context;}//创建Activity级别的上下文static ContextImpl createActivityContext(ActivityThread mainThread,LoadedApk packageInfo, ActivityInfo activityInfo, IBinder activityToken, //代表一个Activityint displayId,Configuration overrideConfiguration) {if (packageInfo == null) throw new IllegalArgumentException("packageInfo");String[] splitDirs = packageInfo.getSplitResDirs();ClassLoader classLoader = packageInfo.getClassLoader();if (packageInfo.getApplicationInfo().requestsIsolatedSplitLoading()) {Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "SplitDependencies");try {classLoader=packageInfo.getSplitClassLoader(activityInfo.splitName);splitDirs = packageInfo.getSplitPaths(activityInfo.splitName);} catch (NameNotFoundException e) {// Nothing above us can handle a NameNotFoundException, better crash.throw new RuntimeException(e);} finally {Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);}}ContextImpl context = new ContextImpl(null, mainThread, packageInfo, activityInfo.splitName,activityToken, null, 0, classLoader, null);// Clamp display ID to DEFAULT_DISPLAY if it is INVALID_DISPLAY.displayId = (displayId != Display.INVALID_DISPLAY) ? displayId : Display.DEFAULT_DISPLAY;final CompatibilityInfo compatInfo = (displayId == Display.DEFAULT_DISPLAY)? packageInfo.getCompatibilityInfo(): CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO;final ResourcesManager resourcesManager =ResourcesManager.getInstance();// Create the base resources for which all configuration contexts for this Activity// will be rebased upon.context.setResources(resourcesManager.createBaseActivityResources(activityToken,packageInfo.getResDir(),splitDirs,packageInfo.getOverlayDirs(),packageInfo.getApplicationInfo().sharedLibraryFiles,displayId,overrideConfiguration,compatInfo,classLoader));context.mDisplay = resourcesManager.getAdjustedDisplay(displayId,context.getResources());return context;}...
}

除了ContextImpl类外,ContextWrapper类也继承了Context,但是ContextWrapper类并不自己实现了Context的方法,而是通过构造方法,代理给另外一个Context的实现。这样ContextWrapper的子类就可以在不修改ContextWrapper类的情况下,修改其调用方法的实现

public class ContextWrapper extends Context {Context mBase;public ContextWrapper(Context base) {mBase = base;}protected void attachBaseContext(Context base) {if (mBase != null) {throw new IllegalStateException("Base context already set");}mBase = base;}...
}

这里使用了代理模式,因为base实际上是Context类,真正最后调用的是ContextImpl的实现方法。

Activity、ContentProvider、Service三者的启动顺序

一个新的应用启动时,会优先初始化其Application类,创建了Application实例后,会立即调用其attach方法。
然后就会初始化应用中声明的ContentProvider:
ContentProvider初始化完成后,会再调用Application类的onCreate方法。

AMS在初始化完客户端的Application类后,会检查是否有需要运行的Service和BroadcastReceiver。
所以初始化顺序是Application.attach() > ContentProvider> Application.onCreate() > Activity/Service/BroadcastReceiver。

ContentProvider比Activity或Service初始化的顺序都要早,所以有些第三方库利用这个特性,通过ContentProvider自动初始化一些功能,而不用在Application中添加初始化代码。

Activity

生命周期

protected void onCreate(Bundle savedInstanceState);
Activity创建,用于初始化数据

protected void onStart();
Activity UI可见

protected void onRestart();
Activity UI从不可见变为可见

protected void onResume();
Activity UI可操作

protected void onPause();
Activity 暂停,UI可见,不可操作

protected void onStop();
Activity停止,UI不可见

protected void onDestroy();
Activity销毁

Service

Service是一种应用程序组件,用于两种使用场景:

后台执行长时间的任务,而不需要与用户交互。例如后台播放音乐
将本应用的功能通过接口的形式暴露给其他应用程序调用
两种启动Service的方式:
1、startService,Service会执行onCreate和onStartCommand。多次启动同一个Service不会多次执行onCreate,会多次执行onStartCommand
2、bindService,Service会执行onBind方法,返回给调用者一个Binder对象,用于功能接口调用。

Service优先级:
Service所在进程的优先级仅次于前台进程的优先级,系统会尽量避免杀死该进程,除非内存压力非常大。如果被系统杀死了,系统会稍后尝试重启该Service,并重新将Intent数据发送Service来恢复杀死之前的状态。

onStartCommand方法的返回值,决定Service被系统杀死后的操作

START_NOT_STICKY 被系统杀死后不会被重启
START_STICKY 被系统杀死后会重建,但是会发送一个null给onStartCommand
START_REDELIVER_INTENT 被系统杀死后会重建,并且会逐一发送等待处理的Intent给onStartCommand

startService调用

创建Service实例
调用Service的attach方法
调用Service的onCreate方法
以token为key保存到Map中,方便后续多次调用onStartCommand
告知AMS启动完成

bindService调用

bindService与startService不同的地方是,bindService需要提供一个ServiceConnection的回调接口,用来告知调用者已连接或断开连接。

Service启动时的生命周期

多次调用startService
执行一次Service的onCreate
多次执行Service的onStartCommand

多次调用bindService
执行一次Service的onCreate
执行一次Service的onBind
调用方的ServiceConnection也只会回调一次

content provider

ContentProvider用于在不同应用程序间提供数据,它在内部实现了跨进程的调用,不需要数据提供者或调用者关心。
调用者通过URI向ContentProvider查询数据。
一般是调用ContentResolver来获取数据

context.getContentResolver().query("查询的ContentProviderUri", "返回的列", "过滤行的条件", "过滤行的参数", "返回行数据的排序方式");

BroadcastReceiver

在Manifesh.xml文件中注册自定义的BroadcastReceiver,当意图过滤器中的动作发生时,会回调BroadcastReceiver中的onRecevie方法.
静态注册

       <receiverandroid:name=".receiver.MyReceiver"android:enabled="true"android:exported="true"><intent-filter><action android:name="android.intent.action.MY_BROADCAST" /><category android:name="android.intent.category.DEFAULT" /></intent-filter></receiver>

动态注册

IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_SCREEN_OFF);registerReceiver(new BroadcastReceiver() {@Overridepublic void onReceive(Context context, Intent intent) {String action = intent == null ? "" : intent.getAction();if (Intent.ACTION_SCREEN_OFF.equalsIgnoreCase(action)) {Log.i(TAG, "onReceive: 屏幕关闭");}context.unregisterReceiver(this);}
}, intentFilter);

在Activity上下文中调用registerReceiver方法,动态注册一个广播接收者,同时指定了意图过滤器。

回到demo

介绍了一下四大组件,回头看一下demo

项目目录解释

  • main里面的java是整个app的入口,一定会有一个主活动,是你打开app后见到的第一个界面
  • res是自动生成的目录,里面用于存放各种资源文件,以及一些布局xml。例如:drawable里放一些背景图片资源。layout一般和activity对应,是当前活动的UI界面,里面会定义各种按钮以及文本框之类的组件。res里的控件都要注册一个id,这个是一种int类型的唯一标识。如图

  • 这里注册了一个id/down,在代码中需要使用的时候就会用

这种形式来获得这个按钮把它包装成一个对象

  • raw 中存储一些资源,音乐文件等
  • menu里是如图右上角菜单的一些组件的设置

  • 下一个比较重要的文件是AndroidManifest.xml
    四大组件都要在里面进行注册,以及一些权限申请也要在里面写明
    gradle用于构建项目依赖一般也是自动生成
    音乐播放器功能主要由两个类实现
package com.example.note;import androidx.appcompat.app.AppCompatActivity;import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.widget.Button;
import android.widget.Toast;public class MusicActivity extends AppCompatActivity {Button last, start, next;public static final String CTRL_ACTION = "com.example.note.CTRL";public static  final String UPDATE_ACTION="com.example.note.UPDATE";int status = 0x11;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_music);last = findViewById(R.id.up);start = findViewById(R.id.start);next = findViewById(R.id.down);Intent intent = new Intent(MusicActivity.this, MyService.class);ActivityReceiver activityReceiver = new ActivityReceiver();IntentFilter filter = new IntentFilter();filter.addAction(UPDATE_ACTION);registerReceiver(activityReceiver, filter);startService(intent);last.setOnClickListener(view -> {Intent intent1=new Intent(CTRL_ACTION);intent1.putExtra("control", 3);this.sendBroadcast(intent1);});start.setOnClickListener(view -> {Intent intent1=new Intent(CTRL_ACTION);intent1.putExtra("control", 1);sendBroadcast(intent1);});next.setOnClickListener(view -> {Intent intent1=new Intent(CTRL_ACTION);intent1.putExtra("control", 4);sendBroadcast(intent1);});}private class ActivityReceiver extends BroadcastReceiver {@Overridepublic void onReceive(Context context, Intent intent) {//获取来自receive中intent的update消息,代表播放状态int update = intent.getIntExtra("update", -1);switch (update) {//播放歌曲case 0x11:status = 0x11;break;//播放>暂停case 0x12:status = 0x12;break;}}}
}

在onCreate方法里先启动MyService

    Intent intent = new Intent(MusicActivity.this, MyService.class);startService(intent);

分别绑定了三个按钮,并且每个按钮设置的监听事件是发送一个广播

        next.setOnClickListener(view -> {Intent intent1=new Intent(CTRL_ACTION);intent1.putExtra("control", 4);sendBroadcast(intent1);});

例如这个点击下一首的通过发送控制信息给Myservice的内部广播类来进行切歌的控制。

MyService类是另一个重要的实现类

package com.example.note;import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.AssetFileDescriptor;
import android.content.res.AssetManager;
import android.media.MediaPlayer;
import android.os.IBinder;
import android.util.Log;
import android.widget.Toast;import androidx.annotation.Nullable;import com.example.note.receiver.MyReceiver;import java.io.IOException;public class MyService extends Service {@NullableMyReceiver serviceReceiver;AssetManager am;String[] musics=new String[]{"music0.mp3","music1.mp3","music2.mp3","music3.mp3","music4.mp3"};MediaPlayer mPlayer;//0x11表示没有播放,0x12代表正在播放,0x13代表暂停int status=0x11;int current=0;public IBinder onBind(Intent intent) {return null;}public void onCreate(){super.onCreate();am=getAssets();//创建BroadcastReceiverserviceReceiver=new MyReceiver();//创建IntentFilterIntentFilter filter=new IntentFilter();filter.addAction(MusicActivity.CTRL_ACTION);registerReceiver(serviceReceiver,filter);mPlayer=new MediaPlayer();//为MediaPlayer播放完成事件绑定监听器mPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {@Overridepublic void onCompletion(MediaPlayer mp) {current++;if (current>=musics.length){current=0;}Intent sendIntent = new Intent(MusicActivity.UPDATE_ACTION);sendIntent.putExtra("current",current);//发送广播,将被Activity组件中的BroadcastReceiver接收sendBroadcast(sendIntent);//准备播放音乐prepareAndPlay(musics[current]);}});}private class MyReceiver extends BroadcastReceiver{@Overridepublic void onReceive(Context context, Intent intent) {int control =intent.getIntExtra("control",-1);switch (control){//播放或暂停case 1://原来处于没有播放状态if (status==0x11){//准备并播放音乐prepareAndPlay(musics[current]);status=0x12;}//原来处于播放状态else if (status==0x12){//暂停mPlayer.pause();//改变为暂停状态status=0x13;}//原来处于暂停状态else if (status==0x13){//播放mPlayer.start();//改变状态status=0x12;}break;//停止声音case 2://如果原来正在播放或暂停if (status==0x12||status==0x13) {//停止播放mPlayer.stop();status = 0x11;}break;case 3://原来处于没有播放或暂停状态if (status==0x11||status==0x13){if(current==0) {current=4;prepareAndPlay(musics[current]);}//准备并播放音乐else {current=current-1;prepareAndPlay(musics[current]);}status=0x12;}//原来处于播放状态else if (status==0x12){//上一首//准备并播放音乐if(current==0) {current=4;prepareAndPlay(musics[current]);}else {current=current-1;prepareAndPlay(musics[current]);}}break;case 4://原来处于没有播放或暂停状态if (status==0x11||status==0x13){if(current==4) {current=0;prepareAndPlay(musics[current]);}   //准备并播放音乐else {current=current+1;prepareAndPlay(musics[current]);}status=0x12;}else if (status==0x12){if(current==4) {current=0;prepareAndPlay(musics[current]);}else {current=current+1;prepareAndPlay(musics[current]);}}break;}//广播通知Activity更改图标、文本框Intent sendIntent=new Intent(MusicActivity.UPDATE_ACTION);sendIntent.putExtra("update",status);sendIntent.putExtra("current",current);//发送广播,将被Activity组件中的BroadcastReceiver接收sendBroadcast(sendIntent);}}private void prepareAndPlay(String music){try{//打开指定音乐文件AssetFileDescriptor afd=am.openFd(music);mPlayer.reset();//使用MediaPlayer加载指定的音乐文件mPlayer.setDataSource(afd.getFileDescriptor(),afd.getStartOffset(),afd.getLength());//准备声音mPlayer.prepare();//播放mPlayer.start();Toast.makeText(this,music+"正在播放",Toast.LENGTH_SHORT).show();}catch (IOException e) {e.printStackTrace();}}
}

这个类里面自己写的时候踩得雷基本上就是MediaPlayer的调用问题,对这个类的api了解不够导致总是出一些异常。姑且不谈
这里我们5首音乐全部放在assets里而非raw里,调用asserts需要通过AssetManage来调用
这个服务里也用到了广播组件,不过这里主要用来接收控制请求,和所有其他组件一样,都要进行注册,这里用了动态注册的方法

 IntentFilter filter=new IntentFilter();filter.addAction(MusicActivity.CTRL_ACTION);//在MusicActivity中定义的静态变量,也是这个广播被识别调用的重要标识,相当于一个身份证。registerReceiver(serviceReceiver,filter);

本demo中没有用到content provider,之后会考虑添加应用场景。

除了四大组件,我们可以看到还有一个很重要的类Intent类。
Intent用于启动Activity, Service, 以及BroadcastReceiver三种组件, 同时还是组件之间通信的重要媒介。
在demo里,可以看到,用广播传递控制信息,接收广播,开启服务,开启活动所用的都是Intent。
更多内容之后在研究吧。

总结

本demo中熟悉了一下三大组件的一些使用,但是代码没有很好的设计,看起来不够清晰。比如music
活动类还可以使用继承Onclicklistender的方式来重写onclick方法,对所有按钮的响应事件写进一个方法中。还有初版需求没有完成留下的没有用的界面,以后在写代码前要先思考整体的框架以及各个模块的功能,然后填充代码。

音乐播放器的实现过程及四大组件入门相关推荐

  1. android音乐播放器的历史,基于Android音乐播放器的研究

    Android平台是目前智能移动终端的主流系统.随着人们生活.工作节奏的加快,乘车.运动.学习等碎片时间的增多,音乐播放器成为人们所关心的必备应用之一,广受大家欢迎. 目前,Android市场上以酷狗 ...

  2. Python制作音乐播放器,帮你随时放飞心情~

    最近网易云音乐闹出不少事情,甚至被各大应用商店下架.它的某些做法小笨聪也着实不敢苟同,但还是希望它整改后能够发展更好,当然不只是在故事式热评方面,还包括更为重要的版权问题. 由此,小笨聪也萌发了制作一 ...

  3. 我用 Python 写了一款炫酷音乐播放器,想听啥随便搜!

    作者:Dragon少年 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明.本文链接:https://blog.csdn.net/hhladminh ...

  4. 基于FPGA的音乐播放器系统设计_kaic

    摘 要 音乐播放器随处可见,广播.CD.MP3.车载播放器.智能家居等系统,都用播放器娱乐着我们的生活.FPGA以硬件描述语言完成的电路设计,具有运算速度快,编程简单又稳定性,长期维护,成本等优点,本 ...

  5. Android开发----音乐播放器(界面设计)

    转眼也沦为"大四狗"的行列当中去,本来打算在暑假的时候找一个实习,结果学校临时安排了"暑期实训" 原本计划好的安排全部被打乱了,哎~~~也只能跟着学校的脚步&q ...

  6. 个人Web音乐播放器实践

    个人Web音乐播放器实践小结 React + Redux web音乐播放器实践 技术栈及相关组件 遇到的问题及解决方法 redux 数据管理 自定义滚动条组件 react-scrollbar 首页歌单 ...

  7. python 本地音乐播放器制作过程

    制作这个播放器的目的是为了将下载下来的mp3文件进行随机或是顺序的播放.选择需要播放的音乐的路径,选择播放方式,经过测试可以完美的播放本地音乐. [阅读全文] 在开始之前介绍一个免费下载mp3音乐的网 ...

  8. 音乐播放器功能的实现,歌词lrc显示,播放过程中来电

    原文地址: http://blog.sina.com.cn/s/blog_afb547c60101hjfd.html 5.1 音乐播放器功能的实现.(社区ID:clarck) 音乐播放器最主要的功能有 ...

  9. android4以下的音乐播放器,Android平台四大音乐播放器对比评测

    二.歌曲添加,谁便利 二.歌曲添加,谁便利 对于手机音乐播放器来说,要播放音乐,首先需要添加歌曲.因而添加歌曲的便利性,自然是考评这类软件的一个方面. 1. 天天动听有扫描全部歌曲和浏览文件夹两种添加 ...

最新文章

  1. 一维有限元法matlab,一维有限元法解常微分方程
  2. IOS开发基础之微博项目
  3. Java-进阶:多线程2
  4. 【专栏必读】王道考研408计算机组成原理万字笔记和题目题型总结(从学生角度辅助大家理解):各章节导航及思维导图
  5. 给linux添加新硬盘
  6. git detached head
  7. nftables-howto-zh中文手册(不完整)
  8. linux安装mysql5.7.24_下载安装 Ubuntu 19.04 “Disco Dingo” | Linux 中国
  9. python 流写入文件_Python数据流写入文件
  10. ARM计划将四核心CPU引入磁盘驱动器
  11. java二维数组详解
  12. 车身控制器BCM系统框图
  13. win10安装影子系统,导致电脑无限蓝屏,解决总结
  14. 冰点还原精灵破解版|冰点还原精灵中文破解版下载(附冰点还原精灵注册机及许可证密钥)
  15. python翻转棋_Python算法做翻转棋子游戏
  16. 微信小程序详细图文教程-10分钟完成微信小程序开发部署发布(3元获取腾讯云服务器带小程序支持系统)
  17. LidarSLAM(一):NDT
  18. 简单的手机html页面源代码,手机页面h5的简单demo
  19. PTA实验4-1-2 求奇数和 (15分) 本题要求计算给定的一系列正整数中奇数的和。
  20. keyshot渲染玻璃打光_keyshot打光技巧,教你如何制作汽车自由式布光效果

热门文章

  1. 很贱的心灵毒鸡汤,句句精辟,直达内心!
  2. Jenkins部署瘦身jar包
  3. 中国人学习日语实用网站网址大集合
  4. JS cookie与web存储(localStorage与sessionStorage)
  5. 自定义控件--最简单九宫格解锁
  6. extjs 解决rowEditing不满足allowBlank时,无法save的问题
  7. tiny6410开发板使用NFS访问Ubuntu主机
  8. oracle contains
  9. 智能手机和平板的GPS精度测试
  10. 怪兽充电一年亏损4个亿,共享经济平台如何借助分账系统打开交易新局面?