VibePlayer

news2025/6/9 18:35:27

源代码地址:

VibePlayer: VibePlayer是一款功能强大的Android音乐播放器应用,专为音乐爱好者设计,提供了丰富的音乐播放和管理功能。

用户需求 

VibePlayer是一款功能强大的Android音乐播放器应用,专为音乐爱好者设计,提供了丰富的音乐播放和管理功能。

功能特点

音乐播放

  • 支持多种播放模式:顺序播放、单曲循环、列表循环、随机播放
  • 前台服务保证后台播放稳定运行

音乐库管理

  • 自动扫描设备中的音乐文件
  • 支持文件浏览器手动导入音乐
  • 播放列表创建和管理

音频处理

  • 内置均衡器,支持多种预设
  • 低音增强和虚拟环绕声效果
  • 支持音频可视化

歌词功能

  • 歌词编辑器,支持添加时间戳
  • 歌词实时同步显示
  • 歌词预览功能

用户界面

  • 现代化UI设计
  • 支持深色/浅色主题切换
  • 响应式布局,适配不同尺寸设备
  • 直观的播放控制界面

权限说明

应用需要以下权限才能正常工作:

  • 读取媒体音频(Android 13及以上)
  • 读取外部存储(Android 13以下)
  • 前台服务
  • 通知权限(可选)
  • 媒体内容控制

系统要求

  • Android 5.0 (API级别21)或更高版本
  • 建议安装在Android 8.0或更高版本上获得最佳体验

使用指南

首次使用

  1. 启动应用后,系统会请求必要的权限
  2. 授予权限后,应用会自动扫描设备中的音乐文件
  3. 若未找到音乐文件,可使用内置的文件浏览器导入音乐

播放控制

  • 底部控制栏提供基本播放控制
  • 点击正在播放的歌曲可进入全屏播放界面
  • 左右滑动可切换歌曲
  • 长按歌曲可查看更多选项

播放列表管理

  • 点击"+"按钮创建新播放列表
  • 长按歌曲可添加到播放列表
  • 在播放列表详情页可管理列表内歌曲

均衡器设置

  • 在均衡器页面可启用/禁用音效处理
  • 选择预设或自定义均衡器设置
  • 调整低音增强和虚拟环绕声效果

歌词编辑

  1. 在全屏播放界面点击歌词编辑按钮
  2. 输入歌词内容,每行一句
  3. 播放歌曲,在适当的时间点点击"添加时间戳"
  4. 保存歌词后可在播放界面同步显示

技术特点

  • 使用MediaPlayer和MediaSession管理音乐播放
  • 支持MediaBrowserService实现跨组件媒体控制
  • 采用Room数据库存储播放列表和歌曲信息
  • 利用Fragment和ViewPager实现多页面导航
  • 前台服务确保后台播放稳定性
  • 适配Android不同版本的权限处理

MainActivity.java:

package com.vibeplayer.app;

import android.Manifest;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.provider.Settings;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;
import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentPagerAdapter;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import androidx.viewpager.widget.ViewPager;

import com.google.android.material.bottomnavigation.BottomNavigationView;
import com.vibeplayer.app.fragment.NowPlayingFragment;
import com.vibeplayer.app.fragment.PlaylistsFragment;
import com.vibeplayer.app.fragment.SettingsFragment;
import com.vibeplayer.app.fragment.SongsFragment;
import com.vibeplayer.app.fragment.EqualizerFragment;
import com.vibeplayer.app.model.Song;
import com.vibeplayer.app.service.MusicPlayerService;
import com.vibeplayer.app.util.MediaScanner;

import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;

public class MainActivity extends AppCompatActivity {
    private static final int PERMISSION_REQUEST_STORAGE = 1;
    private static final int PERMISSION_REQUEST_NOTIFICATION = 2;
    
    private ViewPager viewPager;
    private BottomNavigationView bottomNav;
    
    // 底部播放控制栏组件
    private View playerControlLayout;
    private ImageView btnPlayPause;
    private ImageView btnNext;
    private ImageView btnPrevious;
    private TextView txtSongTitle;
    private TextView txtArtist;
    private SeekBar seekBar;
    private TextView txtCurrentTime;
    private TextView txtTotalTime;
    
    private MusicPlayerService musicService;
    private boolean isBound = false;
    private MediaScanner mediaScanner;
    private Timer timer;
    
