android 一个混合框架的使用

来源:互联网 发布:淘宝特卖是什么 编辑:程序博客网 时间:2024/06/08 02:56
android 一个混合框架的使用,这是一款基于codorva的自研的一款混合框架。其框架使用简单,是android 混合开发难得的实用框架,此框架也是花费很大的人力开发的。其提供js,java 互相通知的接口,java本地接口,web 接口等。以及提供页面切换效果。可以实现页面上区域的柔和混合,可以实现页面直接的柔和混合。

Odpf框架是一种终端应用混合开发模式框架,ODPF框架是一种开发混合模式移动应用(HybridApp)的基础框架,在借鉴了CordovaAppCan等业界主流混合模式开发平台设计思想的基础上,结合Android  Fragment模式开发的优点,设计了基于Fragment的混合模式开发框架。

本文demo下载:http://www.wisdomdd.cn/Wisdom/resource/articleDetail.htm?resourceId=397

1.      开发工程指导

工程结构

工程的res/xml文件夹中有两个配置文件

1、 config.xml:配置js层调用native层的插件配置

配置插件:

 <feature name="OdpfPlugin">        <param name="android-package" value="com.zte.umap.odpf.plugin.FragmentPlugin" />    </feature>          <feature name="SoftKeyBoard">        <param name="android-package" value="com.zte.umap.odpf.plugin.IonicKeyboard" />    </feature>         <feature name="ImageCache">        <param name="android-package" value="com.zte.umap.odpf.plugin.ImageCachePlugin" />    </feature>
2url.xml:配置自定义本地viewurl的映射关系,内容示例:
<?xml version="1.0" encoding="UTF-8"?><UrlMap><url name="contact.html" value = "com.zte.umap.test. ContactFragment"/><url name="tabFragment.html" value = "com.zte.umap.test.TabFragment"/></UrlMap>

Web页面放置于工程的assets/www文件夹下,必须引用 cordova.js和odpf.js文件。

Loading提示效果

Web页面加载需要时间,这时可以在界面中加载loading提示,默认显示的是白底转圈的loading提示,当业务应用需要自定义特殊loading提示时,可以自定义名为布局xml文件放置与应用的layout目录下。在相应接口传递该xml 文件名,Odpf会自动读取加载该视图作为页面加载过程中的loading视图。

应用的入口

需要新建一个Activity,并且定义一个布局文件,监听back事件

package com.zte.umap.test;      import com.zte.umap.odpf.FirstPageCompleteInterface;import com.zte.umap.odpf.ODPFragmentPool;import com.zte.umap.odpf.util.ResUtil;import android.app.Activity;import android.content.Context;  import android.util.Log;import android.view.KeyEvent;import android.view.View;import android.view.ViewTreeObserver.OnGlobalLayoutListener;import android.widget.FrameLayout;import android.widget.ImageView;import android.widget.RelativeLayout;    public class MainActivity extends FragmentActivity implements FirstPageCompleteInterface{       public static String TAG = "MainActivity";                public ODPFragmentPool fragmentPool;              private FrameLayout layout;       private ImageView splashCreenImg;                    @Override    public void onCreate(Bundle savedInstanceState)    {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_fragment);           Log.i(TAG ,"onCreate" );             // 这个布局必须为FrameLayout类型              layout = (FrameLayout) findViewById(R.id.id_content);           splashCreenImg = (ImageView) findViewById(R.id.imgSplashscreen);                  //初始化资源文件获取类           ResUtil.init(getApplicationContext());//获取window池实例           fragmentPool = ODPFragmentPool.getInstance(this);    //首页加载的纯web页面,设置加载结束通知接口           fragmentPool.setFirstPageCompleteInterface(this);   //加载首页           fragmentPool.openInit( R.id.id_content,"index.html",2,4,”progress”);                                  }             /**back 键的拦截*/    @Override    public boolean onKeyUp(int keyCode, KeyEvent event)    {     / /首先调用池的后退处理               if (fragmentPool.goBack()) {                     return true;              } else {                     return super.onKeyUp(keyCode, event);              }      }            @Override    public boolean onKeyDown(int keyCode, KeyEvent event)    {       //判断池的后退处理    if(!fragmentPool.isIndex()){         return true;           }else   {        Log.i(TAG, "onkey down");        return super.onKeyDown(keyCode, event);    }    }      @Override    protected void onDestroy()    {           Log.i(TAG," main fragment destroy");  //调用池的销毁处理           fragmentPool.destroy();        super.onDestroy();               }      /**该接口内隐去欢迎界面,展示首页*/       @Override       public void firstPageProgressComplete() {              // TODO Auto-generated method stub              splashCreenImg.setVisibility(View.GONE);              layout.setVisibility(View.VISIBLE);       }  }
布局文件:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent" >          <FrameLayout        android:id="@+id/id_content"        android:layout_width="fill_parent"        android:layout_height="fill_parent"               />      <ImageView         android:id="@+id/imgSplashscreen"        android:layout_width="fill_parent"        android:layout_height="fill_parent"        android:layout_centerHorizontal="true"        android:layout_centerVertical="true"        android:src="@drawable/startup_bg_16_9"/>         </RelativeLayout>

2.      JS SDK API

window.umap.exec

用途:调用native方法,异步调用

结构

function exec(var json, function success, function fail);

说明

a)      返回值:无

