android 通话自动录音服务

需求:
①:通话自动录音;
②:无界面,只是一个service;
③:录音自动压缩上传;
④:当用户清理后台的时候,要求service不可以被杀死;
⑤:稳定性:1、无网络的情况下;2、上传失败;3、服务报错。
解决方案:
①:通话自动录音
启动一个service,监听用户手机通话状态,当检测到用户处于通话状态下,立即开始录音,通话结束后,停止录音,并保存文件。
此功能的前提条件:
1、录音权限、读写存储空间的权限、读取通话状态的权限;
2、Service不可以被停止,否则无法录音。
3、开机启动(不可以让用户每次开机都主动去打开服务)
②:无界面,只是一个service
方案①
普通的service,监听开机广播,当用户开机的时候,启动service。但是,发现service并没有启动。想要启动一个service,必须要有一个activity,即使你不打开这个activity。
在真正做项目的时候,PM会提出各种你不能理解的需求,比如说本系统,PM要求本应用只是一个录音服务,不可以有任何界面,也不可以在手机桌面上出现应用图标。因此,方案①不可行。
方案②
Android手机在设置里面都一个辅助功能(个别手机也叫:无障碍),利用这个我们可以实现一些强大的功能,前提是用户开启我们的辅助功能,抢红包软件就是利用辅助功能实现的。

这里写图片描述
这里写图片描述

③:录音自动压缩上传
我们只需要在上传之前对文件进行压缩处理,然后再上传即可。
④:当用户清理后台的时候,要求service不可以被杀死
不会被杀死的服务,或许只有系统服务吧。当然类似于QQ、微信他们做的这种全家桶也可以做到。大公司是可以和厂商合作的,他们的应用可以不那么容易被杀死。当然也不提倡这样做,这样就是垃圾软件,破坏了Android开发的美好环境。
其实,如果可以把服务设置成系统服务,那么只要用户不主动在辅助功能页面关掉服务,后台是清理不掉改服务的。本人在小米手机上测试过,设置成系统级别的服务后,当清理后台的时候,即使服务被杀死,也会非常快的重新启动。(感兴趣的同学可以试一下)
⑤:稳定性:1、无网络的情况下;2、上传失败;3、服务报错
思路:
当无网络的情况下,把录音文件的地址保存下来(保存的方式有很多:Sqlite、Sharedpreferences等等),上传失败也一样,失败的原因可能有很多种:网络断开、接口报错等,当网络恢复的时候,可以重新上传,这样就不会丢失录音文件。
代码很简单,注释很详细:
项目的结构:
这里写图片描述

<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<!-- 要存储文件或者创建文件夹的话还需要以下两个权限 -->
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET"/>
<!--允许读取网络状态-->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<!--允许读取wifi网络状态-->
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<service
    android:name=".service.RecorderService"
    android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
    <intent-filter>
        <action android:name="android.accessibilityservice.AccessibilityService" />
    </intent-filter>
    <meta-data
        android:name="android.accessibilityservice"
        android:resource="@xml/accessible_service_config" />
</service>
/**
 * 电话自动录音辅助服务(去电、来电自动录音并上传)。
 * Created by wang.ao in 2017/2/24.
 */

public class RecorderService extends AccessibilityService {
    private static final String TAG = "RecorderService";
    private static final String TAG1 = "手机通话状态";
    /**
     * 音频录制
     */
    private MediaRecorder recorder;
    private SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    /**
     * 监听拨号广播,以便获取用户拨出的电话号码
     */
    private OutCallReceiver outCallReceiver;
    private IntentFilter intentFilter;
    /**
     * 网络状态改变广播,当网络畅通的状态下,把用户未上传的录音文件都上传掉
     */
    private NetworkConnectChangedReceiver networkConnectChangedReceiver;
    private IntentFilter intentFilter2;
    /**
     * 当前通话对象的电话号码
     */
    private String currentCallNum = "";
    /**
     * 区分来电和去电
     */
    private int previousStats = 0;
    /**
     * 当前正在录制的文件
     */
    private String currentFile = "";
    /**
     * 保存未上传的录音文件
     */
    private SharedPreferences unUploadFile;
    private String dirPath = "";
    private boolean isRecording = false;

