Media Data之多媒体数据库(二)MediaProvider

??MediaProvider使用 SQLite 数据库存储图片、视频、音频等多媒体文件的信息,供视频播放器、音乐播放器、图库使用。提供了基本的增删改查等相关方法。路径如下:

/packages/providers/MediaProvider/src/com/android/providers/media/MediaProvider.java

??其中包含以下内部类:

????DatabaseHelper——对于一个特殊数据库的包装类,用来管理数据的创建和版本更新,继承SQLiteOpenHelper

????GetTableAndWhereOutParameter——静态类,获取相关的参数

????ScannerClient——静态类,继承MediaScannerConnectionClient

????ThumbData——方便对变量的操作

??下面依据对数据库的操作进行分析。

1. 创建

??首先在MediaProvider的onCreate方法中分别对内部存储和外部存储进行链接数据库:

//绑定内部存储数据库
attachVolume(INTERNAL_VOLUME);
...
if (Environment.MEDIA_MOUNTED.equals(state) ||
        Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
    //绑定外部存储数据库
    attachVolume(EXTERNAL_VOLUME);
}

为内部存储和外部存储进行创建数据库,如果此存储卷已经链接上了,那么什么也不做,否则就查询存储卷的id并且建立对应的数据库。接下来分析attachVolume方法:

private Uri attachVolume(String volume) {
... ...
    // Update paths to reflect currently mounted volumes
    updateStoragePaths();
    DatabaseHelper helper = null;
    synchronized (mDatabases) {
        helper = mDatabases.get(volume);
        //判断是否已经attached过了
        if (helper != null) {
            if (EXTERNAL_VOLUME.equals(volume)) {
                //确保默认的文件夹已经被创建在挂载的主要存储设备上,
                //对每个存储卷只做一次这种操作,所以当用户手动删除时不会打扰
                ensureDefaultFolders(helper, helper.getWritableDatabase());
            }
            return Uri.parse("content://media/" + volume);
        }
        Context context = getContext();
        if (INTERNAL_VOLUME.equals(volume)) {
            //如果是内部存储则直接实例化DatabaseHelper,传参,之后调用DatabaseHelper的方法
            helper = new DatabaseHelper(context, INTERNAL_DATABASE_NAME, true,
                    false, mObjectRemovedCallback);
        } else if (EXTERNAL_VOLUME.equals(volume)) {
            //如果是外部存储的操作
            final VolumeInfo vol = mStorageManager.getPrimaryPhysicalVolume();
            if (vol != null) {
                final StorageVolume actualVolume = mStorageManager.getPrimaryVolume();
                final int volumeId = actualVolume.getFatVolumeId();

                // Must check for failure!
                // If the volume is not (yet) mounted, this will create a new
                // external-ffffffff.db database instead of the one we expect.  Then, if
                // android.process.media is later killed and respawned, the real external
                // database will be attached, containing stale records, or worse, be empty.
                //数据库都是以类似 external-ffffffff.db 的形式命名的,
                //后面的 8 个 16 进制字符是该 SD 卡 FAT 分区的 Volume ID。
                //该 ID 是分区时决定的,只有重新分区或者手动改变才会更改,
                //可以防止插入不同 SD 卡时数据库冲突。
                if (volumeId == -1) {
                    String state = Environment.getExternalStorageState();
                    if (Environment.MEDIA_MOUNTED.equals(state) ||
                            Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
                    //已经挂载但是sd卡是只读状态
                    } else {
                        //还没有挂载
                    }
                }

                // generate database name based on volume ID
                //根据volume ID设置数据库的名称
                String dbName = "external-" + Integer.toHexString(volumeId) + ".db";
                //通过构造方法去实现创建数据库的过程
                helper = new DatabaseHelper(context, dbName, false,
                        false, mObjectRemovedCallback);
                mVolumeId = volumeId;
            } else {
                //将之前的数据库名字进行转换
                // external database name should be EXTERNAL_DATABASE_NAME
                // however earlier releases used the external-XXXXXXXX.db naming
                // for devices without removable storage, and in that case we need to convert
                // to this new convention
                ... ...
                //根据之前转换的数据库名,创建数据库
                helper = new DatabaseHelper(context, dbFile.getName(), false,
                        false, mObjectRemovedCallback);
            }
        } else {
            throw new IllegalArgumentException("There is no volume named " + volume);
        }
        //标识已经创建过了数据库
        mDatabases.put(volume, helper);

        if (!helper.mInternal) {
            // clean up stray album art files: delete every file not in the database
            File[] files = new File(mExternalStoragePaths[0],
                           ALBUM_THUMB_FOLDER).listFiles();
            HashSet<String> fileSet = new HashSet();
            for (int i = 0; files != null && i < files.length; i++) {
                fileSet.add(files[i].getPath());
            }
            Cursor cursor = query(MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI,
                    new String[] { MediaStore.Audio.Albums.ALBUM_ART }, null, null, null);
            try {
                while (cursor != null && cursor.moveToNext()) {
                    fileSet.remove(cursor.getString(0));
                }
            } finally {
                IoUtils.closeQuietly(cursor);
            }
            Iterator<String> iterator = fileSet.iterator();
            while (iterator.hasNext()) {
                String filename = iterator.next();
                if (LOCAL_LOGV) Log.v(TAG, "deleting obsolete album art " + filename);
                new File(filename).delete();
            }
        }
    }
    if (EXTERNAL_VOLUME.equals(volume)) {
        //给外部存储创建默认的文件夹
        ensureDefaultFolders(helper, helper.getWritableDatabase());
    }
    return Uri.parse("content://media/" + volume);
}

