解析DownloadManager源碼

今天來說說DownloadManager的功能,看類名就知道是一個(gè)下載管理器踩官,處理下載操作的鳍刷,那么這個(gè)怎么用呢租漂?
一般來說搭配BroadcastReceiver 來使用,因?yàn)橄螺d管理器在下載完成的時(shí)候是會(huì)發(fā)送廣播的喊儡,而且在通知欄也可以設(shè)置是否顯示下載的通知拨与,也可以有點(diǎn)擊事件,這在DownloadManager里面有對(duì)應(yīng)的常量艾猜;下載管理器那肯定是下載的买喧,那么我想知道我下載的文件大小呢?下載的進(jìn)度呢匆赃?別急淤毛,首先來說說源碼,以下基本都包括了 也標(biāo)有注釋

/**
 * 下載管理器是一個(gè)處理長(zhǎng)時(shí)間HTTP下載的系統(tǒng)服務(wù)算柳〉偷客戶端可以請(qǐng)求將URI下載到特定的目標(biāo)文件。下載管理器將在 
 * 后臺(tái)進(jìn)行下載瞬项,處理HTTP交互查牌,并在失敗后或連接更改和系統(tǒng)重新啟動(dòng)時(shí)重試下載。
 * 通過此API請(qǐng)求下載的應(yīng)用程序應(yīng)為ACTION_NOTIFICATION_LICKED注冊(cè)廣播接收器滥壕,以便在用戶在通知中或從下 
 * 載UI中單擊正在運(yùn)行的下載時(shí)進(jìn)行適當(dāng)處理纸颜。
 * 必須具有android.Manifest.permission.INTERNET權(quán)限才能使用此類。
 */
@SystemService(Context.DOWNLOAD_SERVICE)
public class DownloadManager {

    /**
     * 特定下載的標(biāo)識(shí)符绎橘,在整個(gè)系統(tǒng)中是唯一的胁孙∵刖耄客戶端使用此ID進(jìn)行與下載相關(guān)的后續(xù)操作
     */
    public final static String COLUMN_ID = Downloads.Impl._ID;
    /**
     * 顯示在系統(tǒng)通知中的標(biāo)題
     */
    public final static String COLUMN_TITLE = Downloads.Impl.COLUMN_TITLE;

     /**
     * 下載的URI
     */
    public final static String COLUMN_URI = Downloads.Impl.COLUMN_URI;

    /**
     * 下載文件的internet媒體類型
     */
    public final static String COLUMN_MEDIA_TYPE = "media_type";

    /**
     * 下載的總大小(字節(jié))涮较。最初是-1
     */
    public final static String COLUMN_TOTAL_SIZE_BYTES = "total_size";

    /**
     * 存儲(chǔ)下載文件的Uri稠鼻。如果提供了目的地,使用該URI狂票。否則默認(rèn)為空候齿,下載開始 
     * 之后使用生成的URI
     */
    public final static String COLUMN_LOCAL_URI = "local_uri";

    /**
     * 下載的當(dāng)前狀態(tài)
     */
    public final static String COLUMN_STATUS = Downloads.Impl.COLUMN_STATUS;

    /**
     * 提供下載狀態(tài)的詳細(xì)信息。取決于COLUMN_STATUS的值闺属。當(dāng)COLUMN_STATUS  為    
     * STATUS_FAILED時(shí)慌盯,表示發(fā)生的錯(cuò)誤類型。如果發(fā)生HTTP錯(cuò)誤掂器,這將保存RFC 2616中定義的HTTP狀態(tài)代    
     * 碼亚皂。否則,它將保存ERROR_*常數(shù)之一国瓮。當(dāng)COLUMN_STATUS為STATUS_PAUSED時(shí)灭必,表示下載暫停的原 
     * 因。保存PAUSED_*常數(shù)之一乃摹。如果COLUMN_STATUS既不是STATUS_FAILED也不是STATUS_PAUSED禁漓,則  
     * 表示此列的值未定。
     * RFC 2616 查看  https://www.rfc-editor.org/rfc/rfc9110.html
     */
    public final static String COLUMN_REASON = "reason";

    /**
     * @link COLUMN_STATUS 下載狀態(tài)  開始時(shí)
     */
    public final static int STATUS_PENDING = 1 << 0;

    /**
     * @link COLUMN_STATUS 下載正在運(yùn)行時(shí)
     */
    public final static int STATUS_RUNNING = 1 << 1;

    /**
     * @link COLUMN_STATUS} 下載等待重試或者恢復(fù)時(shí)
     */
    public final static int STATUS_PAUSED = 1 << 2;

    /**
     * @link COLUMN_STATUS 下載成功完成
     */
    public final static int STATUS_SUCCESSFUL = 1 << 3;

    /**
     * @link COLUMN_STATUS 下載失敗 (不會(huì)重試)
     */
    public final static int STATUS_FAILED = 1 << 4;

    /**
     * 下載錯(cuò)誤  不符合其他錯(cuò)誤的時(shí)候
     */
    public final static int ERROR_UNKNOWN = 1000;
  
    // 下面是一些下載錯(cuò)誤的狀態(tài)
    ...

    /**
     * @link COLUMN_REASON} 由網(wǎng)絡(luò)錯(cuò)誤而暫停下載時(shí)  COLUMN_REASON的值  重試下載之前 正在等待開始
     */
    public final static int PAUSED_WAITING_TO_RETRY = 1;

    /**
     * @link COLUMN_REASON 下載正在等待網(wǎng)絡(luò)連接進(jìn)行時(shí)
     */
    public final static int PAUSED_WAITING_FOR_NETWORK = 2;

    /**
     * @link COLUMN_REASON 當(dāng)下載超過移動(dòng)網(wǎng)絡(luò)下載的大小限制 并且下載正在等待Wi-Fi連接繼續(xù)時(shí)
     */
    public final static int PAUSED_QUEUED_FOR_WIFI = 3;

    /**
     * @link COLUMN_REASON 下載由于某些其他原因而暫停時(shí)
     */
    public final static int PAUSED_UNKNOWN = 4;

    /**
     * 下載完成時(shí)孵睬,發(fā)送廣播事件
     */
    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
    public final static String ACTION_DOWNLOAD_COMPLETE = "android.intent.action.DOWNLOAD_COMPLETE";