b)     输入参数:jsonnative方法与参数定义(如:{“cls”:”Test”,“fn”:”echo”,”params”:[“str”,”hello”]},其中cls表示class名称,fn表示方法名称,params表示参数列表)

c)      输入参数:success:成功时的回调函数

d)     输入参数:fail:失败时的回调函数

 

使用方式

1.首先自己实现NativePlugin

2.res/config.xml配置plugin

3.js中定义json格式串

  var odpf_hello = {"cls":"HelloWorld","fn":"hello","params":["value"]};

4.调用window.umap.exec

 

window.umap.open

用途:加载新页面

结构

function open(var json, function success, function fail);

说明

a)  返回值:无

b) 输入参数:json,输入参数定义,如:var page1_url = {"url":"onePage.html","anim_type":"1","isOpenNewWindow":"true",”priority”:”3” "viewId":"0"};

其中url:新页面地址,http://网络新链接;file://本地html页面可以省略assets/www的前缀,默认就是这个目录;content://native页面,如果在配置文件中找到以url参数为keyvalue值,则加载value代表的页面,如果没有找到,才加载url代表的页面;

 isOpenNewWindow:这个值置为true表示 目标页采用新的window,则当前页面压栈,若false,则表示是在当前页面重新打开,当前页不压栈,举个例子说明如果:页面跳转至B页面,B再跳转至C页面, BC页面跳转时设定isOpenNewWindowtrue,C后退后返回至B页面,否则返回至A页面;


anim_type:动画类型,1:新页面左入,旧页面右出,2:新页面右入,旧页面左出,3:新页面上入,旧页面下出,4:新页面下入,旧页面上出,5淡入淡出,9右切入,10左切入,11上切入,12下切入,13左切出、14右切出、15上切出、16下切出,其他数值则无动画;

priority:新页面的优先级,优先级从0,数字越大优先级越高

c)  输入参数:success:成功时的回调函数

d)  输入参数:fail:失败时的回调函数

 

viewed:打开的新页面的视图容器id,默认情况下都为0为应用入口openInit 传递的view对应的id,当你需要切换为视图中部分加载页面时,这个id 需相应的改变,对应于openInNewView中设置的viewid, 如果不传递该参数,平台则取默认值0

window.umap.back

用途:页面回退

结构

function back(function success, function fail);

说明

a)      返回值:无

b)     输入参数:success:成功时的回调函数

c)      输入参数:fail:失败时的回调函数

 

window.umap.clearHistory

用途:清空历史记录

结构

function clearHistory (function success, function fail);

说明

a)      返回值:无

b)     输入参数:success:成功时的回调函数

c)      输入参数:fail:失败时的回调函数

window.umap.clearRecycle

用途:清空缓存栈

结构

function clearRecycle (function success, function fail);

说明

d)     返回值:无

e)     输入参数:success:成功时的回调函数

f)      输入参数:fail:失败时的回调函数

 

Js访问Native存储

1本地端设置值

获取ODPFragmentPool的对象:

ODPFragmentPool fragmentPool = ODPFragmentPool.getInstance(context);

设置需要保存的值:

public void setKeyValue(String key, String value)