    // 服务连接
    private ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            MusicPlayerService.MusicBinder binder = (MusicPlayerService.MusicBinder) service;
            musicService = binder.getService();
            isBound = true;
            
            // 服务连接后更新UI
            updatePlayerControls();
            startProgressTimer();
        }
        
        @Override
        public void onServiceDisconnected(ComponentName name) {
            isBound = false;
            stopProgressTimer();
        }
    };
    
    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        handleMusicControlIntent(intent);
    }
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        // 处理可能的音乐控制动作
        handleMusicControlIntent(getIntent());
        
        // 初始化媒体扫描器
        mediaScanner = new MediaScanner(this);
        
        // 设置扫描完成监听器
        mediaScanner.setScanCompletedListener(songs -> {
            Log.d("MainActivity", "Auto scan completed, found " + songs.size() + " songs");
            if (isBound && musicService != null) {
                musicService.setSongs(songs);
                runOnUiThread(() -> {
                    if (!songs.isEmpty()) {
                        // 显示播放控制栏
                        playerControlLayout.setVisibility(View.VISIBLE);
                    } else {
                        // 没有找到音乐文件
                        Toast.makeText(this, "未找到音乐文件", Toast.LENGTH_SHORT).show();
                    }
                });
            }
        });
        
        // 检查权限
        checkPermissions();
        
        // 初始化视图
        initializeViews();
        
        // 设置ViewPager适配器
        setupViewPager();
        
        // 设置底部导航
        setupBottomNavigation();
        
        // 设置播放控制栏
        setupPlayerControls();
        
        // 绑定音乐服务
        bindMusicService();
    }
    
    @Override
    protected void onStart() {
        super.onStart();
        if (!isBound) {
            bindMusicService();
        }
        
        // 注册媒体观察者
        mediaScanner.registerMediaObserver();
    }
    
    @Override
    protected void onStop() {
        super.onStop();
        if (isBound) {
            unbindService(serviceConnection);
            isBound = false;
        }
        stopProgressTimer();
        
        // 注销媒体观察者
        mediaScanner.unregisterMediaObserver();
    }
    
    @Override
    protected void onDestroy() {
        super.onDestroy();
        stopProgressTimer();
    }
    
    @Override
    protected void onResume() {
        super.onResume();
        
        // 检查权限状态
        boolean hasPermission = false;
        
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            hasPermission = ContextCompat.checkSelfPermission(this, Manifest.permission.READ_MEDIA_AUDIO)
                    == PackageManager.PERMISSION_GRANTED;
        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            hasPermission = ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
                    == PackageManager.PERMISSION_GRANTED;
        } else {
            hasPermission = true;
        }

        // 如果已经有权限,直接加载音乐
        if (hasPermission) {
            Log.d("MainActivity", "Permission already granted in onResume, loading songs...");
            loadSongs();
        }
        // 如果没有权限且还没有检查过权限,则进行权限检查
        else if (!hasCheckedPermissions) {
            Log.d("MainActivity", "No permission in onResume, checking permissions...");
            checkPermissions();
        }
    }
    
    private void initializeViews() {
        viewPager = findViewById(R.id.viewPager);
        bottomNav = findViewById(R.id.bottomNav);
        
        playerControlLayout = findViewById(R.id.playerControlLayout);
        btnPlayPause = findViewById(R.id.btnPlayPause);
        btnNext = findViewById(R.id.btnNext);
        btnPrevious = findViewById(R.id.btnPrevious);
        txtSongTitle = findViewById(R.id.txtSongTitle);
        txtArtist = findViewById(R.id.txtArtist);
        seekBar = findViewById(R.id.seekBar);
        txtCurrentTime = findViewById(R.id.txtCurrentTime);
        txtTotalTime = findViewById(R.id.txtTotalTime);
    }
    
    private void setupViewPager() {
        ViewPagerAdapter adapter = new ViewPagerAdapter(getSupportFragmentManager());
        adapter.addFragment(new SongsFragment(), "歌曲");
        adapter.addFragment(new PlaylistsFragment(), "播放列表");
        adapter.addFragment(new EqualizerFragment(), "均衡器");
        adapter.addFragment(new SettingsFragment(), "设置");
        viewPager.setAdapter(adapter);
        viewPager.setCurrentItem(0); // 默认显示歌曲页面
        
        // 设置页面切换监听
        viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
            }
            
            @Override
            public void onPageSelected(int position) {
                bottomNav.getMenu().getItem(position).setChecked(true);
            }
            
            @Override
            public void onPageScrollStateChanged(int state) {
            }
        });
    }
    
    private void setupBottomNavigation() {
        bottomNav.setOnNavigationItemSelectedListener(item -> {
            int itemId = item.getItemId();
            if (itemId == R.id.nav_songs) {
                viewPager.setCurrentItem(0);
                return true;
            } else if (itemId == R.id.nav_playlists) {
                viewPager.setCurrentItem(1);
                return true;
            } else if (itemId == R.id.nav_equalizer) {
                viewPager.setCurrentItem(2);
                return true;
            } else if (itemId == R.id.nav_settings) {
                viewPager.setCurrentItem(3);
                return true;
            }
            return false;
        });
    }
    
    private void setupPlayerControls() {
        // 播放/暂停按钮点击事件
        btnPlayPause.setOnClickListener(v -> {
            if (isBound && musicService != null) {
                musicService.playPause();
                updatePlayPauseButton();
            }
        });
        
        // 下一曲按钮点击事件
        btnNext.setOnClickListener(v -> {
            if (isBound && musicService != null) {
                musicService.playNext();
            }
        });
        
        // 上一曲按钮点击事件
        btnPrevious.setOnClickListener(v -> {
            if (isBound && musicService != null) {
                musicService.playPrevious();
            }
        });
        
        // 进度条拖动事件
        seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                if (fromUser && isBound && musicService != null) {
                    musicService.seekTo(progress);
                    updateCurrentTimeText(progress);
                }
            }
            
            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {
            }
            
            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {
            }
        });
        
        // 点击播放控制栏打开全屏播放界面
        playerControlLayout.setOnClickListener(v -> {
            if (isBound && musicService != null && musicService.getCurrentSong() != null) {
                NowPlayingFragment nowPlayingFragment = NowPlayingFragment.newInstance();
                nowPlayingFragment.show(getSupportFragmentManager(), "now_playing");
            }
        });
    }
    
    private void bindMusicService() {
        Intent intent = new Intent(this, MusicPlayerService.class);
        startService(intent);
        bindService(intent, serviceConnection, BIND_AUTO_CREATE);
    }
    
    private void checkPermissions() {
        hasCheckedPermissions = true;  // 标记已经检查过权限
        
        // Android 6.0 (API 23)以下版本不需要动态请求权限
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
            loadSongs();
            return;
        }

        // 检查是否已经有权限
        boolean hasPermission = false;
        
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            // Android 13及以上版本检查READ_MEDIA_AUDIO权限
            hasPermission = ContextCompat.checkSelfPermission(this, Manifest.permission.READ_MEDIA_AUDIO)
                    == PackageManager.PERMISSION_GRANTED;
            
            if (!hasPermission) {
                // 请求音频权限
                ActivityCompat.requestPermissions(this,
                        new String[]{Manifest.permission.READ_MEDIA_AUDIO},
                        PERMISSION_REQUEST_STORAGE);
            }
        } else {
            // Android 13以下版本检查READ_EXTERNAL_STORAGE权限
            hasPermission = ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
                    == PackageManager.PERMISSION_GRANTED;
            
            if (!hasPermission) {
                // 请求存储权限
                ActivityCompat.requestPermissions(this,
                        new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
                        PERMISSION_REQUEST_STORAGE);
            }
        }

        // 如果已经有权限,直接加载音乐
        if (hasPermission) {
            Log.d("MainActivity", "Permission already granted in checkPermissions, loading songs...");
            loadSongs();
        } else {
            Log.d("MainActivity", "Requesting permissions...");
        }
    }
    
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        
        if (grantResults.length == 0) {
            Log.d("MainActivity", "Permission request cancelled");
            return;
        }
        
        switch (requestCode) {
            case PERMISSION_REQUEST_STORAGE:
                if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    Log.d("MainActivity", "Permission granted in onRequestPermissionsResult, loading songs...");
                    loadSongs();
                } else {
                    Log.d("MainActivity", "Permission denied");
                    // 用户拒绝了权限
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
                            !shouldShowRequestPermissionRationale(permissions[0])) {
                        // 用户选择了"不再询问"
                        showPermissionSettingsDialog();
                    } else {
                        showRetryDialog();
                    }
                }
                break;
                
            case PERMISSION_REQUEST_NOTIFICATION:
                if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    // 通知权限已授权,继续检查其他权限
                    checkPermissions();
                } else {
                    // 通知权限被拒绝,但这不是必需的,所以继续加载音乐
                    loadSongs();
                }
                break;
        }
    }
    
    private void showRetryDialog() {
        new AlertDialog.Builder(this)
            .setTitle("权限请求")
            .setMessage("没有存储权限,应用将无法访问音乐文件。是否重新请求权限?")
            .setPositiveButton("重试", (dialog, which) -> checkPermissions())
            .setNegativeButton("退出", (dialog, which) -> finish())
            .setCancelable(false)
            .show();
    }
    
    private void showPermissionSettingsDialog() {
        String message;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            message = "应用需要访问音频文件的权限才能播放音乐。\n\n" +
                    "操作步骤:\n" +
                    "1. 点击\"立即开启\"\n" +
                    "2. 找到\"音频文件访问权限\"\n" +
                    "3. 点击开关开启权限";
        } else {
            message = "应用需要存储权限才能播放音乐。\n\n" +
                    "操作步骤:\n" +
                    "1. 点击\"立即开启\"\n" +
                    "2. 找到\"存储空间\"\n" +
                    "3. 点击开关开启权限";
        }

        new AlertDialog.Builder(this)
            .setTitle("需要开启权限")
            .setMessage(message)
            .setPositiveButton("立即开启", (dialog, which) -> {
                try {
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
                        // Android 13及以上,尝试直接跳转到媒体权限设置
                        Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
                        Uri uri = Uri.fromParts("package", getPackageName(), null);
                        intent.setData(uri);
                        // 尝试直接打开权限页面
                        intent.putExtra(":settings:fragment_args_key", "permission");
                        intent.putExtra(":settings:show_fragment_args", true);
                        Bundle bundle = new Bundle();
                        bundle.putString(":settings:fragment_args_key", "permission");
                        intent.putExtra("android.provider.extra.APP_PACKAGE", getPackageName());
                        startActivity(intent);
                    } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
                        // Android 11及以上,使用MANAGE_EXTERNAL_STORAGE
                        Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
                        intent.setData(Uri.parse("package:" + getPackageName()));
                        startActivity(intent);
                    } else {
                        // Android 10及以下,使用APPLICATION_DETAILS_SETTINGS
                        Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
                        intent.setData(Uri.parse("package:" + getPackageName()));
                        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                        intent.addCategory(Intent.CATEGORY_DEFAULT);
                        intent.putExtra("android.provider.extra.APP_PACKAGE", getPackageName());
                        startActivity(intent);
                    }
                } catch (Exception e) {
                    // 如果特定跳转失败,回退到通用设置页面
                    try {
                        Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
                        Uri uri = Uri.fromParts("package", getPackageName(), null);
                        intent.setData(uri);
                        startActivity(intent);
                    } catch (Exception e2) {
                        // 如果还是失败,使用最基本的设置页面
                        Intent intent = new Intent(Settings.ACTION_SETTINGS);
                        startActivity(intent);
                        Toast.makeText(this, "请在设置中找到本应用并开启所需权限", Toast.LENGTH_LONG).show();
                    }
                }
            })
            .setNegativeButton("退出应用", (dialog, which) -> finish())
            .setCancelable(false)
            .show();
    }
    
    private void loadSongs() {
        Log.d("MainActivity", "Starting to load songs...");
        
        // 再次确认权限
        boolean hasPermission = false;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            hasPermission = ContextCompat.checkSelfPermission(this, Manifest.permission.READ_MEDIA_AUDIO)
                    == PackageManager.PERMISSION_GRANTED;
        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            hasPermission = ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
                    == PackageManager.PERMISSION_GRANTED;
        } else {
            hasPermission = true; // Android 6.0以下版本
        }

        if (!hasPermission) {
            Log.d("MainActivity", "Permission not granted when trying to load songs");
            if (!hasCheckedPermissions) {
                checkPermissions();
            }
            return;
        }

        // 使用增强版的异步扫描方法
        mediaScanner.scanMediaAsync();
    }
    
    private void updatePlayerControls() {
        if (!isBound || musicService == null) {
            playerControlLayout.setVisibility(View.GONE);
            return;
        }
        
        Song currentSong = musicService.getCurrentSong();
        if (currentSong != null) {
            playerControlLayout.setVisibility(View.VISIBLE);
            txtSongTitle.setText(currentSong.getTitle());
            txtArtist.setText(currentSong.getArtist());
            
            updatePlayPauseButton();
            
            int duration = musicService.getDuration();
            seekBar.setMax(duration);
            txtTotalTime.setText(formatTime(duration));
            
            updateSeekBar();
        } else {
            playerControlLayout.setVisibility(View.GONE);
        }
    }
    
    private void updatePlayPauseButton() {
        if (isBound && musicService != null && musicService.isPlaying()) {
            btnPlayPause.setImageResource(R.drawable.ic_pause);
        } else {
            btnPlayPause.setImageResource(R.drawable.ic_play);
        }
    }
    
    private void updateSeekBar() {
        if (isBound && musicService != null && musicService.isPrepared()) {
            int currentPosition = musicService.getCurrentPosition();
            seekBar.setProgress(currentPosition);
            updateCurrentTimeText(currentPosition);
        }
    }
    
    private void updateCurrentTimeText(int currentPosition) {
        txtCurrentTime.setText(formatTime(currentPosition));
    }
    
    private void startProgressTimer() {
        stopProgressTimer();
        timer = new Timer();
        timer.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                runOnUiThread(() -> {
                    if (isBound && musicService != null && musicService.isPlaying()) {
                        updateSeekBar();
                        updatePlayPauseButton();
                    }
                });
            }
        }, 0, 1000);
    }
    
    private void stopProgressTimer() {
        if (timer != null) {
            timer.cancel();
            timer = null;
        }
    }
    
    private String formatTime(int milliseconds) {
        int seconds = (milliseconds / 1000) % 60;
        int minutes = (milliseconds / (1000 * 60)) % 60;
        return String.format("%02d:%02d", minutes, seconds);
    }
    
    // ViewPager适配器
    private static class ViewPagerAdapter extends FragmentPagerAdapter {
        private final List<Fragment> fragmentList = new ArrayList<>();
        private final List<String> fragmentTitleList = new ArrayList<>();
        
        public ViewPagerAdapter(FragmentManager manager) {
            super(manager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
        }
        
        @NonNull
        @Override
        public Fragment getItem(int position) {
            return fragmentList.get(position);
        }
        
        @Override
        public int getCount() {
            return fragmentList.size();
        }
        
        public void addFragment(Fragment fragment, String title) {
            fragmentList.add(fragment);
            fragmentTitleList.add(title);
        }
        
        @Override
        public CharSequence getPageTitle(int position) {
            return fragmentTitleList.get(position);
        }
    }
    
    // 公开方法,供Fragment调用
    public void playSong(int position) {
        if (isBound && musicService != null) {
            musicService.playSong(position);
            updatePlayerControls();
        }
    }
    
    public MusicPlayerService getMusicService() {
        return musicService;
    }
    
    public boolean isServiceBound() {
        return isBound;
    }
    
    private void handleMusicControlIntent(Intent intent) {
        if (intent == null || intent.getAction() == null) {
            return;
        }

        String action = intent.getAction();
        Intent broadcastIntent = new Intent(action);
        LocalBroadcastManager.getInstance(this).sendBroadcast(broadcastIntent);
    }
    
    @Override
    public void onBackPressed() {
        View viewPager = findViewById(R.id.viewPager);
        View fragmentContainer = findViewById(R.id.fragmentContainer);
        
        if (fragmentContainer.getVisibility() == View.VISIBLE) {
            // 如果Fragment容器可见,先处理Fragment的返回栈
            if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
                getSupportFragmentManager().popBackStack();
                // 检查返回栈是否为空
                if (getSupportFragmentManager().getBackStackEntryCount() == 1) {
                    // 如果返回栈即将清空,显示ViewPager
                    viewPager.setVisibility(View.VISIBLE);
                    fragmentContainer.setVisibility(View.GONE);
                }
            }
        } else {
            super.onBackPressed();
        }
    }
    
    // 添加标志位,记录是否已经检查过权限
    private boolean hasCheckedPermissions = false;
} 