下面就是分析创建数据库的源头DatabaseHelper:

@Override
public void onCreate(final SQLiteDatabase db) {
    //在此方法中对63版本以下的都会新建数据库
    updateDatabase(mContext, db, mInternal, 0, getDatabaseVersion(mContext));
}
@Override
public void onUpgrade(final SQLiteDatabase db, final int oldV, final int newV) {
    //对数据库进行更新
    mUpgradeAttempted = true;
    updateDatabase(mContext, db, mInternal, oldV, newV);
}

现在已经找到创建数据库的方法updateDatabase,现在大致分析一下此方法:

private static void updateDatabase(Context context, SQLiteDatabase db, boolean internal,
        int fromVersion, int toVersion) {
    // sanity checks
    int dbversion = getDatabaseVersion(context);
    //对数据库的版本进行判断
    ... ...
    long startTime = SystemClock.currentTimeMicro();
    //对传入的数据库版本进行判断,如果小于63,或者在84到89,92到94之间的,
    //都会去创建数据库
    if (fromVersion < 63 || (fromVersion >= 84 && fromVersion <= 89) ||
            (fromVersion >= 92 && fromVersion <= 94)) {
    //下面就是执行具体的sqlite CRATE语句,创建对应的表
    ... ...
    }
    //下面也是对版本判断之后进行相应操作
    ... ...
    //检查audio_meta的_data值是否是不同的,如果不同就删除audio_meta,
    //在扫描的时候从新创建
    sanityCheck(db, fromVersion);
    long elapsedSeconds = (SystemClock.currentTimeMicro() - startTime) / 1000000;
}

??至此,对于数据库的创建已经分析完毕。

2 更新

@Override
public int update(Uri uri, ContentValues initialValues, String userWhere,
        String[] whereArgs) {
    //将uri进行转换成合适的格式,去除标准化
    uri = safeUncanonicalize(uri);
    int count;
    //对uri进行匹配
    int match = URI_MATCHER.match(uri);
    //返回查询的对应uri的数据库帮助类
    DatabaseHelper helper = getDatabaseForUri(uri);
    //记录更新的次数
    helper.mNumUpdates++;
    //通过可写的方式获得数据库实例
    SQLiteDatabase db = helper.getWritableDatabase();
    String genre = null;
    if (initialValues != null) {
        //获取流派的信息,然后删除掉
        genre = initialValues.getAsString(Audio.AudioColumns.GENRE);
        initialValues.remove(Audio.AudioColumns.GENRE);
    }
    // special case renaming directories via MTP.
    // in this case we must update all paths in the database with
    // the directory name as a prefix
    ... ...
    //根据匹配的uri进行相应的操作
    switch (match) {
        case AUDIO_MEDIA_ID:
        //更新音乐人和专辑字段。首先从缓存中判断是否有值,如果有直接用缓存中的
        //数据,如果没有再从数据库中查询是否有对应的信息,如果有则更新,
        //如果没有插入这条数据.接下来的操作是增加更新次数,并更新流派
        ... ...
        case VIDEO_MEDIA_ID:
        //更新视频,并且发出生成略缩图请求
        ... ...
        case AUDIO_PLAYLISTS_ID_MEMBERS_ID:
        //更新播放列表数据
        ... ...
    }
    ... ...
}

至此,更新操作已完成。

3 插入

