無需Root巷送,無需反編譯驶忌,用VirtualUETool查看修改任意App的布局參數(shù)

UETool是餓了么推出一款開源庫,已經(jīng)出來一段時間了惩系,用來幫助設(shè)計師位岔,程序員如筛,測試人員來在APP上修改View的各項參數(shù)。使用起來也很方便抒抬,但它只能在自己項目里引入依賴來使用杨刨,也就是說用它只能查看自己APP的布局位置信息。如果可以用它來查看手機上安裝的任意APP擦剑,那是不是很酷呢妖胀?我們今天的目標就是:擴展UETool讓它成為一個SuperUETool。先說下我們超級工具VirtualUETool惠勒,無需修改其他應(yīng)用apk赚抡,無需反編譯apk,無需手機Root纠屋,即拿即用涂臣,在Github已開源,歡迎star售担、fork哈~說了這么多赁遗,我們先看下效果吧:

VirtualUETool

VirtualUETool

接下來,我們來聊聊實現(xiàn)思路以及實現(xiàn)過程中遇到的問題族铆,重點在于思路和想法的擴展岩四,希望給你也有新的啟發(fā)。
先說下本文的行文思路:
一哥攘、UETool工作原理梳理
二剖煌、VirtualUETool框架的實現(xiàn)思路梳理

我們這里的介紹重點在于UETool以及對其的改造,對VirtualApp實現(xiàn)插件化功能就不做過多闡述了哈

好了逝淹,那我們開始吧耕姊。

一、UETool工作原理梳理

UETool的基本使用就不說了栅葡,看下官方文檔就很清楚了箩做,基本使用在當前頁面調(diào)用下UETool.showUETMenu這個方法就可以了。既然我們要開始改造UETool,
那我們接下來的重點就聊聊這個東西它的內(nèi)部實現(xiàn)是什么樣的妥畏,也方便我們后續(xù)的修改嘛。
首先從UETool.showUETMenu往下看
UETool.showMenu

private boolean showMenu(int y) {
        //檢查開啟懸浮窗權(quán)限
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            if (!Settings.canDrawOverlays(Application.getApplicationContext())) {
                requestPermission(Application.getApplicationContext());
                Toast.makeText(Application.getApplicationContext(), "After grant this permission, re-enable UETool", Toast.LENGTH_LONG).show();
                return false;
            }
        }
        //啟動UETool懸浮窗
        if (uetMenu == null) {
            uetMenu = new UETMenu(Application.getApplicationContext(), y);
        }
        uetMenu.show();
        return true;
    }

這里主要是申請懸浮窗權(quán)限安吁,就不說了醉蚁。后面下看UETMenu的構(gòu)造方法,這個UETMenu是一個繼承了LinearLayout的普通布局控件鬼店,構(gòu)造方法中主要是初始化UI相關(guān)网棍,看下關(guān)鍵部分:
UETMenu構(gòu)造方法中

public class UETMenu extends LinearLayout

...
subMenus.add(new UETSubMenu.SubMenu(resources.getString(R.string.uet_catch_view), R.drawable.uet_edit_attr, new OnClickListener() {
            @Override
            public void onClick(View v) {
                //查看view屬性
                open(TransparentActivity.Type.TYPE_EDIT_ATTR);
            }
        }));
        subMenus.add(new UETSubMenu.SubMenu(resources.getString(R.string.uet_relative_location), R.drawable.uet_relative_position,
                new OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        //查看view布局位置
                        open(TransparentActivity.Type.TYPE_RELATIVE_POSITION);
                    }
                }));
        subMenus.add(new UETSubMenu.SubMenu(resources.getString(R.string.uet_grid), R.drawable.uet_show_gridding,
                new OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        //顯示網(wǎng)格柵欄,方便查看控件是否對齊
                        open(TransparentActivity.Type.TYPE_SHOW_GRIDDING);
                    }
                }));
...