其余部分代码已经全部开源。

这是我开源的第一个小项目,同时也是接单的第一单。

开发工具:Android Studio、Navicat Premium 17、IntelliJ IDEA、Cursor

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2405700.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【汇编逆向系列】三、函数调用包含单个参数之float类型-xmm0寄存器,sub,rep,stos,movss,mulss,addss指令

一、汇编代码 single_float_param:0000000000000060: F3 0F 11 44 24 08 movss dword ptr [rsp8],xmm00000000000000066: 57 push rdi0000000000000067: 48 83 EC 10 sub rsp,10h000000000000006B: 48 8B FC mov …

基于fpga的疲劳驾驶检测

基于fpga的疲劳驾驶检测 前言一、系统硬件设计二、系统软件设计系统上板实验测试 前言 代码基于网络大佬代码进行修改的。限制性比较大&#xff0c;不太灵活&#xff0c;当个本科毕业设计还是够的。 基于FPGA的疲劳检测模块硬件设计以FPGA核心控制模块为中心&#xff0c;通过…

感谢阿里云RDS产品及时的“光速服务”

❝ 开头还是介绍一下群&#xff0c;如果感兴趣PolarDB ,MongoDB ,MySQL ,PostgreSQL ,Redis, OceanBase, Sql Server等有问题&#xff0c;有需求都可以加群群内有各大数据库行业大咖&#xff0c;可以解决你的问题。加群请联系 liuaustin3 &#xff0c;&#xff08;共3000人左右…