    /**
     * 當(dāng)用戶點(diǎn)擊從下拉系統(tǒng)通知UI時(shí)璃饱,發(fā)送廣播事件。
     */
    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
    public final static String ACTION_NOTIFICATION_CLICKED =
            "android.intent.action.DOWNLOAD_NOTIFICATION_CLICKED";

    // 省略代碼
    ...

    /**
     * 此類包含請(qǐng)求新下載所需的所有信息肪康。URI是必傳的參數(shù)荚恶。默認(rèn)下載目標(biāo)是可以共享的,如果系統(tǒng)需要回收空間  
     * 的話磷支,系統(tǒng)會(huì)在其中刪除文件谒撼。如果覺得這個(gè)有問題,請(qǐng)用外部存儲(chǔ)雾狈±保看這個(gè) @link setDestinationUri(Uri)
     */
    public static class Request {
        /**
         * 移動(dòng)
         */
        public static final int NETWORK_MOBILE = 1 << 0;

        /**
         * WIFI
         */
        public static final int NETWORK_WIFI = 1 << 1;

        @UnsupportedAppUsage
        private Uri mUri;
        private Uri mDestinationUri;
        private List<Pair<String, String>> mRequestHeaders = new ArrayList<Pair<String, String>>();
        private CharSequence mTitle;
        private CharSequence mDescription;
        private String mMimeType;
        // 默認(rèn)為允許的所有網(wǎng)絡(luò)類型
        private int mAllowedNetworkTypes = ~0; 
        private boolean mRoamingAllowed = true;
        private boolean mMeteredAllowed = true;
        private int mFlags = 0;
        private boolean mIsVisibleInDownloadsUi = true;
        private boolean mScannable = false;
        /** if a file is designated as a MediaScanner scannable file, the following value is
         * stored in the database column {@link Downloads.Impl#COLUMN_MEDIA_SCANNED}.
         */
        private static final int SCANNABLE_VALUE_YES = Downloads.Impl.MEDIA_NOT_SCANNED;
        // value of 1 is stored in the above column by DownloadProvider after it is scanned by
        // MediaScanner
        /** if a file is designated as a file that should not be scanned by MediaScanner,
         * the following value is stored in the database column
         * {@link Downloads.Impl#COLUMN_MEDIA_SCANNED}.
         */
        private static final int SCANNABLE_VALUE_NO = Downloads.Impl.MEDIA_NOT_SCANNABLE;

        /**
         * 下載可見
         */
        public static final int VISIBILITY_VISIBLE = 0;

        /**
         * 下載可見,進(jìn)行中和完成后都顯示
         */
        public static final int VISIBILITY_VISIBLE_NOTIFY_COMPLETED = 1;

        /**
         * 下載隱藏 不可見
         */
        public static final int VISIBILITY_HIDDEN = 2;

        /**
         * 下載只在完成后的通知中顯示
         * {@link DownloadManager#addCompletedDownload(String, String,
         * boolean, String, String, long, boolean)}.
         */
        public static final int VISIBILITY_VISIBLE_NOTIFY_ONLY_COMPLETION = 3;

        /**
         * 通知默認(rèn)是可見的
         */
        private int mNotificationVisibility = VISIBILITY_VISIBLE;

        /**
         * @param uri 下載的uri
         */
        public Request(Uri uri) {
            if (uri == null) {
                throw new NullPointerException();
            }
            String scheme = uri.getScheme();
            if (scheme == null || (!scheme.equals("http") && !scheme.equals("https"))) {
                throw new IllegalArgumentException("Can only download HTTP/HTTPS URIs: " + uri);
            }
            mUri = uri;
        }

        Request(String uriString) {
            mUri = Uri.parse(uriString);
        }

        /**
         * 設(shè)置下載文件的本地目標(biāo)善榛。必須得是指向外部存儲(chǔ)上路徑的文件URI辩蛋,并且調(diào)用應(yīng)用程序必須具有    
         * WRITE_EXTERNAL_STORAGE權(quán)限。
         * 默認(rèn)下載保存到共享下載緩存中生成的文件名中移盆,系統(tǒng)可以隨時(shí)刪除下載回收空間悼院。
         * 對(duì)于Q或更高版本,不需要WRITE EXTERNAL_STORAGE權(quán)限咒循,并且uri必須引用應(yīng)用程序擁有的目錄內(nèi)的路徑(比    
         * 如Context.getExternalFilesDir() )或頂級(jí)下載目錄內(nèi)的路徑(Environment.getExternalStoragePublicDirectory()              
         * 和Environment.directory_Downloads返回)据途。
         * 參數(shù):uri–下載文件目標(biāo)的文件uri
         */
        public Request setDestinationUri(Uri uri) {
            mDestinationUri = uri;
            return this;
        }

        /**
         * 下載文件的本地目標(biāo)設(shè)置為應(yīng)用程序外部文件目錄 @link Context#getExternalFilesDir(String)
         * 
         * @param context 
         * @param dirType 傳遞給Context.getExternalFilesDir(String))的目錄類型
         * @param subPath 外部目錄中的路徑绞愚,包括文件名
         */
        public Request setDestinationInExternalFilesDir(Context context, String dirType,
                String subPath) {
            final File file = context.getExternalFilesDir(dirType);
            if (file == null) {
                throw new IllegalStateException("Failed to get external storage files directory");
            } else if (file.exists()) {
                if (!file.isDirectory()) {
                    throw new IllegalStateException(file.getAbsolutePath() +
                            " already exists and is not a directory");
                }
            } else {
                if (!file.mkdirs()) {
                    throw new IllegalStateException("Unable to create directory: "+
                            file.getAbsolutePath());
                }
            }
            setDestinationFromBase(file, subPath);
            return this;
        }