這里添加進懸浮窗點擊展開的三部分妇智,分別是查看view屬性滥玷、查看view布局位置氏身、顯示網(wǎng)格柵欄這三個部分。OK,繼續(xù)往下惑畴,就到了uetMenu.show()這里蛋欣,

public void show() {
        try {
            windowManager.addView(this, getWindowLayoutParams());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

就是往WindowManager中添加了UETMenu這個ViewGroup。接下來我們關(guān)注的重點來了如贷,當點擊各個功能按鈕后統(tǒng)一都調(diào)用了open方法陷虎,往下走。

private void open(@TransparentActivity.Type int type) {
        Activity currentTopActivity = Util.getCurrentActivity();
        if (currentTopActivity == null) {
            return;
        } else if (currentTopActivity.getClass() == TransparentActivity.class) {
            currentTopActivity.finish();
            return;
        }
        //啟動透明activity
        Intent intent = new Intent(currentTopActivity, TransparentActivity.class);
        intent.putExtra(TransparentActivity.EXTRA_TYPE, type);
        currentTopActivity.startActivity(intent);
        currentTopActivity.overridePendingTransition(0, 0);
        UETool.getInstance().setTargetActivity(currentTopActivity);
    }

這里啟動了一個透明的Activity,用于顯示我們顯示繪制布局信息和響應(yīng)我們的手指點擊杠袱,看重點

TransparentActivity.java

switch (type) {
            case TYPE_EDIT_ATTR:
                EditAttrLayout editAttrLayout = new EditAttrLayout(this);
                editAttrLayout.setOnDragListener(new EditAttrLayout.OnDragListener() {
                    @Override
                    public void showOffset(String offsetContent) {
                        board.updateInfo(offsetContent);
                    }
                });
                vContainer.addView(editAttrLayout);
                break;
            case TYPE_RELATIVE_POSITION:
                vContainer.addView(new RelativePositionLayout(this));
                break;
            case TYPE_SHOW_GRIDDING:
                vContainer.addView(new GriddingLayout(this));
                board.updateInfo("LINE_INTERVAL: " + DimenUtil.px2dip(GriddingLayout.LINE_INTERVAL, true));
                break;
            default:
                Toast.makeText(this, getString(R.string.uet_coming_soon), Toast.LENGTH_SHORT).show();
                finish();
                break;
        }

這里我們看到不同的功能在界面添加了不同的Layout,那接下來就分別分析下咯尚猿。
EditAttrLayoutRelativePositionLayout都繼承自CollectViewsLayout,先來看下它們的爸爸~

@Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        try {
            Activity targetActivity = UETool.getInstance().getTargetActivity();
            WindowManager windowManager = targetActivity.getWindowManager();
            Field mGlobalField = Class.forName("android.view.WindowManagerImpl").getDeclaredField("mGlobal");
            mGlobalField.setAccessible(true);

            if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) {
                Field mViewsField = Class.forName("android.view.WindowManagerGlobal").getDeclaredField("mViews");
                mViewsField.setAccessible(true);
                List<View> views = (List<View>) mViewsField.get(mGlobalField.get(windowManager));
                for (int i = views.size() - 1; i >= 0; i--) {
                    View targetView = getTargetDecorView(targetActivity, views.get(i));
                    if (targetView != null) {
                        //獲取當前顯示的view
                        traverse(targetView);
                        break;
                    }
                }
            } else {
                Field mRootsField = Class.forName("android.view.WindowManagerGlobal").getDeclaredField("mRoots");
                mRootsField.setAccessible(true);
                List viewRootImpls;
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                    viewRootImpls = (List) mRootsField.get(mGlobalField.get(windowManager));
                } else {
                    viewRootImpls = Arrays.asList((Object[]) mRootsField.get(mGlobalField.get(windowManager)));
                }
                for (int i = viewRootImpls.size() - 1; i >= 0; i--) {
                    Class clazz = Class.forName("android.view.ViewRootImpl");
                    Object object = viewRootImpls.get(i);
                    Field mWindowAttributesField = clazz.getDeclaredField("mWindowAttributes");
                    mWindowAttributesField.setAccessible(true);
                    Field mViewField = clazz.getDeclaredField("mView");
                    mViewField.setAccessible(true);
                    View decorView = (View) mViewField.get(object);
                    WindowManager.LayoutParams layoutParams = (WindowManager.LayoutParams) mWindowAttributesField.get(object);
                    if (layoutParams.getTitle().toString().contains(targetActivity.getClass().getName())
                            || getTargetDecorView(targetActivity, decorView) != null) {
                        traverse(decorView);
                        break;
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    //遞歸遍歷界面所有view并添加進elements集合中
    private void traverse(View view) {
        //如果在過濾的列表中,忽略
        if (UETool.getInstance().getFilterClasses().contains(view.getClass().getName())) return;
        //如果View不顯示 忽略
        if (view.getAlpha() == 0 || view.getVisibility() != View.VISIBLE) return;
        //如果view tag  == DESABLE_UETOOL  忽略
        if (getResources().getString(R.string.uet_disable).equals(view.getTag())) return;
        elements.add(new Element(view));
        if (view instanceof ViewGroup) {
            ViewGroup parent = (ViewGroup) view;
            for (int i = 0; i < parent.getChildCount(); i++) {
                traverse(parent.getChildAt(i));
            }
        }
    }

onAttachedToWindow方法中查找到當前界面顯示的View并且遞歸遍歷子View楣富,添加至elements集合中凿掂,每個Element中保存由當前View的位置信息和其父級Element
繼續(xù)看EditAttrLayout,這個控件用于顯示當前View屬性內(nèi)容纹蝴,主要看下這里:

//當點擊某個控件位置時 會調(diào)用 triggerActionUp
class ShowMode implements IMode {

        @Override
        public void triggerActionUp(final MotionEvent event) {
            final Element element = getTargetElement(event.getX(), event.getY());
            if (element != null) {
                targetElement = element;
                invalidate();
                if (dialog == null) {
                    dialog = new AttrsDialog(getContext());
                    dialog.setAttrDialogCallback(new AttrsDialog.AttrDialogCallback() {
                        @Override
                        public void enableMove() {
                            mode = new MoveMode();
                            dialog.dismiss();
                        }

                        @Override
                        public void showValidViews(int position, boolean isChecked) {
                            int positionStart = position + 1;
                            if (isChecked) {
                                dialog.notifyValidViewItemInserted(positionStart, getTargetElements(lastX, lastY), targetElement);
                            } else {
                                dialog.notifyItemRangeRemoved(positionStart);
                            }
                        }

                        @Override
                        public void selectView(Element element) {
                            targetElement = element;
                            dialog.dismiss();
                            dialog.show(targetElement);
                        }
                    });
                    dialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
                        @Override
                        public void onDismiss(DialogInterface dialog) {
                            if (targetElement != null) {
                                targetElement.reset();
                                invalidate();
                            }
                        }
                    });
                }
                dialog.show(targetElement);
            }
        }
    }
    //當移動某個控件位置時  會調(diào)用 triggerActionMove 方法
    class MoveMode implements IMode {
    
            @Override
            public void triggerActionMove(MotionEvent event) {
                if (targetElement != null) {
                    boolean changed = false;
                    View view = targetElement.getView();
                    float diffX = event.getX() - lastX;
                    if (Math.abs(diffX) >= moveUnit) {
                        view.setTranslationX(view.getTranslationX() + diffX);
                        lastX = event.getX();
                        changed = true;
                    }
                    float diffY = event.getY() - lastY;
                    if (Math.abs(diffY) >= moveUnit) {
                        view.setTranslationY(view.getTranslationY() + diffY);
                        lastY = event.getY();
                        changed = true;
                    }
                    if (changed) {
                        targetElement.reset();
                        invalidate();
                    }
                }
            }
        }

這里抽象出公共的行為庄萎,不同行為操作單獨處理實現(xiàn),代碼很簡潔骗灶。從上面可以看到惨恭,在點擊控件的時候,有一個AttrsDialog彈窗顯示耙旦,也就是我們看到的顯示控件實現(xiàn)的dialog,瞅瞅瞅瞅~
重點看下列表的adapter實現(xiàn):

public void notifyDataSetChanged(Element element) {
            items.clear();
            for (String attrsProvider : UETool.getInstance().getAttrsProvider()) {
                try {
                    IAttrs attrs = (IAttrs) Class.forName(attrsProvider).newInstance();
                    items.addAll(attrs.getAttrs(element));
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            notifyDataSetChanged();
        }

當adapter的notifyDataSetChanged方法執(zhí)行時脱羡,會從UETool.getInstance().getAttrsProvider()這里來拿我們希望支持的屬性,框架默認支持了一部分基礎(chǔ)屬性免都,我們也可以通過
UETool.putAttrsProviderClass(String customizeClassName)來添加自定義支持的屬性锉罐。先看下默認支持的怎么處理的:

    private Set<String> attrsProviderSet = new LinkedHashSet<String>() {
        {
            add(UETCore.class.getName());
        }
    };

UETCore.java

    @Override
    public List<Item> getAttrs(Element element) {
        List<Item> items = new ArrayList<>();

        View view = element.getView();

        items.add(new SwitchItem("Move", element, SwitchItem.Type.TYPE_MOVE));
        items.add(new SwitchItem("ValidViews", element, SwitchItem.Type.TYPE_SHOW_VALID_VIEWS));

        IAttrs iAttrs = AttrsManager.createAttrs(view);
        if (iAttrs != null) {
            items.addAll(iAttrs.getAttrs(element));
        }

        items.add(new TitleItem("COMMON"));
        items.add(new TextItem("Class", view.getClass().getName()));
        items.add(new TextItem("Id", Util.getResId(view)));
        items.add(new TextItem("ResName", Util.getResourceName(view.getId())));
        items.add(new TextItem("Clickable", Boolean.toString(view.isClickable()).toUpperCase()));
        items.add(new TextItem("Focused", Boolean.toString(view.isFocused()).toUpperCase()));
        items.add(new AddMinusEditItem("Width(dp)", element, EditTextItem.Type.TYPE_WIDTH, px2dip(view.getWidth())));
        items.add(new AddMinusEditItem("Height(dp)", element, EditTextItem.Type.TYPE_HEIGHT, px2dip(view.getHeight())));
        items.add(new TextItem("Alpha", String.valueOf(view.getAlpha())));
        Object background = Util.getBackground(view);
        if (background instanceof String) {
            items.add(new TextItem("Background", (String) background));
        } else if (background instanceof Bitmap) {
            items.add(new BitmapItem("Background", (Bitmap) background));
        }
        items.add(new AddMinusEditItem("PaddingLeft(dp)", element, EditTextItem.Type.TYPE_PADDING_LEFT, px2dip(view.getPaddingLeft())));
        items.add(new AddMinusEditItem("PaddingRight(dp)", element, EditTextItem.Type.TYPE_PADDING_RIGHT, px2dip(view.getPaddingRight())));
        items.add(new AddMinusEditItem("PaddingTop(dp)", element, EditTextItem.Type.TYPE_PADDING_TOP, px2dip(view.getPaddingTop())));
        items.add(new AddMinusEditItem("PaddingBottom(dp)", element, EditTextItem.Type.TYPE_PADDING_BOTTOM, px2dip(view.getPaddingBottom())));

        return items;
    }
    
    static class AttrsManager {
    
            public static IAttrs createAttrs(View view) {
                if (view instanceof TextView) {
                    return new UETTextView();
                } else if (view instanceof ImageView) {
                    return new UETImageView();
                }
                return null;
            }
        }

到這里基本就清楚了,將我們支持的控件屬性逐一添加進來绕娘,用instanceof判斷具體的控件后取出相應(yīng)控件屬性顯示脓规,后面的處理就比較簡單了。
再看RelativePositionLayout,主要就是再手指點擊后查找當前位置View,并在當前View的Canvas上繪制標記線:


    public boolean onTouchEvent(MotionEvent event) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    break;
                case MotionEvent.ACTION_UP:
    
                    final Element element = getTargetElement(event.getX(), event.getY());
                    if (element != null) {
                        relativeElements[searchCount % elementsNum] = element;
                        searchCount++;
                        invalidate();
                    }
                    break;
            }
            return true;
        }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        boolean doubleNotNull = true;
        for (Element element : relativeElements) {
            if (element != null) {
                Rect rect = element.getRect();
                canvas.drawLine(0, rect.top, screenWidth, rect.top, dashLinePaint);
                canvas.drawLine(0, rect.bottom, screenWidth, rect.bottom, dashLinePaint);
                canvas.drawLine(rect.left, 0, rect.left, screenHeight, dashLinePaint);
                canvas.drawLine(rect.right, 0, rect.right, screenHeight, dashLinePaint);
                canvas.drawRect(rect, areaPaint);
            } else {
                doubleNotNull = false;
            }
        }

        if (doubleNotNull) {
            Rect firstRect = relativeElements[searchCount % elementsNum].getRect();
            Rect secondRect = relativeElements[(searchCount - 1) % elementsNum].getRect();

            if (secondRect.top > firstRect.bottom) {
                int x = secondRect.left + secondRect.width() / 2;
                drawLineWithText(canvas, x, firstRect.bottom, x, secondRect.top);
            }

            if (firstRect.top > secondRect.bottom) {
                int x = secondRect.left + secondRect.width() / 2;
                drawLineWithText(canvas, x, secondRect.bottom, x, firstRect.top);
            }

            if (secondRect.left > firstRect.right) {
                int y = secondRect.top + secondRect.height() / 2;
                drawLineWithText(canvas, secondRect.left, y, firstRect.right, y);
            }

            if (firstRect.left > secondRect.right) {
                int y = secondRect.top + secondRect.height() / 2;
                drawLineWithText(canvas, secondRect.right, y, firstRect.left, y);
            }

            drawNestedAreaLine(canvas, firstRect, secondRect);
            drawNestedAreaLine(canvas, secondRect, firstRect);
        }
    }

重點在于getTargetElement方法查找到當前點擊的子View:

    protected Element getTargetElement(float x, float y) {
            Element target = null;
            for (int i = elements.size() - 1; i >= 0; i--) {
                final Element element = elements.get(i);
                if (element.getRect().contains((int) x, (int) y)) {
                    //如果父控件超出屏幕不顯示 跳過
                    if (isParentNotVisible(element.getParentElement())) {
                        continue;
                    }
                    if (element != childElement) {
                        childElement = element;
                        parentElement = element;
                    } else if (parentElement != null) {
                        parentElement = parentElement.getParentElement();
                    }
                    target = parentElement;
                    break;
                }
            }
            if (target == null) {
                Toast.makeText(getContext(), String.format("could not found view in (%1$.0f , %2$.0f), please select view again", x, y), Toast.LENGTH_SHORT).show();
            }
            return target;
        }

最后的GriddingLayout是用來展示柵格化布局的险领,方便查看控件是否對齊侨舆,這個就很簡單了,看下:

     @Override
     protected void onDraw(Canvas canvas) {
         super.onDraw(canvas);
         int startX = 0;
         while (startX < screenWidth) {
             //畫豎線
             canvas.drawLine(startX, 0, startX, screenHeight, paint);
             startX = startX + LINE_INTERVAL;
         }
    
         int startY = 0;
         while (startY < screenHeight) {
             //畫橫線
             canvas.drawLine(0, startY, screenWidth, startY, paint);
             startY = startY + LINE_INTERVAL;
         }
     }

呼~~終于把整個流程梳理完了,UETool的原理流程梳理完了绢陌,那我們要開始改造了挨下。

二、UETool框架的實現(xiàn)思路梳理

我們的目標是在任何已安裝的app中可以像UETool一樣查看布局屬性來使用脐湾。從正常思路來想的話臭笆,這基本是不可能的,除非我們反編譯apk,將UETool的代碼編譯后插入重打包愁铺,或者使用Xposed的框架來hook鹰霍。理論上講我們也只能從這里想辦法了,但有個很致命的問題就是茵乱,前者我們必須要反編譯代碼茂洒,后者又必須要手機root。而且一個apk反編譯一次似将,我們僅僅是想看下布局屬性获黔,能不能簡單點?操作的方式簡單點在验?
基于這些情況玷氏,在這里我們用VirtualApp來做底層框架,用于免root加載apk腋舌,在其加載apk運行后進行hook插入UETool代碼盏触。關(guān)于VirtualApp,這是一個開源的插件化方案。

VirtualApp在你的App內(nèi)創(chuàng)建一個虛擬空間块饺,你可以在虛擬空間內(nèi)任意的安裝赞辩、啟動和卸載APK蛾派,這一切都與外部隔離读第,如同一個沙盒。運行在VA中的APK無需在外部安裝魏铅,即VA支持免安裝運行APK淮腾。

注意:作者明確指出糟需,如果項目需要投入商業(yè)使用,請購買「商業(yè)版」谷朝。我們這里僅做技術(shù)學(xué)習(xí)使用哈~
我們在VirtualApp啟動apk之后的回調(diào)MyComponentDelegate,它會回調(diào)一系列生命周期方法洲押。


    void beforeApplicationCreate(Application application);

    void afterApplicationCreate(Application application);

    void beforeActivityCreate(Activity activity);

    void beforeActivityResume(Activity activity);

    void beforeActivityPause(Activity activity);

    void beforeActivityDestroy(Activity activity);

    void afterActivityCreate(Activity activity);

    void afterActivityResume(Activity activity);

    void afterActivityPause(Activity activity);

    void afterActivityDestroy(Activity activity);

    void onSendBroadcast(Intent intent);

1.由于我們的UETool Menu是在Virtual進程中,而我們需要真正執(zhí)行操作時是在每個apk進程中圆凰,如果在兩個進程中進行消息傳遞杈帐?

進程間通信最簡單的是通過廣播BroadCastReceiver來做,但由于Virtual機制的原因专钉,我們在apk進程內(nèi)部回調(diào)中動態(tài)注冊的廣播無法收到
在外部進程的廣播消息挑童。這里切換了一下思路,通過使用FileObserver來監(jiān)聽文件的變化來實現(xiàn)消息的傳遞跃须,在apk進程內(nèi)我們開啟FileObserver
監(jiān)聽指定文件夾中文件變化炮沐,來執(zhí)行對應(yīng)的操作。

2.由于三方apk并沒有加載UETool的資源res,也就是說通過R.layout回怜、R.id、R.xx都會產(chǎn)生無法找到資源異常

這里操作是替換掉所有R文件相關(guān)操作,通過手動創(chuàng)建控件的方式處理玉雾。

3.由于三方apk并沒有TransparentActivityAndroidManifest.xml中注冊,啟動Activity會報異常

這里我移除了TransparentActivity,不啟動新Activity,通過在當前布局中添加新布局的方式處理,規(guī)避Activity注冊問題翔试。

        View view = Util.getCurrentView(activity);
        ViewGroup viewGroup = null;
        if (view instanceof ViewGroup){
            viewGroup = (ViewGroup) view;
        }
        if (viewGroup != null){
            View viewWithTag = viewGroup.findViewWithTag(EXTRA_TYPE);
            if (viewWithTag != null){
                viewGroup.removeView(viewWithTag);
            }
            vContainer.setTag(EXTRA_TYPE);
            vContainer.setFocusable(false);
            vContainer.setFocusableInTouchMode(false);
            viewGroup.addView(vContainer,new ViewGroup.LayoutParams(viewGroup.getWidth(),viewGroup.getHeight()));
        }

至此,修改后的UETool集成進VirtualApp中,在我們拖入app啟動后,就可在三方app中正常使用UETool啦,至于用來做什么就取決于你的想象力了,比如設(shè)計師可以拿來參考優(yōu)秀app的布局設(shè)計,前端工程師可以拿來參考其他app頁面效果的實現(xiàn)方式,當然你也可以修改下賬戶顯示余額吹吹牛...
感興趣的小伙伴可以下載體驗下哈,Github地址在這里:VirtualUETool

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末复旬,一起剝皮案震驚了整個濱河市垦缅,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌驹碍,老刑警劉巖壁涎,帶你破解...
    沈念sama閱讀 212,599評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異志秃,居然都是意外死亡怔球,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,629評論 3 385
  • 文/潘曉璐 我一進店門浮还,熙熙樓的掌柜王于貴愁眉苦臉地迎上來竟坛,“玉大人,你說我怎么就攤上這事钧舌〉L溃” “怎么了?”我有些...
    開封第一講書人閱讀 158,084評論 0 348
  • 文/不壞的土叔 我叫張陵洼冻,是天一觀的道長崭歧。 經(jīng)常有香客問我,道長撞牢,這世上最難降的妖魔是什么率碾? 我笑而不...
    開封第一講書人閱讀 56,708評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮普泡,結(jié)果婚禮上播掷,老公的妹妹穿的比我還像新娘。我一直安慰自己撼班,他們只是感情好歧匈,可當我...
    茶點故事閱讀 65,813評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著砰嘁,像睡著了一般件炉。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上矮湘,一...
    開封第一講書人閱讀 50,021評論 1 291
  • 那天斟冕,我揣著相機與錄音,去河邊找鬼缅阳。 笑死磕蛇,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播秀撇,決...
    沈念sama閱讀 39,120評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼超棺,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了呵燕?” 一聲冷哼從身側(cè)響起棠绘,我...
    開封第一講書人閱讀 37,866評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎再扭,沒想到半個月后氧苍,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,308評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡泛范,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,633評論 2 327
  • 正文 我和宋清朗相戀三年让虐,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片敦跌。...
    茶點故事閱讀 38,768評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡澄干,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出柠傍,到底是詐尸還是另有隱情麸俘,我是刑警寧澤,帶...
    沈念sama閱讀 34,461評論 4 333
  • 正文 年R本政府宣布惧笛,位于F島的核電站从媚,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏患整。R本人自食惡果不足惜拜效,卻給世界環(huán)境...
    茶點故事閱讀 40,094評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望各谚。 院中可真熱鬧紧憾,春花似錦、人聲如沸昌渤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,850評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽膀息。三九已至般眉,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間潜支,已是汗流浹背甸赃。 一陣腳步聲響...
    開封第一講書人閱讀 32,082評論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留冗酿,地道東北人埠对。 一個月前我還...
    沈念sama閱讀 46,571評論 2 362
  • 正文 我出身青樓络断,卻偏偏與公主長得像,于是被迫代替她去往敵國和親鸠窗。 傳聞我的和親對象是個殘疾皇子妓羊,可洞房花燭夜當晚...
    茶點故事閱讀 43,666評論 2 350

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

  • ¥開啟¥ 【iAPP實現(xiàn)進入界面執(zhí)行逐一顯】 〖2017-08-25 15:22:14〗 《//首先開一個線程,因...
    小菜c閱讀 6,375評論 0 17
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴謹 對...
    cosWriter閱讀 11,093評論 1 32
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫稍计、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,066評論 4 62
  • 昨天檢查作業(yè)裕循,我班有一個學(xué)生沒寫完臣嚣,我讓他給我家長的號碼,我打過去竟然是空號剥哑,他騙了我硅则,我教了他快兩年了,他竟然對...
    一葉扁舟_8744閱讀 232評論 1 0
  • 春天來了株婴,小草舒展怎虫,柳錢嫩黃,各色樹枝頭打著花苞兒困介。淺淺地大审,毛絨絨的,像襁褓的嬰兒座哩,誰不喜歡呢徒扶!
    昇夫閱讀 124評論 0 0