例如:

fragmentPool. setKeyValue(“ip”,”10.46.75.26”)

 

2、JS端获取本地端保存的值:(key值要与本地端的一致)

     var ip = Odpf.getValue(“ip”);

 

Jsnative互相调用

Jsnative的方法即cordova插件的编写

native层编写相应的类和方法,类继承于CordovaPlugin,实现execute方法,在配置文件中配置,并在js层封装接口

1Native层插件类示例:

public class IonicKeyboard extends CordovaPlugin{      public void initialize(CordovaInterface cordova, CordovaWebView webView) {        super.initialize(cordova, webView);          //calculate density-independent pixels (dp)        //http://developer.android.com/guide/practices/screens_support.html        DisplayMetrics dm = new DisplayMetrics();        cordova.getActivity().getWindowManager().getDefaultDisplay().getMetrics(dm);        final float density = dm.density;          final CordovaWebView appView = webView;          //http://stackoverflow.com/a/4737265/1091751 detect if keyboard is showing        final View rootView = cordova.getActivity().getWindow().getDecorView().findViewById(android.R.id.content).getRootView();        OnGlobalLayoutListener list = new OnGlobalLayoutListener() {            int previousHeightDiff = 0;            @Override            public void onGlobalLayout() {                Rect r = new Rect();                //r will be populated with the coordinates of your view that area still visible.                rootView.getWindowVisibleDisplayFrame(r);                  int heightDiff = rootView.getRootView().getHeight() - (r.bottom);                int pixelHeightDiff = (int)(heightDiff / density);                if (pixelHeightDiff > 100 && pixelHeightDiff != previousHeightDiff) { // if more than 100 pixels, its probably a keyboard...                    appView.sendJavascript("cordova.plugins.Keyboard.isVisible = true");                    appView.sendJavascript("cordova.fireWindowEvent('native.keyboardshow', { 'keyboardHeight':" + Integer.toString(pixelHeightDiff)+"});");                      //deprecated                    appView.sendJavascript("cordova.fireWindowEvent('native.showkeyboard', { 'keyboardHeight':" + Integer.toString(pixelHeightDiff)+"});");                }                else if ( pixelHeightDiff != previousHeightDiff && ( previousHeightDiff - pixelHeightDiff ) > 100 ){                    appView.sendJavascript("cordova.plugins.Keyboard.isVisible = false");                    appView.sendJavascript("cordova.fireWindowEvent('native.keyboardhide')");                      //deprecated                    appView.sendJavascript("cordova.fireWindowEvent('native.hidekeyboard')");                }                previousHeightDiff = pixelHeightDiff;             }        };          rootView.getViewTreeObserver().addOnGlobalLayoutListener(list);    }      public boolean execute(String action, JSONArray args, final CallbackContext callbackContext) throws JSONException {           Log.e("Ionickeyboard"," plugin execute");        if ("hide".equals(action)) {            cordova.getThreadPool().execute(new Runnable() {                public void run() {                    //http://stackoverflow.com/a/7696791/1091751                    InputMethodManager inputManager = (InputMethodManager) cordova.getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);                    View v = cordova.getActivity().getCurrentFocus();                      if (v == null) {                        callbackContext.error("No current focus");                    } else {                       Log.e("Ionickeyboard"," plugin execute hide");                        inputManager.hideSoftInputFromWindow(v.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);                        callbackContext.success(); // Thread-safe.                    }                }            });            return true;        }        if ("show".equals(action)) {            cordova.getThreadPool().execute(new Runnable() {                public void run() {                  Log.e("Ionickeyboard"," plugin execute show");                    ((InputMethodManager) cordova.getActivity().getSystemService(Context.INPUT_METHOD_SERVICE)).toggleSoftInput(0, InputMethodManager.HIDE_IMPLICIT_ONLY);                    callbackContext.success(); // Thread-safe.                }            });            return true;        }        return false;  // Returning false results in a "MethodNotFound" error.    }

2.config.xml中配置
<feature name="SoftKeyBoard">        <param name="android-package" value="com.zte.umap.odpf.plugin.IonicKeyboard" /></feature>

3.编写相应Js文件,封装接口

skbShow:function(success, fail){              cordova.exec(                     success,                     fail,                      "SoftKeyBoard",                      "show",                      []);             },         skbHide:function(success, fail){              cordova.exec(                     success,                     fail,                      "SoftKeyBoard",                      "hide",                      []);             }

Odpf中合入了IonicKeyboard插件,具体的使用方式请参见Ionic的介绍

 

 

Native调用js

首先在js层定义一个方法


document.addEventListener('reDrawView', function (event) {    alert(event. param) ;}, false);
native,使用WebviewFragment实例的loadUrl方法
String param = “index”;webviewFragment.loadUrl("javascript:reDrawView('"+ param +"');");

3.      ODPFragmentPool

Odpf框架的主类,可以理解为window池,一个window 对应一个不同类型的页面视图。一个页面依附于fragmentfragment又嵌套在window内。该widow视图可以是纯web页面、可以是自定义的本地view、或者是本地Viewweb页面的混合,利用该类接口可以实现页面之间的切换和管理。

 

接口说明

public static ODPFragmentPool getInstance(Context context)

功能

获取ODPF池对象

参数

context

上下文

返回值

ODPFragmentPool的单例对象

说明


 

public BaseFragment openInit(FrameLayout view, String url, int priority, int capacity, String FirstProgress)

功能

应用首页加载接口,为应用的入口部分

参数

view

摆放页面的布局容器view,该布局必须是FrameLayout


url

载入的页面,一个页面对应一个windowurl需要带参数时,格式为 xxx.html#a=b ”#”分割,后面带参数。

url指向的是本地位于assets/www文件夹下的web文件,当有相对路径时要写上

 


priority

该页面的优先级,当池存储的容量大于限额时,需要删除存储的window,优先级低的删除的概率大,介于09之间


capacity

池容量


FirstProgress

一级视图页面加载时的loading 布局xml 文件名例如“progress_bar

返回值

用于显示首页的fragment,每个fragment依附于一个window之上

说明

本地调用

 

 

 

public BaseFragment openInNewView(FrameLayout view ,int viewId ,String url, int priority, String secdProgress)

功能

应用加载二级视图时接口,为应用的入口部分

参数

view

摆放二级页面的视图容器view,该布局必须是FrameLayout


ViewId

用于标示该view id ,必须大于0


url

载入的页面,一个页面对应一个windowurl需要带参数时,格式为 xxx.html#a=b ”#”分割,后面带参数。

url指向的是本地位于assets/www文件夹下的web文件,当有相对路径时要写上

 


priority

该页面的优先级,当池存储的容量大于限额时,需要删除存储的window,优先级低的删除的概率大,介于09之间


secdProgress

二级视图页面加载时的loading 布局xml 文件名例如“progress_bar2

返回值

用于显示二级视图页面的fragment,每个fragment依附于一个window之上

说明

本地调用

 

 

 

public void setFirstPageCompleteInterface(FirstPageCompleteInterface firstPageComplete)

功能

应用首页为纯web页面时,页面数据加载完成的通知接口

参数

firstPageComplete

实现了FirstPageCompleteInterface的类对象

返回值


说明

因为一个纯web页面加载是需要时间的,尤其复杂页面会出现长时间的白屏,这时可以给应用添加一个欢迎页面,当首页加载完成后再隐藏欢迎页,show出首页。

 

public void open( String url ,int viewId int anim_type, Boolean isOpenNewWindow, int priority )

功能

应用加载新页面

参数

url

载入的页面,一个页面对应一个windowurl指向的是本地位于assets/www文件夹下的web文件,当有相对路径时要写上。

url需要带参数时,格式为 xxx.html#a=b ”#”分割,后面带参数。


viewId

需要打开的新视图 需要在哪个view 容器上展示,最根级的目录 id ,其余viewid openInNewView中传递的id 一致


anim_type

页面载入时的动画切换类型,

1表示右入,

2表示左入,

3:新页面上入,旧页面下出,

4:新页面下入,旧页面上出,

5淡入淡出,

输入其他数值则没有动画;

当该页面回退时,动画与载入相反


isOpenNewWindow

这个值置为true表示 目标页采用新的window,则当前页面压栈,若false,则表示是在当前页面重新打开,当前页不压栈,举个例子说明如果:页面跳转至b页面,b再跳转至C页面, bc页面跳转时设定isOpenNewWindowtrue,c后退后返回至b页面,否则返回至a页面;


priority

该页面的优先级,当池存储的容量大于限额时,需要删除存储的window,优先级低的删除的概率大,介于09之间,值越大优先级越高。

返回值

用于显示首页的fragment,每个fragment依附于一个window之上

说明

该接口封装至js层,可以在web页面直接调用进行页面跳转

 

 

public Boolean isIndex()

功能

当前页面是不是首页,已无可以回退的历史页面

返回值

true表示 无可以回退的历史页

说明

主要用于物理键back的拦截事件

 

public Boolean goBack()

功能

回退

返回值

true表示 可以回退

说明

主要用于物理键back的拦截事件,进行后退操作。该接口封装至js层,可以进行页面的后退

 

public void clearHistory()

功能

清空历史记录

返回值


说明

清空之前的历史页面记录。该接口封装至js层,可以清空之前的历史记录

 

public void clearRecycle ()

功能

清空缓存栈

返回值


说明

清空之前的缓存对象。该接口封装至js层,可以清空之前的缓存对象

 

public void setKeyValue(String key, String value)

功能

设置属性值,用于js层获取数据

返回值


说明

js层可以通过注入的对象获取到设置的属性值,

js层调用方式为Odpf.getValue(String    key)

 

 

public BaseFragment getFragmentByUrlKey(String url)

功能

获取池里面的fragment对象

参数

Open接口中传递的url,但是不带参数,截取的是“#”之前的字段

返回值

url 对应的池中的fragment对象

说明


 

 

FirstPageCompleteInterface口类

public interface FirstPageCompleteInterface{         public void firstPageProgressComplete ();}

4.      BaseFragement

自定义本地页面,当需要载入纯本地页面或者一个页面中即有部分本地控件,又有web页面时就用到了BaseFragment类。

自定义的native页面是以fragment形式加入到window池中的,所有自定义的视图需继承BaseFragment类。

该类中定义了几个方法可以用于子类使用。当载入一个本地自定义页面,也需要用ODPFragmentPoolopen或者openInit方法传入一个url,这个url不是www文件夹中的页面而是在url.xml中配置的,他指向了实际加载的native类。url.xmlname可以只配置url域名,不用带上参数。

接口说明

public String  getUrlName()

功能

获取open接口传递的url参数,主要是用于获取url中的各个参数

返回值


说明

清空之前的历史页面记录。该接口封装至js层,可以清空之前的历史记录

 

public void ODPCreate()

功能

生命周期事件之create

返回值


说明

在这里实现自定义本地页面的create事件,具体时机见页面生命周期说明

 

 

public void ODPDestroy()

 

功能

生命周期事件之Destroy

返回值


说明

在这里实现自定义本地页面的Destroy事件

public void ODPResume()

功能

生命周期事件之Resume

返回值


说明

在这里实现自定义本地页面的Resume事件

 

public void ODPPause()

功能

生命周期事件之Pause

返回值


说明

在这里实现自定义本地页面的Pause事件

 

5.      WebviewFragment

该类的使用场景为自定义native页面中需要摆放web页面。WebviewFragment类继承于fragment,布局中放置了webview控件,用于加载web页面。

接口说明

public static WebviewFragment newInstance(String url)

功能

创建WebviewFragment类的实例,传递需要载入的url,当fragment视图创建后,自动载入该url

参数

需要载入的urlurl指向的是本地位于assets/www文件夹下的web文件,当有相对路径时要写上

返回值

WebviewFragment实例

说明

fragment视图创建后,自动载入该url

 

public void loadUrl(String url)

功能

webview控件中 加载url

参数

需要载入的urlurl指向的是本地位于assets/www文件夹下的web文件,当有相对路径时要写上,可以加载js代码,

返回值


说明

相当于webViewloadurl 方法

 

 

public void setPageFinishedInterface(PageFinishedInterface pageFinished  )

功能

页面加载完成的通知接口

参数

PageFinishedInterface

实现了PageFinishedInterface的类对象

返回值

使用场景:因为web页面加载需要时间的,尤其复杂页面会出现长时间的白屏,这时可以给应用添加一个loading视图,当首页加载完成后再隐藏showweb页面。

 

public void setPageStartedInterface(PageStartedInterface pageStarted  )

功能

页面开始加载的通知接口

参数

pageStarted 

实现了PageStartedInterface的类对象

返回值


 

 

Webview 对象

public CordovaWebView appView,可以用WebviewFragement示例获取

 

PageFinishedInterface接口类

public interface PageFinishedInterface{       public void onPageFinished (WebviewFragment fragment,String url);}

参数说明:

fragment:当前WebviewFragment 的实例,

url:当前WebviewFragment加载的url

 

PageStartedInterface接口类

public interface PageStartedInterface{          public void onPageStarted (WebviewFragment fragment,String url);    } 

参数说明:

fragment:当前WebviewFragment 的实例,

url:当前WebviewFragment加载的url

 

FragmentHiddenInterface接口类

public interface FragmentHiddenInterface{

   

