月度归档: 2021 年 3 月

Python解释器和IPython

Python解释器和IPython

简介
今天给大家介绍一下Python的一个功能非常强大的解释器IPython。虽然Python本身自带解释器,但是相对而言IPython的功能更加的强大。

Python解释器
Python是自带解释器的,我们在命令行输入python即可进入python的解释器环境:

$> python
Python 2.7.15 (default, Oct 2 2018, 11:47:18)
[GCC 4.2.1 Compatible Apple LLVM 10.0.0 (clang-1000.11.45.2)] on darwin
Type “help”, “copyright”, “credits” or “license” for more information.
>>> site = “www.flydean.com”
>>> site
‘www.flydean.com’
>>>

python解释器的提示符是>>>。

python提供了一个非常有用的命令help,我们可以使用help来查看要使用的命令。

>>> help
Type help() for interactive help, or help(object) for help about object.

在Python3中,还提供了tab的补全功能:

>>> site
‘www.flydean.com’
>>> site.
site.capitalize( site.expandtabs( site.isalpha( site.isprintable( site.lower( site.rindex( site.splitlines( site.upper(
site.casefold( site.find( site.isdecimal( site.isspace( site.lstrip( site.rjust( site.startswith( site.zfill(
site.center( site.format( site.isdigit( site.istitle( site.maketrans( site.rpartition( site.strip(
site.count( site.format_map( site.isidentifier( site.isupper( site.partition( site.rsplit( site.swapcase(
site.encode( site.index( site.islower( site.join( site.replace( site.rstrip( site.title(
site.endswith( site.isalnum( site.isnumeric( site.ljust( site.rfind( site.split( site.translate(

使用起来非常的方便。

和Python自带的解释器之外,还有一个更加强大的解释器叫做IPython。我们一起来看看。

IPython
IPython是一个非常强大的解释器,通常它是和jupyter notebook一起使用的。在IPython3.X中,IPython和Jupyter是作为一个整体一起发布的。但是在IPython4.X之后,Jupyter已经作为一个单独的项目,从IPython中分离出来了。

使用IPython很简单,输入IPython命令即可:

$> ipython
Python 3.6.4 |Anaconda, Inc.| (default, Jan 16 2018, 12:04:33)
Type ‘copyright’, ‘credits’ or ‘license’ for more information
IPython 6.2.1 — An enhanced Interactive Python. Type ‘?’ for help.

In [1]: site= “www.flydean.com”

In [2]: site
Out[2]: ‘www.flydean.com’

IPython的提示符是In [1]:

基本上Python自带的命令在IPython中都是可以使用的。

IPython提供了4个非常有用的命令:

command description
? Introduction and overview of IPython’s features.
%quickref Quick reference.
help Python’s own help system.
object? Details about ‘object’, use ‘object??’ for extra details.
魔法函数
IPython中有两种魔法函数,一种是Line magics,一种是Cell magics。

Line magics 接收本行的输入作为函数的输入,是以%开头的。而Cell magics可以接收多行的数据,直到你输入空白回车为止。是以%%开头的。

比如我们想要看一个timeit的魔法函数的用法,可以使用Object?来表示:

$> In [4]: %timeit?
Docstring:
Time execution of a Python statement or expression

Usage, in line mode:
%timeit [-n<N> -r<R> [-t|-c] -q -p<P> -o] statement
or in cell mode:
%%timeit [-n<N> -r<R> [-t|-c] -q -p<P> -o] setup_code
code
code…

timeit用来统计程序的执行时间,我们分别看下Line magics和Cell magics的使用:

In [4]: %timeit?

In [5]: %timeit range(1000)
199 ns ± 3.8 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

In [6]: %%timeit range(1000)
…: range(1000)
…:
208 ns ± 12.1 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

事实上,如果只是LIne magics的话,我们可以省略前面的%,但是对于Cell magics来说,是不能省略的。

In [7]: timeit range(1000)

200 ns ± 4.03 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

常见的魔法函数有下面几种:

代码相关的: %run, %edit, %save, %macro, %recall, etc.
shell环境相关的: %colors, %xmode, %automagic, etc.
其他的函数: %reset, %timeit, %%writefile, %load, or %paste.
运行和编辑
使用%run 可以方便的运行外部的python脚本。

In [8]: run?
Docstring:
Run the named file inside IPython as a program.

Usage::

%run [-n -i -e -G]
[( -t [-N<N>] | -d [-b<N>] | -p [profile options] )]
( -m mod | file ) [args]

run有几个非常有用的参数,比如-t 可以用来统计程序的时间。-d可以进行调试环境,-p可以进行profiler分析。

使用%edit 可以编辑多行代码,在退出之后,IPython将会执行他们。

如果不想立即执行的话,可以加上-x参数。

Debug
可以使用%debug 或者 %pdb 来进入IPython的调试环境:

In [11]: debug
> /Users/flydean/.pyenv/versions/anaconda3-5.1.0/lib/python3.6/site-packages/IPython/core/compilerop.py(99)ast_parse()
97 Arguments are exactly the same as ast.parse (in the standard library),
98 and are passed to the built-in compile function.”””
—> 99 return compile(source, filename, symbol, self.flags | PyCF_ONLY_AST, 1)
100
101 def reset_compiler_flags(self):

ipdb>

In [12]: pdb
Automatic pdb calling has been turned ON

In [13]: pdb
Automatic pdb calling has been turned OFF

或者可以使用 %run -d theprogram.py 来调试一个外部程序。

History
IPython可以存储你的输入数据和程序的输出数据,IPython的一个非常重要的功能就是可以获取到历史的数据。

在交互环境中,一个简单的遍历历史输入命令的方式就是使用up- 和 down- 箭头。

更强大的是,IPython将所有的输入和输出都保存在In 和 Out这两个变量中,比如In[4]。

In [1]: site = “www.flydean.com”

In [2]: site
Out[2]: ‘www.flydean.com’

In [3]: In
Out[3]: [”, ‘site = “www.flydean.com”‘, ‘site’, ‘In’]

可以使用 _ih[n]来访问特定的input:

In [4]: _ih[2]
Out[4]: ‘site’

_i, _ii, _iii 可以分别表示前一个,前前一个和前前前一个输入。

除此之外,全局变量 _i 也可以用来访问输入,也就是说:

_i<n> == _ih[<n>] == In[<n>]
_i14 == _ih[14] == In[14]

同样的,对于输出来说也存在着三种访问方式:

_<n> == _oh[<n>] == Out[<n>]
_12 == Out[12] == _oh[12]

*后的三个输出也可以通过 _, __ 和 ___来获取。

还可以使用%history来列出之前的历史数据进行选择。

history可以和 %edit,%rerun,%recall,%macro,%save和%pastebin 配和使用:

通过传入数字,可以选择历史的输入行号。

%pastebin 3 18-20

上面的例子会选择第3行和第18-20行输入。

运行系统命令
使用!可以直接运行系统命令:

In [27]: !pwd
/Users/flydean/Downloads

还可以用变量接收运行的结果,比如 : files = !ls

Android Fragment 你应该知道的一切

Android Fragment 你应该知道的一切

之前写过两篇Fragment的介绍,主要就是介绍其功能:Android Fragment 真正的完全解析(上)和Android Fragment 真正的完全解析(下) 有兴趣的可以凑合看下。本文目标教你如何用好Fragment,即Fragment的一些使用的建议,(多数内容来自:android programming the big nerd ranch guide 一书,直接百度,你懂的,虽然是基础书籍,还是很值得一看的)。

1、概述
首先我们简单回顾一下,相信大家对Fragment的都不陌生,对于Fragment的使用,一方面Activity需要在布局中为Fragment安排位置,另一方面需要管理好Fragment的生命周期。Activity中有个FragmentManager,其内部维护fragment队列,以及fragment事务的回退栈。

一般情况下,我们在Activity里面会这么添加Fragment:

public class MainActivity extends FragmentActivity
{

private ContentFragment mContentFragment ;

@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

FragmentManager fm = getSupportFragmentManager();
mContentFragment = (ContentFragment) fm.findFragmentById(R.id.id_fragment_container);

if(mContentFragment == null )
{
mContentFragment = new ContentFragment();
fm.beginTransaction().add(R.id.id_fragment_container,mContentFragment).commit();
}

}

}

针对上面代码,问两个问题:
1、为什么需要判null呢?

主要是因为,当Activity因为配置发生改变(屏幕旋转)或者内存不足被系统杀死,造成重新创建时,我们的fragment会被保存下来,但是会创建新的FragmentManager,新的FragmentManager会首先会去获取保存下来的fragment队列,重建fragment队列,从而恢复之前的状态。

2、add(R.id.id_fragment_container,mContentFragment)中的布局的id有何作用?

一方面呢,是告知FragmentManager,此fragment的位置;另一方面是此fragment的唯一标识;就像我们上面通过fm.findFragmentById(R.id.id_fragment_container)查找~~

好了,简单回顾了一下基本用法,具体的还请参考上面的博客或者其他资料,接下来,介绍一些使用的意见~~

2、Fragment Arguments
下面描述一个简单的场景,比如我们某个按钮触发Activity跳转,需要通过Intent传递参数到目标Activity的Fragment中,那么此Fragment如何获取当前的Intent的值呢?

有哥们会说,这个简单?看我的代码(问题代码):

public class ContentFragment extends Fragment
{

private String mArgument ;
public static final String ARGUMENT =”argument”;
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);

mArgument = getActivity().getIntent().getStringExtra(ARGUMENT);

}

我们直接在Fragment的onCreate中,拿到宿主Activty,宿主Activity中肯定能通过getIntent拿到Intent,然后通过get方法,随意拿参数~~
这么写,功能上是实现了,但是呢?存在一个大问题:我们用Fragment的一个很大的原因,就是为了复用。你这么写,相当于这个Fragment已经完全和当前这个宿主Activity绑定了,复用直接废了~~~所以呢?我们换种方式,推荐使用arguments来创建Fragment。

public class ContentFragment extends Fragment
{

private String mArgument;
public static final String ARGUMENT = “argument”;

@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
// mArgument = getActivity().getIntent().getStringExtra(ARGUMENT);
Bundle bundle = getArguments();
if (bundle != null)
mArgument = bundle.getString(ARGUMENT);

}

/**
* 传入需要的参数,设置给arguments
* @param argument
* @return
*/
public static ContentFragment newInstance(String argument)
{
Bundle bundle = new Bundle();
bundle.putString(ARGUMENT, argument);
ContentFragment contentFragment = new ContentFragment();
contentFragment.setArguments(bundle);
return contentFragment;
}

给Fragment添加newInstance方法,将需要的参数传入,设置到bundle中,然后setArguments(bundle),*后在onCreate中进行获取;
这样就完成了Fragment和Activity间的解耦。当然了这里需要注意:

setArguments方法必须在fragment创建以后,添加给Activity前完成。千万不要,首先调用了add,然后设置arguments。

3、Fragment的startActivityForResult
依旧是一个简单的场景:两个Fragment,一个展示文章列表的Fragment(叫做ListTitleFragment),一个显示详细信息的Fragment(叫做:ContentFragment),当然了,这两个Fragment都有其宿主Activity。

现在,我们点击列表Fragment中的列表项,传入相应的参数,去详细信息的Fragment展示详细的信息,在详细信息页面,用户可以进行点评,当用户点击back以后,我们以往点评结果显示在列表的Fragment对于的列表项中;

也就是说,我们点击跳转到对应Activity的Fragment中,并且希望它能够返回参数,那么我们肯定是使用Fragment.startActivityForResult ;

在Fragment中存在startActivityForResult()以及onActivityResult()方法,但是呢,没有setResult()方法,用于设置返回的intent,这样我们就需要通过调用getActivity().setResult(ListTitleFragment.REQUEST_DETAIL, intent);。

详细代码:

ListTitleFragment

public class ListTitleFragment extends ListFragment
{

public static final int REQUEST_DETAIL = 0x110;
private List<String> mTitles = Arrays.asList(“Hello”, “World”, “Android”);
private int mCurrentPos ;
private ArrayAdapter<String> mAdapter ;

@Override
public void onActivityCreated(Bundle savedInstanceState)
{
super.onActivityCreated(savedInstanceState);
setListAdapter(mAdapter = new ArrayAdapter<String>(getActivity(), android.R.layout.simple_list_item_1, mTitles));
}

@Override
public void onListItemClick(ListView l, View v, int position, long id)
{
mCurrentPos = position ;
Intent intent = new Intent(getActivity(),ContentActivity.class);
intent.putExtra(ContentFragment.ARGUMENT, mTitles.get(position));
startActivityForResult(intent, REQUEST_DETAIL);
}

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data)
{
Log.e(“TAG”, “onActivityResult”);
super.onActivityResult(requestCode, resultCode, data);
if(requestCode == REQUEST_DETAIL)
{
mTitles.set(mCurrentPos, mTitles.get(mCurrentPos)+” — “+data.getStringExtra(ContentFragment.RESPONSE));
mAdapter.notifyDataSetChanged();
}
}
}

ContentFragment
public class ContentFragment extends Fragment
{

private String mArgument;
public static final String ARGUMENT = “argument”;
public static final String RESPONSE = “response”;

@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
Bundle bundle = getArguments();
if (bundle != null)
{
mArgument = bundle.getString(ARGUMENT);
Intent intent = new Intent();
intent.putExtra(RESPONSE, “good”);
getActivity().setResult(ListTitleFragment.REQUEST_DETAIL, intent);
}

}

public static ContentFragment newInstance(String argument)
{
Bundle bundle = new Bundle();
bundle.putString(ARGUMENT, argument);
ContentFragment contentFragment = new ContentFragment();
contentFragment.setArguments(bundle);
return contentFragment;
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState)
{
Random random = new Random();
TextView tv = new TextView(getActivity());
tv.setText(mArgument);
tv.setGravity(Gravity.CENTER);
tv.setBackgroundColor(Color.argb(random.nextInt(100),
random.nextInt(255), random.nextInt(255), random.nextInt(255)));
return tv;
}
}

贴出了两个Fragment的代码,可以看到我们在ListTitleFragment.onListItemClick,使用startActivityForResult()跳转到目标Activity,在目标Activity的Fragment(ContentFragment)中获取参数,然后调用getActivity().setResult(ListTitleFragment.REQUEST_DETAIL, intent);进行设置返回的数据;*后在ListTitleFragment.onActivityResult()拿到返回的数据进行回显;
为大家以后在遇到类似问题时,提供了解决方案;也说明了一个问题:fragment能够从Activity中接收返回结果,但是其自设无法产生返回结果,只有Activity拥有返回结果。

接下来我要贴一下,这两个Fragment的宿主Activity:

ListTitleActivity

public class ListTitleActivity extends FragmentActivity
{

private ListTitleFragment mListFragment;

@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_single_fragment);

FragmentManager fm = getSupportFragmentManager();
mListFragment = (ListTitleFragment) fm.findFragmentById(R.id.id_fragment_container);

if(mListFragment == null )
{
mListFragment = new ListTitleFragment();
fm.beginTransaction().add(R.id.id_fragment_container,mListFragment).commit();
}

}
}

ContentActivity:

public class ContentActivity extends FragmentActivity
{

private ContentFragment mContentFragment;

@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_single_fragment);

FragmentManager fm = getSupportFragmentManager();
mContentFragment = (ContentFragment) fm.findFragmentById(R.id.id_fragment_container);

if(mContentFragment == null )
{
String title = getIntent().getStringExtra(ContentFragment.ARGUMENT);
mContentFragment = ContentFragment.newInstance(title);
fm.beginTransaction().add(R.id.id_fragment_container,mContentFragment).commit();
}

}
}

有没有发现两个Activity中的代码*其的类似,且使用了同一个布局文件:

activity_single_fragment.xml

<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”
android:id=”@+id/id_fragment_container”
>

</RelativeLayout>

为什么要贴这Acticity的代码呢?因为我们项目中,如果原则上使用Fragment,会发现大量类似的代码,那么我们就可以抽象一个Activity出来,托管我们的Single Fragment。
详细见下一节。

4、SingleFragmentActivity
于是抽象出来的Activity的代码为:

package com.example.demo_zhy_23_fragments;

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;

public abstract class SingleFragmentActivity extends FragmentActivity
{
protected abstract Fragment createFragment();

@Override
protected void onCreate(Bundle savedInstanceState)
{

super.onCreate(savedInstanceState);
setContentView(R.layout.activity_single_fragment);

FragmentManager fm = getSupportFragmentManager();
Fragment fragment =fm.findFragmentById(R.id.id_fragment_container);

if(fragment == null )
{
fragment = createFragment() ;

fm.beginTransaction().add(R.id.id_fragment_container,fragment).commit();
}
}

}

那么,有了这个SingleFragmentActivity,我们的ContentActivity和ListTitleActivity也能大变身了~
package com.example.demo_zhy_23_fragments;

import android.support.v4.app.Fragment;

public class ContentActivity extends SingleFragmentActivity
{
private ContentFragment mContentFragment;

@Override
protected Fragment createFragment()
{
String title = getIntent().getStringExtra(ContentFragment.ARGUMENT);

mContentFragment = ContentFragment.newInstance(title);
return mContentFragment;
}
}

package com.example.demo_zhy_23_fragments;

import android.support.v4.app.Fragment;

public class ListTitleActivity extends SingleFragmentActivity
{
private ListTitleFragment mListFragment;

@Override
protected Fragment createFragment()
{
mListFragment = new ListTitleFragment();
return mListFragment;
}
}

是不是简洁很多,相信优先使用Fragment的项目,类似的Activity非常多,使用SingleFragmentActivity来简化你的代码吧~~

好了,此代码是来自文章开始推荐的书中的,再次推荐一下~~。

5、FragmentPagerAdapter与FragmentStatePagerAdapter
相信这两个PagerAdapter的子类,大家都不陌生吧~~自从Fragment问世,使用ViewPager再结合上面任何一个实例的制作APP主页的案例特别多~~~

那么这两个类有何区别呢?

主要区别就在与对于fragment是否销毁,下面细说:

FragmentPagerAdapter:对于不再需要的fragment,选择调用detach方法,仅销毁视图,并不会销毁fragment实例。

FragmentStatePagerAdapter:会销毁不再需要的fragment,当当前事务提交以后,会彻底的将fragmeng从当前Activity的FragmentManager中移除,state标明,销毁时,会将其onSaveInstanceState(Bundle outState)中的bundle信息保存下来,当用户切换回来,可以通过该bundle恢复生成新的fragment,也就是说,你可以在onSaveInstanceState(Bundle outState)方法中保存一些数据,在onCreate中进行恢复创建。

如上所说,使用FragmentStatePagerAdapter当然更省内存,但是销毁新建也是需要时间的。一般情况下,如果你是制作主页面,就3、4个Tab,那么可以选择使用FragmentPagerAdapter,如果你是用于ViewPager展示数量特别多的条目时,那么建议使用FragmentStatePagerAdapter。

篇幅原因,具体的案例就不写了,大家自行测试。

6、Fragment间的数据传递
上面3中,我们展示了,一般的两个Fragment间的数据传递。

那么还有一种比较特殊的情况,就是两个Fragment在同一个Activity中:例如,点击当前Fragment中按钮,弹出一个对话框(DialogFragment),在对话框中的操作需要返回给触发的Fragment中,那么如何数据传递呢?对于对话框的使用推荐:Android 官方推荐 : DialogFragment 创建对话框

我们继续修改我们的代码:现在是ListTitleFragment , ContentFragment , 添加一个对话框:EvaluateDialog,用户点击ContentFragment 内容时弹出一个评价列表,用户选择评价。

现在我们的关注点在于:ContentFragment中如何优雅的拿到EvaluateDialog中返回的评价:

记住我们在一个Activity中,那么肯定不是使用startActivityForResult;但是我们返回的数据,依然在onActivityResult中进行接收。

好了看代码:

ContentFragment

public class ContentFragment extends Fragment
{

private String mArgument;
public static final String ARGUMENT = “argument”;
public static final String RESPONSE = “response”;
public static final String EVALUATE_DIALOG = “evaluate_dialog”;
public static final int REQUEST_EVALUATE = 0X110;

//…

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState)
{
Random random = new Random();
TextView tv = new TextView(getActivity());
ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
tv.setLayoutParams(params);
tv.setText(mArgument);
tv.setGravity(Gravity.CENTER);
tv.setBackgroundColor(Color.argb(random.nextInt(100),
random.nextInt(255), random.nextInt(255), random.nextInt(255)));
// set click
tv.setOnClickListener(new OnClickListener()
{

@Override
public void onClick(View v)
{
EvaluateDialog dialog = new EvaluateDialog();
//注意setTargetFragment
dialog.setTargetFragment(ContentFragment.this, REQUEST_EVALUATE);
dialog.show(getFragmentManager(), EVALUATE_DIALOG);
}
});
return tv;
}

//接收返回回来的数据
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data)
{
super.onActivityResult(requestCode, resultCode, data);

if (requestCode == REQUEST_EVALUATE)
{
String evaluate = data
.getStringExtra(EvaluateDialog.RESPONSE_EVALUATE);
Toast.makeText(getActivity(), evaluate, Toast.LENGTH_SHORT).show();
Intent intent = new Intent();
intent.putExtra(RESPONSE, evaluate);
getActivity().setResult(Activity.REQUEST_OK, intent);
}

}
}

删除了一些无关代码,注意看,我们在onCreateView中为textview添加了click事件,用于弹出我们的dialog,注意一行代码:
dialog.setTargetFragment(ContentFragment.this, REQUEST_EVALUATE);

我们调用了Fragment.setTargetFragment ,这个方法,一般就是用于当前fragment由别的fragment启动,在完成操作后返回数据的,符合我们的需求吧~~~注意,这句很重要。

接下来看EvaluateDialog代码:

package com.example.demo_zhy_23_fragments;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;

public class EvaluateDialog extends DialogFragment
{
private String[] mEvaluteVals = new String[] { “GOOD”, “BAD”, “NORMAL” };
public static final String RESPONSE_EVALUATE = “response_evaluate”;

@Override
public Dialog onCreateDialog(Bundle savedInstanceState)
{
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());

builder.setTitle(“Evaluate :”).setItems(mEvaluteVals,
new OnClickListener()
{
@Override
public void onClick(DialogInterface dialog, int which)
{
setResult(which);
}
});
return builder.create();
}

// 设置返回数据
protected void setResult(int which)
{
// 判断是否设置了targetFragment
if (getTargetFragment() == null)
return;

Intent intent = new Intent();
intent.putExtra(RESPONSE_EVALUATE, mEvaluteVals[which]);
getTargetFragment().onActivityResult(ContentFragment.REQUEST_EVALUATE,
Activity.RESULT_OK, intent);

}
}

重点就是看点击后的setResult了,我们首先判断是否设置了targetFragment,如果设置了,意味我们要返回一些数据到targetFragment。
我们创建intent封装好需要传递数据,*后手动调用onActivityResult进行返回数据~~

*后我们在ContentFragment的onActivityResult接收即可。

 

数字字符串和列表

Python基础之:数字字符串和列表

简介
Python的主要应用是进行科学计算,科学计算的基础就是数字,字符串和列表。本文将会详细的给大家介绍一下这三个数据类型的使用情况。

数字
数字是任何科学计算中非常中要的类型,在Python中*常见的数字类型就是int和float。

看几个基本的数字操作:

In [8]: 1+1
Out[8]: 2

In [9]: 3*2 + 10
Out[9]: 16

In [10]: (65 + 23) / 4
Out[10]: 22.0

上面我们可以看到,没有小数的是int类型,带有小数的是float类型。

除法运算 (/) 永远返回浮点数类型。如果要做 floor division得到一个整数结果(忽略小数部分)你可以使用 // 运算符;如果要计算余数,可以使用 %

In [11]: 54 / 4
Out[11]: 13.5

In [12]: 54 // 4
Out[12]: 13

In [13]: 54 % 4
Out[13]: 2

** 可以表示乘方运算:

In [14]: 4 ** 3
Out[14]: 64
我们可以将数字的运算赋值给特定的变量,并且可以使用该变量进行后续的运算。

In [15]: a = 12

In [16]: b = 14

In [17]: a * b
Out[17]: 168

在交互式环境中,_表示上一个输出:

In [17]: a * b
Out[17]: 168

In [18]: 100 + _
Out[18]: 268

除了int和float,Python还支持其他的数据类型,比如Decimal和Fraction,甚至还支持复数。

字符串
Python中字符串有三种表示形式,可以使用单引号,双引号和三引号来表示。

In [19]: site1 = ‘www.flydean.com’

In [20]: site2= “www.flydean.com”

In [21]: site3= “””www.flydean.com”””
三引号主要用于跨行输出,字符串中的回车换行会自动包含到字符串中,如果不想包含,在行尾添加一个 \ 即可。如下:

print(“””\
Usage: thingy [OPTIONS]
-h Display this usage message
-H hostname Hostname to connect to
“””)

如果需要转义的话,可以使用反斜杠 \

In [22]: site4 = “www.\”flydean\”.com”

In [23]: site4
Out[23]: ‘www.”flydean”.com’

如果你不希望前置了 \ 的字符转义成特殊字符,可以使用 原始字符串 方式,在引号前添加 r 即可:

In [24]: print(r”www.\”flydean\”.com”)
www.\”flydean\”.com

字符串通过 + 来进行连接,也可以使用 * 来进行复制:

In [25]: “www” + “flydean.com”
Out[25]: ‘wwwflydean.com’

In [26]: “www.flydean.com” * 3
Out[26]: ‘www.flydean.comwww.flydean.comwww.flydean.com’

相邻的两个或多个 字符串字面值 (引号引起来的字符)将会自动连接到一起.

In [27]: “www” “flydean.com”
Out[27]: ‘wwwflydean.com’

注意,上面的自动连接操作,只能对两个字面量有效,如果是变量的话则会报错。

字符串会被看做是由字符组成的数组,所以可以通过string[index]的形式来进行访问。

In [28]: site5 = “www.flydean.com”

In [29]: site5[3]
Out[29]: ‘.’

如果索引是负数的话,会从右边开始计数:

In [30]: site5[-3]
Out[30]: ‘c’

因为-0 和 0 是一样的,所以负数是从 -1 开始的。

除了索引,字符串还支持 切片。索引可以得到单个字符,而 切片 可以获取子字符串:

In [31]: site5[1:5]
Out[31]: ‘ww.f’

注意切片的开始总是被包括在结果中,而结束不被包括。这使得 s[:i] + s[i:] 总是等于 s

In [33]: site5[:4]+site5[4:]
Out[33]: ‘www.flydean.com’

切片的索引有默认值,省略开始索引时默认为0。

如果索引超出了字符串的范围就会发送越界错误。

In [34]: site5[100]
—————————————————————————
IndexError Traceback (most recent call last)
<ipython-input-34-fc1f475f725b> in <module>()
—-> 1 site5[100]

IndexError: string index out of range

但是,切片中的越界索引会被自动处理:

In [36]: site5[:100]
Out[36]: ‘www.flydean.com’

因为字符串是不可变的,所以我们不能通过索引的形式来对字符串进行修改:

In [37]: site[2] = “A”
—————————————————————————
TypeError Traceback (most recent call last)
<ipython-input-37-9147d44bd80c> in <module>()
—-> 1 site[2] = “A”

TypeError: ‘str’ object does not support item assignment

len用来统计字符串的长度:

In [38]: len(site5)
Out[38]: 15

字符串对象str
字符串的本质是字符串对象str。

可以看下str的基本方法:

In [39]: site5.
capitalize() encode() format() isalpha() islower() istitle() lower() replace() rpartition() splitlines() title()
casefold() endswith() format_map() isdecimal() isnumeric() isupper() lstrip() rfind() rsplit() startswith() translate()
center() expandtabs() index() isdigit() isprintable() join() maketrans() rindex() rstrip() strip() upper()
count() find() isalnum() isidentifier() isspace() ljust() partition() rjust() split() swapcase() zfill()

感兴趣的同学可以自行去研究。

列表
列表是用方括号表示的数据的集合。列表中的数据可以是多种数据类型,但是一般情况下,我们在一个列表中使用同一个数据类型。

In [40]: ages = [ 10, 14, 18, 20 ,25]

In [41]: ages
Out[41]: [10, 14, 18, 20, 25]

和字符串一样,列表也支持索引和切片。事实上,只要是 sequence 类型的数据类型,都支持索引和切片。

In [42]: ages[3]
Out[42]: 20

In [43]: ages[:2]
Out[43]: [10, 14]

In [44]: ages[:]
Out[44]: [10, 14, 18, 20, 25]

注意,列表的切片会返回一个新的列表。但是这个新的列表是浅拷贝,意味着新列表的元素是原列表中元素的引用。

列表还支持拼接操作:

In [45]: ages + [9, 11]
Out[45]: [10, 14, 18, 20, 25, 9, 11]

和String的不可变性不同,列表是可变的,这就意味着我们可以通过索引来修改列表的值:

In [46]: ages[0] = 100

In [47]: ages
Out[47]: [100, 14, 18, 20, 25]

列表的底层类型是list,我们可以看下list中的方法:

In [51]: ages.
append() count() insert() reverse()
clear() extend() pop() sort()
copy() index() remove()

我们可以使用append来附加list的值,也可以使用count来统计list的元素个数等等。

上面我们提到了,列表的切片是原列表的引用,所以我们可以通过给切片赋值,来修改原始列表的值:

>>> letters = [‘a’, ‘b’, ‘c’, ‘d’, ‘e’, ‘f’, ‘g’]
>>> letters
[‘a’, ‘b’, ‘c’, ‘d’, ‘e’, ‘f’, ‘g’]
>>> # replace some values
>>> letters[2:5] = [‘C’, ‘D’, ‘E’]
>>> letters
[‘a’, ‘b’, ‘C’, ‘D’, ‘E’, ‘f’, ‘g’]
>>> # now remove them
>>> letters[2:5] = []
>>> letters
[‘a’, ‘b’, ‘f’, ‘g’]
>>> # clear the list by replacing all the elements with an empty list
>>> letters[:] = []
>>> letters

列表还可以进行嵌套,构建多层的列表:

>>> a = [‘a’, ‘b’, ‘c’]
>>> n = [1, 2, 3]
>>> x = [a, n]
>>> x
[[‘a’, ‘b’, ‘c’], [1, 2, 3]]
>>> x[0]
[‘a’, ‘b’, ‘c’]
>>> x[0][1]
‘b’

Python中的流程控制

Python基础之:Python中的流程控制

 

文章目录
简介
while语句
if 语句
for语句
Break
Continue
pass
简介
流程控制无非就是if else之类的控制语句,今天我们来看一下Python中的流程控制会有什么不太一样的地方。

while语句
python中的while语句和其他语言没有什么不一样,我使用while语句来编写一个斐波拉赫数列:

In [56]: while x < 10 :
…: print(x)
…: x, y = y, x+y
…:

if 语句
python中的 if 可以和 elif 或者 else 配合使用:

>>> x = int(input(“Please enter an integer: “))
Please enter an integer: 42
>>> if x < 0:
… x = 0
… print(‘Negative changed to zero’)
… elif x == 0:
… print(‘Zero’)
… elif x == 1:
… print(‘Single’)
… else:
… print(‘More’)

More

if语句很简单,这里就不做过多的介绍。

for语句
Python中的for语句主要用来对序列进行迭代,比如列表或者字符串:

In [57]: ages = [ 10, 14, 18, 20 ,25]

In [58]: for age in ages:
…: print(age)
…:

遍历过程中,为了防止在遍历的时候原序列被修改,我们可以遍历序列的拷贝:

In [59]: for age in ages.copy():
…: print(age)
…:

for语句和range()函数的结合,可以得到不一样的效果。

range()用来生成给定范围内的集合:

In [61]: for age in range(5):
…: print(age)
…:

range()函数还可以带步长作为第三个参数:

In [62]: for age in range(5, 10 , 2):
…: print(age)
…:

Range()和len()组合,可以方便的变量列表:

>>> a = [‘Mary’, ‘had’, ‘a’, ‘little’, ‘lamb’]
>>> for i in range(len(a)):
… print(i, a[i])

0 Mary
1 had
2 a
3 little
4 lamb

Break
break用来跳出*近的for或者while循环。

要注意的是,for循环可以和else一起使用:

In [64]: for n in range(2, 10):
…: for x in range(2, n):
…: if n % x == 0:
…: print(n, ‘equals’, x, ‘*’, n//x)
…: break
…: else:
…: print(n, ‘is a prime number’)
…:
2 is a prime number
3 is a prime number
4 equals 2 * 2
5 is a prime number
6 equals 2 * 3
7 is a prime number
8 equals 2 * 4
9 equals 3 * 3

循环中的else语句,会在for循环执行完毕,之后执行。如果我们使用break对for循环进行了中断,那么else语句将不会被执行。

Continue
continue用来跳过此次循环中的后面部分,继续执行下一次循环。

还是刚才的例子,我们使用continue进行改装:

In [68]: for n in range(2, 10):
…: for x in range(2, n):
…: if n % x == 0:
…: print(n, ‘equals’, x, ‘*’, n//x)
…: continue
…: else:
…: print(n, ‘is a prime number’)
…:
2 is a prime number
3 is a prime number
4 equals 2 * 2
4 is a prime number
5 is a prime number
6 equals 2 * 3
6 equals 3 * 2
6 is a prime number
7 is a prime number
8 equals 2 * 4
8 equals 4 * 2
8 is a prime number
9 equals 3 * 3
9 is a prime number

可以看到,在continue中,else语句会一直执行。

pass
pass表示的是什么都不做。是一个空的执行。

通常我们使用pass作为函数或条件子语句的占位符,表示具体的内容可以在未来进行填充。

可以在while中使用pass:

>>> while True:
… pass # Busy-wait for keyboard interrupt (Ctrl+C)

可以在类中使用pass:

>>> class MyEmptyClass:
… pass

可以在函数中使用pass:

>>> def initlog(*args):
… pass # Remember to implement this!

基于 Windows Server 2019 混合 Docker for windows 搭建 Nextcloud 简易攻略

环境:
物理主机系统:Windows server 2019 Datecenter (v1809, 17763.805)
Docker:Docker for windows 2.1.0.4 (Engine: 19.03.4, Compose: 1.24.1)
MySQL:MySQL for win64 8.0.18 Community Server
Nginx:Nginx for windows 1.16.1
Nextcloud:17.0.0 (hub.docker.com/_/nextcloud, nextcloud:latest, nextcloud:apache, OS:Linux/amd64)
可选附加:
Onlyoffice-document-server: https://hub.docker.com/r/onlyoffice/documentserver, tag:latest
redis: https://hub.docker.com/_/redis, tag:latest
总之除了 Nextcloud,附加的 onlyoffice-document-server 和 redis 之外,其余均为基于 windows 的软件

Nextcloud 镜像的选择:在官方 Docker 页面中主要版本有默认的 Apache 版和采用容器化 Nginx 的 FPM 版,在我个人实际搭建过程中 FPM 版的 Nginx 和宿主机 windows 之间隔着一层 NAT,配置调试起来显得十分麻烦,故选择运行起来更简单的 Apache 版

Docker for windows 安装是全自动创建 Docker Host 的 Hyper-V 虚拟机,网络使用 NAT 转发,Host IP 为 10.75.0.1,Container IP 为 10.0.75.0

MySQL Community 创建好供 Nextcloud 使用的 utf8mb4 编码的数据库(database),并设置好相关的用户名和密码,也可以直接使用默认的 root 账户,记得要修改密码

NextCloud 和 Nginx 的搭建后续重点讲述

重点讲述:
Nextcloud 容器:
这个其实百度和谷歌上有一大批的教程,只是个人在实际环境运行中出现了各种各样的小细节问题,还有更多的是这些教程大多数都过时了,当然接下来的所有讲述都是仅供参考,毕竟每个人的运行环境都不同

Compose 文本内包含了 redis 缓存容器和 onlyoffice-document-server 文档服务器容器配置,觉得有用请自取

我个人将一些比较重要的文件和 log 通过 volume 挂载到物理系统 windows server 的 D 盘中,Nextcloud 则是直接将整个 PHP 程序和数据文件夹 /var/www/html 转移到 D:/Docker/nextcloud 中,方便以后直接使用 windows 管理文档

nextcloud 容器用的 “wyxls/nextcloud:full” 镜像是我自建的,Nextcloud 的官方镜像默认不带 smbclient 和 crontab,会影响到外部存储挂载 APP 使用 (因为我主要 windows 的 smb 共享),于是我根据官方提供的 Dockerfile 自建了镜像并上传到 docker hub,不需要的话可以改成 nextcloud 官方的 image ( https://hub.docker.com/_/nextcloud)

Dockerfile example:( https://github.com/nextcloud/docker/blob/master/.examples/dockerfiles/full/apache/Dockerfile)

version: ‘3’
#初始化网络模块,为了让 Nextcloud 和 onlyoffice+redis 协作
networks:
nextcloud:
#services 以下都是容器
services:
#redis 容器,暴露 6379 端口供其他容器使用
redis:
image: redis
container_name: redis
hostname: redis
restart: always
networks:
– nextcloud
expose:
– 6379
#nextcloud 容器,宿主机 10000 端口转发 80 端口访问
nextcloud:
image: wyxls/nextcloud:full
container_name: nextcloud
restart: always
depends_on:
– redis
environment:
– UID=1000
– GID=1000
– UPLOAD_MAX_SIZE=5G
– APC_SHM_SIZE=128M
– OPCACHE_MEM_SIZE=128
– CRON_PERIOD=15m
– TZ=Aisa/Shanghai
– NEXTCLOUD_TABLE_PREFIX=oc_
volumes:
– D:/Docker/nextcloud:/var/www/html
ports:
– 10000:80
networks:
– nextcloud
#onlyoffice 容器,宿主机 10005 端口转发 443 端口访问,在 nextcloud 的 onlyoffice 设置里必须以 https+宿主端口访问
onlyoffice:
container_name: onlyoffice
image: onlyoffice/documentserver:latest
stdin_open: true
tty: true
restart: always
depends_on:
– nextcloud
volumes:
– D:/Docker/onlyoffice/document_data:/var/www/onlyoffice/Data
– D:/Docker/onlyoffice/document_log:/var/log/onlyoffice
– D:/Docker/onlyoffice/document_fonts:/usr/share/fonts/truetype/custom
– D:/Docker/onlyoffice/document_forgotten:/var/lib/onlyoffice/documentserver/App_Data/cache/files/forgotten
ports:
– 10005:443
networks:
– nextcloud
在 Docker 容器中运行的 Nextcloud 默认以 root 权限运行所有程序,所以访问时会提示设置权限 chmod 0770,但在我个人实践中无论是 chown 还是 chmod 都无法解决这个问题,后来还是强行忽略文件权限检查

在 /var/www/html/config/config.php 中加入以下一行:

‘check_data_directory_permissions’ => false, #检查数据目录权限
此外别忘了还要添加 Trusted_Domains,不然 Nextcloud 的 Web 端无法访问:

‘trusted_domains’ =>
array (
0 => ‘example.com’,
1 -> ‘localhost’,
),
Nginx:
由于 Docker for windows 是基于 Hyper-V 虚拟机模拟出的 Linux/amd64 系统,相当于物理 windows——Hyper-V 虚拟机( Docker Host 宿主机)——Docker Container (容器)两层 NAT 网络,所以需要使用 Nginx 当中间人进行反向代理,下面是我个人配置的 conf,仅供参考

因为我个人的 Windows Server 内网可以直接通过 SMB 访问管理文件,而 Nextcloud 只进行外网访问,所以我只做了 HTTPS 监听+反向代理,有内网 HTTP 访问需求的可以将 SSL 相关部分注释掉

原本在反向代理 proxy_pass 段有 connect, read, send 等 timeout 限制,但后来发现添加后网页访问和 windows 客户端同步变得异常缓慢,而且频繁报错,故删除

server {
listen 10002 ssl; #Nginx 监听端口
server_name example.com localhost 192.168.x.x; #域名, IP, 本地地址都可以填写
root D:/nextcloud; #nextcloud 目录
index index.php;

ssl_certificate D:/SSL-Certificates/fullchain.cer;
ssl_certificate_key D:/SSL-Certificates/private.key;
ssl_protocols SSLv2 SSLv3 TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
ssl_ciphers ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP;
ssl_prefer_server_ciphers on;

#以下部分为隐藏 header, 为了解决 nextcloud 自检问题
proxy_hide_header Strict-Transport-Security;
proxy_hide_header X-Content-Type-Options;
proxy_hide_header X-Robots-Tag;
proxy_hide_header X-Frame-Options;
proxy_hide_header X-Download-Options;
proxy_hide_header X-Permitted-Cross-Domain-Policies;
proxy_hide_header Referrer-Policy;
proxy_hide_header X-XSS-Protection;

add_header Strict-Transport-Security “max-age=31536000; includeSubDomains; preload” always;
add_header X-Content-Type-Options nosniff;
add_header X-Robots-Tag “none”;
add_header X-Frame-Options “SAMEORIGIN”;
add_header X-Download-Options “noopen”;
add_header X-Permitted-Cross-Domain-Policies “none”;
add_header Referrer-Policy “no-referrer”;
add_header X-XSS-Protection “1; mode=block”;

client_max_body_size 10G;
fastcgi_buffers 64 4K;
fastcgi_hide_header X-Powered-By;

location / {
proxy_pass http://localhost:10000/; #反向代理地址
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

location = /.well-known/carddav { #解决自检 carddav 未正常配置解析提示问题
return 301 $scheme://$http_host/remote.php/dav;
}

location = /.well-known/caldav { #解决自检 carddav 未正常配置解析提示问题
return 301 $scheme://$http_host/remote.php/dav;
}

location = /robots.txt {
allow all;
log_not_found off;
access_log off;
}

location ~ ^\/(?:build|tests|config|lib|3rdparty|templates|data)\/ {
deny all;
}

location ~ ^\/(?:\.|autotest|occ|issue|indie|db_|console) {
deny all;
}

}
在初步搭好 Nextcloud 后实际使用中经常出现访问超时,原因是 Nextcloud 在反向代理中可能会无法侦测正确的协议,需要强行覆写( Nextcloud 默认 latest 镜像用 Apache 作为 Web server 监听 HTTP 80 端口)

‘overwriteprotocol’ => ‘https’,
额外部分:
Onlyoffice-document-server:
Nextcloud 官方提供连接 onlyoffice 服务的 APP,与 onlyoffice 对接后能实现 Nextcloud 内通过 Web 访问直接打开并编辑 office 相关文档 (pptx, xlsx, docs 等),通过 Docker 可以一键配置

version: ‘3’
services:
onlyoffice:
container_name: onlyoffice
image: onlyoffice/documentserver:latest
stdin_open: true
tty: true
restart: always
depends_on:
– nextcloud
volumes:
– D:/Docker/onlyoffice/document_data:/var/www/onlyoffice/Data
– D:/Docker/onlyoffice/document_log:/var/log/onlyoffice
– D:/Docker/onlyoffice/document_fonts:/usr/share/fonts/truetype/custom
– D:/Docker/onlyoffice/document_forgotten:/var/lib/onlyoffice/documentserver/App_Data/cache/files/forgotten
ports:
– 10005:443
networks:
– nextcloud
证书安装:在 /var/www/onlyoffice/Data 中建立”certs”文件夹并将 SSL 证书及私钥以”onlyoffice.crt”和”onlyoffice.key”保存,或者直接在 yml 中 environment 环境参数添加,在 Docker-Settings-Shared Drives 设置共享后可以直接使用 windows 物理路径指定,比如:D:\SSL-certificates\onlyoffice.crt

environment:
– SSL_CERTIFICATE_PATH=证书路径
– SSL_KEY_PATH=私钥路径
通过访问 https://example.com:10005 可以查看 Document Server 运行状况,显示 Document Server is running 表示成功

*后在 Nextcloud 内设置 Document Editing Service address 为 https://example.com:10005,如果页面下方出现 settings 一类选项则表示已成功连接

Redis:
Nextcloud 官方推荐使用 Redis 缓存 Nextcloud,我自己也不太懂原理,但官方既然推荐了就一起部署上啦

version: ‘3’
services:
redis:
image: redis
container_name: redis
hostname: redis
restart: always
networks:
– nextcloud
expose:
– 6379
记得要在 Nextcloud 对应的 config/config.php 中添加相关内容

‘memcache.local’ => ‘\OC\Memcache\APCu’, #redis
‘memcache.distributed’ => ‘\OC\Memcache\Redis’, #redis
‘memcache.locking’ => ‘\OC\Memcache\Redis’, #redis
‘redis’ => array( #redis
‘host’ => ‘redis’, #如果 redis 部署在物理机上填 localhost,这里由于 redis 和 nextcloud 在同一网络 nextcloud 内,所以可用 redis 代替
‘port’ => 6379,
),
结语:
onlyoffice 目前我个人测试只能在 Docker 内部 Nextcloud 使用,详细原因猜测是 onlyoffice 内置的 Nginx 没正确配置监听或允许外部网络域名访问(反正我软路由的另一个 Nextcloud 对接时显示 Connetion refused )

我凭借着记忆将大致的搭建过程写了出来,难免会有所纰漏,烦请各位朋友查漏指正,有什么问题可以回复交流

此外如果对过程中任何一部分有修正改进的建议,请务必告诉我,我对 Nginx、PHP、MySQL 参数调优真的是一窍不通

牢骚话:

有人可能会问为什么不干脆用 Linux 物理系统来搭,没辙啊,老爸只会用 windows,老妈想着吃饭时间连上去看 iqiyi 的电视剧,家里其他人又想摆一台公用的共享 NAS,我自己对 centOS 又不是特别熟悉,*后翻了一大堆的论坛帖子、网站文章自己摸索,目前也就只能这样先用着了

其实*关键是没钱买群晖,而且家里人的要求比较杂,群晖不能很好地满足,于是这份攻略就诞生了

曾经想过 ESXi 6.7 组建 FreeNAS + centOS + Docker (Nextcloud)+ Windows 7 的大虚拟机,把路由 LEDE 也虚拟化,从此就可以把那台软路由也扔掉,但后来发现搞不定 N 卡直通后给 Windows,重启虚拟机就带着 ESXi 一起死机的问题。据谷歌搜索说是因为 ESXi 6.7 在显卡*次直通给虚拟机后关机时没能正常 reset 显卡状态,导致第二次虚拟机启动时显卡处于正在使用状态而带着宿主机一起 Boom,折腾了两天还是解决不了,随后我就放弃并滚回去用 windows 了

Android Fragment 真正的完全解析(下)

上篇博客中已经介绍了Fragment产生原因,以及一些基本的用法和各种API,如果你还不了解,请看:Android Fragment 真正的完全解析(上)。

本篇将介绍上篇博客提到的:如何管理Fragment回退栈,Fragment如何与Activity交互,Fragment与Activity交互的*佳实践,没有视图的Fragment的用处,使用Fragment创建对话框,如何与ActionBar,MenuItem集成等~~

1、管理Fragment回退栈
类似与Android系统为Activity维护一个任务栈,我们也可以通过Activity维护一个回退栈来保存每次Fragment事务发生的变化。如果你将Fragment任务添加到回退栈,当用户点击后退按钮时,将看到上一次的保存的Fragment。一旦Fragment完全从后退栈中弹出,用户再次点击后退键,则退出当前Activity。

看这样一个效果图:

%title插图%num %title插图%num

点击*个按钮,切换到第二个界面,点击第二个按钮,切换到第三个界面,然后点击Back键依次回退。这像不像初学Android时的Activity跳转,当然了,这里肯定不是,不然我就跪了。这里是Fragment实现的,用户点击Back,实际是Fragment回退栈不断的弹栈。

如何添加一个Fragment事务到回退栈:

FragmentTransaction.addToBackStack(String)

下面讲解代码:很明显一共是3个Fragment和一个Activity.

先看Activity的布局文件:

<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” >
</FrameLayout>

</RelativeLayout>
不同的Fragment就在这个FrameLayout中显示。
MainActivity.java

package com.zhy.zhy_fragments;

import android.app.Activity;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.os.Bundle;
import android.view.Window;

public class MainActivity extends Activity
{

@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_main);

FragmentManager fm = getFragmentManager();
FragmentTransaction tx = fm.beginTransaction();
tx.add(R.id.id_content, new FragmentOne(),”ONE”);
tx.commit();
}

}
很简单,直接将FragmentOne添加到布局文件中的FrameLayout中,注意这里并没有调用FragmentTransaction.addToBackStack(String),因为我不喜欢在当前显示时,点击Back键出现白板。而是正确的相应Back键,即退出我们的Activity.
下面是FragmentOne

package com.zhy.zhy_fragments;

import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.Button;

public class FragmentOne extends Fragment implements OnClickListener
{

private Button mBtn;

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState)
{
View view = inflater.inflate(R.layout.fragment_one, container, false);
mBtn = (Button) view.findViewById(R.id.id_fragment_one_btn);
mBtn.setOnClickListener(this);
return view;
}

@Override
public void onClick(View v)
{
FragmentTwo fTwo = new FragmentTwo();
FragmentManager fm = getFragmentManager();
FragmentTransaction tx = fm.beginTransaction();
tx.replace(R.id.id_content, fTwo, “TWO”);
tx.addToBackStack(null);
tx.commit();

}

}

我们在点击FragmentOne中的按钮时,使用了replace方法,如果你看了前一篇博客,一定记得replace是remove和add的合体,并且如果不添加事务到回退栈,前一个Fragment实例会被销毁。这里很明显,我们调用tx.addToBackStack(null);将当前的事务添加到了回退栈,所以FragmentOne实例不会被销毁,但是视图层次依然会被销毁,即会调用onDestoryView和onCreateView,证据就是:仔细看上面的效果图,我们在跳转前在文本框输入的内容,在用户Back得到*个界面的时候不见了。
接下来FragmentTwo

package com.zhy.zhy_fragments;

import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.Button;

public class FragmentTwo extends Fragment implements OnClickListener
{

private Button mBtn ;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState)
{
View view = inflater.inflate(R.layout.fragment_two, container, false);
mBtn = (Button) view.findViewById(R.id.id_fragment_two_btn);
mBtn.setOnClickListener(this);
return view ;
}
@Override
public void onClick(View v)
{
FragmentThree fThree = new FragmentThree();
FragmentManager fm = getFragmentManager();
FragmentTransaction tx = fm.beginTransaction();
tx.hide(this);
tx.add(R.id.id_content , fThree, “THREE”);
// tx.replace(R.id.id_content, fThree, “THREE”);
tx.addToBackStack(null);
tx.commit();
}

}

这里点击时,我们没有使用replace,而是先隐藏了当前的Fragment,然后添加了FragmentThree的实例,*后将事务添加到回退栈。这样做的目的是为了给大家提供一种方案:如果不希望视图重绘该怎么做,请再次仔细看效果图,我们在FragmentTwo的EditText填写的内容,用户Back回来时,数据还在~~~
*后FragmentThree就是简单的Toast了:

package com.zhy.zhy_fragments;

import android.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.Toast;

public class FragmentThree extends Fragment implements OnClickListener
{

private Button mBtn;

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState)
{
View view = inflater.inflate(R.layout.fragment_three, container, false);
mBtn = (Button) view.findViewById(R.id.id_fragment_three_btn);
mBtn.setOnClickListener(this);
return view;
}

@Override
public void onClick(View v)
{
Toast.makeText(getActivity(), ” i am a btn in Fragment three”,
Toast.LENGTH_SHORT).show();
}

}

好了,经过上面的介绍,应该已经知道Fragment回退栈是怎么一回事了,以及hide,replace等各自的应用的场景。
这里*其注意一点:上面的整体代码不具有任何参考价值,纯粹为了显示回退栈,在后面讲解了Fragment与Activity通信以后,会重构上面的代码!

2、Fragment与Activity通信
因为所有的Fragment都是依附于Activity的,所以通信起来并不复杂,大概归纳为:

a、如果你Activity中包含自己管理的Fragment的引用,可以通过引用直接访问所有的Fragment的public方法

b、如果Activity中未保存任何Fragment的引用,那么没关系,每个Fragment都有一个唯一的TAG或者ID,可以通过getFragmentManager.findFragmentByTag()或者findFragmentById()获得任何Fragment实例,然后进行操作。

c、在Fragment中可以通过getActivity得到当前绑定的Activity的实例,然后进行操作。

注:如果在Fragment中需要Context,可以通过调用getActivity(),如果该Context需要在Activity被销毁后还存在,则使用getActivity().getApplicationContext()。

3、Fragment与Activity通信的*佳实践
因为要考虑Fragment的重复使用,所以必须降低Fragment与Activity的耦合,而且Fragment更不应该直接操作别的Fragment,毕竟Fragment操作应该由它的管理者Activity来决定。

下面我通过两种方式的代码,分别重构,FragmentOne和FragmentTwo的点击事件,以及Activity对点击事件的响应:

首先看FragmentOne

package com.zhy.zhy_fragments;

import android.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.Button;

public class FragmentOne extends Fragment implements OnClickListener
{
private Button mBtn;

/**
* 设置按钮点击的回调
* @author zhy
*
*/
public interface FOneBtnClickListener
{
void onFOneBtnClick();
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState)
{
View view = inflater.inflate(R.layout.fragment_one, container, false);
mBtn = (Button) view.findViewById(R.id.id_fragment_one_btn);
mBtn.setOnClickListener(this);
return view;
}

/**
* 交给宿主Activity处理,如果它希望处理
*/
@Override
public void onClick(View v)
{
if (getActivity() instanceof FOneBtnClickListener)
{
((FOneBtnClickListener) getActivity()).onFOneBtnClick();
}
}

}

可以看到现在的FragmentOne不和任何Activity耦合,任何Activity都可以使用;并且我们声明了一个接口,来回调其点击事件,想要管理其点击事件的Activity实现此接口就即可。可以看到我们在onClick中首先判断了当前绑定的Activity是否实现了该接口,如果实现了则调用。
再看FragmentTwo

package com.zhy.zhy_fragments;

import android.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.Button;

public class FragmentTwo extends Fragment implements OnClickListener
{

private Button mBtn ;

private FTwoBtnClickListener fTwoBtnClickListener ;

public interface FTwoBtnClickListener
{
void onFTwoBtnClick();
}
//设置回调接口
public void setfTwoBtnClickListener(FTwoBtnClickListener fTwoBtnClickListener)
{
this.fTwoBtnClickListener = fTwoBtnClickListener;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState)
{
View view = inflater.inflate(R.layout.fragment_two, container, false);
mBtn = (Button) view.findViewById(R.id.id_fragment_two_btn);
mBtn.setOnClickListener(this);
return view ;
}
@Override
public void onClick(View v)
{
if(fTwoBtnClickListener != null)
{
fTwoBtnClickListener.onFTwoBtnClick();
}
}

}

与FragmentOne*其类似,但是我们提供了setListener这样的方法,意味着Activity不仅需要实现该接口,还必须显示调用mFTwo.setfTwoBtnClickListener(this)。
*后看Activity :

package com.zhy.zhy_fragments;

import android.app.Activity;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.os.Bundle;
import android.view.Window;

import com.zhy.zhy_fragments.FragmentOne.FOneBtnClickListener;
import com.zhy.zhy_fragments.FragmentTwo.FTwoBtnClickListener;

public class MainActivity extends Activity implements FOneBtnClickListener,
FTwoBtnClickListener
{

private FragmentOne mFOne;
private FragmentTwo mFTwo;
private FragmentThree mFThree;

@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_main);

mFOne = new FragmentOne();
FragmentManager fm = getFragmentManager();
FragmentTransaction tx = fm.beginTransaction();
tx.add(R.id.id_content, mFOne, “ONE”);
tx.commit();
}

/**
* FragmentOne 按钮点击时的回调
*/
@Override
public void onFOneBtnClick()
{

if (mFTwo == null)
{
mFTwo = new FragmentTwo();
mFTwo.setfTwoBtnClickListener(this);
}
FragmentManager fm = getFragmentManager();
FragmentTransaction tx = fm.beginTransaction();
tx.replace(R.id.id_content, mFTwo, “TWO”);
tx.addToBackStack(null);
tx.commit();
}

/**
* FragmentTwo 按钮点击时的回调
*/
@Override
public void onFTwoBtnClick()
{
if (mFThree == null)
{
mFThree = new FragmentThree();

}
FragmentManager fm = getFragmentManager();
FragmentTransaction tx = fm.beginTransaction();
tx.hide(mFTwo);
tx.add(R.id.id_content, mFThree, “THREE”);
// tx.replace(R.id.id_content, fThree, “THREE”);
tx.addToBackStack(null);
tx.commit();
}

}

代码重构结束,与开始的效果一模一样。上面两种通信方式都是值得推荐的,随便选择一种自己喜欢的。这里再提一下:虽然Fragment和Activity可以通过getActivity与findFragmentByTag或者findFragmentById,进行任何操作,甚至在Fragment里面操作另外的Fragment,但是没有特殊理由是*对不提倡的。Activity担任的是Fragment间类似总线一样的角色,应当由它决定Fragment如何操作。另外虽然Fragment不能响应Intent打开,但是Activity可以,Activity可以接收Intent,然后根据参数判断显示哪个Fragment。
4、如何处理运行时配置发生变化
运行时配置发生变化,*常见的就是屏幕发生旋转,如果你不知道如何处理屏幕变化可以参考:Android 屏幕旋转 处理 AsyncTask 和 ProgressDialog 的*佳方案

这里提一下:很多人觉得强制设置屏幕的方向就可以了,但是有一点,当你的应用被至于后台(例如用户点击了home),长时间没有返回的时候,你的应用也会被重新启动。比如上例:如果你把上面的例子你至于FragmentThree界面,然后处于后台状态,长时间后你会发现当你再次通过home打开时,上面FragmentThree与FragmentOne叠加在一起,这就是因为你的Activity重新启动,在原来的FragmentThree上又绘制了一个FragmentOne。

好了,下面看一段代码:

Activity:

package com.zhy.zhy_fragments;

import android.app.Activity;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.os.Bundle;
import android.view.Window;

public class MainActivity extends Activity

{
private FragmentOne mFOne;

@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_main);

mFOne = new FragmentOne();
FragmentManager fm = getFragmentManager();
FragmentTransaction tx = fm.beginTransaction();
tx.add(R.id.id_content, mFOne, “ONE”);
tx.commit();

}

}

Fragment
package com.zhy.zhy_fragments;

import android.app.Fragment;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

public class FragmentOne extends Fragment
{
private static final String TAG = “FragmentOne”;

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState)
{
Log.e(TAG, “onCreateView”);
View view = inflater.inflate(R.layout.fragment_one, container, false);
return view;
}

@Override
public void onCreate(Bundle savedInstanceState)
{
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);

Log.e(TAG, “onCreate”);
}

@Override
public void onDestroyView()
{
// TODO Auto-generated method stub
super.onDestroyView();
Log.e(TAG, “onDestroyView”);
}

@Override
public void onDestroy()
{
// TODO Auto-generated method stub
super.onDestroy();
Log.e(TAG, “onDestroy”);
}

}

很简单的代码,当你运行之后,不断的旋转屏幕,你会发现每旋转一次屏幕,屏幕上就多了一个FragmentOne的实例,并且后台log会打印出许多套生命周期的回调。
类似:

07-20 08:18:46.651: E/FragmentOne(1633): onCreate
07-20 08:18:46.651: E/FragmentOne(1633): onCreate
07-20 08:18:46.651: E/FragmentOne(1633): onCreate
07-20 08:18:46.681: E/FragmentOne(1633): onCreateView
07-20 08:18:46.831: E/FragmentOne(1633): onCreateView
07-20 08:18:46.891: E/FragmentOne(1633): onCreateView

这是为什么呢,因为当屏幕发生旋转,Activity发生重新启动,默认的Activity中的Fragment也会跟着Activity重新创建;这样造成当旋转的时候,本身存在的Fragment会重新启动,然后当执行Activity的onCreate时,又会再次实例化一个新的Fragment,这就是出现的原因。
那么如何解决呢:

其实通过检查onCreate的参数Bundle savedInstanceState就可以判断,当前是否发生Activity的重新创建:

默认的savedInstanceState会存储一些数据,包括Fragment的实例:通过打印可以看出:

07-20 08:23:12.952: E/FragmentOne(1782): Bundle[{android:fragments=android.app.FragmentManagerState@40d0b7b8, android:viewHierarchyState=Bundle[{android:focusedViewId=2131230721, android:views=android.util.SparseArray@40d0af68}]}]
所以,我们简单改一下代码,只有在savedInstanceState==null时,才进行创建Fragment实例:
package com.zhy.zhy_fragments;

import android.app.Activity;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.os.Bundle;
import android.util.Log;
import android.view.Window;

public class MainActivity extends Activity

{
private static final String TAG = “FragmentOne”;
private FragmentOne mFOne;

@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_main);

Log.e(TAG, savedInstanceState+””);

if(savedInstanceState == null)
{
mFOne = new FragmentOne();
FragmentManager fm = getFragmentManager();
FragmentTransaction tx = fm.beginTransaction();
tx.add(R.id.id_content, mFOne, “ONE”);
tx.commit();
}

}

}

现在无论进行多次旋转都只会有一个Fragment实例在Activity中。
现在还存在一个问题,就是重新绘制时,Fragment发生重建,原本的数据如何保持?

其实和Activity类似,Fragment也有onSaveInstanceState的方法,在此方法中进行保存数据,然后在onCreate或者onCreateView或者onActivityCreated进行恢复都可以。

由于篇幅原因,就不贴测试代码了。

5、Fragmeny与ActionBar和MenuItem集成
Fragment可以添加自己的MenuItem到Activity的ActionBar或者可选菜单中。

a、在Fragment的onCreate中调用 setHasOptionsMenu(true);

b、然后在Fragment子类中实现onCreateOptionsMenu

c、如果希望在Fragment中处理MenuItem的点击,也可以实现onOptionsItemSelected;当然了Activity也可以直接处理该MenuItem的点击事件。

代码:

Fragment

package com.zhy.zhy_fragments;

import android.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;

public class FragmentOne extends Fragment
{

@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState)
{
View view = inflater.inflate(R.layout.fragment_one, container, false);
return view;
}

@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater)
{
inflater.inflate(R.menu.fragment_menu, menu);
}

@Override
public boolean onOptionsItemSelected(MenuItem item)
{
switch (item.getItemId())
{
case R.id.id_menu_fra_test:
Toast.makeText(getActivity(), “FragmentMenuItem1”, Toast.LENGTH_SHORT).show();
break;
}
return true;
}

}

Activity
package com.zhy.zhy_fragments;

import android.app.Activity;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.Window;
import android.widget.Toast;

public class MainActivity extends Activity

{
private static final String TAG = “FragmentOne”;
private FragmentOne mFOne;

@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_main);

Log.e(TAG, savedInstanceState + “”);

if (savedInstanceState == null)
{
mFOne = new FragmentOne();
FragmentManager fm = getFragmentManager();
FragmentTransaction tx = fm.beginTransaction();
tx.add(R.id.id_content, mFOne, “ONE”);
tx.commit();
}

}

@Override
public boolean onCreateOptionsMenu(Menu menu)
{
super.onCreateOptionsMenu(menu);
getMenuInflater().inflate(R.menu.main, menu);
return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item)
{
switch (item.getItemId())
{
case R.id.action_settings:
Toast.makeText(this, “setting”, Toast.LENGTH_SHORT).show();
return true;
default:
//如果希望Fragment自己处理MenuItem点击事件,一定不要忘了调用super.xxx
return super.onOptionsItemSelected(item);
}
}

}

效果图:

%title插图%num

%title插图%num

好了,可以很好的看到,Fragment可以添加MenuItem,也可以自己处理点击~~~

6、没有布局的Fragment的作用
没有布局文件Fragment实际上是为了保存,当Activity重启时,保存大量数据准备的

请参考博客:Android 屏幕旋转 处理 AsyncTask 和 ProgressDialog 的*佳方案

7、使用Fragment创建对话框
这是Google推荐的方式,我也单独写过博客介绍,请参考:Android 官方推荐 : DialogFragment 创建对话框

好了,终于把Fragment相关的联系到一起了,上述基本包含了Fragment所有的用法~~~相信大家如果能够看完,一定有不少的收获~~~

有任何问题,欢迎留言~~~

两篇结束,相信你对Fragment已经有了一定的了解,那么在项目中的*佳实践是什么呢?请移步:Android Fragment 你应该知道的一切

KMS服务器关于Vol Windows/Office命令

可用的KMS服务器地址:

zh.us.to 有效

kms.03k.org 有效

kms.chinancce.com 有效

kms.shuax.com 有效

kms.dwhd.org 有效

kms.luody.info 有效

kms.digiboy.ir 有效

kms.lotro.cc 有效

www.zgbs.cc 有效

cy2617.jios.org 有效

 

假如你的服务器IP是1.1.1.1

Windows用下面两条命令:(需要cmd在管理员权限下执行)

slmgr -skms 1.1.1.1

slmgr -ato
查看Windows状态用下面命令(KMS连接周期,剩余时间等):

slmgr.vbs /dlv
Office用下面这条命令:(需要cmd在管理员权限下执行)

cscript “E:\Microsoft Office\Office16\OSPP.VBS” /sethst:1.1.1.1
查看Office状态用下面这条命令:(需要cmd在管理员权限下执行)

cscript “E:\Microsoft Office\Office16\OSPP.VBS” /dstatus
其中E:\Microsoft Office\Office16   是Office2016中OSPP.VBS 所在的目录,Office16代表Office2016;Office15达标Office2013;Office14代表Office2010;

tidb分布式数据库是mysql_常见问题-分布式数据库 TiDB-帮助文档-京东智联云

常见问题

1. 分布式数据库 TiDB 是基于 MySQL 开发的吗?

不是,但是分布式数据库 TiDB 支持 MySQL 语法和协议。

2. 用起来简单吗?

是的,分布式数据库 TiDB 用起来很简单。启动整套服务后,就可以将 分布式数据库 TiDB 当做一个普通的 MySQL Server 来用,您可以将 分布式数据库 TiDB 用在任何以 MySQL 作为后台存储服务的应用中,基本上不需要修改应用代码,并且兼容大部分 MySQL 管理工具。

3. 适用的场景?

原业务的 MySQL 的业务遇到单机容量或者性能瓶颈时,可以考虑使用 分布式数据库 TiDB 无缝替换 MySQL。

4. 如何将运行在 MySQL 上的应用迁移到 分布式数据库 TiDB 上?

分布式数据库 TiDB 大多数 MySQL 语法,一般不需要修改代码,可以用工具 Checker 检查 MySQL 中的 Schema 是否兼容。

5. TiDB 具备高可用的特性吗?

TiDB 具备高可用特性,TiDB、TiKV、PD 这三个组件都能容忍部分实例失效,不影响整个集群的可用性。

6. TiDB 数据是强一致的吗?

TiDB 使用 Raft 在多个副本之间做数据同步,从而保证数据的强一致,单个副本失效时,不影响数据的可靠性。

7. TiDB 用户名长度限制?

在 TiDB 中用户名*长为 32 字符。

8. 一个事务中的语句数量*大是多少?

一个事务中的语句数量,默认限制*大为 5000 条。

9. 如何通过扩展 分布式数据库 TiDB 提高性能?

随着业务不断增长时,数据库可能会面临三方面瓶颈: – *是存储资源不够,也就是磁盘空间不够; – 第二是计算资源不够用,如 CPU 占用较高; – 第三是吞吐跟不上;

这时可以对数据库集群做水平扩展。 – 如果是存储资源不够,可以通过添加 TiKV Server 节点来解决。新节点启动后,PD 会自动将其他节点的部分数据迁移过去,无需人工介入。 – 如果是计算资源不够,可以查看 TiDB 节点 和 TiKV 节点的 CPU 消耗情况,再考虑添加 TiDB 节点或者是 TiKV 节点来解决。 – 如果是吞吐跟不上,一般可以考虑同时增加 TiDB 节点 和 TiKV 节点。
————————————————
版权声明:本文为CSDN博主「冰浠」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_30980025/article/details/114471493

Python基础之:函数

Python基础之:函数

文章目录
简介
内置函数
自定义函数
参数的默认值
关键字参数
特殊参数
参数解包
Lambda
函数标注
简介
函数是结构化编程的基础,也是代码复用的基石。Python中通过def来自定义函数。本文将会深入探索Python中函数的秘密。

内置函数
除了用户的自定义函数之外,Python内置了一些非常有用的函数:

内置函数
abs() delattr() hash() memoryview() set()
all() dict() help() min() setattr()
any() dir() hex() next() slice()
ascii() divmod() id() object() sorted()
bin() enumerate() input() oct() staticmethod()
bool() eval() int() open() str()
breakpoint() exec() isinstance() ord() sum()
bytearray() filter() issubclass() pow() super()
bytes() float() iter() print() tuple()
callable() format() len() property() type()
chr() frozenset() list() range() vars()
classmethod() getattr() locals() repr() zip()
compile() globals() map() reversed() __import__()
complex() hasattr() max() round()
自定义函数
Python中使用def来定义函数,并使用return来返回特定的值。

看一个简单的函数的例子:

def my_function(x, y, z):
if z > 1:
return z * (x + y)
else:
return z / (x + y)

把我们之前讲的斐波拉赫数列的例子重新用函数来定义,可以这样写:

def fib(n):
a, b = 0, 1
while a < n:
print(a, end=’ ‘)
a, b = b, a+b
print()

# 调用函数
fib(1000)

函数的内容需要使用空格或者tab来进行缩进。

参数的默认值
在Python中,我们可以给参数设置默认值,这样如果在函数调用的过程中没有传递参数的时候,就会使用默认值作为参数。

在我们之前定义的函数my_function中,我们可以给z设置一个默认值:

def my_function(x, y, z=10):
if z > 1:
return z * (x + y)
else:
return z / (x + y)

这样我们在调用my_function可以只用传递两个参数,*后的z可以使用默认的参数值。

注意,默认值只会执行一次,如果你传入的参数是可变对象(列表,字典和类实例)的话,我们需要注意这个问题:

def f(a, L=[]):
L.append(a)
return L

print(f(1))
print(f(2))
print(f(3))

# 输出
[1]
[1, 2]
[1, 2, 3]

如果不想在后面的调用中共享默认值,那么可以把默认值的赋值放到函数体内部:

def f(a, L=None):
if L is None:
L = []
L.append(a)
return L

关键字参数
我们可以使用key=value的方式对函数进行调用。

还是前面的函数:

def my_function(x, y, z=10):
if z > 1:
return z * (x + y)
else:
return z / (x + y)

我们可以这样调用:

my_function(1,y=3,z=5)
my_function(1,y=3)

但是不能这样用:

my_function(y=3,1)

关键字的参数必须要放在非关键词参数的后面。也不能对参数进行多次赋值:

>>> def function(a):
… pass

>>> function(0, a=0)
Traceback (most recent call last):
File “<stdin>”, line 1, in <module>
TypeError: function() got multiple values for keyword argument ‘a’

通过上面的讨论我们可以看出,Python函数中的参数有两种,一种是带默认值的参数,一种是不带默认值的参数。

注意,不带默认值的参数一定要在带默认值的参数之前 。

看一个错误的例子:

In [69]: def fa(a=100,b,c=200):
…: pass
File “<ipython-input-69-d5678b64f352>”, line 1
def fa(a=100,b,c=200):
^
SyntaxError: non-default argument follows default argument

而向函数传递参数也有两种方式,一种是不带关键字的传递,一种是带关键字的传递。

注意,非关键词参数的传递一定要在关键词参数传递之前。

举个错误的例子:

In [70]: def fa(a,b=100,c=200):
…: pass
…:

In [71]: fa(a=100,30)
File “<ipython-input-71-5a229b8e420e>”, line 1
fa(a=100,30)
^
SyntaxError: positional argument follows keyword argument

那么问题来了,如果有多个关键词参数和多个非关键词参数,有没有简便的方法来定义这样的函数呢?

有的,那就是 *arguments 和 **keywords

*arguments用来接收所有多余的非关键词参数。而**keywords用来接收所有额外的关键词参数。

注意,*arguments一定要出现在 **keywords 的前面。

举个例子:

def cheeseshop(kind, *arguments, **keywords):
print(“– Do you have any”, kind, “?”)
print(“– I’m sorry, we’re all out of”, kind)
for arg in arguments:
print(arg)
print(“-” * 40)
for kw in keywords:
print(kw, “:”, keywords[kw])

我们可以这样调用:

cheeseshop(“Limburger”, “It’s very runny, sir.”,
“It’s really very, VERY runny, sir.”,
shopkeeper=”Michael Palin”,
client=”John Cleese”,
sketch=”Cheese Shop Sketch”)

将会得到下面的结果:

— Do you have any Limburger ?
— I’m sorry, we’re all out of Limburger
It’s very runny, sir.
It’s really very, VERY runny, sir.
—————————————-
shopkeeper : Michael Palin
client : John Cleese
sketch : Cheese Shop Sketch

特殊参数
函数可以按位置传参,可以按照关键词传参,也可以混合传参。

在某些情况下,我们可能需要限制传参的类型,比如只接收按位置传递,只接收按关键词传递,或者只接受混合传递。

看下特殊参数的定义:

def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):
———– ———- ———-
| | |
| 按位置或者关键词 |
| – 只允许按关键词传递
— 只允许按位置传递

注意,参数之间是以 / 和 * 来进行区分的。

我们举个例子:

>>> def standard_arg(arg):
… print(arg)

>>> def pos_only_arg(arg, /):
… print(arg)

>>> def kwd_only_arg(*, arg):
… print(arg)

>>> def combined_example(pos_only, /, standard, *, kwd_only):
… print(pos_only, standard, kwd_only)

上面定义了4种传参方式的函数。

*个函数就是标准形式,可以按位置传递,也可以按关键词传递。

第二个函数只允许按照位置传递。

第三个函数只允许按照关键词来传递。

第四个函数是混合模式。

参数解包
有时候我们需要将列表或者字典的值转换为函数的参数。那么就需要用到参数解包的功能。

* 操作符 可以用来解包列表和元组。

>>> list(range(3, 6)) # normal call with separate arguments
[3, 4, 5]
>>> args = [3, 6]
>>> list(range(*args)) # call with arguments unpacked from a list
[3, 4, 5]

** 操作符 可以用来解包字典。

>>> def parrot(voltage, state=’a stiff’, action=’voom’):
… print(“– This parrot wouldn’t”, action, end=’ ‘)
… print(“if you put”, voltage, “volts through it.”, end=’ ‘)
… print(“E’s”, state, “!”)

>>> d = {“voltage”: “four million”, “state”: “bleedin’ demised”, “action”: “VOOM”}
>>> parrot(**d)

Lambda
熟悉java的朋友可能知道,在JDK8中,Java引入了Lambda表达式。同样的Python中也有Lambda。

你可以将Lambda看做是匿名函数。可以在任何需要函数的地方使用Lambda表达式。

看一个Lambda的例子:

>>> def make_incrementor(n):
… return lambda x: x + n

>>> f = make_incrementor(42)
>>> f(0)
42
>>> f(1)

还可以将lambda的返回值作为参数:

>>> pairs = [(1, ‘one’), (2, ‘two’), (3, ‘three’), (4, ‘four’)]
>>> pairs.sort(key=lambda pair: pair[1])
>>> pairs
[(4, ‘four’), (1, ‘one’), (3, ‘three’), (2, ‘two’)]

函数标注
之前我们讨论的是简单的自定义函数形式,我们并不知道函数的参数类型和返回值类型,其实函数可以写得更加详细一些,这就要用到函数标注了。

所谓函数标注就是用户自定义函数中的类型的可选元数据信息。

函数标注是以字典的形式存放在 __annotations__ 属性中的。我们在参数的名称后面加上冒号,后面跟一个表达式,那么这个表达式会被求值为标注的值。对于返回值来说,返回值标注的定义是加上一个组合符号 ->,后面跟一个表达式,该标注位于形参列表和表示 def 语句结束的冒号之间。

举个例子:

>>> def f(ham: str, eggs: str = ‘eggs’) -> str:
… print(“Annotations:”, f.__annotations__)
… print(“Arguments:”, ham, eggs)
… return ham + ‘ and ‘ + eggs

>>> f(‘spam’)
Annotations: {‘ham’: <class ‘str’>, ‘return’: <class ‘str’>, ‘eggs’: <class ‘str’>}
Arguments: spam eggs
‘spam and eggs’

其实使用函数标注写出来的程序更加清晰,可读性更高。

阿里云ECS 云服务器和轻量应用服务器 区别

什么是阿里云轻量应用服务器?

轻量应用服务器是面向入门级云计算及简单应用用户,提供基于单台云服务器的域名管理、应用部署、安全和运维管理的一站式综合服务。用户可以选择精品应用镜像(比如wordpress),并可在控制台实现全方位的服务器、域名、防火墙、安全防控、监控等管理和操作。轻量应用服务器 (Simple Application Server),是可快速搭建且易于管理的轻量级云服务器。

如下介绍:

%title插图%num

注:学生身份购买,会便宜一些。

阿里云轻量应用服务器的优点是什么?
1.易用易上手。

产品针对单机用户的购买和使用做了操作和使用优化,提供精品镜像、资源套餐、多产品关联(VPC、安全产品、域名管理等)。简单三步就可以轻松开始使用轻量应用服务器

a.选择镜像,并购买(创建)服务器;

b.配置应用:通过查看控制台已经安装好的应用信息,并通过登录服务器查看初始化应用的密码,登录应用后台配置应用;

c.将域名解析到服务器IP。

2.性价比高。

在集合多个必用或常用产品、提供更多基础功能,优化使用体验的同时,并未增加额外费用,且提供了更加优惠的价格,旨在提供更多用户低门槛上云实践机会。

阿里云轻量应用服务器与阿里云ecs云服务器有什么区别?
跟ECS相比,采用同ECS共享版相同的CPU,均采用Intel(R) Xeon(R) CPU E5-2682 v4@ 2.50GHz CPU,但轻量应用服务器将系统盘升级为SSD,虽然容量小了一半,但使用SSD,性能应该是有不错的提升。相同CPU,内存不知道是否相同型号的情况下,单来比较磁盘读写性能,可见速度的提升明显。

介绍
所以就有了轻量应用服务器(轻量应用服务器),基于 ECS 发展出的轻量服务器,完美解决了上述的三个痛点:

轻量应用服务器 是 IaaS 产品但是提供定制化应用镜像且有可视化控制台可以操作,不怕新手不会用。轻量应用服务器 在提供
提供定制化应用镜像的同时,用户依旧可以拥有 root 权限,对系统进行个性化操作
精简了一些 ECS 的功能,比如说安骑士。把安全组的操作也做了适当减法更适合用户操作
不过别看多了轻量两个字性能就会不如 ECS,在 CPU 和 磁盘性能上均没有缩水,后面会有测评。

优点
控制台
轻量应用服务器

那么从控制台的对比上,ECS 是把所有的内容都告诉用户你的地域、操作系统、标签等等,但是新手看了难免一头雾水。而 轻量应用服务器 则做了减法,让控制台变得更加的直观简介,只告诉你重要的信息。

站在新手视角肯定是 轻量应用服务器 更简洁更舒服,站在我的角度么,我配置好服务器基本上很少会来看一下 ECS 的控制台,但一回来肯定是需要一些信息的,那么 ECS 能告诉我越多肯定越好。

应用镜像

应用镜像像 WordPress、phpwind 其实都是基于 LAMP 镜像运行的,而 轻量应用服务器 提供的 LAMP 细节好评就是:php 是通过 php-fpm 方式运行的,而不是 mod_php,而且 MySQL 也是 5.7 版本的不是说万年 5.1 或者 5.5,能支持更多的 PHP 程序。在创建虚拟主机绑定域名商相较于一键包配合图形化控制台的确更加好用。

功能

轻量应用服务器 在功能上肯定是更讨喜的,安装应用镜像基本上能做到开箱即用,三大金刚 Apache Httpd、PHP、MySQL 都会默认安装好,而且添加域名、设置 HTTPS 等操作都是一键式的,*大得降低了 HTTPS 入门门槛,而且 HTTPS 的配置跑分是 A 哟,为了照顾新手不会使用 HSTS 所以没开,不然跑到 A+ 肯定是妥妥的。

ECS 能就只有空白的系统镜像,任何环境都是需要用户自行安装的,这在一定程度上增加了用户使用的学习成本。而 ECS 更多体现的就是专业性了,虽然复杂但是十分强大的安全组、弹性IP、均衡负载等等。

基础运维

比如说 ECS 控制台操作起来也更加的方便,点击一下就能远程连接了,操作的过程中也支持命令的右键复制和粘贴,也大大降低了用户使用终端控制的学习成本,不然还要下个 Putty 或者 Xshell 啥的也是麻烦。 然后默认只支持 密钥 登录 Linux,这个也是好评,不用密码当然更安全。

同样不考虑使用 Putty 或者 Xshell 等 OpenSSH 连接工具的话,ECS控制台的远程终端控制实在是难用,不过借助 DMS 产品倒是能扩展使用的灵活性。

防火墙

轻量的防火墙设置同 ECS 的安全组相比很简单直观更适合新手的使用,没有一些非常复杂的设置,新手看到 udp、tcp 真的是头都大了额,如果新手看教程的话,一般只会说 “记得一定要开启443端口才能使用HTTPS” 这样的话,但是一看到 ECS 又是 TCP 又是 UDP,而且端口还要写成 443/443 的形式,直接就是一个头两个大了。

缺点
无法满足企业特性
轻量应用服务器有轻量两个字,意味着其无法持续进行高负载运行(即 CPU 长时间高负载占用),如果我们要利用 轻量应用服务器 来进行持续的渲染、转码、机器学习等持续高负载操作那么就不可以了,也不支持 Nvme 这样的超高性能的 SSD,这对于读写敏感的操作就不好了。

三大金刚的版本问题
Apache Httpd、PHP、MySQL 是会持续更新的,而且它们也均有爆发过大规模严重漏洞的历史,不过目前还没有看到应用镜像中的三大金刚如何升级版本号的姿势。

像 WordPress 这样的程序完全可以适应高版本的 PHP,使用 PHP7.0 或者 7.1 可以拥有更高的性能还可以降低负载,但是默认只有 php5.6 可以选,既然使用 php-fpm 方式驱动其实完全可以提供多版本 PHP 的选择。

总结
可以说 轻量应用服务器 是专门针对云计算入门新手或者只有轻量计算需求的用户,其对标的产品只会是 ECS 的共享型,当一到要使用 ECS 的企业型来发展性能、功能了,轻量应用服务器 的优势其实也就没有了。

如果你只是轻度使用云计算产品例如搭建一个博客、官网,如果你需要一个更快启动的轻量服务器,如果你需要一个纯粹的虚拟专用机,如果你并没有阿里云内网的需求,那么 轻量应用服务器 *对是你*好的选择。

评测
我们对比的是华东1地域的安装了 CentOS 7.3 系统的 1核心1G内存的 ECS 和 轻量应用服务器 产品。看了评测就可以跟深入的了解,ECS 和 轻量应用服务器 的不同只在于概念而不在于性能。

CPU 性能
根据下面的对比可以发现,轻量应用服务器 的性能和 ECS 并没有差距,大家都是新一代的 Xeon CPU,同样的虚拟化技术。只不过 轻量应用服务器 不可以持续高负载占用。

轻量应用服务器:

System Benchmarks Index Values BASELINE RESULT INDEX
Dhrystone 2 using register variables 116700.0 34281977.0 2937.6
Double-Precision Whetstone 55.0 2962.7 538.7
Execl Throughput 43.0 4860.0 1130.2
File Copy 1024 bufsize 2000 maxblocks 3960.0 1054197.7 2662.1
File Copy 256 bufsize 500 maxblocks 1655.0 283098.8 1710.6
File Copy 4096 bufsize 8000 maxblocks 5800.0 3165160.0 5457.2
Pipe Throughput 12440.0 1734490.3 1394.3
Pipe-based Context Switching 4000.0 375732.0 939.3
Process Creation 126.0 18345.1 1456.0
Shell Scripts (1 concurrent) 42.4 6747.0 1591.3
Shell Scripts (8 concurrent) 6.0 921.5 1535.8
System Call Overhead 15000.0 2939229.7 1959.5
========
System Benchmarks Index Score 1649.5

ECS:

System Benchmarks Index Values BASELINE RESULT INDEX
Dhrystone 2 using register variables 116700.0 34582858.9 2963.4
Double-Precision Whetstone 55.0 2984.8 542.7
Execl Throughput 43.0 5118.8 1190.4
File Copy 1024 bufsize 2000 maxblocks 3960.0 1073823.9 2711.7
File Copy 256 bufsize 500 maxblocks 1655.0 284701.5 1720.3
File Copy 4096 bufsize 8000 maxblocks 5800.0 3213933.6 5541.3
Pipe Throughput 12440.0 1722785.7 1384.9
Pipe-based Context Switching 4000.0 377999.7 945.0
Process Creation 126.0 18704.4 1484.5
Shell Scripts (1 concurrent) 42.4 6852.6 1616.2
Shell Scripts (8 concurrent) 6.0 937.2 1562.0
System Call Overhead 15000.0 2979190.8 1986.1
========
System Benchmarks Index Score 1673.4

磁盘性能
由下面的磁盘跑分也可以看见,轻量应用服务器 的 20G SSD 系统盘 和 ECS 的 20G SSD 云盘的性能都是一致的,都是 1800 的iops,也都是 90M 左右的读写没有差距。

轻量应用服务器:

/dev/vda:
Timing cached reads: 21070 MB in 2.00 seconds = 10549.25 MB/sec
Timing buffered disk reads: 270 MB in 3.01 seconds = 89.76 MB/sec

[root@izbp1auqkyqtj4iozs7athz dev]# fio –bs=4k –ioengine=libaio –iodepth=1 –direct=1 –rw=read –time_based –runtime=600 –refill_buffers –norandommap –randrepeat=0 –group_reporting –name=fio-read –size=10G –filename=/dev/vda
fio-read: (g=0): rw=read, bs=4K-4K/4K-4K/4K-4K, ioengine=libaio, iodepth=1
fio-2.2.8
Starting 1 process
Jobs: 1 (f=1): [R(1)] [100.0% done] [7200KB/0KB/0KB /s] [1800/0/0 iops] [eta 00m:00s]
fio-read: (groupid=0, jobs=1): err= 0: pid=24361: Sun Sep 10 15:04:27 2017
read : io=4206.7MB, bw=7179.3KB/s, iops=1794, runt=600004msec
slat (usec): min=3, max=165, avg= 6.78, stdev= 2.02
clat (usec): min=2, max=84748, avg=548.56, stdev=2727.93
lat (usec): min=131, max=84755, avg=555.59, stdev=2727.93
clat percentiles (usec):
| 1.00th=[ 141], 5.00th=[ 241], 10.00th=[ 302], 20.00th=[ 306],
| 30.00th=[ 314], 40.00th=[ 322], 50.00th=[ 334], 60.00th=[ 350],
| 70.00th=[ 362], 80.00th=[ 382], 90.00th=[ 442], 95.00th=[ 502],
| 99.00th=[ 1012], 99.50th=[23936], 99.90th=[40704], 99.95th=[42752],
| 99.99th=[68096]
bw (KB /s): min= 4624, max= 7784, per=100.00%, avg=7182.85, stdev=181.15
lat (usec) : 4=0.01%, 250=6.25%, 500=88.69%, 750=3.79%, 1000=0.27%
lat (msec) : 2=0.36%, 4=0.06%, 10=0.03%, 20=0.04%, 50=0.48%
lat (msec) : 100=0.04%
cpu : usr=0.50%, sys=1.81%, ctx=1076903, majf=1, minf=33
IO depths : 1=100.0%, 2=0.0%, 4=0.0%, 8=0.0%, 16=0.0%, 32=0.0%, >=64=0.0%
submit : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0%
complete : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0%
issued : total=r=1076894/w=0/d=0, short=r=0/w=0/d=0, drop=r=0/w=0/d=0
latency : target=0, window=0, percentile=100.00%, depth=1

Run status group 0 (all jobs):
READ: io=4206.7MB, aggrb=7179KB/s, minb=7179KB/s, maxb=7179KB/s, mint=600004msec, maxt=600004msec

Disk stats (read/write):
vda: ios=1078167/690, merge=0/399, ticks=598805/4230, in_queue=602927, util=98.01%

ECS:

/dev/vdb:
Timing cached reads: 19778 MB in 2.00 seconds = 9901.01 MB/sec
Timing buffered disk reads: 270 MB in 3.02 seconds = 89.54 MB/sec

[root@iZbp1258gr0v9v184jdqqkZ dev]# fio –bs=4k –ioengine=libaio –iodepth=1 –direct=1 –rw=read –time_based –runtime=600 –refill_buffers –norandommap –randrepeat=0 –group_reporting –name=fio-read –size=10G –filename=/dev/vdb
fio-read: (g=0): rw=read, bs=4K-4K/4K-4K/4K-4K, ioengine=libaio, iodepth=1
fio-2.2.8
Starting 1 process
Jobs: 1 (f=1): [R(1)] [100.0% done] [6940KB/0KB/0KB /s] [1735/0/0 iops] [eta 00m:00s]
fio-read: (groupid=0, jobs=1): err= 0: pid=9552: Sun Sep 10 15:23:44 2017
read : io=4218.9MB, bw=7200.2KB/s, iops=1800, runt=600001msec
slat (usec): min=3, max=344, avg= 6.97, stdev= 2.32
clat (usec): min=1, max=85711, avg=546.79, stdev=4741.30
lat (usec): min=145, max=85717, avg=553.99, stdev=4741.28
clat percentiles (usec):
| 1.00th=[ 147], 5.00th=[ 149], 10.00th=[ 151], 20.00th=[ 153],
| 30.00th=[ 155], 40.00th=[ 159], 50.00th=[ 167], 60.00th=[ 199],
| 70.00th=[ 229], 80.00th=[ 241], 90.00th=[ 249], 95.00th=[ 270],
| 99.00th=[ 374], 99.50th=[56576], 99.90th=[67072], 99.95th=[67072],
| 99.99th=[67072]
bw (KB /s): min= 6380, max= 7624, per=100.00%, avg=7202.00, stdev=111.69
lat (usec) : 2=0.01%, 50=0.01%, 250=90.56%, 500=8.76%, 750=0.06%
lat (usec) : 1000=0.02%
lat (msec) : 2=0.02%, 4=0.01%, 10=0.01%, 20=0.01%, 50=0.01%
lat (msec) : 100=0.55%
cpu : usr=0.50%, sys=1.98%, ctx=1080029, majf=0, minf=34
IO depths : 1=100.0%, 2=0.0%, 4=0.0%, 8=0.0%, 16=0.0%, 32=0.0%, >=64=0.0%
submit : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0%
complete : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0%
issued : total=r=1080027/w=0/d=0, short=r=0/w=0/d=0, drop=r=0/w=0/d=0
latency : target=0, window=0, percentile=100.00%, depth=1

Run status group 0 (all jobs):
READ: io=4218.9MB, aggrb=7200KB/s, minb=7200KB/s, maxb=7200KB/s, mint=600001msec, maxt=600001msec

Disk stats (read/write):
vdb: ios=1079640/0, merge=0/0, ticks=589523/0, in_queue=589382, util=98.27%

 

 

友情链接: SITEMAP | 旋风加速器官网 | 旋风软件中心 | textarea | 黑洞加速器 | jiaohess | 老王加速器 | 烧饼哥加速器 | 小蓝鸟 | tiktok加速器 | 旋风加速度器 | 旋风加速 | quickq加速器 | 飞驰加速器 | 飞鸟加速器 | 狗急加速器 | hammer加速器 | trafficace | 原子加速器 | 葫芦加速器 | 麦旋风 | 油管加速器 | anycastly | INS加速器 | INS加速器免费版 | 免费vqn加速外网 | 旋风加速器 | 快橙加速器 | 啊哈加速器 | 迷雾通 | 优途加速器 | 海外播 | 坚果加速器 | 海外vqn加速 | 蘑菇加速器 | 毛豆加速器 | 接码平台 | 接码S | 西柚加速器 | 快柠檬加速器 | 黑洞加速 | falemon | 快橙加速器 | anycast加速器 | ibaidu | moneytreeblog | 坚果加速器 | 派币加速器 | 飞鸟加速器 | 毛豆APP | PIKPAK | 安卓vqn免费 | 一元机场加速器 | 一元机场 | 老王加速器 | 黑洞加速器 | 白石山 | 小牛加速器 | 黑洞加速 | 迷雾通官网 | 迷雾通 | 迷雾通加速器 | 十大免费加速神器 | 猎豹加速器 | 蚂蚁加速器 | 坚果加速器 | 黑洞加速 | 银河加速器 | 猎豹加速器 | 海鸥加速器 | 芒果加速器 | 小牛加速器 | 极光加速器 | 黑洞加速 | movabletype中文网 | 猎豹加速器官网 | 烧饼哥加速器官网 | 旋风加速器度器 | 哔咔漫画 | PicACG | 雷霆加速