        /**
         * 下載文件的本地目標(biāo)設(shè)置為應(yīng)用程序外部文件目錄 @link Context#getExternalStoragePublicDirectory(String)
         * @param dirType 傳遞給Context.getExternalStoragePublicDirectory(String))的目錄類型
         * @param subPath 外部目錄中的路徑,包括文件名
         */
        public Request setDestinationInExternalPublicDir(String dirType, String subPath) {
            File file = Environment.getExternalStoragePublicDirectory(dirType);
            if (file == null) {
                throw new IllegalStateException("Failed to get external storage public directory");
            }

            final Context context = AppGlobals.getInitialApplication();
            if (context.getApplicationInfo().targetSdkVersion
                    >= Build.VERSION_CODES.Q || !Environment.isExternalStorageLegacy()) {
                try (ContentProviderClient client = context.getContentResolver()
                        .acquireContentProviderClient(Downloads.Impl.AUTHORITY)) {
                    final Bundle extras = new Bundle();
                    extras.putString(Downloads.DIR_TYPE, dirType);
                    client.call(Downloads.CALL_CREATE_EXTERNAL_PUBLIC_DIR, null, extras);
                } catch (RemoteException e) {
                    throw new IllegalStateException("Unable to create directory: "
                            + file.getAbsolutePath());
                }
            } else {
                if (file.exists()) {
                    if (!file.isDirectory()) {
                        throw new IllegalStateException(file.getAbsolutePath()
                                + " already exists and is not a directory");
                    }
                } else if (!file.mkdirs()) {
                    throw new IllegalStateException("Unable to create directory: "
                            + file.getAbsolutePath());
                }
            }
            setDestinationFromBase(file, subPath);
            return this;
        }

        private void setDestinationFromBase(File base, String subPath) {
            if (subPath == null) {
                throw new NullPointerException("subPath cannot be null");
            }
            // 通過已編碼的路徑段 創(chuàng)建新的Uri颖医。
            mDestinationUri = Uri.withAppendedPath(Uri.fromFile(base), subPath);
        }

        /**
         * 添加請(qǐng)求頭
         */
        public Request addRequestHeader(String header, String value) {
            if (header == null) {
                throw new NullPointerException("header cannot be null");
            }
            if (header.contains(":")) {
                throw new IllegalArgumentException("header may not contain ':'");
            }
            if (value == null) {
                value = "";
            }
            mRequestHeaders.add(Pair.create(header, value));
            return this;
        }

        /**
         * 設(shè)置標(biāo)題
         */
        public Request setTitle(CharSequence title) {
            mTitle = title;
            return this;
        }

        /**
         * 描述
         */
        public Request setDescription(CharSequence description) {
            mDescription = description;
            return this;
        }

        /**
         * 類型
         */
        public Request setMimeType(String mimeType) {
            mMimeType = mimeType;
            return this;
        }

        /**
         * 設(shè)置通知是否顯示
         */
        public Request setNotificationVisibility(int visibility) {
            mNotificationVisibility = visibility;
            return this;
        }

        /**
         * 設(shè)置下載的網(wǎng)絡(luò)類型
         * 一般把兩種 都設(shè)置上 第三種過時(shí)了  無所謂
         */
        public Request setAllowedNetworkTypes(int flags) {
            mAllowedNetworkTypes = flags;
            return this;
        }

       ...
    }

    /**
     * 查詢
     */
    public static class Query {
        /**
         * 查詢排序  升序
         */
        public static final int ORDER_ASCENDING = 1;

        /**
         * 查詢排序  倒序
         */
        public static final int ORDER_DESCENDING = 2;

        private long[] mIds = null;
        private Integer mStatusFlags = null;
        private String mFilterString = null;
        private String mOrderByColumn = Downloads.Impl.COLUMN_LAST_MODIFICATION;
        private int mOrderDirection = ORDER_DESCENDING;
        private boolean mOnlyIncludeVisibleInDownloadsUi = false;

        /**
         * 設(shè)置條件id
         */
        public Query setFilterById(long... ids) {
            mIds = ids;
            return this;
        }

        /**
         * 設(shè)置條件 字符串
         */
        public Query setFilterByString(@Nullable String filter) {
            mFilterString = filter;
            return this;
        }

        /**
         * 設(shè)置條件 狀態(tài)
         * @link STATUS_PENDING  STATUS_RUNNING STATUS_PAUSED  STATUS_SUCCESSFUL  STATUS_FAILED
         */
        public Query setFilterByStatus(int flags) {
            mStatusFlags = flags;
            return this;
        }

        /**
         * 查詢是否包含在系統(tǒng)的下載UI中不可見的下載 默認(rèn)是false  全部查詢
         * 如果為true位衩,則查詢將僅包括應(yīng)在系統(tǒng)的下載UI中顯示的下載
         */
        @UnsupportedAppUsage
        public Query setOnlyIncludeVisibleInDownloadsUi(boolean value) {
            mOnlyIncludeVisibleInDownloadsUi = value;
            return this;
        }

        /**
         * 返回游標(biāo)的排序順序
         * @param column  @link COLUMN_LAST_MODIFIED_TIMESTAMP  @link COLUMN_TOTAL_SIZE_BYTES 
         * @param direction @link ORDER_ASCENDING or @link ORDER_DESCENDING
         */
        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
        public Query orderBy(String column, int direction) {
            if (direction != ORDER_ASCENDING && direction != ORDER_DESCENDING) {
                throw new IllegalArgumentException("Invalid direction: " + direction);
            }

            if (column.equals(COLUMN_LAST_MODIFIED_TIMESTAMP)) {
                mOrderByColumn = Downloads.Impl.COLUMN_LAST_MODIFICATION;
            } else if (column.equals(COLUMN_TOTAL_SIZE_BYTES)) {
                mOrderByColumn = Downloads.Impl.COLUMN_TOTAL_BYTES;
            } else {
                throw new IllegalArgumentException("Cannot order by " + column);
            }
            mOrderDirection = direction;
            return this;
        }

      private final ContentResolver mResolver;
      private final String mPackageName;

      private Uri mBaseUri = Downloads.Impl.CONTENT_URI;
      private boolean mAccessFilename;
    
      public DownloadManager(Context context) {
        mResolver = context.getContentResolver();
        mPackageName = context.getPackageName();

        // Callers can access filename columns when targeting old platform
        // versions; otherwise we throw telling them it's deprecated.
        mAccessFilename = context.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N;
    }

    /**
     * Makes this object access the download provider through /all_downloads URIs rather than
     * /my_downloads URIs, for clients that have permission to do so.
     * @hide
     */
    @UnsupportedAppUsage
    public void setAccessAllDownloads(boolean accessAllDownloads) {
        if (accessAllDownloads) {
            mBaseUri = Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI;
        } else {
            mBaseUri = Downloads.Impl.CONTENT_URI;
        }
    }