??关于插入,有两个方法插入,一个是大量的插入bulkInsert方法传入的是ContentValues数组;一个是insert,传入的是单一个ContentValues。下面分别分析:

@Override
public int bulkInsert(Uri uri, ContentValues values[]) {
    //首先对传入的Uri进行匹配
    int match = URI_MATCHER.match(uri);
    if (match == VOLUMES) {
        //如果是匹配的是存储卷,则直接调用父类的方法,进行循环插入
        return super.bulkInsert(uri, values);
    }
    //对DatabaseHelper和SQLiteDatabase的初始化
    DatabaseHelper helper = getDatabaseForUri(uri);
    if (helper == null) {
        throw new UnsupportedOperationException(
                "Unknown URI: " + uri);
    }
    SQLiteDatabase db = helper.getWritableDatabase();
    if (db == null) {
        throw new IllegalStateException("Couldn‘t open database for " + uri);
    }

    if (match == AUDIO_PLAYLISTS_ID || match == AUDIO_PLAYLISTS_ID_MEMBERS) {
        //插入播放列表的数据,在playlistBulkInsert中是开启的事务进行插入
        return playlistBulkInsert(db, uri, values);
    } else if (match == MTP_OBJECT_REFERENCES) {
        //将MTP对象的ID转换成音频的ID,最终也是调用到playlistBulkInsert
        int handle = Integer.parseInt(uri.getPathSegments().get(2));
        return setObjectReferences(helper, db, handle, values);
    }
    //如果不满足上述的条件,则开启事务进行插入其他的数据
    db.beginTransaction();
    ArrayList<Long> notifyRowIds = new ArrayList<Long>();
    int numInserted = 0;
    try {
        int len = values.length;
        for (int i = 0; i < len; i++) {
            if (values[i] != null) {
                //循环调用insertInternal去插入相关的数据
                insertInternal(uri, match, values[i], notifyRowIds);
            }
        }
        numInserted = len;
        db.setTransactionSuccessful();
    } finally {
        //结束事务
        db.endTransaction();
    }

    // Notify MTP (outside of successful transaction)
    if (uri != null) {
        if (uri.toString().startsWith("content://media/external/")) {
            notifyMtp(notifyRowIds);
        }
    }
    //通知更新
    getContext().getContentResolver().notifyChange(uri, null);
    return numInserted;
}

@Override
public Uri insert(Uri uri, ContentValues initialValues) {
    int match = URI_MATCHER.match(uri);
    ArrayList<Long> notifyRowIds = new ArrayList<Long>();
    //只是调用insertInternal进行插入
    Uri newUri = insertInternal(uri, match, initialValues, notifyRowIds);
    if (uri != null) {
        if (uri.toString().startsWith("content://media/external/")) {
            notifyMtp(notifyRowIds);
        }
    }
    // do not signal notification for MTP objects.
    // we will signal instead after file transfer is successful.
    if (newUri != null && match != MTP_OBJECTS) {
        getContext().getContentResolver().notifyChange(uri, null);
    }
    return newUri;
}

insertInternal方法比较简单,但是类别较多,暂时不做分析。

4 删除