    @Override
    protected void onServiceConnected() {
        Log.i(TAG, "onServiceConnected");
        Toast.makeText(getApplicationContext(), "自动录音服务已启动", Toast.LENGTH_LONG).show();
    }

    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
        // TODO Auto-generated method stub
        Log.i(TAG, "eventType " + event.getEventType());
    }

    @Override
    public void onInterrupt() {
        // TODO Auto-generated method stub
        Log.i(TAG, "onServiceConnected");
    }

    @Override
    public boolean onUnbind(Intent intent) {
        return super.onUnbind(intent);
    }

    @Override
    public void onCreate() {
        super.onCreate();
        TelephonyManager tm = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
        // 监听电话状态
        tm.listen(new MyListener(), PhoneStateListener.LISTEN_CALL_STATE);
        outCallReceiver = new OutCallReceiver();
        intentFilter = new IntentFilter();
        //设置拨号广播过滤
        intentFilter.addAction("android.intent.action.NEW_OUTGOING_CALL");
        registerReceiver(outCallReceiver, intentFilter);
        //注册拨号广播接收器
        networkConnectChangedReceiver = new NetworkConnectChangedReceiver();
        intentFilter2 = new IntentFilter();
        //设置网络状态改变广播过滤
        intentFilter2.addAction("android.net.conn.CONNECTIVITY_CHANGE");
        intentFilter2.addAction("android.net.wifi.WIFI_STATE_CHANGED");
        intentFilter2.addAction("android.net.wifi.STATE_CHANGE");
        //注册网络状态改变广播接收器
        registerReceiver(networkConnectChangedReceiver, intentFilter2);
        unUploadFile = getSharedPreferences("un_upload_file", 0);
        unUploadFile.edit().putString("description", "未上传的录音文件存放路径").commit();
        dirPath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/com.ct.phonerecorder/";
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Toast.makeText(getApplicationContext(), "进程被关闭,无法继续录音,请打开录音服务", Toast.LENGTH_LONG).show();
        if (outCallReceiver != null) {
            unregisterReceiver(outCallReceiver);
        }
        if (networkConnectChangedReceiver != null) {
            unregisterReceiver(networkConnectChangedReceiver);
        }
    }

    class MyListener extends PhoneStateListener {
        @Override
        public void onCallStateChanged(int state, String incomingNumber) {
            // TODO Auto-generated method stub
            Log.d(TAG1, "空闲状态" + incomingNumber);
            switch (state) {
                case TelephonyManager.CALL_STATE_IDLE:
                    Log.d(TAG1, "空闲");
                    if (recorder != null && isRecording) {
                        recorder.stop();// 停止录音
                        recorder.release();
                        recorder = null;
                        Log.d("电话", "通话结束,停止录音");
                        uploadFile(currentFile);
                    }
                    isRecording = false;
                    break;
                case TelephonyManager.CALL_STATE_RINGING:
                    Log.d(TAG1, "来电响铃" + incomingNumber);
                    // 进行初始化

                    break;
                case TelephonyManager.CALL_STATE_OFFHOOK:
                    Log.d(TAG1, "摘机" + (!incomingNumber.equals("") ? incomingNumber : currentCallNum));
                    initRecord(!incomingNumber.equals("") ? incomingNumber : currentCallNum);
                    // 开始录音
                    if (recorder != null) {
                        recorder.start();
                        isRecording = true;
                    }
                default:
                    break;
            }
            super.onCallStateChanged(state, incomingNumber);
        }

    }

    /**
     * 当录音结束后,自动上传录音文件。
     * ①网络可用:直接上传;
     * ②网络不可用:保存文件路径,待网络可用的时候再进行上传;
     * ③上传失败的文件,也保存文件路径,或者重新上传。
     */
    public void uploadFile(String file) {
        ZipUtils.zipFile(dirPath + file, dirPath + file + ".zip");
        if (NetWorkUtils.isNetworkConnected(getApplicationContext())) {
            //上传文件
//            OkHttpUtils.postFile()
        } else {
            saveUnUploadFIles(dirPath + file + ".zip");
        }
    }

    /**
     * 保存未上传的录音文件
     *
     * @param file 未上传的录音文件路径
     */
    private void saveUnUploadFIles(String file) {
        String files = unUploadFile.getString("unUploadFile", "");
        if (files.equals("")) {
            files = file;
        } else {
            StringBuilder sb = new StringBuilder(files);
            files = sb.append(";").append(file).toString();
        }
        unUploadFile.edit().putString("unUploadFile", files).commit();
    }

    /**
     * 上传因为网络或者其他原因,暂未上传或者上传失败的文件,重新上传
     */
    public void uploadUnUploadedFiles() {
        //获取当前还未上传的文件,并把这些文件上传
        String files = unUploadFile.getString("unUploadFile", "");
        unUploadFile.edit().putString("unUploadFile", "").commit();
        if (files.equals("")) {
            return;
        }
        String[] fileArry = files.split(";");
        int len = fileArry.length;
        for (String file : fileArry) {
            upload(file);
        }
    }

    /**
     * 文件上传
     *
     * @param file 要上传的文件
     */
    public void upload(final String file) {
        File file1 = new File(file);
        if (file1 == null || !file1.exists()) {
            //文件不存在
            return;
        }
        if (!NetWorkUtils.isNetworkConnected(getApplicationContext())) {
            saveUnUploadFIles(file);
            return;
        }
        Map<String, String> map = new HashMap<String, String>();
        map.put("type", "1");
        final String url = "http://192.168.1.158:8082/uploader";
        OkHttpUtils.post()//
                .addFile("mFile", file1.getName(), file1)//
                .url(url)//
                .params(map).build()//
                .execute(new StringCallback() {

                    @Override
                    public void onResponse(String response, int id) {
                        Log.e(TAG, "成功 response=" + response);
                    }

                    @Override
                    public void onError(Call call, Exception e, int id) {
                        Log.e(TAG, "失败 response=" + e.toString());
                        saveUnUploadFIles(file);
                    }
                });
    }

    /**
     * 初始化录音机,并给录音文件重命名
     *
     * @param incomingNumber 通话号码
     */
    private void initRecord(String incomingNumber) {
        previousStats = TelephonyManager.CALL_STATE_RINGING;
        recorder = new MediaRecorder();
        recorder.setAudioSource(MediaRecorder.AudioSource.MIC);// Microphone
        recorder.setOutputFormat(MediaRecorder.OutputFormat.RAW_AMR);// 设置输出3gp格式
        File out = new File(dirPath);
        if (!out.exists()) {
            out.mkdirs();
        }
        recorder.setOutputFile(dirPath
                + getFileName((previousStats == TelephonyManager.CALL_STATE_RINGING ? incomingNumber : currentCallNum))
        );
        recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);// 设置音频编码格式
        try {
            recorder.prepare();// 做好准备
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    /**
     * 获取录音文件的名称
     *
     * @param incomingNumber 通话号码
     * @return 获取录音文件的名称
     */
    private String getFileName(String incomingNumber) {
        Date date = new Date(System.currentTimeMillis());
        currentFile = incomingNumber + " " + dateFormat.format(date) + ".mp3";
        return currentFile;
    }

    /**
     * 拨号广播接收器,并获取拨号号码
     */
    public class OutCallReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            Log.d(TAG1, "当前手机拨打了电话:" + currentCallNum);
            if (intent.getAction().equals(Intent.ACTION_NEW_OUTGOING_CALL)) {
                currentCallNum = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER);
                Log.d(TAG1, "当前手机拨打了电话:" + currentCallNum);
            } else {
                Log.d(TAG1, "有电话,快接听电话");
            }
        }
    }

    /**
     * 网络状态change广播接收器
     */
    public class NetworkConnectChangedReceiver extends BroadcastReceiver {
        private static final String TAG = "network status";

        @Override
        public void onReceive(Context context, Intent intent) {
            /**
             * 这个监听网络连接的设置,包括wifi和移动数据的打开和关闭。.
             * 最好用的还是这个监听。wifi如果打开,关闭,以及连接上可用的连接都会接到监听。见log
             * 这个广播的最大弊端是比上边两个广播的反应要慢,如果只是要监听wifi,我觉得还是用上边两个配合比较合适
             */
            if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) {
                ConnectivityManager manager = (ConnectivityManager) context
                        .getSystemService(Context.CONNECTIVITY_SERVICE);
                Log.i(TAG, "CONNECTIVITY_ACTION");

                NetworkInfo activeNetwork = manager.getActiveNetworkInfo();
                if (activeNetwork != null) { // connected to the internet
                    if (activeNetwork.isConnected()) {
                        //当前网络可用
                        if (activeNetwork.getType() == ConnectivityManager.TYPE_WIFI) {
                            // connected to wifi
                            Log.e(TAG, "当前WiFi连接可用 ");
                        } else if (activeNetwork.getType() == ConnectivityManager.TYPE_MOBILE) {
                            // connected to the mobile provider's data plan
                            Log.e(TAG, "当前移动网络连接可用 ");
                        }
                        uploadUnUploadedFiles();
                    } else {
                        Log.e(TAG, "当前没有网络连接,请确保你已经打开网络 ");
                    }

                } else {   // not connected to the internet
                    Log.e(TAG, "当前没有网络连接,请确保你已经打开网络 ");
                }


            }
        }
    }

}
/**
 * 获取网络连接状态工具类
 * Created by wang.ao in 2017/2/24.
 */