    /** {@hide} */
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
    public void setAccessFilename(boolean accessFilename) {
        mAccessFilename = accessFilename;
    }

    /**
     * Notify {@link DownloadManager} that the given {@link MediaStore} items
     * were just deleted so that {@link DownloadManager} internal data
     * structures can be cleaned up.
     *
     * @param idToMime map from {@link BaseColumns#_ID} to
     *            {@link ContentResolver#getType(Uri)}.
     * @hide
     */
    @SystemApi
    @RequiresPermission(android.Manifest.permission.WRITE_MEDIA_STORAGE)
    public void onMediaStoreDownloadsDeleted(@NonNull LongSparseArray<String> idToMime) {
        try (ContentProviderClient client = mResolver
                .acquireUnstableContentProviderClient(mBaseUri)) {
           final Bundle callExtras = new Bundle();
           final long[] ids = new long[idToMime.size()];
           final String[] mimeTypes = new String[idToMime.size()];
           for (int i = idToMime.size() - 1; i >= 0; --i) {
               ids[i] = idToMime.keyAt(i);
               mimeTypes[i] = idToMime.valueAt(i);
           }
           callExtras.putLongArray(android.provider.Downloads.EXTRA_IDS, ids);
           callExtras.putStringArray(android.provider.Downloads.EXTRA_MIME_TYPES,
                   mimeTypes);
           client.call(android.provider.Downloads.CALL_MEDIASTORE_DOWNLOADS_DELETED,
                   null, callExtras);
        } catch (RemoteException e) {
            // Should not happen
        }
    }

    /**
     *  加入下載隊(duì)列,一旦下載管理器準(zhǔn)備好執(zhí)行下載并且連接可用熔萧,下載將自動(dòng)開始
     *
     * @param request 下載的請(qǐng)求
     * @return  返回下載的id  唯一的id 
     */
    public long enqueue(Request request) {
        ContentValues values = request.toContentValues(mPackageName);
        Uri downloadUri = mResolver.insert(Downloads.Impl.CONTENT_URI, values);
        long id = Long.parseLong(downloadUri.getLastPathSegment());
        return id;
    }

    /**
     * Marks the specified download as 'to be deleted'. This is done when a completed download
     * is to be removed but the row was stored without enough info to delete the corresponding
     * metadata from Mediaprovider database. Actual cleanup of this row is done in DownloadService.
     *
     * @param ids the IDs of the downloads to be marked 'deleted'
     * @return the number of downloads actually updated
     * @hide
     */
    public int markRowDeleted(long... ids) {
        if (ids == null || ids.length == 0) {
            // called with nothing to remove!
            throw new IllegalArgumentException("input param 'ids' can't be null");
        }
        return mResolver.delete(mBaseUri, getWhereClauseForIds(ids), getWhereArgsForIds(ids));
    }

    /**
     * 取消下載并將從下載管理器中刪除糖驴。如果每次下載都在運(yùn)行,將停止佛致,并且不再可以通過下載管理器訪問贮缕。如果有下載的部分  
     * 或完整文件,則會(huì)將其刪除
     * @param ids 要?jiǎng)h除下載的ID
     * @return 返回實(shí)際刪除的下載數(shù)
     */
    public int remove(long... ids) {
        return markRowDeleted(ids);
    }

    /**
     * 向下載管理器中 查詢已請(qǐng)求的下載晌杰。
     */
    public Cursor query(Query query) {
        return query(query, UNDERLYING_COLUMNS);
    }

    /** @hide */
    public Cursor query(Query query, String[] projection) {
        Cursor underlyingCursor = query.runQuery(mResolver, projection, mBaseUri);
        if (underlyingCursor == null) {
            return null;
        }
        return new CursorTranslator(underlyingCursor, mBaseUri, mAccessFilename);
    }

    /**
     * 打開下載的文件進(jìn)行閱讀。下載必須已完成
     *  @param id 下載的id 
     */
    public ParcelFileDescriptor openDownloadedFile(long id) throws FileNotFoundException {
        return mResolver.openFileDescriptor(getDownloadUri(id), "r");
    }

    /**
     * 如果文件下載成功筷弦,返回下載文件id的Uri肋演。否則返回null。
     * @param id–下載文件的id烂琴。
     */
    public Uri getUriForDownloadedFile(long id) {
        // to check if the file is in cache, get its destination from the database
        Query query = new Query().setFilterById(id);
        Cursor cursor = null;
        try {
            cursor = query(query);
            if (cursor == null) {
                return null;
            }
            if (cursor.moveToFirst()) {
                int status = cursor.getInt(cursor.getColumnIndexOrThrow(COLUMN_STATUS));
                if (DownloadManager.STATUS_SUCCESSFUL == status) {
                    return ContentUris.withAppendedId(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, id);
                }
            }
        } finally {
            if (cursor != null) {
                cursor.close();
            }
        }
        // downloaded file not found or its status is not 'successfully completed'
        return null;
    }

