这篇文章的标题有些言简意赅了,也突出了这篇文章的核心,那就是通知栏的操作,你可以看到市面上的音乐类APP都会有这个操作,通过音乐通知栏可以播放暂停、上一曲、下一曲、收藏、显示歌词等等。当然我这个Demo目前不考虑这么多,先实现播放暂停、上一曲、下一曲这些基本功能再说,你说对吧。
在第四篇文章的到最后显示了通知栏,那么为什么我要把通知的的操作单独放到一篇文章来进行讲解呢?因为里面有很多业务逻辑,还有通信的关系,所以才这么做的。
要实现具体的业务功能,首先要监听到点击事件,这一点是毋庸置疑的,谁赞成,谁反对。首先增加几个全局变量,打开Constant
/**
* 歌曲播放
*/
public static final String PLAY = "play";
/**
* 歌曲暂停
*/
public static final String PAUSE = "pause";
/**
* 上一曲
*/
public static final String PREV = "prev";
/**
* 下一曲
*/
public static final String NEXT = "next";
/**
* 关闭通知栏
*/
public static final String CLOSE = "close";
/**
* 进度变化
*/
public static final String PROGRESS = "progress";
这些都是用来表明当前歌曲的状态的,至关重要。之前我通过RemoteViews来指定一个布局文件,从而实现自定义通知栏样式的效果,那么对于通知栏页面的按钮的点击事件,也是交给RemoteViews来完成来的,下面进行实例化,把它变成成员变量。
在Service中实例化
private static RemoteViews remoteViews;
然后单独写一个方法对RemoteViews进行初始化配置。
/**
* 初始化自定义通知栏 的按钮点击事件
*/
private void initRemoteViews() {
remoteViews = new RemoteViews(this.getPackageName(), R.layout.notification);
//通知栏控制器上一首按钮广播操作
Intent intentPrev = new Intent(PREV);
PendingIntent prevPendingIntent = PendingIntent.getBroadcast(this, 0, intentPrev, 0);
//为prev控件注册事件
remoteViews.setOnClickPendingIntent(R.id.btn_notification_previous, prevPendingIntent);
//通知栏控制器播放暂停按钮广播操作 //用于接收广播时过滤意图信息
Intent intentPlay = new Intent(PLAY);
PendingIntent playPendingIntent = PendingIntent.getBroadcast(this, 0, intentPlay, 0);
//为play控件注册事件
remoteViews.setOnClickPendingIntent(R.id.btn_notification_play, playPendingIntent);
//通知栏控制器下一首按钮广播操作
Intent intentNext = new Intent(NEXT);
PendingIntent nextPendingIntent = PendingIntent.getBroadcast(this, 0, intentNext, 0);
//为next控件注册事件
remoteViews.setOnClickPendingIntent(R.id.btn_notification_next, nextPendingIntent);
//通知栏控制器关闭按钮广播操作
Intent intentClose = new Intent(CLOSE);
PendingIntent closePendingIntent = PendingIntent.getBroadcast(this, 0, intentClose, 0);
//为close控件注册事件
remoteViews.setOnClickPendingIntent(R.id.btn_notification_close, closePendingIntent);
}
目前通知栏上看到的按钮只有四个,因为播放和暂停是一个按钮,到时候可以根据MediaPlayer的播放状态做进一步的处理,上面四个按钮,点击之后会发送一个广播,既然有广播,那自然要有一个广播接收器,就好比,你到淘宝上买衣服,别人给你发货了,你总要设置一个收货地址吧。这是一个道理的。至于广播接收器,可以写在Service里面,作为一个内部类使用。那么先创建这个内部类。
/**
* 广播接收器 (内部类)
*/
public class MusicReceiver extends BroadcastReceiver {
public static final String TAG = "MusicReceiver";
@Override
public void onReceive(Context context, Intent intent) {
//UI控制
UIControl(intent.getAction(), TAG);
}
}
然后来看看UIControl方法。
/**
* 页面的UI 控制 ,通过服务来控制页面和通知栏的UI
*
* @param state 状态码
* @param tag
*/
private void UIControl(String state, String tag) {
switch (state) {
case PLAY:
BLog.d(tag,PLAY+" or "+PAUSE);
break;
case PREV:
BLog.d(tag,PREV);
break;
case NEXT:
BLog.d(tag,NEXT);
break;
case CLOSE:
BLog.d(tag,CLOSE);
break;
default:
break;
}
}
对应四个通知栏的按钮,这是是作为广播的接收。但是要实际收到,还要注册才行。
所以要注册动态广播。
/**
* 注册动态广播
*/
private void registerMusicReceiver() {
musicReceiver = new MusicReceiver();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(PLAY);
intentFilter.addAction(PREV);
intentFilter.addAction(NEXT);
intentFilter.addAction(CLOSE);
registerReceiver(musicReceiver, intentFilter);
}
在这里你可以发现,我对四个值进行了拦截过滤,也就是说当我点击通知栏的上一曲按钮时,会发送动作名为PREV的广播,而这个时候MusicReceiver拦截到PREV的广播,传递给onReceive。然后在onReceive对不同的动作做不同的处理,目前我只是打印了日志而已。
现在你可以将showNotification方法中的如下代码删除掉。
RemoteViews remoteViews = new RemoteViews(this.getPackageName(), R.layout.notification);
然后在Service中的onCreate中调用。
@Override
public void onCreate() {
super.onCreate();
initRemoteViews();
//注册动态广播
registerMusicReceiver();
showNotification();
BLog.d(TAG, "onCreate");
}
initRemoteViews 方法一定要在 showNotification之前调用,否则你就等着null Object 然后APP崩溃吧。
在服务销毁的时候要解绑广播接收者
@Override
public void onDestroy() {
super.onDestroy();
if (musicReceiver != null) {
//解除动态注册的广播
unregisterReceiver(musicReceiver);
}
}
下面运行一下,日志如下:
现在通知栏的按钮点击事件就已经监听到了,下面做通知栏的点击事件。
只要是通知栏按钮以外的点击都属于通知栏的点击,这个要区分开,别搞混了。在写代码要想清楚一点,当我们点击通知栏的时候,要进入那个页面,我仔细观察过其他音乐APP的这个点击通知栏的效果,是从那个页面切换到后台,下次点击通知栏时就进入到那个页面,也就是说它点击跳转的页面是动态的,所以不能是写死的。有了这个业务需求那么就可以开始写代码了。这里也是需要用到广播的,只不过不再是写内部类了。在com.llw.goodmusic下面新建一个receiver的包,然后创建NotificationClickReceiver,里面的两个可以不用勾选。
创建好之后,打开AndroidManifest.xml你会看到如下代码:
<receiver android:name=".receiver.NotificationClickReceiver"/>
下面进入MusicService中,
//点击整个通知时发送广播
Intent intent = new Intent(getApplicationContext(), NotificationClickReceiver.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast(getApplicationContext(), 0,
intent, PendingIntent.FLAG_UPDATE_CURRENT);
然后通过.setContentIntent(pendingIntent)设置进去,如下图所示
下面进入到NotificationClickReceiver中。
package com.llw.goodmusic.receiver;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import com.llw.goodmusic.utils.BLog;
/**
* 通知点击广播接收器 跳转到栈顶的Activity ,而不是new 一个新的Activity
*
* @author llw
*/
public class NotificationClickReceiver extends BroadcastReceiver {
public static final String TAG = "NotificationClickReceiver";
@Override
public void onReceive(Context context, Intent intent) {
BLog.d(TAG,"通知栏点击");
}
}
然后运行,运行之后点击通知栏,再看日志打印,如下所示:
在上面已经实现了通知栏的点击监听了,下面就要开始进行业务逻辑的处理了。先解决通知栏的点击业务处理,再解决通知栏按钮的点击处理,打开AndroidManager,注意这是之前我自己写的,不是系统的。在里面增加
/**
* 弱引用
*/
private static WeakReference<Activity> activityWeakReference;
private static Object activityUpdateLock = new Object();
/**
* 得到当前Activity
* @return
*/
public static Activity getCurrentActivity() {
Activity currentActivity = null;
synchronized (activityUpdateLock){
if (activityWeakReference != null) {
currentActivity = activityWeakReference.get();
}
}
return currentActivity;
}
/**
* 设置当前Activity
* @return
*/
public static void setCurrentActivity(Activity activity) {
synchronized (activityUpdateLock){
activityWeakReference = new WeakReference<Activity>(activity);
}
}
然后进入到BasicApplication中,在onCreate中写入:
registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
}
@Override
public void onActivityStarted(Activity activity) {
}
@Override
public void onActivityResumed(Activity activity) {
ActivityManager.setCurrentActivity(activity);
}
@Override
public void onActivityPaused(Activity activity) {
}
@Override
public void onActivityStopped(Activity activity) {
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
}
@Override
public void onActivityDestroyed(Activity activity) {
}
});
通过上面得代码就可以得到栈顶的Activity,那么怎么来使用这个Activity呢,进入到NotificationClickReceiver
@Override
public void onReceive(Context context, Intent intent) {
BLog.d(TAG,"通知栏点击");
//获取栈顶的Activity
Activity currentActivity = ActivityManager.getCurrentActivity();
intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
intent.setClass(context, currentActivity.getClass());
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
context.startActivity(intent);
}
这样就可以实现,点击通知栏时跳转到栈顶的Activity而不是新建一个Activity。
下面就是针对通知栏的信息显示做处理了,首先肯定要根据不同的音乐显示不同的歌曲信息,这一点毋庸置疑。那么这样的话就不能一开始就显示通知栏了,而是在点击播放按钮的时候显示通知栏,当切歌,或者暂停时更新这个通知栏的状态,于是就可以在MusicService中写入一个这样的方法。
/**
* 初始化通知
*/
private void initNotification() {
String channelId = "play_control";
String channelName = "播放控制";
int importance = NotificationManager.IMPORTANCE_HIGH;
createNotificationChannel(channelId, channelName, importance);
//点击整个通知时发送广播
Intent intent = new Intent(getApplicationContext(), NotificationClickReceiver.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast(getApplicationContext(), 0,
intent, PendingIntent.FLAG_UPDATE_CURRENT);
//初始化通知
notification = new NotificationCompat.Builder(this, "play_control")
.setContentIntent(pendingIntent)
.setWhen(System.currentTimeMillis())
.setSmallIcon(R.mipmap.icon_big_logo)
.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.icon_big_logo))
.setCustomContentView(remoteViews)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setAutoCancel(false)
.setOnlyAlertOnce(true)
.setOngoing(true)
.build();
}
这里就是把原来的showNotification方法改了一下,把显示通知的代码放到改变通知栏状态的时候使用。当然还是要在onCreate中调用这个方法的。
下面先在MusiceService中定义这些变量
/**
* 歌曲间隔时间
*/
private static final int INTERNAL_TIME = 1000;
/**
* 歌曲列表
*/
private static List<Song> mList = new ArrayList<>();
/**
* 音乐播放器
*/
public MediaPlayer mediaPlayer;
/**
* 记录播放的位置
*/
int playPosition = 0;
/**
* 通知
*/
private static Notification notification;
/**
* 通知栏视图
*/
private static RemoteViews remoteViews;
/**
* 通知ID
*/
private int NOTIFICATION_ID = 1;
/**
* 通知管理器
*/
private static NotificationManager manager;
/**
* 音乐广播接收器
*/
private MusicReceiver musicReceiver;
然后写入一个更改通知栏样式的方法,每次对音乐进行控制时都会调用。
/**
* 更改通知的信息和UI
* @param position 歌曲位置
*/
public void updateNotificationShow(int position) {
//播放状态判断
if (mediaPlayer.isPlaying()) {
remoteViews.setImageViewResource(R.id.btn_notification_play, R.drawable.pause_black);
} else {
remoteViews.setImageViewResource(R.id.btn_notification_play, R.drawable.play_black);
}
//封面专辑
remoteViews.setImageViewBitmap(R.id.iv_album_cover, MusicUtils.getAlbumPicture(this, mList.get(position).getPath(), 0));
//歌曲名
remoteViews.setTextViewText(R.id.tv_notification_song_name, mList.get(position).getSong());
//歌手名
remoteViews.setTextViewText(R.id.tv_notification_singer, mList.get(position).getSinger());
//发送通知
manager.notify(NOTIFICATION_ID, notification);
}
在这个方法里面我调用MusicUtils工具类的getAlbumPicture方法。这个方法我做了一点点改动
改动如下图所示:
下面就是点击播放时的音乐方法了。
/**
* 播放
*/
public void play(int position) {
if (mediaPlayer == null) {
mediaPlayer = new MediaPlayer();
//监听音乐播放完毕事件,自动下一曲
mediaPlayer.setOnCompletionListener(this);
}
//播放时 获取当前歌曲列表是否有歌曲
mList = LitePal.findAll(Song.class);
if (mList.size() <= 0) {
return;
}
try {
//切歌前先重置,释放掉之前的资源
mediaPlayer.reset();
playPosition = position;
//设置播放音频的资源路径
mediaPlayer.setDataSource(mList.get(position).path);
mediaPlayer.prepare();
mediaPlayer.start();
//显示通知
updateNotificationShow(position);
} catch (IOException e) {
e.printStackTrace();
}
}
在上面的播放方法中,首先初始化了MediaPlayer,然后添加了播放完成的监听,这个在后面也是要实现的。然后获取当前的播放位置赋值给成员变量,之后通过位置得到歌曲的路径,通过路径来播放音乐,播放音乐之后将位置传递给显示通知栏的方法,此时通知栏的信息久会更改。
在onCreate方法中添加如下代码,获取本地歌曲数据。
mList = LitePal.findAll(Song.class);
这样做是避免空对象导致APP的崩溃。
接下来就是上一曲的方法
/**
* 上一首
*/
public void previousMusic() {
if (playPosition <= 0) {
playPosition = mList.size() - 1;
} else {
playPosition -= 1;
}
play(playPosition);
}
通过播放位置,先判断当前是为第一首歌,是则将播放位置移动到最后一首,不是则直接减一,之后则调用play方法播放上一首歌曲。
下一曲的方法
/**
* 下一首
*/
public void nextMusic() {
if (playPosition >= mList.size() - 1) {
playPosition = 0;
} else {
playPosition += 1;
}
play(playPosition);
}
先判断当前是否为最后一首,是的话则从移动到第一首,不是则加一到下一首。然后调用play方法播放下一首歌曲。
暂停继续音乐
/**
* 暂停/继续 音乐
*/
public void pauseOrContinueMusic() {
if (mediaPlayer.isPlaying()) {
mediaPlayer.pause();
} else {
mediaPlayer.start();
}
//更改通知栏播放状态
updateNotificationShow(playPosition);
}
更改播放状态
最后是关闭通知栏的方法
/**
* 关闭音乐通知栏
*/
public void closeNotification() {
if (mediaPlayer != null) {
if (mediaPlayer.isPlaying()) {
mediaPlayer.pause();
}
}
manager.cancel(NOTIFICATION_ID);
}
下面就是调用的地方了
然后还要实现MediaPlayer的音乐播放完成的监听,
public class MusicService extends Service implements MediaPlayer.OnCompletionListener
然后重写onCompletion方法,在里面直接调用nextMusic播放下一曲即可。
/**
* 当前音乐播放完成监听
*
* @param mp
*/
@Override
public void onCompletion(MediaPlayer mp) {
//下一曲
nextMusic();
}
下面就要设置通知出现的入口,一般来说是在点击播放按钮,当前有音乐播放时,才会显示通知。然后在layout下面新建一个通用的底部通知布局。
play_control_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<!--底部播放控制布局-->
<LinearLayout
android:id="@+id/lay_bottom"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:background="@color/bottom_bg_color"
android:gravity="center_vertical"
android:paddingLeft="@dimen/dp_8"
android:paddingTop="@dimen/dp_8"
android:paddingRight="@dimen/dp_16"
android:paddingBottom="@dimen/dp_8">
<!-- logo和播放进度 使用相对布局达成覆盖的效果-->
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<!--logo-->
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/iv_logo"
android:layout_width="@dimen/dp_48"
android:layout_height="@dimen/dp_48"
android:padding="1dp"
android:src="@mipmap/icon_music"
app:shapeAppearanceOverlay="@style/circleImageStyle"
app:strokeColor="@color/white"
app:strokeWidth="@dimen/dp_2" />
<!--播放进度 自定义View-->
<com.llw.goodmusic.view.MusicRoundProgressView
android:id="@+id/music_progress"
android:layout_width="@dimen/dp_48"
android:layout_height="@dimen/dp_48"
app:radius="22dp"
app:strokeColor="@color/gold_color"
app:strokeWidth="2dp" />
</RelativeLayout>
<!--歌曲信息 歌名 - 歌手 -->
<com.google.android.material.textview.MaterialTextView
android:id="@+id/tv_song_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:ellipsize="marquee"
android:focusable="true"
android:focusableInTouchMode="true"
android:marqueeRepeatLimit="marquee_forever"
android:paddingLeft="@dimen/dp_12"
android:paddingRight="@dimen/dp_12"
android:singleLine="true"
android:text="Good Music"
android:textColor="@color/white"
android:textSize="@dimen/sp_16" />
<!--歌曲控制按钮-->
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_play"
android:layout_width="@dimen/dp_36"
android:layout_height="@dimen/dp_36"
android:insetLeft="@dimen/dp_0"
android:insetTop="@dimen/dp_0"
android:insetRight="@dimen/dp_0"
android:insetBottom="@dimen/dp_0"
android:theme="@style/Theme.MaterialComponents.Light.NoActionBar"
app:backgroundTint="@color/transparent"
app:cornerRadius="@dimen/dp_18"
app:icon="@mipmap/icon_play"
app:iconGravity="textStart"
app:iconPadding="@dimen/dp_0"
app:iconSize="@dimen/dp_36" />
</LinearLayout>
</layout>
其实就是之前LocalMusicActivity的底部布局。
下面进入MainActivity中,
/**
* 底部logo图标,点击之后弹出当前播放歌曲详情页
*/
private ShapeableImageView ivLogo;
/**
* 底部当前播放歌名
*/
private MaterialTextView tvSongName;
/**
* 底部当前歌曲控制按钮, 播放和暂停
*/
private MaterialButton btnPlay;
/**
* 自定义进度条
*/
private MusicRoundProgressView musicProgress;
/**
* 列表位置
*/
private int listPosition = 0;
然后在initData中,通过引入的布局绑定控件,并且添加点击监听,下面就可以在
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.lay_local_music:
//本地音乐
startActivity(new Intent(context, LocalMusicActivity.class));
break;
case R.id.btn_play:
if (mList.size() == 0) {
show("没有可播放的音乐,请到 “本地音乐” 进行扫描");
return;
}
musicService.play(listPosition);
break;
default:
break;
}
}
下面运行测试一波:
现在已经搞定了后台播放和通知栏控制音乐,下一篇就该是通知栏和Activity的双向控制了。
源码地址:Good Music
文章浏览阅读202次。packagecode;//importjava.awt.*;//importjava.awt.Canvas;//importjava.awt.event.*;//importjavax.swing.*;importjava.util.Random;importjavax.microedition.lcdui.*;//写界面所需要的包/***//***俄罗斯方块*高雷*2007年1..._240×320java游戏
文章浏览阅读779次,点赞14次,收藏19次。然后,实现系统的数据管理和服务功能,包括用户的注册与登录、电影的分类与展示、电影信息的查询与推荐、座位的选择与预订、在线支付与电子票生成等。此外,随着在线视频平台的兴起,越来越多的人选择在线观看电影,这对传统电影院产生了巨大的冲击。研究意义: 开发在线电影院售票平台对于提升用户的观影体验、优化电影院的运营效率、促进电影产业的发展具有重要的意义。该系统旨在通过技术手段解决传统电影院售票中的问题,提供一个集成化的电影信息展示、座位选择、在线支付和用户评价平台,同时也为电影院和电影制作方提供有效的工具。
文章浏览阅读509次。保护我们剩下的人的通话信息安全,使用TOX可以让你在和家人,朋友,爱人交流时保护你的隐私不受政府无孔不入的的偷窥.关于TOX:其他牛逼的软件因为一些细化服务问你要钱的时候, TOX分文不取 . 你用了TOX, 想干嘛就干嘛.网友评论:项目源码展示:源码测试效果:最后,如果你学C/C++编程有什么不懂的,可以来问问我哦,或许我能够..._基于c++的即时聊天系统设计
文章浏览阅读584次。鱼弦:CSDN内容合伙人、CSDN新星导师、全栈领域创作新星创作者 、51CTO(Top红人+专家博主) 、github开源爱好者(go-zero源码二次开发、游戏后端架构 https://github.com/Peakchen)当Java服务在Linux系统中运行时,可能会出现swap分区被占用的内存泄露问题,导致系统性能下降或者崩溃。下面是该问题的故障及解决方法、底层结构、架构图、工作原理、使用场景详解和实际应用方式、原理详细描述、相关命令使用示例以及文献材料链接。_linux swap占用很高
文章浏览阅读662次。Alt+F11,然后插入-模块:复制下面代码到编辑窗口:Sub 半角标点符号转换为全角标点符号()'中英互译文档中将中文段落中的英文标点符号替换为中文标点符号 Dim i As Paragraph, ChineseInterpunction() As Variant, EnglishInterpunction() As Variant Dim MyRange..._替换半角宏
文章浏览阅读2.8k次。#.简介: WebView是Android提供的用来展示展示web页面的View,内部使用webkit浏览器引擎(一个轻量级的浏览器引擎),除了展示Web页面外,还可与Web页面内的JS脚本交互调用。WebView内部的WebSetting对象负责管理WebView的参数配置; WebViewClient负责处理WebView的各种请求和通知事件,在对应事件发生时会执行WebViewClient的对应回调; ChromeWebviewClient辅助Webview处理与JS一些交互......_android webview真正加载完成
文章浏览阅读1.6k次。_bitcoin 调试环境搭建
文章浏览阅读4.3k次,点赞93次,收藏94次。为了解决贝塞尔曲线无法局部修正、控制性减弱、曲线次数过高、不易拼接的缺陷,引入B样条曲线(B-Spline)。本文介绍B样条曲线的基本概念:节点向量、支撑性、次数阶数、加权性质、节点生成算法等,为后续曲线计算打下基础。_样条曲线生成
文章浏览阅读902次。配置本地repo库下载我的阿里云盘文件文件放置#创建目录mkdir -p /opt/cloudera/parcel-repo/mkdir -p /opt/cloudera/cm/yum install createrepoCDH 6.2.0 的三个文件放到/opt/cloudera/parcel-repo/中,并且注意把sha256后缀的文件名修改为sha#执行createrepo命令生成rpm元数据 最终/opt/cloudera/parcel-repo/会多一个repodata目录_/opt/cloudera/cm-agent/service/mgmt/mgmt.sh: line 76: /usr/java/jdk1.8.0_181
文章浏览阅读943次,点赞2次,收藏2次。uni.canvasToTempFilePath_uni.canvastotempfilepath
文章浏览阅读3.1k次。SRAM :静态RAM,不用刷新,速度可以非常快,像CPU内部的cache,都是静态RAM,缺点是一个内存单元需要的晶体管数量多,因而价格昂贵,容量不大。DRAM:动态RAM,需要刷新,容量大。SDRAM:同步动态RAM,需要刷新,速度较快,容量大。DDR SDRAM:双通道同步动态RAM,需要刷新,速度快,容量大。........................_sdram 干扰
文章浏览阅读7.3k次。假设表格有A、B、C、D四列数据,希望导入到你的数据库中表格table,对应的字段分别是col1、col2、col3、col4。_excel数据怎么生成sql语句