public class NetWorkUtils {
    /**
     * 判断是否有网络连接
     * @param context
     * @return
     */
    public static boolean isNetworkConnected(Context context) {
        if (context != null) {
            ConnectivityManager mConnectivityManager = (ConnectivityManager) context
                    .getSystemService(Context.CONNECTIVITY_SERVICE);
            NetworkInfo mNetworkInfo = mConnectivityManager.getActiveNetworkInfo();
            if (mNetworkInfo != null) {
                return mNetworkInfo.isAvailable();
            }
        }
        return false;
    }


    /**
     * 判断WIFI网络是否可用
     * @param context
     * @return
     */
    public static boolean isWifiConnected(Context context) {
        if (context != null) {
            ConnectivityManager mConnectivityManager = (ConnectivityManager) context
                    .getSystemService(Context.CONNECTIVITY_SERVICE);
            NetworkInfo mWiFiNetworkInfo = mConnectivityManager
                    .getNetworkInfo(ConnectivityManager.TYPE_WIFI);
            if (mWiFiNetworkInfo != null) {
                return mWiFiNetworkInfo.isAvailable();
            }
        }
        return false;
    }


    /**
     * 判断MOBILE网络是否可用
     * @param context
     * @return
     */
    public static boolean isMobileConnected(Context context) {
        if (context != null) {
            ConnectivityManager mConnectivityManager = (ConnectivityManager) context
                    .getSystemService(Context.CONNECTIVITY_SERVICE);
            NetworkInfo mMobileNetworkInfo = mConnectivityManager
                    .getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
            if (mMobileNetworkInfo != null) {
                return mMobileNetworkInfo.isAvailable();
            }
        }
        return false;
    }