    /**
     * 如果文件下載成功爹殊,返回下載文件id的媒體類型。否則返回null奸绷。
     */
    public String getMimeTypeForDownloadedFile(long id) {
        Query query = new Query().setFilterById(id);
        Cursor cursor = null;
        try {
            cursor = query(query);
            if (cursor == null) {
                return null;
            }
            while (cursor.moveToFirst()) {
                return cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_MEDIA_TYPE));
            }
        } finally {
            if (cursor != null) {
                cursor.close();
            }
        }
        // downloaded file not found or its status is not 'successfully completed'
        return null;
    }

    /**
     * 重新啟動(dòng)下載
     */
    @UnsupportedAppUsage
    public void restartDownload(long... ids) {
        Cursor cursor = query(new Query().setFilterById(ids));
        try {
            for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
                int status = cursor.getInt(cursor.getColumnIndex(COLUMN_STATUS));
                if (status != STATUS_SUCCESSFUL && status != STATUS_FAILED) {
                    throw new IllegalArgumentException("Cannot restart incomplete download: "
                            + cursor.getLong(cursor.getColumnIndex(COLUMN_ID)));
                }
            }
        } finally {
            cursor.close();
        }

        ContentValues values = new ContentValues();
        values.put(Downloads.Impl.COLUMN_CURRENT_BYTES, 0);
        values.put(Downloads.Impl.COLUMN_TOTAL_BYTES, -1);
        values.putNull(Downloads.Impl._DATA);
        values.put(Downloads.Impl.COLUMN_STATUS, Downloads.Impl.STATUS_PENDING);
        values.put(Downloads.Impl.COLUMN_FAILED_CONNECTIONS, 0);
        mResolver.update(mBaseUri, values, getWhereClauseForIds(ids), getWhereArgsForIds(ids));
    }

    /**
     * 即使下載的大小 大于getMaxBytesOverMobile梗夸,也強(qiáng)制下載繼續(xù)。
     */
    public void forceDownload(long... ids) {
        ContentValues values = new ContentValues();
        values.put(Downloads.Impl.COLUMN_STATUS, Downloads.Impl.STATUS_PENDING);
        values.put(Downloads.Impl.COLUMN_CONTROL, Downloads.Impl.CONTROL_RUN);
        values.put(Downloads.Impl.COLUMN_BYPASS_RECOMMENDED_SIZE_LIMIT, 1);
        mResolver.update(mBaseUri, values, getWhereClauseForIds(ids), getWhereArgsForIds(ids));
    }

    /**
     * 如果下載已完成号醉,則重命名
     */
    public boolean rename(Context context, long id, String displayName) {
        if (!FileUtils.isValidFatFilename(displayName)) {
            throw new SecurityException(displayName + " is not a valid filename");
        }

        final String filePath;
        final Query query = new Query().setFilterById(id);
        try (Cursor cursor = query(query)) {
            if (cursor == null) {
                throw new IllegalStateException("Missing cursor for download id=" + id);
            }
            if (cursor.moveToFirst()) {
                final int status = cursor.getInt(cursor.getColumnIndexOrThrow(COLUMN_STATUS));
                if (status != DownloadManager.STATUS_SUCCESSFUL) {
                    throw new IllegalStateException("Download is not completed yet: "
                            + DatabaseUtils.dumpCurrentRowToString(cursor));
                }
                filePath = cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_LOCAL_FILENAME));
                if (filePath == null) {
                    throw new IllegalStateException("Download doesn't have a valid file path: "
                            + DatabaseUtils.dumpCurrentRowToString(cursor));
                } else if (!new File(filePath).exists()) {
                    throw new IllegalStateException("Downloaded file doesn't exist anymore: "
                            + DatabaseUtils.dumpCurrentRowToString(cursor));
                }
            } else {
                throw new IllegalStateException("Missing download id=" + id);
            }
        }

        final File before = new File(filePath);
        final File after = new File(before.getParentFile(), displayName);

        if (after.exists()) {
            throw new IllegalStateException("File already exists: " + after);
        }
        if (!before.renameTo(after)) {
            throw new IllegalStateException(
                    "Failed to rename file from " + before + " to " + after);
        }

        // TODO: DownloadProvider.update() should take care of updating corresponding
        // MediaProvider entries.
        MediaStore.scanFile(mResolver, before);
        MediaStore.scanFile(mResolver, after);

        final ContentValues values = new ContentValues();
        values.put(Downloads.Impl.COLUMN_TITLE, displayName);
        values.put(Downloads.Impl._DATA, after.toString());
        values.putNull(Downloads.Impl.COLUMN_MEDIAPROVIDER_URI);
        final long[] ids = { id };

        return mResolver.update(
                mBaseUri, values, getWhereClauseForIds(ids), getWhereArgsForIds(ids)) == 1;
    }

   ...

    /**
     * 添加文件到下載數(shù)據(jù)庫(kù)系統(tǒng)中
     * Q或更高版本反症,路徑必須位于所擁有的目錄{例如Context.getExternalFilesDir(String)},或者如果應(yīng)用程序在舊存儲(chǔ)模型下  
     * 運(yùn)行(請(qǐng)參見android:requestLegacyExternalStorage)畔派,路徑也可以位于頂級(jí)下載目錄中(由 
     * Environment.getExternalStoragePublicDirectory(String)和 Environment.directory_Downloads返回)
     * {@link Environment#getExternalStoragePublicDirectory(String)} with
     * {@link Environment#DIRECTORY_DOWNLOADS}).
     * 返回id
     */
    @Deprecated
    public long addCompletedDownload(String title, String description,
            boolean isMediaScannerScannable, String mimeType, String path, long length,
            boolean showNotification) {
        return addCompletedDownload(title, description, isMediaScannerScannable, mimeType, path,
                length, showNotification, false, null, null);
    }

    @Deprecated
    public long addCompletedDownload(String title, String description,
            boolean isMediaScannerScannable, String mimeType, String path, long length,
            boolean showNotification, Uri uri, Uri referer) {
        return addCompletedDownload(title, description, isMediaScannerScannable, mimeType, path,
                length, showNotification, false, uri, referer);
    }

    @Deprecated
    public long addCompletedDownload(String title, String description,
            boolean isMediaScannerScannable, String mimeType, String path, long length,
            boolean showNotification, boolean allowWrite) {
        return addCompletedDownload(title, description, isMediaScannerScannable, mimeType, path,
                length, showNotification, allowWrite, null, null);
    }

    @Deprecated
    public long addCompletedDownload(String title, String description,
            boolean isMediaScannerScannable, String mimeType, String path, long length,
            boolean showNotification, boolean allowWrite, Uri uri, Uri referer) {
        // make sure the input args are non-null/non-zero
        validateArgumentIsNonEmpty("title", title);
        validateArgumentIsNonEmpty("description", description);
        validateArgumentIsNonEmpty("path", path);
        validateArgumentIsNonEmpty("mimeType", mimeType);
        if (length < 0) {
            throw new IllegalArgumentException(" invalid value for param: totalBytes");
        }

        // if there is already an entry with the given path name in downloads.db, return its id
        Request request;
        if (uri != null) {
            request = new Request(uri);
        } else {
            request = new Request(NON_DOWNLOADMANAGER_DOWNLOAD);
        }
        request.setTitle(title)
                .setDescription(description)
                .setMimeType(mimeType);
        if (referer != null) {
            request.addRequestHeader("Referer", referer.toString());
        }
        ContentValues values = request.toContentValues(null);
        values.put(Downloads.Impl.COLUMN_DESTINATION,
                Downloads.Impl.DESTINATION_NON_DOWNLOADMANAGER_DOWNLOAD);
        values.put(Downloads.Impl._DATA, path);
        values.put(Downloads.Impl.COLUMN_MIME_TYPE, resolveMimeType(new File(path)));
        values.put(Downloads.Impl.COLUMN_STATUS, Downloads.Impl.STATUS_SUCCESS);
        values.put(Downloads.Impl.COLUMN_TOTAL_BYTES, length);
        values.put(Downloads.Impl.COLUMN_MEDIA_SCANNED,
                (isMediaScannerScannable) ? Request.SCANNABLE_VALUE_YES :
                        Request.SCANNABLE_VALUE_NO);
        values.put(Downloads.Impl.COLUMN_VISIBILITY, (showNotification) ?
                Request.VISIBILITY_VISIBLE_NOTIFY_ONLY_COMPLETION : Request.VISIBILITY_HIDDEN);
        values.put(Downloads.Impl.COLUMN_ALLOW_WRITE, allowWrite ? 1 : 0);
        Uri downloadUri = mResolver.insert(Downloads.Impl.CONTENT_URI, values);
        if (downloadUri == null) {
            return -1;
        }
        return Long.parseLong(downloadUri.getLastPathSegment());
    }

   ...

    /**
     *  DownloadProvider返回的游標(biāo)铅碍,并顯示一組不同的列,這些列是在DownloadManager.COLUMN_*常 
     * 量中定義线椰。
     * 主要是封裝了一下  返回的各種狀態(tài)胞谈,以及成功失敗的狀態(tài)碼
     */
    private static class CursorTranslator extends CursorWrapper {
        private final Uri mBaseUri;
        private final boolean mAccessFilename;

        public CursorTranslator(Cursor cursor, Uri baseUri, boolean accessFilename) {
            super(cursor);
            mBaseUri = baseUri;
            mAccessFilename = accessFilename;
        }

        @Override
        public int getInt(int columnIndex) {
            return (int) getLong(columnIndex);
        }

        @Override
        public long getLong(int columnIndex) {
            if (getColumnName(columnIndex).equals(COLUMN_REASON)) {
                return getReason(super.getInt(getColumnIndex(Downloads.Impl.COLUMN_STATUS)));
            } else if (getColumnName(columnIndex).equals(COLUMN_STATUS)) {
                return translateStatus(super.getInt(getColumnIndex(Downloads.Impl.COLUMN_STATUS)));
            } else {
                return super.getLong(columnIndex);
            }
        }

        @Override
        public String getString(int columnIndex) {
            final String columnName = getColumnName(columnIndex);
            switch (columnName) {
                case COLUMN_LOCAL_URI:
                    return getLocalUri();
                case COLUMN_LOCAL_FILENAME:
                    if (!mAccessFilename) {
                        throw new SecurityException(
                                "COLUMN_LOCAL_FILENAME is deprecated;"
                                        + " use ContentResolver.openFileDescriptor() instead");
                    }
                default:
                    return super.getString(columnIndex);
            }
        }

        private String getLocalUri() {
            long destinationType = getLong(getColumnIndex(Downloads.Impl.COLUMN_DESTINATION));
            if (destinationType == Downloads.Impl.DESTINATION_FILE_URI ||
                    destinationType == Downloads.Impl.DESTINATION_EXTERNAL ||
                    destinationType == Downloads.Impl.DESTINATION_NON_DOWNLOADMANAGER_DOWNLOAD) {
                String localPath = super.getString(getColumnIndex(COLUMN_LOCAL_FILENAME));
                if (localPath == null) {
                    return null;
                }
                return Uri.fromFile(new File(localPath)).toString();
            }

            // return content URI for cache download
            long downloadId = getLong(getColumnIndex(Downloads.Impl._ID));
            return ContentUris.withAppendedId(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, downloadId).toString();
        }

        private long getReason(int status) {
            switch (translateStatus(status)) {
                case STATUS_FAILED:
                    return getErrorCode(status);

                case STATUS_PAUSED:
                    return getPausedReason(status);

                default:
                    return 0; // arbitrary value when status is not an error
            }
        }

        private long getPausedReason(int status) {
            switch (status) {
                case Downloads.Impl.STATUS_WAITING_TO_RETRY:
                    return PAUSED_WAITING_TO_RETRY;

                case Downloads.Impl.STATUS_WAITING_FOR_NETWORK:
                    return PAUSED_WAITING_FOR_NETWORK;

                case Downloads.Impl.STATUS_QUEUED_FOR_WIFI:
                    return PAUSED_QUEUED_FOR_WIFI;

                default:
                    return PAUSED_UNKNOWN;
            }
        }

        private long getErrorCode(int status) {
            if ((400 <= status && status < Downloads.Impl.MIN_ARTIFICIAL_ERROR_STATUS)
                    || (500 <= status && status < 600)) {
                // HTTP status code
                return status;
            }

            switch (status) {
                case Downloads.Impl.STATUS_FILE_ERROR:
                    return ERROR_FILE_ERROR;

                case Downloads.Impl.STATUS_UNHANDLED_HTTP_CODE:
                case Downloads.Impl.STATUS_UNHANDLED_REDIRECT:
                    return ERROR_UNHANDLED_HTTP_CODE;

                case Downloads.Impl.STATUS_HTTP_DATA_ERROR:
                    return ERROR_HTTP_DATA_ERROR;

                case Downloads.Impl.STATUS_TOO_MANY_REDIRECTS:
                    return ERROR_TOO_MANY_REDIRECTS;

                case Downloads.Impl.STATUS_INSUFFICIENT_SPACE_ERROR:
                    return ERROR_INSUFFICIENT_SPACE;

                case Downloads.Impl.STATUS_DEVICE_NOT_FOUND_ERROR:
                    return ERROR_DEVICE_NOT_FOUND;

                case Downloads.Impl.STATUS_CANNOT_RESUME:
                    return ERROR_CANNOT_RESUME;

                case Downloads.Impl.STATUS_FILE_ALREADY_EXISTS_ERROR:
                    return ERROR_FILE_ALREADY_EXISTS;

                default:
                    return ERROR_UNKNOWN;
            }
        }

        private int translateStatus(int status) {
            switch (status) {
                case Downloads.Impl.STATUS_PENDING:
                    return STATUS_PENDING;

                case Downloads.Impl.STATUS_RUNNING:
                    return STATUS_RUNNING;

                case Downloads.Impl.STATUS_PAUSED_BY_APP:
                case Downloads.Impl.STATUS_WAITING_TO_RETRY:
                case Downloads.Impl.STATUS_WAITING_FOR_NETWORK:
                case Downloads.Impl.STATUS_QUEUED_FOR_WIFI:
                    return STATUS_PAUSED;

                case Downloads.Impl.STATUS_SUCCESS:
                    return STATUS_SUCCESSFUL;

                default:
                    assert Downloads.Impl.isStatusError(status);
                    return STATUS_FAILED;
            }
        }
    }
}