从零开始打造 OpenSTLinux 6.6 Yocto 系统(基于STM32CubeMX)(十一)

下载buildroot https://buildroot.org/download.html下载交叉工具链 使用ST官方交叉工具链的话&#xff0c;在buildroot配置外部工具会有问题&#xff0c;所以直接使用正点原子的交叉编译工具 buildroot构建根文件系统 - 参考正点原子 配置 buildroot tar -vxf buildroot-20…

Linux68 FTP 测试 上传下载

6.在vi编辑器里&#xff0c;哪个命令能将光标移到第200行&#xff1f;&#xff08; B &#xff09; 7.A、200g B、:200 C、g200 D、G200 假如您需要找出 /etc/my.conf 文件属于哪个包 (package) &#xff0c;您可以执行&#xff08; D &#xff09;C A、 rpm -q /etc/my.co…

山东大学《数据可视化》期末复习宝典

&#x1f308; 个人主页&#xff1a;十二月的猫-CSDN博客 &#x1f525; 系列专栏&#xff1a;&#x1f3c0;山东大学期末速通专用_十二月的猫的博客-CSDN博客 &#x1f4aa;&#x1f3fb; 十二月的寒冬阻挡不了春天的脚步&#xff0c;十二点的黑夜遮蔽不住黎明的曙光 目录 1…