    /**
     * 获取当前网络连接的类型信息
     * @param context
     * @return
     */
    public static int getConnectedType(Context context) {
        if (context != null) {
            ConnectivityManager mConnectivityManager = (ConnectivityManager) context
                    .getSystemService(Context.CONNECTIVITY_SERVICE);
            NetworkInfo mNetworkInfo = mConnectivityManager.getActiveNetworkInfo();
            if (mNetworkInfo != null && mNetworkInfo.isAvailable()) {
                return mNetworkInfo.getType();
            }
        }
        return -1;
    }


    /**
     * 获取当前的网络状态 :没有网络0:WIFI网络1:3G网络2:2G网络3
     *
     * @param context
     * @return
     */
    public static int getAPNType(Context context) {
        int netType = 0;
        ConnectivityManager connMgr = (ConnectivityManager) context
                .getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();
        if (networkInfo == null) {
            return netType;
        }
        int nType = networkInfo.getType();
        if (nType == ConnectivityManager.TYPE_WIFI) {
            netType = 1;// wifi
        } else if (nType == ConnectivityManager.TYPE_MOBILE) {
            int nSubType = networkInfo.getSubtype();
            TelephonyManager mTelephony = (TelephonyManager) context
                    .getSystemService(Context.TELEPHONY_SERVICE);
            if (nSubType == TelephonyManager.NETWORK_TYPE_UMTS
                    && !mTelephony.isNetworkRoaming()) {
                netType = 2;// 3G
            } else {
                netType = 3;// 2G
            }
        }
        return netType;
    }
}

public class ZipUtils {
    private static final int BUFF_SIZE = 1024;