以上基本就是DownloadManager的源碼了,其實(shí)也沒有太多憨愉,首先構(gòu)建一個(gè)請(qǐng)求烦绳,把要下載的url傳進(jìn)去,然后設(shè)置一些下載請(qǐng)求的參數(shù)配紫,比如通知欄的標(biāo)題径密、描述、網(wǎng)絡(luò)類型躺孝、是否顯示在通知欄等等睹晒;然后通過ContentValues傳遞參數(shù)趟庄,加入下載隊(duì)列,然后開始下載伪很;

                ...
                val request = DownloadManager.Request(Uri.parse(fileMsg.url)) //添加下載文件的網(wǎng)絡(luò)路徑
                request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileMsg.name) //添加保存文件路徑與名稱
                request.setTitle(fileMsg.name) //添加在通知欄里顯示的標(biāo)題
                request.setDescription(StringUtils.getString(R.string.down_loading)) //添加在通知欄里顯示的描述
                request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_MOBILE or DownloadManager.Request.NETWORK_WIFI) //設(shè)置下載的網(wǎng)絡(luò)類型
                request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED) //下載中與下載完成后都會(huì)在通知中顯示| 另外可以選 DownloadManager.Request.VISIBILITY_VISIBLE 僅在下載中時(shí)顯示在通知中,完成后會(huì)自動(dòng)隱藏
                val downloadId = downloadManager.enqueue(request) //加入隊(duì)列戚啥,會(huì)返回一個(gè)唯一下載id
                // 線程池 更好的利用資源
                executorServiceSingle.submit(UpdateProcessTask(downloadManager, downloadId, item))