@Override
public int delete(Uri uri, String userWhere, String[] whereArgs) {
    uri = safeUncanonicalize(uri);
    int count;
    int match = URI_MATCHER.match(uri);
    // handle MEDIA_SCANNER before calling getDatabaseForUri()
    //因为如果匹配的uri是扫描过程中的,此时uri直接通过getDatabaseForUri获取不到
    //数据库,需要对uri进行重新拼装
    if (match == MEDIA_SCANNER) {
        if (mMediaScannerVolume == null) {
            return 0;
        }
        DatabaseHelper database = getDatabaseForUri(
                Uri.parse("content://media/" + mMediaScannerVolume + "/audio"));
        if (database == null) {
            Log.w(TAG, "no database for scanned volume " + mMediaScannerVolume);
        } else {
            database.mScanStopTime = SystemClock.currentTimeMicro();
            String msg = dump(database, false);
            //删除掉在数据库的log表记录
            logToDb(database.getWritableDatabase(), msg);
        }
        mMediaScannerVolume = null;
        //因为只涉及到1行,所以返回值是1
        return 1;
    }
    if (match == VOLUMES_ID) {
        //对外部存储设备进行关闭数据库的操作
        detachVolume(uri);
        count = 1;
    } else if (match == MTP_CONNECTED) {
        synchronized (mMtpServiceConnection) {
            if (mMtpService != null) {
                // MTP has disconnected, so release our connection to MtpService
                getContext().unbindService(mMtpServiceConnection);
                count = 1;
                // mMtpServiceConnection.onServiceDisconnected might not get called,
                // so set mMtpService = null here
                mMtpService = null;
            } else {
                count = 0;
            }
        }
    } else {
        final String volumeName = getVolumeName(uri);
        //初始化DatabaseHelper和SQLiteDatabase
        ... ...
        synchronized (sGetTableAndWhereParam) {
            //拼装字段
            getTableAndWhere(uri, match, userWhere, sGetTableAndWhereParam);
            if (sGetTableAndWhereParam.table.equals("files")) {
                String deleteparam =
                       uri.getQueryParameter(MediaStore.PARAM_DELETE_DATA);
                if (deleteparam == null || ! deleteparam.equals("false")) {
                    database.mNumQueries++;
                    Cursor c = db.query(sGetTableAndWhereParam.table,
                            sMediaTypeDataId,
                            sGetTableAndWhereParam.where, whereArgs, null, null, null);
                    String [] idvalue = new String[] { "" };
                    String [] playlistvalues = new String[] { "", "" };
                    try {
                        while (c.moveToNext()) {
                            final int mediaType = c.getInt(0);
                            final String data = c.getString(1);
                            final long id = c.getLong(2);

                            if (mediaType == FileColumns.MEDIA_TYPE_IMAGE) {
                                //判断是图片类型,直接删除源文件
                                deleteIfAllowed(uri, data);
                                MediaDocumentsProvider.onMediaStoreDelete(
                                    getContext(),volumeName,
                                    FileColumns.MEDIA_TYPE_IMAGE, id);
                                idvalue[0] = String.valueOf(id);
                                database.mNumQueries++;
                                //查询略缩图文件并删除
                                Cursor cc = db.query("thumbnails", sDataOnlyColumn,
                                            "image_id=?", idvalue, null, null, null);
                                try {
                                    while (cc.moveToNext()) {
                                        deleteIfAllowed(uri, cc.getString(0));
                                    }
                                    database.mNumDeletes++;
                                    //删除数据库中的信息
                                    db.delete("thumbnails", "image_id=?", idvalue);
                                } finally {
                                    IoUtils.closeQuietly(cc);
                                }
                            } else if (mediaType == FileColumns.MEDIA_TYPE_VIDEO) {
                                //如果是视频文件,直接删除源文件
                                deleteIfAllowed(uri, data);
                                MediaDocumentsProvider.onMediaStoreDelete(
                                    getContext(),volumeName,
                                    FileColumns.MEDIA_TYPE_VIDEO, id);
                            } else if (mediaType == FileColumns.MEDIA_TYPE_AUDIO) {
                                //如果是音频文件并且判断是否是外部存储
                                if (!database.mInternal) {
                                    MediaDocumentsProvider.onMediaStoreDelete(
                                        getContext(),volumeName,
                                        FileColumns.MEDIA_TYPE_AUDIO, id);
                                    idvalue[0] = String.valueOf(id);
                                    database.mNumDeletes += 2; // also count the one below
                                    //删除流派信息
                                  db.delete("audio_genres_map","audio_id=?",idvalue);
                                    // for each playlist that the item appears in, move
                                    // all the items behind it forward by one
                                    Cursor cc = db.query("audio_playlists_map",
                                                sPlaylistIdPlayOrder,
                                                "audio_id=?", idvalue, null, null, null);
                                    try {
                                        while (cc.moveToNext()) {
                                            playlistvalues[0] = "" + cc.getLong(0);
                                            playlistvalues[1] = "" + cc.getInt(1);
                                            database.mNumUpdates++;
                                            //删除对应播放列表信息
                                            db.execSQL("UPDATE audio_playlists_map" +
                                                    " SET play_order=play_order-1" +
                                                    " WHERE playlist_id=? AND play_order>?",
                                                    playlistvalues);
                                        }
                                        db.delete("audio_playlists_map", "audio_id=?", idvalue);
                                    } finally {
                                        IoUtils.closeQuietly(cc);
                                    }
                                }
                            } else if (mediaType == FileColumns.MEDIA_TYPE_PLAYLIST) {
                                // TODO, maybe: remove the audio_playlists_cleanup trigger and
                                // implement functionality here (clean up the playlist map)
                            }
                        }
                    } finally {
                        IoUtils.closeQuietly(c);
                    }
                }
            }
            //对其他的匹配类型进行删除
            switch (match) {
               //删除MTP,流派信息,视频文件的略缩图
               ... ...
            }
            // Since there are multiple Uris that can refer to the same files
            // and deletes can affect other objects in storage (like subdirectories
            // or playlists) we will notify a change on the entire volume to make
            // sure no listeners miss the notification.
            Uri notifyUri = Uri.parse("content://" + MediaStore.AUTHORITY + "/" + volumeName);
            getContext().getContentResolver().notifyChange(notifyUri, null);
        }
    }
    return count;
}