    /**
     * @param zos           压缩流
     * @param parentDirName 父目录
     * @param file          待压缩文件
     * @param buffer        缓冲区
     *                      URL:http://www.bianceng.cn/OS/extra/201609/50420.htm
     * @return 只要目录中有一个文件压缩失败,就停止并返回
     */
    private static boolean zipFile(ZipOutputStream zos, String parentDirName, File file, byte[] buffer) {
        String zipFilePath = parentDirName + file.getName();
        if (file.isDirectory()) {
            zipFilePath += File.separator;
            for (File f : file.listFiles()) {
                if (!zipFile(zos, zipFilePath, f, buffer)) {
                    return false;
                }
            }
            return true;
        } else {
            try {
                BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
                ZipEntry zipEntry = new ZipEntry(zipFilePath);
                zipEntry.setSize(file.length());
                zos.putNextEntry(zipEntry);
                while (bis.read(buffer) != -1) {
                    zos.write(buffer);
                }
                bis.close();
                return true;
            } catch (FileNotFoundException ex) {
                ex.printStackTrace();
            } catch (IOException ex) {
                ex.printStackTrace();
            }
            return false;
        }
    }

    /**
     * @param srcPath 待压缩的文件或目录
     * @param dstPath 压缩后的zip文件
     * @return 只要待压缩的文件有一个压缩失败就停止压缩并返回(等价于windows上直接进行压缩)
     */
    public static boolean zipFile(String srcPath, String dstPath) {
        File srcFile = new File(srcPath);
        if (!srcFile.exists()) {
            return false;
        }
        byte[] buffer = new byte[BUFF_SIZE];
        try {
            ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(dstPath));
            boolean result = zipFile(zos, "", srcFile, buffer);
            zos.close();
            return result;
        } catch (FileNotFoundException ex) {
            ex.printStackTrace();
        } catch (IOException ex) {
            ex.printStackTrace();
        }
        return false;
    }

    /**
     * @param srcPath 待解压的zip文件
     * @param dstPath zip解压后待存放的目录
     * @return 只要解压过程中发生错误,就立即停止并返回(等价于windows上直接进行解压)
     */
    public static boolean unzipFile(String srcPath, String dstPath) {
        if (TextUtils.isEmpty(srcPath) || TextUtils.isEmpty(dstPath)) {
            return false;
        }
        File srcFile = new File(srcPath);
        if (!srcFile.exists() || !srcFile.getName().toLowerCase(Locale.getDefault()).endsWith("zip")) {
            return false;
        }
        File dstFile = new File(dstPath);
        if (!dstFile.exists() || !dstFile.isDirectory()) {
            dstFile.mkdirs();
        }
        try {
            ZipInputStream zis = new ZipInputStream(new FileInputStream(srcFile));
            BufferedInputStream bis = new BufferedInputStream(zis);
            ZipEntry zipEntry = null;
            byte[] buffer = new byte[BUFF_SIZE];
            if (!dstPath.endsWith(File.separator)) {
                dstPath += File.separator;
            }
            while ((zipEntry = zis.getNextEntry()) != null) {
                String fileName = dstPath + zipEntry.getName();
                File file = new File(fileName);
                File parentDir = file.getParentFile();
                if (!parentDir.exists()) {
                    parentDir.mkdirs();
                }
                FileOutputStream fos = new FileOutputStream(file);
                while (bis.read(buffer) != -1) {
                    fos.write(buffer);
                }
                fos.close();
            }
            bis.close();
            zis.close();
            return true;
        } catch (FileNotFoundException ex) {
            ex.printStackTrace();
        } catch (IOException ex) {
            ex.printStackTrace();
        }
        return false;
    }
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,520评论 25 707
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,600评论 18 139
  • 我不是诗人 但是却想为你写诗 你是诗里最美好的意象 想让你出现在诗的每一页 我不是歌者 但是却想为你歌唱 你是歌中...
    麦麦sky阅读 179评论 0 2
  • 《东狭西谷》 《赢旦儿哥》 《走出匠乡》 《墨斗》 《赢旦儿》 《墨斗》 《跨出鲁班壑》 《廊桥工匠》 《还愿》 ...
    银河谜米阅读 202评论 0 2
  • 也不知道这样究竟过了多少个夜晚,细想一下,也数的过来。 喜欢的,不喜欢的,有兴趣的,没兴趣的,都是惘然,都是价值连...
    外滩世界阅读 151评论 0 0