项目目录设置
拓展知识
maven私服配置
maven仓库分为远端服务器和私服
那为什么用私服呢?
-
节省自己的外网带宽
建立私服可以减少组织自己的开支,大量的对于外部远程仓库的重复请求会消耗很大的带宽,利用私服代理外部仓库后,对外的重复构件下载得以消除,即降低外网带宽的压力。 -
加速Maven的构建
不停的请求外部仓库无疑是比较耗时的, 但Maven的一些内部机制(如快照检测)要求Maven在执行构建的时候不停地检查远程仓库的数据。
因此当配置了很多远程仓库时,构建的速度会被大大降低。使用私服可以很好地解决这个问题。 -
部署第三方构件
当某个构件无法从外部远程仓库下载怎么办?
这样的例子很多,如组织内部的生成的私有的构件肯定无法从外部仓库获取,Oracle的JDBC驱动由于版权原因不能发布到外网的中心仓库。
建立私服之后便可以将这些构件部署到本地私服中,供内部的Maven项目使用。 -
提高稳定行,增强控制
Maven构建搞定依赖于远程仓库,因此,当Internet不稳定的时候,Maven构建也会变的不稳定,甚至无法构建。
使用私服后即使暂时没有Internet连接Maven也可以正常运行,因为私服中缓存了大量的构件。
此外一些私服软件(如:Nexus)还提供了很多额外的功能,如权限管理,RELEASE/SNAPSHOT区分等,管理员可以对仓库进行一些更高级的控制。 -
降低中央仓库的负荷
数百万的请求,存储数T的数据,需要相相当大的财力。使用私服可以避免很多对中央仓库的重复请求。
下一步就是安装nexus了
nexus 安装等教程这里有一篇很好的博文可以参考一下
apache Http网页服务器
接下来就是了解一下apache http服务器
Apache HTTP Server(简称Apache),是Apache软件基金会的一个开放源代码的网页服务器,可以在大多数电脑操作系统中运行,由于其具有的跨平台性和安全性,被广泛使用,是最流行的Web服务器端软件之一。
来自百度词条
知道是个什么就要下载配置安装
配置过程可以参考apache安装等教程
做完这两步我们来看一下怎么配置一下整个项目目录
bmob 后端云设置(可选作为用户设置)
制作程序
相关配置
我们看一下图片上的一些介绍,然后我们进行代码的编写
首先是等待页
等待也就是一个点击程序时候可以有一个广告页面,这个页面可以跳过或者不跳过
这里放入关键代码
public class SplashActivity extends BaseUIActivity{
public static final int CODE = 1001;
public static final int TOTAL_TIME = 3000;
public static final int INTERVAL_TIME = 1000;
private TextView mTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_splash);
mTextView = (TextView) findViewById(R.id.time_text_view);
final Handler handler = new MyHandler(this);
Message message = Message.obtain();
message.what = CODE;
message.arg1 = TOTAL_TIME;
handler.sendMessage(message);
mTextView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
BookListActivity.start(SplashActivity.this);
SplashActivity.this.finish();
handler.removeMessages(CODE);
}
});
}
public static class MyHandler extends Handler {
public final WeakReference<SplashActivity> mWeakReference;
public MyHandler(SplashActivity activity) {
mWeakReference = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
SplashActivity activity = mWeakReference.get();
if (msg.what == CODE) {
if (activity != null) {
// 设置textview,更新UI
int time = msg.arg1;
activity.mTextView.setText(time / INTERVAL_TIME + "秒,点击跳过");
// 发送倒计时
Message message = Message.obtain();
message.what = CODE;
message.arg1 = time - INTERVAL_TIME;
if (time > 0) {
sendMessageDelayed(message, INTERVAL_TIME);
} else {
BookListActivity.start(activity);
activity.finish();
}
}
}
}
}
}
在这一段里面可以看到,我们的程序界面的设置,但是我们发现,没有BaseUIActivity这个类,我们来看下他是干什么的
public class BaseUIActivity extends BaseActivity {
@Override
public void onCreate(@Nullable Bundle savedInstanceState, @Nullable PersistableBundle persistentState) {
super.onCreate(savedInstanceState, persistentState);
SystemUI.fixSystemUI(this);
}
}
名字可以看出,我写了一个重置UI的选项,那这个是怎么实现的
public class SystemUI {
public static void fixSystemUI(Activity mActivity) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
//获取最顶层的View
mActivity.getWindow().getDecorView()
.setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
mActivity.getWindow().setStatusBarColor(Color.TRANSPARENT);
}
}
}
这里要检查一下程序的版本,所以单独写一个类来实现,那接下来要做的就是进入之后的显示类 BookListActivity
这些数据全部来自网络端,那网络端要怎么实现,之前提到apache HTTP 看一下列表
上代码
/**
* FileName BookListActivity
*
* @author ziqin
* @version 1.0
* @date 2020/12/17 18:03
* Descriptopn: 书籍列表类
*/
public class BookListActivity extends BaseActivity {
private static final String TAG = "BookListActivity";
private ListView mListView;
private List<BookListResult.Book> mBooks = new ArrayList<>();
private AsyncHttpClient mClient;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_book_list);
requsetPermiss();
init();
}
private void init() {
mListView = (ListView) findViewById(R.id.book_list_view);
String url ="http://10.0.2.2:8888/lib.json";
mClient = new AsyncHttpClient();
mClient.get(url, new AsyncHttpResponseHandler() {
@Override
public void onStart() {
super.onStart();
}
@Override
public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) {
final String result = new String(responseBody);
Gson gson = new Gson();
BookListResult bookListResult = gson.fromJson(result, BookListResult.class);
mBooks = bookListResult.getData();
mListView.setAdapter(new BookListAdapter());
}
@Override
public void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error) {
}
@Override
public void onFinish() {
super.onFinish();
}
});
}
/**
* 权限申请
*/
private void requsetPermiss() {
//危险权限
request(new OnPermissionsResult() {
@Override
public void OnSuccess() {
}
@Override
public void OnFail(List<String> noPermissions) {
Log.i("noPermissions:", noPermissions.toString());
}
});
}
public static void start(Context context) {
Intent intent = new Intent(context, BookListActivity.class);
context.startActivity(intent);
}
private class BookListAdapter extends BaseAdapter {
@Override
public int getCount() {
return mBooks.size();
}
@Override
public Object getItem(int i) {
return mBooks.get(i);
}
@Override
public long getItemId(int i) {
return i;
}
@Override
public View getView(int position, View view, ViewGroup viewGroup) {
final BookListResult.Book book = mBooks.get(position);
ViewHolder viewHolder = new ViewHolder();
if (view == null) {
view = getLayoutInflater().inflate(R.layout.item_book_list_view, null);
viewHolder.mNameTextView = (TextView) view.findViewById(R.id.name_text_view);
viewHolder.mButton = (Button) view.findViewById(R.id.book_btn);
view.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) view.getTag();
}
viewHolder.mNameTextView.setText(book.getBookname());
final String path = Environment.getExternalStorageDirectory() + "/yuedukongjian/" + book.getBookname() + ".txt";
Log.i(TAG,ExistSDCard()+"");
Log.i(TAG,path);
File file = new File(path);
// try {
// file.createNewFile();
// } catch (IOException e) {
// e.printStackTrace();
// }
// try {
// FileWriter fileWriter = new FileWriter(file);
// fileWriter.write("sssss");
// } catch (IOException e) {
// e.printStackTrace();
// }
viewHolder.mButton.setText(file.exists() ? "点击打开" : "点击下载");
final ViewHolder finalViewHolder = viewHolder;
viewHolder.mButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (file.exists()) {
BookActivity.start(BookListActivity.this, path);
} else {
mClient.addHeader("Accept-Encoding", "identity");
mClient.get(book.getBookfile(), new FileAsyncHttpResponseHandler(file) {
@Override
public void onFailure(int statusCode, Header[] headers, Throwable throwable, File file) {
finalViewHolder.mButton.setText("下载失败");
}
@Override
public void onSuccess(int statusCode, Header[] headers, File file) {
finalViewHolder.mButton.setText("点击打开");
}
@Override
public void onProgress(long bytesWritten, long totalSize) {
super.onProgress(bytesWritten, totalSize);
finalViewHolder.mButton.setText(String.valueOf(bytesWritten * 100 / totalSize) + "%");
}
});
}
}
});
return view;
}
class ViewHolder {
public TextView mNameTextView;
public Button mButton;
}
}
private boolean ExistSDCard() {
if (android.os.Environment.getExternalStorageState().equals(
android.os.Environment.MEDIA_MOUNTED)) {
return true;
} else
return false;
}
//第一次按下时间
private long firstClick;
@Override
public void onBackPressed() {
AppExit();
//super.onBackPressed();
}
/**
* 再按一次退出
*/
public void AppExit() {
if (System.currentTimeMillis() - this.firstClick > 2000L) {
this.firstClick = System.currentTimeMillis();
Toast.makeText(this, "再按一次退出", Toast.LENGTH_LONG).show();
return;
}
finish();
}
}
这里用到AsyncHttp,小文件完全够了,并且用的是listview,也可以考虑替换,这里已经够用了就不考虑
里面涉及到一个端口在init()里面,可以看一下这里对应上面的HTTP设置 10.0.2.2是android studio对主机地址的取值
String url ="http://10.0.2.2:8888/lib.json";
接下来获取到json我们可以先打印出来,对应的用插件转换为对应的类
并且对下载放了一个对应的加载数据 看最后一本书,点击之后显示下载的进度
public class BookListResult {
@SerializedName("status")
private int mStatus;
@SerializedName("msg")
private String mMessage;
@SerializedName("data")
private List<Book> mData;
public int getStatus() {
return mStatus;
}
public void setStatus(int status) {
mStatus = status;
}
public String getMessage() {
return mMessage;
}
public void setMessage(String message) {
mMessage = message;
}
public List<Book> getData() {
return mData;
}
public void setData(List<Book> data) {
mData = data;
}
public static class Book {
@SerializedName("bookname")
private String mBookname;
@SerializedName("bookfile")
private String mBookfile;
public String getBookname() {
return mBookname;
}
public void setBookname(String bookname) {
mBookname = bookname;
}
public String getBookfile() {
return mBookfile;
}
public void setBookfile(String bookfile) {
mBookfile = bookfile;
}
}
}
json文件如下
{
"status": 1,
"data": [
{
"bookname": "大主宰",
"bookfile": "http://10.0.2.2:8888/txt/大主宰.txt"
},
{
"bookname": "鬼吹灯",
"bookfile": "http://10.0.2.2:8888/txt/鬼吹灯.txt"
},
{
"bookname": "盘龙",
"bookfile": "http://10.0.2.2:8888/txt/大主宰.txt"
},
{
"bookname": "庆余年",
"bookfile": "http://10.0.2.2:8888/txt/庆余年.txt"
},
{
"bookname": "圣墟",
"bookfile": "http://10.0.2.2:8888/txt/圣墟.txt"
},
{
"bookname": "心理罪",
"bookfile": "http://10.0.2.2:8888/txt/心理罪.txt"
},
{
"bookname": "重生九零小俏媳",
"bookfile": "http://10.0.2.2:8888/txt/重生九零小俏媳.txt"
}
],
"msg": "成功"
}
贝塞尔曲线类这里准备了直接导入的类,不赘述
那我们就要提一下,集成的动态权限
上代码
/**
* FileName BaseActivity
*
* @author ziqin
* @version 1.0
* @date 2020/12/23 19:20
* Descriptopn:
*/
public class BaseActivity extends AppCompatActivity {
//申请运行时权限的Code
private static final int PERMISSION_REQUEST_CODE = 1000;
//申明所需权限
private String[] mStrPermission = {
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_EXTERNAL_STORAGE,
};
//保存没有同意的权限
private List<String> mPerList = new ArrayList<>();
//保存没有同意的失败权限
private List<String> mPerNoList = new ArrayList<>();
private OnPermissionsResult permissionsResult;
/**
* 一个方法请求权限
*
* @param permissionsResult
*/
protected void request(OnPermissionsResult permissionsResult) {
if (!checkPermissionsAll()) {
requestPermissionAll(permissionsResult);
}
}
/**
* 判断单个权限
*
* @param permissions
* @return
*/
protected boolean checkPermissions(String permissions) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
int check = checkSelfPermission(permissions);
return check == PackageManager.PERMISSION_GRANTED;
}
return false;
}
/**
* 判断是否需要申请权限
*
* @return
*/
protected boolean checkPermissionsAll() {
mPerList.clear();
for (int i = 0; i < mStrPermission.length; i++) {
boolean check = checkPermissions(mStrPermission[i]);
//如果不同意则请求
if (!check) {
mPerList.add(mStrPermission[i]);
}
}
return mPerList.size() > 0 ? false : true;
}
/**
* 请求权限
*
* @param mPermissions
*/
protected void requestPermission(String[] mPermissions) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
requestPermissions(mPermissions, PERMISSION_REQUEST_CODE);
}
}
/**
* 申请所有权限
*
* @param permissionsResult
*/
protected void requestPermissionAll(OnPermissionsResult permissionsResult) {
this.permissionsResult = permissionsResult;
requestPermission((String[]) mPerList.toArray(new String[mPerList.size()]));
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
mPerNoList.clear();
if (requestCode == PERMISSION_REQUEST_CODE) {
if (grantResults.length > 0) {
for (int i = 0; i < grantResults.length; i++) {
if (grantResults[i] == PackageManager.PERMISSION_DENIED) {
//你有失败的权限
mPerNoList.add(permissions[i]);
}
}
if (permissionsResult != null) {
if (mPerNoList.size() == 0) {
permissionsResult.OnSuccess();
} else {
permissionsResult.OnFail(mPerNoList);
}
}
}
}
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
protected interface OnPermissionsResult {
void OnSuccess();
void OnFail(List<String> noPermissions);
}
}
有了这些之后,我们就要来绘制一下点进书籍的时候显示的页面,不过有的方法正在淘汰,要注意一下
public class BookActivity extends AppCompatActivity {
public static final String FILE_PATH = "file_path";
public static final String BOOKMARK = "bookmark";
private BookPageView mBookPageView;
private TextView mProgressTextView;
private View mSettingView;
private RecyclerView mRecyclerView;
private static final String TAG = "BookActivity";
private int mCurrentLength;
private BookPageBezierHelper mHelper;
private Bitmap mCurrentPageBitmap;
private Bitmap mNextPageBitmap;
private String mFilePath;
private int mWidth;
private int mHeight;
private int mLastLength;
private TextToSpeech mTTS;
private SeekBar mSeekBar;
private int mTotalLength;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// fullscreen
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
}
setContentView(R.layout.activity_book);
if (getIntent() != null) {
mFilePath = getIntent().getStringExtra(FILE_PATH);
if(!TextUtils.isEmpty(mFilePath)){
mTotalLength = (int) new File(mFilePath).length();
}
} else {
// todo can not find the book path
}
// init view
mBookPageView = (BookPageView) findViewById(R.id.book_page_view);
mProgressTextView = (TextView) findViewById(R.id.progress_text_view);
mSettingView = findViewById(R.id.setting_view);
mRecyclerView = (RecyclerView) findViewById(R.id.settingRecyclerView);
mSeekBar = (SeekBar) findViewById(R.id.seekBar);
// get Size
DisplayMetrics displayMetrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
mWidth = displayMetrics.widthPixels;
mHeight = displayMetrics.heightPixels;
openBookByProgress(R.drawable.book_bg, 0);
//set progress
mHelper.setOnProgressChangedListener(new BookPageBezierHelper.OnProgressChangedListener() {
@Override
public void setProgress(int currentLength, int totalLength) {
mCurrentLength = currentLength;
float progress = mCurrentLength * 100 / totalLength;
mProgressTextView.setText(String.format("%s%%", progress));
}
});
// set user setting view listener.
mBookPageView.setOnUserNeedSettingListener(new BookPageView.OnUserNeedSettingListener() {
@Override
public void onUserNeedSetting() {
mSettingView.setVisibility(mSettingView.getVisibility() == View.VISIBLE
? View.GONE : View.VISIBLE);
}
});
// set recyclerView data.
List<String> settingList = new ArrayList<>();
settingList.add("添加书签");
settingList.add("读取书签");
settingList.add("设置背景");
settingList.add("语音朗读");
settingList.add("跳转进度");
LinearLayoutManager layoutManager = new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false);
mRecyclerView.setLayoutManager(layoutManager);
mRecyclerView.setAdapter(new HorizontalAdapter(this, settingList));
mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
openBookByProgress(R.drawable.book_bg, seekBar.getProgress()* mTotalLength /100);
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
openBookByProgress(R.drawable.book_bg, seekBar.getProgress()* mTotalLength /100);
}
});
}
private void openBookByProgress(int backgroundResourceID, int progress) {
// set book helper
mHelper = new BookPageBezierHelper(mWidth, mHeight, progress);
mBookPageView.setBookPageBezierHelper(mHelper);
// current page , next page
mCurrentPageBitmap = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.ARGB_8888);
mNextPageBitmap = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.ARGB_8888);
mBookPageView.setBitmaps(mCurrentPageBitmap, mNextPageBitmap);
// set background
mHelper.setBackground(this, backgroundResourceID);
// open book
if (!TextUtils.isEmpty(mFilePath)) {
try {
mHelper.openBook(mFilePath);
mHelper.draw(new Canvas(mCurrentPageBitmap));
mBookPageView.invalidate();
} catch (IOException e) {
e.printStackTrace();
// todo can not find the book path
}
} else {
// todo can not find the book path
}
}
public static void start(Context context, String filePath) {
Intent intent = new Intent(context, BookActivity.class);
intent.putExtra(FILE_PATH, filePath);
context.startActivity(intent);
}
private class HorizontalAdapter extends RecyclerView.Adapter {
private Context mContext;
private List<String> mData = new ArrayList<>();
public HorizontalAdapter(Context context, List<String> list) {
mContext = context;
mData = list;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
TextView itemView = new TextView(mContext);
return new ViewHolder(itemView);
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) {
TextView textView = (TextView) holder.itemView;
textView.setWidth(350);
textView.setHeight(180);
textView.setTextSize(16);
textView.setTextColor(Color.WHITE);
textView.setGravity(Gravity.CENTER);
textView.setText(mData.get(position));
final SharedPreferences sharedPreferences =
mContext.getSharedPreferences("yuedu_book_preference", MODE_PRIVATE);
final SharedPreferences.Editor editor = sharedPreferences.edit();
textView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
switch (position) {
case 0:
// add bookmark
// get progress
// save progress to preference.
editor.putInt(BOOKMARK, mCurrentLength);
editor.apply();
break;
case 1:
// get bookmark from preference.
// reload book to the progress.
mLastLength = sharedPreferences.getInt(BOOKMARK, 0);
openBookByProgress(R.drawable.book_bg, mLastLength);
break;
case 2:
openBookByProgress(R.drawable.book_bg2, mLastLength);
break;
case 3:
if (mTTS == null) {
mTTS = new TextToSpeech(mContext, new TextToSpeech.OnInitListener() {
@Override
public void onInit(int status) {
if (status == TextToSpeech.SUCCESS) {
int result = mTTS.setLanguage(Locale.CHINA);
if (result == TextToSpeech.LANG_MISSING_DATA
|| result == TextToSpeech.LANG_NOT_SUPPORTED) {
Log.e(TAG, "onInit: language is not available.");
Uri uri = Uri.parse("http://acj2.pc6.com/pc6_soure/2017-6/com.iflytek.vflynote_208.apk");
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);
} else {
Log.i(TAG, "onInit: init success.");
// mTTS.speak(mHelper.getCurrentPageContent(), TextToSpeech.QUEUE_FLUSH, null);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
mTTS.speak(mHelper.getCurrentPageContent(), TextToSpeech.QUEUE_FLUSH, null, "悦读");
} else {
mTTS.speak(mHelper.getCurrentPageContent(), TextToSpeech.QUEUE_FLUSH, null);
}
}
} else {
Log.e(TAG, "onInit: error");
}
}
});
} else {
if (mTTS.isSpeaking()) {
mTTS.stop();
} else {
// mTTS.speak(mHelper.getCurrentPageContent(), TextToSpeech.QUEUE_FLUSH, null);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
mTTS.speak(mHelper.getCurrentPageContent(), TextToSpeech.QUEUE_FLUSH, null, "悦读");
} else {
mTTS.speak(mHelper.getCurrentPageContent(), TextToSpeech.QUEUE_FLUSH, null);
}
}
}
break;
case 4:
mSeekBar.setVisibility(View.VISIBLE);
break;
}
}
});
}
@Override
public int getItemCount() {
return mData.size();
}
public class ViewHolder extends RecyclerView.ViewHolder {
private TextView mTextView;
public ViewHolder(TextView itemView) {
super(itemView);
mTextView = itemView;
}
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if(mTTS != null){
mTTS.shutdown();
}
}
}
另外的功能可根据需要看代码怎么实现,也可以引用一些接口实现语音功能
List<String> settingList = new ArrayList<>();
settingList.add("添加书签");
settingList.add("读取书签");
settingList.add("设置背景");
settingList.add("语音朗读");
settingList.add("跳转进度");
成果
书籍显示,这里要注意一下看一下服务器端的解析,否则会乱码
添加背景和翻页效果
结束