5 查询

public Cursor query(Uri uri, String[] projectionIn, String selection,
        String[] selectionArgs, String sort) {
    uri = safeUncanonicalize(uri);
    int table = URI_MATCHER.match(uri);
    List<String> prependArgs = new ArrayList<String>();
    // handle MEDIA_SCANNER before calling getDatabaseForUri()
    if (table == MEDIA_SCANNER) {
        if (mMediaScannerVolume == null) {
            return null;
        } else {
            // create a cursor to return volume currently being scanned by the media scanner
            MatrixCursor c = new MatrixCursor(
                new String[] {MediaStore.MEDIA_SCANNER_VOLUME});
            c.addRow(new String[] {mMediaScannerVolume});
            //直接返回的是有关存储卷的cursor
            return c;
        }
    }
    // Used temporarily (until we have unique media IDs) to get an identifier
    // for the current sd card, so that the music app doesn‘t have to use the
    // non-public getFatVolumeId method
    if (table == FS_ID) {
        MatrixCursor c = new MatrixCursor(new String[] {"fsid"});
        c.addRow(new Integer[] {mVolumeId});
        return c;
    }
    if (table == VERSION) {
        MatrixCursor c = new MatrixCursor(new String[] {"version"});
        c.addRow(new Integer[] {getDatabaseVersion(getContext())});
        return c;
    }
    //初始化DatabaseHelper和SQLiteDatabase
    String groupBy = null;
    DatabaseHelper helper = getDatabaseForUri(uri);
    if (helper == null) {
        return null;
    }
    helper.mNumQueries++;
    SQLiteDatabase db = null;
    try {
        db = helper.getReadableDatabase();
    } catch (Exception e) {
        e.printStackTrace();
        return null;
    }
    if (db == null) return null;
    // SQLiteQueryBuilder类是组成查询语句的帮助类
    SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
    //获取uri里面的查询字符
    String limit = uri.getQueryParameter("limit");
    String filter = uri.getQueryParameter("filter");
    String [] keywords = null;
    if (filter != null) {
        filter = Uri.decode(filter).trim();
        if (!TextUtils.isEmpty(filter)) {
            //对字符进行筛选
            String [] searchWords = filter.split(" ");
            keywords = new String[searchWords.length];
            for (int i = 0; i < searchWords.length; i++) {
                String key = MediaStore.Audio.keyFor(searchWords[i]);
                key = key.replace("\\", "\\\\");
                key = key.replace("%", "\\%");
                key = key.replace("_", "\\_");
                keywords[i] = key;
            }
        }
    }
    if (uri.getQueryParameter("distinct") != null) {
        qb.setDistinct(true);
    }
    boolean hasThumbnailId = false;
    //对匹配的其他类型进行设置查询语句的操作
    switch (table) {
        case IMAGES_MEDIA:
                //设置查询的表是images
                qb.setTables("images");
                if (uri.getQueryParameter("distinct") != null)
                    //设置为唯一的
                    qb.setDistinct(true);
                break;
         //其他类型相类似
         ... ...
    }
       //根据拼装的搜索条件,进行查询
       Cursor c = qb.query(db, projectionIn, selection,
                combine(prependArgs, selectionArgs), groupBy, null, sort, limit);

        if (c != null) {
            String nonotify = uri.getQueryParameter("nonotify");
            if (nonotify == null || !nonotify.equals("1")) {
                //通知更新数据库
                c.setNotificationUri(getContext().getContentResolver(), uri);
            }
        }
        return c;
    }

??至此,关于MediaProvider的增删改查,创建数据库等操作的分析已经完成。

时间: 2024-10-13 23:25:40

Media Data之多媒体数据库(二)MediaProvider的相关文章

android 多媒体数据库(非原创)

首先给大家讲android的多媒体数据库.MediaStore这个类是android系统提供的一个多媒体数据库,android中多媒体信息都可以从这里提取.这个MediaStore包括了多媒体数据库的所有信息,包括音频 ,视频和图像,android把所有的多媒体数据库接口进行了封装,所有的数据库不用自己进行创建,直接调用利用ContentResolver去掉用那些封装好的接口就可以进行数据库的操作了.今天我就介绍一些 这些接口的用法. 首先,要得到一个ContentResolver实例,Cont