但是在下載之前,咱們還需要有其他的邏輯操作锉试,咱們不能來一個(gè)就直接下載吧猫十?是不是得判斷當(dāng)前文件的地址是不是下載成功?或者正在下載呆盖?所以咱們得有一段邏輯判斷拖云,下面就開始寫代碼

    /**
     * 廣播接受器, 下載完成監(jiān)聽器
     */
    val receiver: BroadcastReceiver = object : BroadcastReceiver() {
        override fun onReceive(context: Context?, intent: Intent) {
            val action = intent.action
             // 下載完成
            if (action == DownloadManager.ACTION_DOWNLOAD_COMPLETE) {
                //獲取當(dāng)前完成任務(wù)的ID
                val reference = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1)
                // 自行根據(jù)需求寫邏輯
            }
            if (action == DownloadManager.ACTION_NOTIFICATION_CLICKED) {
                //廣播被點(diǎn)擊了
            }
        }
    }

  fun downloadFile(activity: Activity, executorServiceSingle: ExecutorService, url: String) {
        try {
            val downloadManager = activity.getSystemService(AppCompatActivity.DOWNLOAD_SERVICE) as DownloadManager
            // 查詢是否存在
            val (isExit, id) = queryExist(activity, url)
            // 存在即是下載過了
            if (isExit) {
                id?.let {
                    // 根據(jù)下載返回的id或者下載文件的uri  然后打開這個(gè)文件
                    val uri = downloadManager.getUriForDownloadedFile(it)
                    openFileInBrowser(activity, uri, url)
                }
            } else {
                val request = DownloadManager.Request(Uri.parse(fileMsg.url)) //添加下載文件的網(wǎng)絡(luò)路徑
                request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileMsg.name) //添加保存文件路徑與名稱
                request.setTitle(name) //添加在通知欄里顯示的標(biāo)題
                request.setDescription(StringUtils.getString(R.string.down_loading)) //添加在通知欄里顯示的描述
                request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_MOBILE or DownloadManager.Request.NETWORK_WIFI) //設(shè)置下載的網(wǎng)絡(luò)類型
                request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED) //下載中與下載完成后都會(huì)在通知中顯示| 另外可以選 DownloadManager.Request.VISIBILITY_VISIBLE 僅在下載中時(shí)顯示在通知中,完成后會(huì)自動(dòng)隱藏
                val downloadId = downloadManager.enqueue(request) //加入隊(duì)列,會(huì)返回一個(gè)唯一下載id
                executorServiceSingle.submit(UpdateProcessTask(downloadManager, downloadId))
            }
        } catch (e: Exception) {
            StringUtils.getString(R.string.prompt_msg_15).toast()
        }
    }

    // 查詢是否下載過
    fun queryExist(context: Context, url: String): Pair<Boolean, Long?> {
        //獲取下載管理器
        val manager = context.getSystemService(AppCompatActivity.DOWNLOAD_SERVICE) as DownloadManager
        //獲取下載器任務(wù)隊(duì)列
        val query = DownloadManager.Query()
       // 過濾條件 查詢下載成功的
        query.setFilterByStatus(DownloadManager.STATUS_SUCCESSFUL)
        manager.query(query).use {
            while (it.moveToNext()) {
                if (it.getString(it.getColumnIndex(DownloadManager.COLUMN_URI)).contains(url)) {
                    val id = it.getLong(it.getColumnIndex(DownloadManager.COLUMN_ID))
                    // 進(jìn)行替換路徑 把file:// 去掉
                    val path = it.getString(it.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI)).replace("file://","")
                    // 有一些文件的路徑需要解碼应又,不然下面的判斷不存在
                    val file = File(Uri.decode(path))
                    if (file.exists()) {
                        return Pair(true, id)
                    }
                }
            }
        }
        return Pair(false, null)
    }

  // 打開文件
  private fun openFileInBrowser(context: Context, uri: Uri, url: String) {
        try {
            val intent = Intent(Intent.ACTION_VIEW)
            intent.putExtra(Browser.EXTRA_APPLICATION_ID, context.packageName)
            intent.setDataAndType(uri, getMimeType(url))
            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
            context.startActivity(Intent.createChooser(intent, "Chooser"))
        } catch (e: ActivityNotFoundException) {
            StringUtils.getString(R.string.prompt_msg_17).toast()
        }
    }

  // 獲取類型  先是獲取文件的擴(kuò)展名  然后根據(jù)擴(kuò)展名給定MIME類型
  fun getMimeType(url: String?): String? {
        var type: String? = ""
        val extension = MimeTypeMap.getFileExtensionFromUrl(url)
        if (extension != null) {
            type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension)
        }
        return type
    }