【Elasticsearch】映射:Join 类型、Flattened 类型、多表关联设计

映射&#xff1a;Join 类型、Flattened 类型、多表关联设计 1.Join 类型1.1 主要应用场景1.1.1 一对多关系建模1.1.2 多层级关系建模1.1.3 需要独立更新子文档的场景1.1.4 文档分离但需要关联查询 1.2 使用注意事项1.3 与 Nested 类型的区别 2.Flattened 类型2.1 实际运用场景和…

SpringBoot十二、SpringBoot系列web篇之过滤器Filte详解

一、前言 JavaWeb三大组件Servlet、Filter、Listener&#xff0c;其中之一便是过滤器Filter。 其实&#xff0c;Filter我们平常用的不多&#xff0c;一般多为项目初期搭建web架构的时候使用&#xff0c;后面用的就少了&#xff0c;在日常业务开发中不太可能碰到需要手写Filte…

【RTSP从零实践】1、根据RTSP协议实现一个RTSP服务

&#x1f601;博客主页&#x1f601;&#xff1a;&#x1f680;https://blog.csdn.net/wkd_007&#x1f680; &#x1f911;博客内容&#x1f911;&#xff1a;&#x1f36d;嵌入式开发、Linux、C语言、C、数据结构、音视频&#x1f36d; &#x1f923;本文内容&#x1f923;&a…

