android MediaScanner 详解

Android中多媒体文件扫描操作详解(Java 实现) 要完成多媒体文件的扫描,我们可以使用 Android 提供的 MediaScannerConnection 类。该类允许我们通知媒体扫描服务有新的文件需要被扫描,以便在媒体库中显示出来。在 Android 开发中,有时我们需要扫描设备上的多媒体文件,例如图片、音频或视频文件。通过以上代码,我们可以在 Android 应用中实现多媒体文件的扫描操作。在上述示例中,我们创建了一个包含两个文件路径和对应 MIME 类型的字符串数组。例如,我们可以在文件保存或删除后调用扫描方法,以确保媒体库中显示最新的文件。 阅读详情

MediaScanner分析

一 MediaScannerService

多媒体扫描是从MediaScannerService开始的。这是一个单独的package。位于

packages/providers/MediaProvider:含以下java文件

l         MediaProvider.java

l         MediaScannerReceiver.java

l         MediaScannerService.java

l         MediaThumbRequest.java

分析这个目录的Android.mk文件,发现它运行的进程名字就是android.process.media。

application android:process=android.process.media

1.1    MediaScannerReceiver

这个类从BroadcastReceiver中派生,用来接收任务的。

MediaScannerReceiver extends BroadcastReceiver

在它重载的onRecieve函数内有以下几种走向:

if (action.equals(Intent.ACTION_BOOT_COMPLETED)) {

            // 收到”启动完毕“广播后,扫描内部存储

            scan(context, MediaProvider.INTERNAL_VOLUME);

        } else {

            ……….

                if (action.equals(Intent.ACTION_MEDIA_MOUNTED) &&

                        externalStoragePath.equals(path)) {

               /收到MOUNT信息后,扫描外部存储

                    scan(context, MediaProvider.EXTERNAL_VOLUME);

                }

 else if (action.equals(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE) &&

                        path != null && path.startsWith(externalStoragePath + "/")) {

               //收到请求要求扫描某个文件,注意不会扫描内部存储上的文件

                    scanFile(context, path);

        …………………………..

        }

……下面是它调用的scan函数:

scan(Context context, String volume)

Bundle args = new Bundle();

        args.putString("volume", volume);

//直接启动MediaScannerService了,

        context.startService(

                new Intent(context, MediaScannerService.class).putExtras(args));

 

总结:

MediaScannerReceiver是用来接收任务的,它收到广播后,会启动MediaService进行扫描工作。

下面看看MediaScannerService.

1.2    MediaScannerService

MSS标准的从Service中派生下来,

MediaScannerService extends Service implements Runnable

//注意:是一个Runnable…,可能有线程之类的东西存在

下面从Service的生命周期的角度来看看它的工作。

1. onCreate

public void onCreate()

       PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);

        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);

      //获得电源锁,防止在扫描过程中休眠

      //单独搞一个线程去跑扫描工作,防止ANR

     Thread thr = new Thread(null, this, "MediaScannerService");

        thr.start();

2. onStartCommand

@Override

    public int onStartCommand(Intent intent, int flags, int startId)

    {

      //注意这个handler,是在另外一个线程中创建的,往这个handler里sendMessage

     //都会在那个线程里边处理

  //不明白的可以去查看handler和Looper机制

//这里就是同步机制,等待mServiceHandler在另外那个线程创建完毕

while (mServiceHandler == null) {

            synchronized (this) {

                try {

                    wait(100);

                } catch (InterruptedException e) {

                }

            }

        }

 

        if (intent == null) {

            Log.e(TAG, "Intent is null in onStartCommand: ",

                new NullPointerException());

            return Service.START_NOT_STICKY;

        }

 

        Message msg = mServiceHandler.obtainMessage();

        msg.arg1 = startId;

        msg.obj = intent.getExtras();

//把MediaScannerReceiver发出的消息传递到另外那个线程去处理。

        mServiceHandler.sendMessage(msg);

      ………….

基本上MSR(MediaScannerReceiver)发出的请求都会传到onStartCommand中处理。如果有多个存储的话,也只能一个一个扫描了。

下面看看那个线程的主函数

3. run

public void run()

    {

        // reduce priority below other background threads to avoid interfering

        // with other services at boot time.

        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND +

                Process.THREAD_PRIORITY_LESS_FAVORABLE);

//不明白的去看看Looper和handler的实现

        Looper.prepare();//把这个looper对象设置到线程本地存储

 

        mServiceLooper = Looper.myLooper();

        mServiceHandler = new ServiceHandler();//创建handler,默认会把这个looper

//的消息队列赋值给handler的消息队列,这样往handler中发送消息就是往这个线程的looper发

 

        Looper.loop();//消息循环,内部会处理消息队列中的消息

//也就是handleMessage函数

}