class UpdateProcessTask(private val downloadManager: DownloadManager, private val downloadId: Long) : Runnable {
    override fun run() {
        do {
            val sizeArray = getBytesAndStatus(downloadManager, downloadId)
            if (sizeArray[0] <= 0) {
                sizeArray[0] = 0
            }
            if (sizeArray[1] <= 0) {
                sizeArray[1] = 1
            }
            val downloadProcess = sizeArray[0].toFloat() / sizeArray[1]
            //獲取到文件下載的狀態(tài)
            val downloadState = sizeArray[2]
            // 如果需要更新下載進(jìn)度  那么在這里發(fā)送 下載的進(jìn)度和狀態(tài)
            LiveEventBus.get(EventKeys.FILE_DOWNLOAD_PROCESS, ChatMessageBean::class.java).post(downloadProcess)
            SystemClock.sleep(200)
        } while (sizeArray[0] < sizeArray[1])

    }
}

    /**
     * 通過query查詢下載狀態(tài)宙项,包括已下載數(shù)據(jù)大小,總大小株扛,下載狀態(tài)
     * 根據(jù)DownloadManager 的常量獲取對(duì)應(yīng)的數(shù)據(jù)
     * @param downloadId
     */
    fun getBytesAndStatus(downloadManager: DownloadManager, downloadId: Long): IntArray {
        val bytesAndStatus = intArrayOf(0, 1, 0)
        val query = DownloadManager.Query().setFilterById(downloadId)
        var cursor: Cursor? = null
        try {
            cursor = downloadManager.query(query)
            if (cursor != null && cursor.moveToFirst()) {
                //已經(jīng)下載文件大小
                bytesAndStatus[0] = cursor.getInt(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR))
                //下載文件的總大小
                bytesAndStatus[1] = cursor.getInt(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_TOTAL_SIZE_BYTES))
                //下載狀態(tài)
                bytesAndStatus[2] = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS))
            }
        } finally {
            cursor?.close()
        }
        return bytesAndStatus
    }

至此 基本就已經(jīng)完成了簡(jiǎn)單的需求下載顯示進(jìn)度尤筐,打開文件等這只是一部分模板代碼,自行可根據(jù)實(shí)際需求修改洞就,比如UI部分盆繁,比如更新的邏輯,還有通知的點(diǎn)擊事件等等旬蟋,這里只是寫出用法以及源碼的方法油昂。
源碼中包括了幾個(gè)部分:
Request請(qǐng)求:設(shè)置基本信息,標(biāo)題倾贰、描述冕碟、類型等等;下載文件的本地目標(biāo)設(shè)置為外部文件目錄中的路徑匆浙;通知顯示等等鸣哀;
Query查詢:根據(jù)下載的id或下載中的狀態(tài)條件查詢下載的進(jìn)度,大小等吞彤;
DownloadManager:添加下載到隊(duì)列我衬,根據(jù)id移除下載,根據(jù)id獲取uri等饰恕;
CursorTranslator:封裝了DownloadProvider返回的游標(biāo)挠羔,顯示了一組不同的列,這些列是在DownloadManager.COLUMN_*常量中定義的埋嵌,某些列直接對(duì)應(yīng)于基礎(chǔ)值破加,而其他列則根據(jù)基礎(chǔ)數(shù)據(jù)計(jì)算。
當(dāng)然還有很多方法雹嗦,這里就不一一提了范舀,如果感興趣的話合是,可以去看源碼。
如果有遺漏或者不對(duì)的地方锭环,請(qǐng)指正@我

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末聪全,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子辅辩,更是在濱河造成了極大的恐慌难礼,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,734評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件玫锋,死亡現(xiàn)場(chǎng)離奇詭異蛾茉,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)撩鹿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門谦炬,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人节沦,你說我怎么就攤上這事键思。” “怎么了散劫?”我有些...
    開封第一講書人閱讀 164,133評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵稚机,是天一觀的道長(zhǎng)幕帆。 經(jīng)常有香客問我获搏,道長(zhǎng),這世上最難降的妖魔是什么失乾? 我笑而不...
    開封第一講書人閱讀 58,532評(píng)論 1 293
  • 正文 為了忘掉前任常熙,我火速辦了婚禮,結(jié)果婚禮上碱茁,老公的妹妹穿的比我還像新娘裸卫。我一直安慰自己,他們只是感情好纽竣,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,585評(píng)論 6 392
  • 文/花漫 我一把揭開白布墓贿。 她就那樣靜靜地躺著,像睡著了一般蜓氨。 火紅的嫁衣襯著肌膚如雪聋袋。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,462評(píng)論 1 302
  • 那天穴吹,我揣著相機(jī)與錄音幽勒,去河邊找鬼。 笑死港令,一個(gè)胖子當(dāng)著我的面吹牛啥容,可吹牛的內(nèi)容都是我干的锈颗。 我是一名探鬼主播,決...
    沈念sama閱讀 40,262評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼咪惠,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼击吱!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起硝逢,我...
    開封第一講書人閱讀 39,153評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤姨拥,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后渠鸽,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體叫乌,經(jīng)...
    沈念sama閱讀 45,587評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,792評(píng)論 3 336
  • 正文 我和宋清朗相戀三年徽缚,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了憨奸。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,919評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡凿试,死狀恐怖排宰,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情那婉,我是刑警寧澤板甘,帶...
    沈念sama閱讀 35,635評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站详炬,受9級(jí)特大地震影響盐类,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜呛谜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,237評(píng)論 3 329
  • 文/蒙蒙 一在跳、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧隐岛,春花似錦猫妙、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至妒牙,卻和暖如春彼哼,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背单旁。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工沪羔, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,048評(píng)論 3 370
  • 正文 我出身青樓蔫饰,卻偏偏與公主長(zhǎng)得像琅豆,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子篓吁,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,864評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容