Android多媒体数据库之MediaStore研究

应网友要求,今天给大家讲android的多媒体数据库.MediaStore这个类是android系统提供的一个多媒体数据库,android 中多媒体信息都可以从这里提取.这个MediaStore包括了多媒体数据库的所有信息,包括音频,视频和图像,android把所有的多媒体数据库接口 进行了封装,所有的数据库不用自己进行创建,直接调用利用ContentResolver去掉用那些封装好的接口就可以进行数据库的操作了.今天我就介绍 一些这些接口的用法. 首先,要得到一个ContentResolver

MySQL学习笔记_12_Linux下C++/C连接MySQL数据库(二) --返回数据的SQL

 Linux下C++/C连接MySQL数据库(二) --返回数据的SQL 引: 返回数据的SQL是指通过查询语句从数据库中取出满足条件的数据记录 从MySQL数据库值哦功能检索数据有4个步骤: 1)发出查询 2)检索数据 3)处理数据 4)整理所需要的数据 用mysql_query()发出查询,检索数据可以使用mysql_store_result()或mysql_use_result(),取决与怎样检索数据,接着是调用mysql_fetch_row()来处理数据,最后,还必须调用mysql_

mysql通过data文件恢复数据库的方式

1.首先定位mysql的my.ini配置文件,查找datadir的位置 #Path to the database rootdatadir="C:/ProgramData/MySQL/MySQL Server 5.5/Data/" 一般情况下,该文件夹是隐藏,需要显示所有文件. 2.将data目录下的文件拷贝至datadir.重启启动数据库即可 net start mysql mysql通过data文件恢复数据库的方式,布布扣,bubuko.com

JAVA使用JDBC连接MySQL数据库 二(2)

本文是对 <JAVA使用JDBC连接MySQL数据库 二>的改进. 上节使用的是PreparedStatement来执行数据库语句,但是preparedStatement需要传递一个sql语句参数,才能创建.然而,DBHelper类只是起到打开和关闭数据库的作用,所以sql语句是要放到应用层部分的,而不是放到DBHelper类中. 而statment不需要传递一个sql语句参数,就能创建. 修改部分如下: public class DBHelper { String driver = &quo

微信上传素材返回 &#39;{&quot;errcode&quot;:41005,&quot;errmsg&quot;:&quot;media data missing&quot;}&#39;,php5.6返回

问题描述: php5.5已经把通过@加文件路径上传文件的方式给放入到Deprecated中了.php5.6默认是不支持这种方式了 解决办法curl处理 function curl_post($url, $data, $header = array()){ if(function_exists('curl_init')) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); if(is_array($header) && !emp

mysql通过data目录恢复数据库

mysql通过data目录恢复数据库 阅读:1236次   时间:2010-03-24 06:53:30   字体:[大 中 小] 重装系统后,MySQL服务没有了,但是数据库的文件还在,这个时候我想恢复以前的数据库, 起码要把数据导出来. MySQL重装N次,永远提示Could not start service error:0! 后来终于找到一个方儿,就是先把以前的库文件都拷贝出来,把以前的MySQL文 件全部清除,然后装个新的,这个时候MySQl可以正常启动了,然后在新的MySQL里建一个

微信公众号开发---上传临时素材到公众号遇到的问题:&quot;errcode&quot;:41005,&quot;errmsg&quot;:&quot;media data missing

1.上传临时素材到公众号遇到的问题:"errcode":41005,"errmsg":"media data missing 解决办法:因为php版本的原因,上传素材一直保错.php的curl的curl_setopt 函数存在版本差异 php5.5已经把通过@加文件路径上传文件的方式给放入到Deprecated中了.php5.6默认是不支持这种方式了 特殊处理1:curl_setopt ( $ch, CURLOPT_SAFE_UPLOAD, false);

论文阅读 | CrystalBall: A Visual Analytic System for Future Event Discovery and Analysis from Social Media Data

CrystalBall: A Visual Analytic System for Future Event Discovery and Analysis from Social Media Data 论文地址 1 Abstract 这是一个可以预测未来一段时间内所发生事情的可视化系统,使用的是twitter数据进行分析. 2 Introduction 识别一个未来的事件并不可以依靠消息量的突增来判断(一般一个很大的事件的到来会引发交际圈的热烈讨论),一个未来时间的发生并不一定可以激发消息量的增