上面handler中加入了一个扫描请求(假设是外部存储的),所以要分析handleMessage函数。

4. handleMessage

private final class ServiceHandler extends Handler

    {

        @Override

        public void handleMessage(Message msg)

        {

            Bundle arguments = (Bundle) msg.obj;

            String filePath = arguments.getString("filepath");

           

            try {

……… 这里不讲了

                } else {

                    String volume = arguments.getString("volume");

                    String[] directories = null;

                    if (MediaProvider.INTERNAL_VOLUME.equals(volume)) {

                     //是扫描内部存储的请求?  

// scan internal media storage

                        directories = new String[] {

                                Environment.getRootDirectory() + "/media",

                        };

                    }

                    else if (MediaProvider.EXTERNAL_VOLUME.equals(volume)) {

                      //是扫描外部存储的请求?获取外部存储的路径 

                       directories = new String[] {

                                Environment.getExternalStorageDirectory().getPath(),

                                };

                    }

                    if (directories != null) {

//真正的扫描开始了,上面只不过是把存储路径取出来罢了.

                        scan(directories, volume);

                         …..

//扫描完了,就把service停止了

            stopSelf(msg.arg1);

        }

};

5. scan函数

private void scan(String[] directories, String volumeName) {

       mWakeLock.acquire();

//下面这三句话很深奥…

//从 getContentResolver获得一个ContentResover,然后直接插入

//根据AIDL,这个ContentResover的另一端是MediaProvider。只要去看看它的

//insert函数就可以了

//反正这里知道获得了一个扫描URI即可。

  ContentValues values = new ContentValues();

   values.put(MediaStore.MEDIA_SCANNER_VOLUME, volumeName);

   Uri scanUri = getContentResolver().insert(MediaStore.getMediaScannerUri(), values);

 

        Uri uri = Uri.parse("file://" + directories[0]);

//发送广播,通知扫描开始了

        sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_STARTED, uri));

       

        try {

            if (volumeName.equals(MediaProvider.EXTERNAL_VOLUME)) {

                 openDatabase(volumeName);   

            }

//创建真正的扫描器

            MediaScanner scanner = createMediaScanner();

//交给扫描器去扫描文件夹  scanDirectories

            scanner.scanDirectories(directories, volumeName);

        } catch (Exception e) {

           Log.e(TAG, "exception in MediaScanner.scan()", e);

        }

//删除扫描路径

        getContentResolver().delete(scanUri, null, null);

//通知扫描完毕

        sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_FINISHED, uri));

        mWakeLock.release();

}

说说上面那个深奥的地方,在MediaProvider中重载了insert函数,insert函数会调用insertInternal函数。

如下:

private Uri insertInternal(Uri uri, ContentValues initialValues) {

        long rowId;

        int match = URI_MATCHER.match(uri);

        // handle MEDIA_SCANNER before calling getDatabaseForUri()

//刚才那个insert只会走下面这个分支,其实就是获得一个地址….

//太绕了!!!!!

        if (match == MEDIA_SCANNER) {

            mMediaScannerVolume = initialValues.getAsString(MediaStore.MEDIA_SCANNER_VOLUME);

            return MediaStore.getMediaScannerUri();

        }

……..

再看看它创建了什么样的Scanner,这就是MSS中的createMediaScanner

private MediaScanner createMediaScanner() {

//下面这个MediaScanner在framework/base/中,待会再分析

        MediaScanner scanner = new MediaScanner(this);

//设置当前的区域,这个和字符编码有重大关系。

        Locale locale = getResources().getConfiguration().locale;

        if (locale != null) {

            String language = locale.getLanguage();

            String country = locale.getCountry();

            String localeString = null;

            if (language != null) {

                if (country != null) {

//给扫描器设置当前国家和语言。

                    scanner.setLocale(language + "_" + country);

                } else {

                    scanner.setLocale(language);

                }

            }   

        }

        return scanner;

}

至此,MSS的任务完成了。接下来是MediaScanner的工作了。

6. 总结

MSS的工作流程如下:

l         1 单独启动一个带消息循环的工作线程。

l         2 主线程接收系统发来的任务,然后发送给工作线程去处理。

l         3 工作线程接收任务,创建一个MediaScanner去扫描。

l         4 MSS顺带广播一下扫描工作启动了,扫描工作完毕了。

 

二 MediaScanner

MediaScanner位置在

frameworks/base/media/下,包括jni和java文件。

先看看java实现。

这个类巨复杂,而且和MediaProvider交互频繁。在分析的时候要时刻回到MediaProvider去看看。

1.       初始化

public class MediaScanner

{

static {

//libmedia_jni.so的加载是在MediaScanner类中完成的

//这么重要的so为何放在如此不起眼的地方加载???

        System.loadLibrary("media_jni");

        native_init();

}

public MediaScanner(Context c) {

        native_setup();//调用jni层的初始化,暂时不用看了,无非就是一些

//初始化工作,待会在再进去看看

……..

    }

刚才MSS中是调用scanDirectories函数,我们看看这个。

2.       scanDirectories

public void scanDirectories(String[] directories, String volumeName) {

        try {

            long start = System.currentTimeMillis();

            initialize(volumeName);//初始化

            prescan(null);//扫描前的预处理

            long prescan = System.currentTimeMillis();

 

            for (int i = 0; i < directories.length; i++) {

//扫描文件夹,这里有一个很重要的参数 mClient

// processDirectory是一个native函数

                processDirectory(directories[i], MediaFile.sFileExtensions, mClient);

            }

            long scan = System.currentTimeMillis();

            postscan(directories);//扫描后处理

            long end = System.currentTimeMillis();

           …..打印时间,异常处理…没了…

下面简单讲讲initialize ,preScan和postScan都干嘛了。

private void initialize(String volumeName) {

//打开MediaProvider,获得它的一个实例

        mMediaProvider = mContext.getContentResolver().acquireProvider("media");

//得到一些uri

        mAudioUri = Audio.Media.getContentUri(volumeName);

        mVideoUri = Video.Media.getContentUri(volumeName);

        mImagesUri = Images.Media.getContentUri(volumeName);

        mThumbsUri = Images.Thumbnails.getContentUri(volumeName);

//外部存储的话,可以支持播放列表之类的东西,搞了一些个缓存池之类的

//如mGenreCache等

        if (!volumeName.equals("internal")) {

            // we only support playlists on external media

            mProcessPlaylists = true;

           mGenreCache = new HashMap<String, Uri>();

         …

preScan,这个函数很复杂:

大概就是创建一个FileCache,用来缓存扫描文件的一些信息,例如last_modified等。这个FileCache是从MediaProvider中已有信息构建出来的,也就是历史信息。后面根据扫描得到的新信息来对应更新历史信息。

postScan,这个函数做一些清除工作,例如以前有video生成了一些缩略图,现在video文件被干掉了,则对应的缩略图也要被干掉。

另外还有一个mClient,这个是从MediaScannerClient派生下来的一个东西,里边保存了一个文件的一些信息。后续再分析。

 

刚才说到,具体扫描工作是在processDirectory函数中完成的。这个是一个native函数。

在frameworks/base/media/jni/android_media_MediaScanner.cpp中。

 

三  MediaScanner JNI层分析

MediaScanner JNI层内容比较多,单独搞一节分析吧。

先看看android_media_MediaScanner这个文件。

1. native_init函数,jni对应的函数如下

static void

android_media_MediaScanner_native_init(JNIEnv *env)

{

     jclass clazz;

clazz = env->FindClass("android/media/MediaScanner");

//得都JAVA类中mNativeContext这个成员id

    fields.context = env->GetFieldID(clazz, "mNativeContext", "I");

   //不熟悉JNI的自己去学习下吧

}

3.       native_setup函数,jni对应函数如下:

android_media_MediaScanner_native_setup(JNIEnv *env, jobject thiz)

{

//创建MediaScanner对象

    MediaScanner *mp = createMediaScanner();

//太变态了,自己不保存这个对象指针.

//却把它设置到java对象的mNativeContext去保存

    env->SetIntField(thiz, fields.context, (int)mp);

}

//创建MediaScanner函数

static MediaScanner *createMediaScanner() {

#if BUILD_WITH_FULL_STAGEFRIGHT

   ..

//使用google自己的

  return new StagefrightMediaScanner;

   #endif

#ifndef NO_OPENCORE

//使用opencore提供的

….

    return new PVMediaScanner();

#endif

 

4.       processDirectories函数,jni对应如下:

android_media_MediaScanner_processDirectory(JNIEnv *env, jobject thiz, jstring path, jstring extensions, jobject client)

{

    MediaScanner *mp = (MediaScanner *)env->GetIntField(thiz, fields.context);

//每次都要回调到JAVA中去取这个Scanner!!

 ………

    const char *pathStr = env->GetStringUTFChars(path, NULL);

    const char *extensionsStr = env->GetStringUTFChars(extensions, NULL);

…….

//又在C++这里搞一个client,然后把java的client放到C++Client中去保存

//而且还是栈上的临时变量..

MyMediaScannerClient myClient(env, client);

//scanner扫描文件夹,用得是C++的client

    mp->processDirectory(pathStr, extensionsStr, myClient, ExceptionCheck, env);

    env->ReleaseStringUTFChars(path, pathStr);

    env->ReleaseStringUTFChars(extensions, extensionsStr);

}

到这里似乎就没有了,那么扫描后的数据库是怎么更新的呢?为什么要传入一个client进去呢?看来必须得trace到scanner中去才知道了。

 //  这里注意,在android4.0, 4.1都是摒弃了PVMediaScanner的处理,都是使用的stagefright这个多媒体框架完成,在android2.3的时候还有PVMediaScanner的接口,即使用

     OpenCore,而在4.0,4.1上已经去除了这个接口。

四 PVMediaScanner

这个在external/opencore/android/mediascanner.cpp中。

1.       processDirectory

status_t MediaScanner::processDirectory(const char *path, const char* extensions,

        MediaScannerClient& client, ExceptionCheck exceptionCheck, void* exceptionEnv)

{

    InitializeForThread();

    int error = 0;

status_t result = PVMFSuccess;

….

//调用client的设置区域函数

   client.setLocale(mLocale);

//扫描文件夹,咋还没开始??

   result = doProcessDirectory(pathBuffer, pathRemaining, extensions, client, exceptionCheck, exceptionEnv);

..

2.       doProcessDirectory

status_t MediaScanner::doProcessDirectory(char *path, int pathRemaining, const char* extensions,

        MediaScannerClient& client, ExceptionCheck exceptionCheck, void* exceptionEnv)

{

…终于看到点希望了

//打开这个文件夹,枚举其中的内容。

//题外话,这个时候FileManager肯定删不掉这个文件夹!!

    DIR* dir = opendir(path);

    while ((entry = readdir(dir))) {

        const char* name = entry->d_name;

//不处理.和..文件夹

 if (isDirectory) {

……..

//不处理.开头的文件夹。如果是文件夹,递归调用doProcessDirectory

//深度优先啊!

            int err = doProcessDirectory(path, pathRemaining - nameLength - 1, extensions, client, exceptionCheck, exceptionEnv);

                if (err) {

                    LOGE("Error processing '%s' - skipping/n", path);

                    continue;

                }

            } else if (fileMatchesExtension(path, extensions)) {

//是一个可以处理的文件,交给client处理

//彻底疯掉了….这是干嘛呢???

              client.scanFile(path, statbuf.st_mtime, statbuf.st_size);

这里要解释下,刚才createMediaScanner中,明明创建的是PVMediaScanner,为何这里看得是MediaScanner代码呢?

l         因为PVMediaScanner从MediaScanner中派生下来的,而且没有重载processDirectory函数

l         Eclaire没有PVMediaScanner这样的东西,估计是froyo又改了点啥吧。

 

FT,processDirctory无非是列举一个目录内容,然后又反回去调用client的scanFile处理了。为何搞这么复杂?只有回去看看C++的client干什么了。

 

3.       MediaScannerClient---JNI层

JNI中的这个类是这样的:

class MyMediaScannerClient : public MediaScannerClient

//这是它的scanFile实现

virtual bool scanFile(const char* path, long long lastModified, long long fileSize)

    {

//再次崩溃了,C++的client调用了刚才传进去的java Client的

//scanFile函数…不过这次还传进去了该文件的路径,最后修改时间和文件大小。     

  mEnv->CallVoidMethod(mClient, mScanFileMethodID, pathStr, lastModified, fileSize);

…..想死,,,

    }

没办法了,只能再去看看MediaScanner.java传进去的那个client了。

4.       MediaScannerClient----JAVA层

这个类在MediaScanner.java中实现。

private class MyMediaScannerClient implements MediaScannerClient:

public void scanFile(String path, long lastModified, long fileSize) {

//调用doScanFile..很烦..

           doScanFile(path, null, lastModified, fileSize, false);

…

//下面是doScanFile

public Uri doScanFile(String path, String mimeType, long lastModified, long fileSize, boolean scanAlways) {

//预处理,看看之前创建的文件缓存中有没有这个文件

 

   FileCacheEntry entry = beginFile(path, mimeType, lastModified, fileSize);

                // rescan for metadata if file was modified since last scan

                if (entry != null && (entry.mLastModifiedChanged || scanAlways)) {

//如果事先有这个文件的信息,则需要修改一些信息,如长度,最后修改时间等

       …..

//真正的扫描文件

                        processFile(path, mimeType, this);

//扫描完了,做最后处理

                    endFile(entry, ringtones, notifications, alarms, music, podcasts);

processFile又是jni层的。

对应android_media_MediaScanner_processFile函数

android_media_MediaScanner_processFile(JNIEnv *env, jobject thiz, jstring path, jstring mimeType, jobject client)

{

    MediaScanner *mp = (MediaScanner *)env->GetIntField(thiz, fields.context);

  //无语了,又搞一个  MyMediaScannerClient

MyMediaScannerClient myClient(env, client);

    mp->processFile(pathStr, mimeTypeStr, myClient);

…}

第一次到PVMediaScanner中来了

status_t PVMediaScanner::processFile(const char *path, const char* mimeType, MediaScannerClient& client)

{

    status_t result;

    InitializeForThread();

//调用client的beginFile,估计是做一些啥预处理

    client.setLocale(locale());

    client.beginFile();

   

    //LOGD("processFile %s mimeType: %s/n", path, mimeType);

    const char* extension = strrchr(path, '.');

 

    if (extension && strcasecmp(extension, ".mp3") == 0) {

        result = parseMP3(path, client);//client又传进去了

…根据后缀名去扫描….

}

client.endFile();

到parseMP3去看看。这个在

static PVMFStatus parseMP3(const char *filename, MediaScannerClient& client)

{//这个函数太专业了,和编解码有关,我们重点关注client在这里干什么了

…

//原来,解析器从文件中解析出一个信息,就调用client的addStringTag

if (!client.addStringTag("duration", buffer))

….

addStringTag在JNI的client中处理。

这个MediaScannerClient是在opencore中的那个MediaScanner.cpp中实现的,而android_media_MediaScanner.cpp中的是MyMediaScannerClient,从MediaScannerClient派生下来的

    bool MediaScannerClient::addStringTag(const char* name, const char* value)

{

    if (mLocaleEncoding != kEncodingNone) {

        //字符串编码之类的转换。不详述了

        bool nonAscii = false;

        const char* chp = value;

        char ch;

        while ((ch = *chp++)) {

            if (ch & 0x80) {

                nonAscii = true;

                break;

            }

        }

//如果不是ASCII编码的话,内部先保存一下这些个tag信息

//待会扫描完后再集中做一次字符串编码转换

        if (nonAscii) {

            // save the strings for later so they can be used for native encoding detection

            mNames->push_back(name);

            mValues->push_back(value);

            return true;

        }

        // else fall through

    }

//调用子类的handleStringTag

    return handleStringTag(name, value);

}

class MyMediaScannerClient : public MediaScannerClient{

//调用到子类的handleStringTag了

virtual bool handleStringTag(const char* name, const char* value)

{

//又传递到JAVA层的handleStringTag来处理

//麻木了..

    mEnv->CallVoidMethod(mClient, mHandleStringTagMethodID, nameStr, valueStr);

       }

JAVA层

MediaScannerService中的MyMediaScannerClient类

public void handleStringTag(String name, String value) {

//下层扫描的文件tag信息,全部处理后赋值给java层这个MyScannerClient了

例如MP3的title,专辑名等等。

               ….

              int num = parseSubstring(value, 0, 0);

                mTrack = (num * 1000) + (mTrack % 1000);

            } else if (name.equalsIgnoreCase("duration")) {

                mDuration = parseSubstring(value, 0, 0);

            } else if (name.equalsIgnoreCase("writer") || name.startsWith("writer;")) {

                mWriter = value.trim();

到这里了,还没写到数据库呢?啥时候更新数据库?看来是在client.endFile()中了。

但是这个endClient并没有调用到JAVA层去。那在哪里结束呢?

还记得JAVA中的doScanFile函数吗,对了,这个endFile就是在那里直接由JAVA调用的。

private Uri endFile(FileCacheEntry entry, boolean ringtones, boolean notifications,

                boolean alarms, boolean music, boolean podcasts)

                throws RemoteException {

            // update database

            Uri tableUri;

            boolean isAudio = MediaFile.isAudioFileType(mFileType);

            boolean isVideo = MediaFile.isVideoFileType(mFileType);

            boolean isImage = MediaFile.isImageFileType(mFileType);

          ….

//来了一个新文件,直接插入数据库

                result = mMediaProvider.insert(tableUri, values);

//或者更新数据库

mMediaProvider.update(result, values, null, null);

这回真算是完了。

5.流程总结

l         MediaScanner(MS)调用scanDirectories中的processDirectory,进入到JNI层

l         JNI调用PVMediaScanner的processDirectory

l         PVMediaScanner的processDirectory为目录下的文件调用MyMediaScannerClient的scanFile

l         MyMediaScannerClient

Mediaprovider简介 Mediaprovider的简单说明。 MediaProvider包括五个类: com.android.providers.media.MediaProvider com.android.providers.media.MediaScannerCursor com.android.providers.media.MediaScannerReceiver com.android.providers.media.MediaScannerService com.android.providers.media.MediaThumbRequest 立即下载

相关推荐

android MediaProvider和MediaScanner详解

关于Android媒体存储和手机数据库扫描流程以及优化的部分代码贴图

MediaScanner分析

MediaScanner分析 一 MediaScannerService 多媒体扫描是从MediaScannerService开始的。这是一个单独的package。位于 packages/providers/MediaProvider:含以下java文件 l MediaProvider.java l MediaScannerReceiver.java l MediaScannerSer

ihustmse的专栏 1275

LM Studio 模型加载与转换实战指南:从GGUF格式到本地部署

本文提供了使用LM Studio加载与转换GGUF格式大语言模型的完整实战指南。详细阐述了为何GGUF格式是本地部署的最佳选择,并逐步指导用户如何将非GGUF模型(如Transformers格式)转换为GGUF格式,以及在LM Studio中进行加载、配置GPU卸载等关键优化,最终实现流畅的本地离线对话体验。

weixin_29061425的博客 921

MediaProvider流程分析

MediaProvider流程分析 1. 概要 Android MediaProvider 使用 SQLite 数据库存储图片、视频、音频、文档等多媒体文件的信息,供视频播放器、音乐播放器、图库、文档编辑器使用。按照谷歌的要求,Android上其他应用获取文件列表的时候修需要通过contentprovider来读取.类似的还有TelephonyProvider、CalendarProvider、C...

wangwei890702的专栏 1万+

基于Arduino与Velostat的智能鞋垫:DIY足底压力监测系统全解析

压力传感器是物联网和健康监测领域的基础元件,其核心原理是将物理压力转换为可测量的电信号。通过压阻效应,材料的电阻值随压力变化,从而实现压力感知。这项技术为低成本、定制化的健康监测方案提供了可能,尤其在运动分析和康复训练中具有重要价值。本文聚焦于利用Velostat压敏材料和Arduino平台,详细解析如何从传感器制作、信号调理到无线数据传输,构建一套完整的足底压力监测系统。文中深入探讨了传感器阵列构建、信号滤波等关键技术,并针对常见问题提供了实用的解决方案,为创客和开发者实现可穿戴健康监测设备提供了清晰的工

weixin_32818781的博客 402

WhatsApp网页版扫码分析

扫码登录本质上是WhatsApp的多设备登录,设备之间可以独立运行,那么当我们首次通过WhatsApp扫码登录二维码时,到底发生了那些事情?交换了那些数据?

SocialMarketing的博客 5230

android MediaScanner详解

android系统在SD卡插入后,MediaScanner服务会在后台自动扫描SD上的文件资源,将SD上的音乐媒体信息加入到MediaStore数据库中。程序可以直接从MediaStore中读取相应的媒体信息。通过注册监听MediaScanner广播的Intent,可以获知MediaScanner服务是否在进行后台的扫描工作:        Intent.ACTION_MEDIA_SCANN

prettyice的专栏 8170

从SPI协议到文件系统:手把手教你用HAL库给W25Q16移植FATFS(附扇区管理优化方案)

本文详细介绍了如何在STM32平台上,通过HAL库的SPI接口为W25Q16 Flash芯片移植FATFS文件系统。文章从SPI通信基础讲起,深入解析了FATFS底层驱动(diskio.c)的移植要点,并重点探讨了针对W25Q16的4KB物理扇区特性进行扇区管理优化的两种方案,旨在帮助开发者构建稳定高效的文件存储方案。

happy2的博客 1043

android_9.0 MediaScanner 媒体扫描详解

1. 概述 MediaScanner 是 Android 多媒体系统中重要的一员,MediaScanner 与媒体文件预扫描相关。我们知道,Android 系统每次开机或者重新插拔 SD 卡之后都会去扫描系统存储空间中的媒体文件,并将媒体文件相关的信息存储到媒体数据库中。这样后续 Gallery、Music、VideoPlayer 等应用便可以直接查询媒体数据库,根据需要提取信息做显示。 如果进入...

岁月斑驳7的博客 1万+

代码生成 | 安时积分法模型搭建

安时积分法是电池电量计量最基础的方法,今天我们用simulink建模的方式做一个安时积分模型,从而更好地理解安时积分的思想也掌握建模的基础操 ​新建文件 打开MATLAB启动simulink新建一个模型文件 定义变量 和手写代码一样,先定义几个后面要用到的变量,在simulink建模时新建变量用Data Store Memory模块。双击填入要定义的变量名,然后在signal attributes赋初值 如此方法新建四个变量current(电流)初值1000mA、tatol_cap(总容量

ningvay的博客 1万+

【原创】下载RealEstate10K数据集原始视频的方法

目前互联网上能搜到下载RealEstate10K数据集原始视频的方法都已经不能用了,这篇博客介绍一种目前可用的下载RealEstate10K数据集原始视频的方法,并给出自动化的脚本代码。

沉迷单车的追风少年 2462

android MediaScanner详解 ---- MediaScanner扫描得到多媒体信息

MediaScanner与音乐信息扫描==         Android系统在SD卡插入后,MediaScanner服务会在后台自动扫描SD上的文件资源,将SD上的音乐媒体信息加入到MediaStore数据库中。程序可以直接从MediaStore中读取相应的媒体信息。通过注册监听MediaScanner广播的Intent,可以获知MediaScanner服务是否在进行后台的扫描工作:

雁峰 497

读懂 SAP Fiori Administration Catalogs:从管理员入口、权限编排到 Spaces 和 Pages 的落地实践

SAP Fiori 管理员目录的核心价值在于为系统配置提供标准化入口,解决权限与展示分离的问题。随着 SAP_UI 7.55 版本更新,SAP_FLP_ADMIN 角色整合了关键管理工具(如 Launchpad Designer、Content Manager),并通过 Business Catalog 和 Space 的协同作用,确保管理员既能访问配置工具,又能直观查看相关功能入口。项目实施中需注意配置层级(系统级/客户端级)的选择,以及多人协作时的数据同步问题,以提升交付效率。

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 194

android MediaScanner详解-扫描得到多媒体信息

转自 https://blog.youkuaiyun.com/hellofeiya/article/details/8848562 MediaScanner与音乐信息扫描 Android系统在SD卡插入后,MediaScanner服务会在后台自动扫描SD上的文件资源,将SD上的音乐媒体信息加入到MediaStore数据库中。程序可以直接从MediaStore中读取相应的媒体信息。通过注册监听Med...

书山有路勤为径 1349

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

转:android MediaScanner详解 ---- MediaScanner扫描得到多媒体信息

转自:http://blog.youkuaiyun.com/hellofeiya/article/details/8848562 MediaScanner与音乐信息扫描== Android系统在SD卡插入后,MediaScanner服务会在后台自动扫描SD上的文件资源,将SD上的音乐媒体信息加入到MediaStore数据库中。程序可以直接从MediaStore中读取相应的媒体信息。通过注...

weixin_34419321的博客 172

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

Android学习——MediaScanner源码分析

神马是MediaScanner呢?在Android的SDK里面是看不到这个类的,因为被google隐藏了。通过Android的源码我们可以看到MediaScanner的类注解多了一个@hide的标注。所以对于一般应用开发者,此文意义不是很大,大家可以绕道。 在 前两篇文章中,最后我们都了解了Android的媒体文件的扫描是在MediaScannerService中调用MediaScanner的...

jandroid 190

STM32F407中文手册

STM32f407中文手册 本参考手册面向应用开发人员,提供有关使用 STM32F405xx/07xx、STM32F415xx/17xx、 STM32F42xxx 和 STM32F43xxx 微控制器存储器与外设的完整信息。STM32F405xx/07xx、STM32F415xx/17xx、STM32F42xxx 和 STM32F43xxx 构成一个微 控制器系列,各产品具有不同的存储器大小、封装和外设。有关订购信息以及器件的机械与电气特性,请参见数据手册。有关 ARM Cortex™-M4F 内核的信息,请参见《Cortex™-M4F 技术参考手册》。

Android MediaScanner使用简单介绍

1. 运行扫描 仅仅有系统开机的时候才会运行MediaScanner,其他情景下须要手动运行扫描(拍摄,下载等)。 手动运行扫描的方法是发送MediaScanner广播: 1.1 扫描指定文件: Intent.ACTION_MEDIA_SCANNER_SCAN_FILE 1.2 扫描指定文件夹: “android.intent.action.MED...

aoe41606的博客 485

mediascanner详解

OK, 我们现在开始来大概分析一下android framework中MediaScanner部分的流程,若大家发现分析过程中有错误,欢迎拍砖指正。 分析流程之前,我们先给自己定个要用MediaScanner解决的问题,这样我们才会有目标感,才知道我们要干什么。否则,干巴巴的分析流程,一般都会很容易的迷失在各种code的迷雾中。 我们这里要定的目标是:获取某个MP3文件的artist ...

weixin_34025051的博客 999
上一篇: Android接口回调机制
下一篇: 是时候学习ReJava了
Linux_dream_2015
博客等级 码龄11年 12粉丝 16原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值