    public void onFragmentHidden ();

}

WebviewFragment,通过onHiddenChanged接口触发的实例隐藏时,调用该接口,并且调用页面的ODPFPause事件,通知页面回到后台,从而相应的做一些处理

 

FragmentShownInterface接口类

public interface FragmentShownInterface{

   

    public void onFragmentShown ();

}

WebviewFragment,通过onHiddenChanged接口触发的实例显示时,调用该接口,并且调用页面的ODPFResume事件,通知页面回到前台,从而相应的做一些处理

 

6.      页面生命周期事件

类似于AndroidActivity,window池中的每个window也有类似的生命周期事件。有四种ODPCreateODPDestroyODPResumeODPPause

 

ODPCreate事件:

n  web 页面

Js接口为ODPCreate , 参数为url 截取“#”之后的参数字符串

document.addEventListener(ODPCreate, function (event) { 

 

//add function content

  alert(event.url) ;

}, false);

n  自定义本地页面

接口为

public void ODPCreate(),而url可以通过getUrlName()方法获取,这里获取的是整url

 

window为非首次创建,加载视图时,调用该事件。一般用于window 在池的堆栈中复用的情况。

如图:b页面被复用的条件:

首先b页面必须曾经加载过。

b页面在栈里还有记录,b页面window实例存储于实例池中;

b页面已经被回退过,栈里已经没有记录,或者没有被压栈,但是在window实例池中因为没有超过容量限制,b页面的window实例依然在window实例池中。

当再次打开b页面的时候,b页面对应的window实例展示到屏幕中,并调用ODPCreate事件。每次打开b页面时传递的url ,可携带不同的参数,这些值可以获取到,在ODPCreate方法里做一些页面数据的调整。

  

ODPDestroy事件

n  web页面

Js接口为ODPDestroy,无参数

document.addEventListener(ODPDestroy, function (event) { 

 

//add function content

 }, false);

n  自定义本地页面

接口为

public void ODPDestroy ()

 