行为设计模式之Iterator(迭代器)

行为设计模式之Iterator&#xff08;迭代器&#xff09; 摘要&#xff1a; 迭代器模式(Iterator)是一种行为设计模式&#xff0c;它提供顺序访问聚合对象元素的方法&#xff0c;同时不暴露内部结构。该模式由迭代器接口(Iterator)、具体迭代器(ConcreteIterator)、聚合接口(Ag…

FPGA点亮ILI9488驱动的SPI+RGB接口LCD显示屏(一)

FPGA点亮ILI9488驱动的SPIRGB接口LCD显示屏 ILI9488 RGB接口初始化 目录 前言 一、ILI9488简介 二、3线SPI接口简介 三、配置寄存器介绍 四、手册和初始化verilog FPGA代码 总结 前言 ILI9488是一款广泛应用于嵌入式系统和电子设备的彩色TFT LCD显示控制器芯片。本文将介…

如何实现本地mqtt服务器和云端服务器同步?

有时候&#xff0c;一个物联网项目&#xff0c;A客户想要本地使用&#xff0c;B客户想要线上使用&#xff0c;C客户想要本地部署&#xff0c;当有网环境时能线上使用。这个时候就需要本地MQTT服务和线上MQTT服务能相互自动转发。 后来经我一翻研究&#xff0c;其实Activemq支持…

windows10下搭建nfs服务器

windows10下搭建nfs服务器 有参考这篇博客 Windows10搭建NFS服务 - fuzidage - 博客园 下载 NFS Server这个app 通过网盘分享的文件&#xff1a;nfs1268 (1).exe 链接: https://pan.baidu.com/s/1rE4h710Uh-13kWGXvjkZzw 提取码: mwa4 --来自百度网盘超级会员v5的分享 下载后…

华为云Flexus+DeepSeek征文|Dify - LLM 云服务单机部署大语言模型攻略指南

前言&#xff1a;在当今人工智能快速发展的时代&#xff0c;华为云推出的 Dify - LLM 对话式 AI 开发平台为企业和开发者提供了便捷的大语言模型应用开发解决方案。 通过在华为云 Flexus 云服务器上单机部署 Dify&#xff0c;并成功集成 DeepSeek 模型&#xff0c;我们能够快速…

JAVA反序列化应用 : URLDNS案例

反序列化的基本原理 基础普及 &#xff1a; 对象初始化数据方法 &#xff1a;1、使用构造方法 2、使用封装中的 set,get方法 这边我们就使用 1 注意 我们之后还需要进行 接入 序列化的接口 &#xff1a; 先进行序列化 &#xff1a; 反序列化&#xff1a; 反序列化导致的安…

Vue-Leaflet地图组件开发(三)地图控件与高级样式设计

第三篇&#xff1a;Vue-Leaflet地图控件与高级样式设计 1. 专业级比例尺组件实现 1.1 比例尺控件集成 import { LControl } from "vue-leaflet/vue-leaflet";// 在模板中添加比例尺控件 <l-control-scaleposition"bottomleft":imperial"false&qu…

174页PPT家居制造业集团战略规划和运营管控规划方案

甲方集团需要制定一个清晰的集团价值定位&#xff0c;从“指引多元”、“塑造 能力”以及“强化协同”等方面引领甲方做大做强 集团需要通过管控模式、组织架构及职能、授权界面、关键流程、战略 实施和组织演进路径&#xff0c;平衡风险控制和迅速发展&#xff0c;保证战略落地…

wsl开启即闪退

[ 问题 ]&#xff1a; 在一次电脑卡住&#xff0c;强制关机重启后&#xff0c;遇到打开WSL就闪退的问题在CMD中打开WSL&#xff0c;出现如上图的描述&#xff1a; C:\Users\admin>wsl wsl: 检测到 localhost 代理配置&#xff0c;但未镜像到 WSL。NAT 模式下的 WSL 不支持…

【P2P】直播网络拓扑及编码模式

以下从 P2P 直播的常见拓扑模式出发,分析各种方案的特点与适用场景,并给出推荐。 一、P2P 直播的核心挑战 实时性要求高 直播场景下,延迟必须控制在可接受范围(通常 <2 秒),同时要保证画面连贯、不卡顿。带宽分布不均 每个节点(观众)上传带宽与下载带宽差异较大,且…

Python数据可视化科技图表绘制系列教程(二)

目录 表格风格图 使用Seaborn函数绘图 设置图表风格 设置颜色主题 图表分面 绘图过程 使用绘图函数绘图 定义主题 分面1 分面2 【声明】&#xff1a;未经版权人书面许可&#xff0c;任何单位或个人不得以任何形式复制、发行、出租、改编、汇编、传播、展示或利用本博…