 当用户按后退,离开当前window, 或者当前window不压栈而打开新window,当前window调用该事件。

如图,a页面打开b页面后,再backa页面时,b页面调用ODPDestroy方法,进行一些必要的清理

或者 c页面打开b页面,isOpenNewWindowfalse,在当前页面重新打开,当前c页不压栈,则c页面调用ODPDestroy方法,进行一些必要的清理

 


ODPPause事件

n  web页面

Js接口为ODPPause,无参数

document.addEventListener(ODPPause, function (event) { 

 

//add function content

 }, false);

n  自定义本地页面

接口为

public void ODPPause ()

 当用户打开新window,离开当前window,当前window压栈,回到后台时调用该事件。

 

如图:a页面点击链接跳转至b页面,a页面调用ODPPause事件处理例如暂停播放器等操作,回到后台

ODPResume事件

n  web页面

Js接口为ODPResume,无参数

document.addEventListener(ODPResume, function (event) { 

 

//add function content

 }, false);

n  自定义本地页面

接口为

public void ODPResume ()

 当用户需要后退,回到前一个window,前一个window回到前台时调用该事件。

 

如图:a页面点击链接打开b页面,然后在b页面点击后退,回到a页面时,a页面调用ODPResume事件开启页面展示动画等操作,回到前台。





下载demo:http://www.wisdomdd.cn/Wisdom/resource/articleDetail.htm?resourceId=397











阅读全文
0 0
原创粉丝点击